kopia lustrzana https://github.com/cirospaciari/socketify.py
preliminar support for Quart, fix ASGI receive behavior, fix custom Task behavior
rodzic
f562556844
commit
2ae9059e05
|
@ -1,6 +1,5 @@
|
||||||
from socketify import ASGI
|
from socketify import ASGI
|
||||||
|
|
||||||
|
|
||||||
async def app(scope, receive, send):
|
async def app(scope, receive, send):
|
||||||
assert scope['type'] == 'http'
|
assert scope['type'] == 'http'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from quart import Quart
|
||||||
|
|
||||||
|
|
||||||
|
app = Quart(__name__)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def plaintext():
|
||||||
|
return "Hello, World!", {"Content-Type": "text/plain"}
|
||||||
|
|
||||||
|
# Quart perform really baddly for sure needs more optimizations, but socketify ASGI + PyPy performs better than uvicorn+httptools+gunicorn
|
|
@ -1,16 +1,16 @@
|
||||||
|
|
||||||
from socketify import App
|
from socketify import App
|
||||||
import os
|
import os
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
def run_app():
|
def run_app():
|
||||||
app = App(request_response_factory_max_items=200_000)
|
app = App(request_response_factory_max_items=200_000)
|
||||||
router = app.router()
|
router = app.router()
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def home(res, req):
|
def home(res, req):
|
||||||
res.send(b"Hello, World!")
|
res.send(b"Hello, World!")
|
||||||
|
|
||||||
app.listen(
|
app.listen(
|
||||||
8000,
|
8000,
|
||||||
lambda config: print(
|
lambda config: print(
|
||||||
|
@ -29,7 +29,7 @@ def create_fork():
|
||||||
|
|
||||||
|
|
||||||
# fork limiting the cpu count - 1
|
# fork limiting the cpu count - 1
|
||||||
# for i in range(1, multiprocessing.cpu_count()):
|
for i in range(1, multiprocessing.cpu_count()):
|
||||||
# create_fork()
|
create_fork()
|
||||||
|
|
||||||
run_app() # run app on the main process too :)
|
run_app() # run app on the main process too :)
|
||||||
|
|
|
@ -25,7 +25,6 @@ async def task_wrapper(task):
|
||||||
|
|
||||||
EMPTY_RESPONSE = {"type": "http.request", "body": b"", "more_body": False}
|
EMPTY_RESPONSE = {"type": "http.request", "body": b"", "more_body": False}
|
||||||
|
|
||||||
|
|
||||||
@ffi.callback("void(uws_websocket_t*, const char*, size_t, uws_opcode_t, void*)")
|
@ffi.callback("void(uws_websocket_t*, const char*, size_t, uws_opcode_t, void*)")
|
||||||
def ws_message(ws, message, length, opcode, user_data):
|
def ws_message(ws, message, length, opcode, user_data):
|
||||||
socket_data = ffi.from_handle(user_data)
|
socket_data = ffi.from_handle(user_data)
|
||||||
|
@ -427,7 +426,7 @@ def asgi(ssl, response, info, user_data, aborted):
|
||||||
"http_version": "1.1",
|
"http_version": "1.1",
|
||||||
"server": (app.SERVER_HOST, app.SERVER_PORT),
|
"server": (app.SERVER_HOST, app.SERVER_PORT),
|
||||||
"client": (
|
"client": (
|
||||||
ffi.unpack(info.remote_address, info.remote_address_size).decode("utf8"),
|
None if info.remote_address == ffi.NULL else ffi.unpack(info.remote_address, info.remote_address_size).decode("utf8"),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
"scheme": app.SERVER_SCHEME,
|
"scheme": app.SERVER_SCHEME,
|
||||||
|
@ -444,7 +443,9 @@ def asgi(ssl, response, info, user_data, aborted):
|
||||||
else:
|
else:
|
||||||
data_queue = None
|
data_queue = None
|
||||||
|
|
||||||
|
sended_empty = False
|
||||||
async def receive():
|
async def receive():
|
||||||
|
nonlocal sended_empty
|
||||||
if bool(aborted[0]):
|
if bool(aborted[0]):
|
||||||
return {"type": "http.disconnect"}
|
return {"type": "http.disconnect"}
|
||||||
if data_queue:
|
if data_queue:
|
||||||
|
@ -458,8 +459,15 @@ def asgi(ssl, response, info, user_data, aborted):
|
||||||
else:
|
else:
|
||||||
return data_queue.queue.get(False) # consume queue
|
return data_queue.queue.get(False) # consume queue
|
||||||
|
|
||||||
# no more body, just empty
|
# no more body, just EMPTY RESPONSE
|
||||||
return EMPTY_RESPONSE
|
if not sended_empty:
|
||||||
|
sended_empty = True
|
||||||
|
return EMPTY_RESPONSE
|
||||||
|
|
||||||
|
# already sended empty body so wait for aborted request
|
||||||
|
while not bool(aborted[0]):
|
||||||
|
await asyncio.sleep(0.01) #10ms is good enought
|
||||||
|
return {"type": "http.disconnect"}
|
||||||
|
|
||||||
async def send(options):
|
async def send(options):
|
||||||
if bool(aborted[0]):
|
if bool(aborted[0]):
|
||||||
|
|
|
@ -50,12 +50,22 @@ class Loop:
|
||||||
else:
|
else:
|
||||||
self._task_factory = create_task
|
self._task_factory = create_task
|
||||||
self.run_async = self._run_async_pypy
|
self.run_async = self._run_async_pypy
|
||||||
# custom task factory
|
|
||||||
|
# TODO: check if any framework breaks without current_task(loop) support
|
||||||
|
# custom task factory for other tasks
|
||||||
def pypy_task_factory(loop, coro, context=None):
|
def pypy_task_factory(loop, coro, context=None):
|
||||||
return create_task(loop, coro, context=context)
|
return create_task(loop, coro, context=context)
|
||||||
|
|
||||||
self.loop.set_task_factory(pypy_task_factory)
|
self.loop.set_task_factory(pypy_task_factory)
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
# TODO: check if any framework breaks without current_task(loop) support
|
||||||
|
# custom task factory for other tasks
|
||||||
|
def cpython_task_factory(loop, coro, context=None):
|
||||||
|
return create_task(loop, coro, context=context)
|
||||||
|
|
||||||
|
self.loop.set_task_factory(cpython_task_factory)
|
||||||
|
|
||||||
# CPython performs equals or worse using TaskFactory
|
# CPython performs equals or worse using TaskFactory
|
||||||
self.run_async = self._run_async_cpython
|
self.run_async = self._run_async_cpython
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ from asyncio import (
|
||||||
exceptions,
|
exceptions,
|
||||||
futures,
|
futures,
|
||||||
_register_task,
|
_register_task,
|
||||||
_enter_task,
|
# _enter_task,
|
||||||
_leave_task,
|
# current_task,
|
||||||
|
# _leave_task,
|
||||||
_unregister_task,
|
_unregister_task,
|
||||||
)
|
)
|
||||||
import contextvars
|
import contextvars
|
||||||
|
@ -36,7 +37,9 @@ class RequestTask:
|
||||||
|
|
||||||
Differences:
|
Differences:
|
||||||
|
|
||||||
- This class is only used by socketify.py loop.run_async
|
- This class do not support current_task
|
||||||
|
|
||||||
|
- This class executes the first step like node.js Promise
|
||||||
|
|
||||||
- This class is not thread-safe.
|
- This class is not thread-safe.
|
||||||
|
|
||||||
|
@ -115,9 +118,12 @@ class RequestTask:
|
||||||
self._log_destroy_pending = False
|
self._log_destroy_pending = False
|
||||||
if self._loop.get_debug():
|
if self._loop.get_debug():
|
||||||
self._source_traceback = format_helpers.extract_stack(sys._getframe(1))
|
self._source_traceback = format_helpers.extract_stack(sys._getframe(1))
|
||||||
# self._loop.call_soon(self.__step, context=self._context)
|
|
||||||
self.__step()
|
|
||||||
_register_task(self)
|
_register_task(self)
|
||||||
|
# if current_task():
|
||||||
|
# self._loop.call_soon(self.__step, context=self._context)
|
||||||
|
# else:
|
||||||
|
self.__step()
|
||||||
|
|
||||||
|
|
||||||
def _reuse(self, coro, loop, default_done_callback=None):
|
def _reuse(self, coro, loop, default_done_callback=None):
|
||||||
"""Reuse an future that is not pending anymore."""
|
"""Reuse an future that is not pending anymore."""
|
||||||
|
@ -148,9 +154,12 @@ class RequestTask:
|
||||||
self._fut_waiter = None
|
self._fut_waiter = None
|
||||||
self._coro = coro
|
self._coro = coro
|
||||||
|
|
||||||
# self._loop.call_soon(self.__step, context=self._context)
|
|
||||||
self.__step()
|
|
||||||
_register_task(self)
|
_register_task(self)
|
||||||
|
# if current_task():
|
||||||
|
# self._loop.call_soon(self.__step, context=self._context)
|
||||||
|
# else:
|
||||||
|
self.__step()
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return base_tasks._task_repr(self)
|
return base_tasks._task_repr(self)
|
||||||
|
@ -489,8 +498,7 @@ class RequestTask:
|
||||||
self._must_cancel = False
|
self._must_cancel = False
|
||||||
coro = self._coro
|
coro = self._coro
|
||||||
self._fut_waiter = None
|
self._fut_waiter = None
|
||||||
|
# _enter_task(self._loop, self)
|
||||||
_enter_task(self._loop, self)
|
|
||||||
# Call either coro.throw(exc) or coro.send(None).
|
# Call either coro.throw(exc) or coro.send(None).
|
||||||
try:
|
try:
|
||||||
if exc is None:
|
if exc is None:
|
||||||
|
@ -560,7 +568,7 @@ class RequestTask:
|
||||||
new_exc = RuntimeError(f"Task got bad yield: {result!r}")
|
new_exc = RuntimeError(f"Task got bad yield: {result!r}")
|
||||||
self._loop.call_soon(self.__step, new_exc, context=self._context)
|
self._loop.call_soon(self.__step, new_exc, context=self._context)
|
||||||
finally:
|
finally:
|
||||||
_leave_task(self._loop, self)
|
# _leave_task(self._loop, self)
|
||||||
self = None # Needed to break cycles when an exception occurs.
|
self = None # Needed to break cycles when an exception occurs.
|
||||||
|
|
||||||
def __wakeup(self, future):
|
def __wakeup(self, future):
|
||||||
|
|
Ładowanie…
Reference in New Issue