update uSockets and projects

pull/106/head
Ciro 2023-01-18 16:04:41 -03:00
rodzic 59619dd697
commit 291af489f1
13 zmienionych plików z 351 dodań i 189 usunięć

Wyświetl plik

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "socketify"
version = "0.0.6"
version = "0.0.7"
authors = [
{ name="Ciro Spaciari", email="ciro.spaciari@gmail.com" },
]

Wyświetl plik

@ -58,7 +58,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup(
name="socketify",
version="0.0.6",
version="0.0.7",
platforms=["any"],
author="Ciro Spaciari",
author_email="ciro.spaciari@gmail.com",

Wyświetl plik

@ -1,3 +1,5 @@
import asyncio
from .socketify import (
App,
AppOptions,

Wyświetl plik

@ -8,7 +8,10 @@ import sys
import logging
import uuid
import asyncio
is_pypy = platform.python_implementation() == "PyPy"
async def task_wrapper(task):
try:
return await task
@ -19,6 +22,7 @@ async def task_wrapper(task):
finally:
return None
EMPTY_RESPONSE = {"type": "http.request", "body": b"", "more_body": False}
@ -162,8 +166,10 @@ def ws_upgrade(ssl, response, info, socket_context, user_data, aborted):
_id = uuid.uuid4()
app.server._socket_refs[_id] = ws
def unregister():
app.server._socket_refs.pop(_id, None)
ws.unregister = unregister
lib.uws_res_upgrade(
ssl,
@ -495,7 +501,15 @@ def asgi(ssl, response, info, user_data, aborted):
class _ASGI:
def __init__(self, app, options=None, websocket=True, websocket_options=None, task_factory_max_items=100_000, lifespan=True):
def __init__(
self,
app,
options=None,
websocket=True,
websocket_options=None,
task_factory_max_items=100_000,
lifespan=True,
):
self.server = App(options, task_factory_max_items=0)
self.SERVER_PORT = None
self.SERVER_HOST = ""
@ -514,27 +528,35 @@ class _ASGI:
def run_task(task):
factory(loop, task_wrapper(task))
loop._run_once()
self._run_task = run_task
else:
def run_task(task):
create_task(loop, task_wrapper(task))
loop._run_once()
self._run_task = run_task
else:
if sys.version_info >= (3, 8): # name fixed to avoid dynamic name
def run_task(task):
future = loop.create_task(task_wrapper(task), name='socketify.py-request-task')
future = loop.create_task(
task_wrapper(task), name="socketify.py-request-task"
)
future._log_destroy_pending = False
loop._run_once()
self._run_task = run_task
else:
def run_task(task):
future = loop.create_task(task_wrapper(task))
future._log_destroy_pending = False
loop._run_once()
self._run_task = run_task
self._run_task = run_task
self.app = app
self.ws_compression = False
@ -686,10 +708,12 @@ class _ASGI:
return self
# signal stop
self.status = 3
self.stop_future.set_result({
self.stop_future.set_result(
{
"type": "lifespan.shutdown",
"asgi": {"version": "3.0", "spec_version": "2.3"},
})
}
)
# run until end or fail
while self.status == 3:
self.server.loop.run_once()
@ -714,8 +738,8 @@ class ASGI:
options=None,
websocket=True,
websocket_options=None,
task_factory_max_items=100_000, #default = 100k = +20mib in memory
lifespan=True
task_factory_max_items=100_000, # default = 100k = +20mib in memory
lifespan=True,
):
self.app = app
self.options = options
@ -737,7 +761,7 @@ class ASGI:
self.websocket,
self.websocket_options,
self.task_factory_max_items,
self.lifespan
self.lifespan,
)
if self.listen_options:
(port_or_options, handler) = self.listen_options

Wyświetl plik

@ -2,6 +2,7 @@ import inspect
import os
import logging
from . import App, AppOptions, AppListenOptions
help = """
Usage: python -m socketify APP [OPTIONS]
python3 -m socketify APP [OPTIONS]
@ -43,37 +44,56 @@ Example:
"""
# --reload Enable auto-reload. This options also disable --workers or -w option.
# --reload-dir PATH Set reload directories explicitly, instead of using the current working directory.
# --reload-include TEXT Set extensions to include while watching for files.
# Includes '.py,.html,.js,.png,.jpeg,.jpg and .webp' by default;
# these defaults can be overridden with `--reload-exclude`.
# --reload-exclude TEXT Set extensions to include while watching for files.
# --reload-delay INT Milliseconds to delay reload between file changes. [default: 1000]
# --reload Enable auto-reload. This options also disable --workers or -w option.
# --reload-dir PATH Set reload directories explicitly, instead of using the current working directory.
# --reload-include TEXT Set extensions to include while watching for files.
# Includes '.py,.html,.js,.png,.jpeg,.jpg and .webp' by default;
# these defaults can be overridden with `--reload-exclude`.
# --reload-exclude TEXT Set extensions to include while watching for files.
# --reload-delay INT Milliseconds to delay reload between file changes. [default: 1000]
def is_wsgi(module):
return hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 2
return (
hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 2
)
def is_asgi(module):
return hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 3
return (
hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 3
)
def is_ssgi(module):
return False # no spec yet
def is_ssgi(module):
return False # no spec yet
def is_socketify(module):
return hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 1
return (
hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 1
)
def is_factory(module):
return hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 0
return (
hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 0
)
def str_bool(text):
text = str(text).lower()
return text == "true"
def load_module(file, reload=False):
try:
[full_module, app] = file.split(':')
[full_module, app] = file.split(":")
import importlib
module =importlib.import_module(full_module)
module = importlib.import_module(full_module)
if reload:
importlib.reload(module)
@ -85,6 +105,8 @@ def load_module(file, reload=False):
except Exception as error:
logging.exception(error)
return None
def execute(args):
arguments_length = len(args)
if arguments_length <= 2:
@ -93,8 +115,11 @@ def execute(args):
if arguments_length == 2 and (args[1] == "--version" or args[1] == "-v"):
import pkg_resources # part of setuptools
import platform
version = pkg_resources.require("socketify")[0].version
return print(f"Running socketify {version} with {platform.python_implementation()} {platform.python_version()} on {platform.system()}")
return print(
f"Running socketify {version} with {platform.python_implementation()} {platform.python_version()} on {platform.system()}"
)
elif arguments_length < 2:
return print(help)
@ -111,7 +136,7 @@ def execute(args):
if selected_option:
options[selected_option] = option
selected_option = None
elif option.startswith('--') or option.startswith('-'):
elif option.startswith("--") or option.startswith("-"):
if selected_option is None:
selected_option = option
else: # --factory, --reload etc
@ -126,31 +151,40 @@ def execute(args):
if interface == "auto":
if is_asgi(module):
from . import ASGI as Interface
interface = "asgi"
elif is_wsgi(module):
from . import WSGI as Interface
interface = "wsgi"
elif is_ssgi(module):
from . import SSGI as Interface
interface = "ssgi"
else:
interface = "socketify"
elif interface == "asgi" or interface == "asgi3":
from . import ASGI as Interface
interface = "asgi"
# you may use ASGI in SSGI so no checks here
if is_wsgi(module):
return print("Cannot use WSGI interface as ASGI interface")
if not is_asgi(module):
return print("ASGI interface must be callable with 3 parameters async def app(scope, receive and send)")
return print(
"ASGI interface must be callable with 3 parameters async def app(scope, receive and send)"
)
elif interface == "wsgi":
from . import WSGI as Interface
# you may use WSGI in SSGI so no checks here
if is_asgi(module):
return print("Cannot use ASGI interface as WSGI interface")
if not is_wsgi(module):
return print("WSGI interface must be callable with 2 parameters def app(environ, start_response)")
return print(
"WSGI interface must be callable with 2 parameters def app(environ, start_response)"
)
elif interface == "ssgi":
# if not is_ssgi(module):
@ -161,15 +195,19 @@ def execute(args):
elif interface != "socketify":
return print(f"{interface} interface is not supported yet")
auto_reload = options.get('--reload', False)
workers = int(options.get("--workers", options.get("-w", os.environ.get('WEB_CONCURRENCY', 1))))
auto_reload = options.get("--reload", False)
workers = int(
options.get(
"--workers", options.get("-w", os.environ.get("WEB_CONCURRENCY", 1))
)
)
if workers < 1 or auto_reload:
workers = 1
port = int(options.get("--port", options.get("-p", 8000)))
host = options.get("--host", options.get("-h", "127.0.0.1"))
uds = options.get('--uds', None)
lifespan = options.get('--lifespan', "auto")
uds = options.get("--uds", None)
lifespan = options.get("--lifespan", "auto")
lifespan = False if lifespan == "off" else True
task_factory_maxitems = int(options.get("--task-factory-maxitems", 100000))
@ -189,22 +227,21 @@ def execute(args):
else: # if is socketify websockets must be set in app
websockets = False
else:
#websocket dedicated module
# websocket dedicated module
ws_module = load_module(websockets)
if not ws_module:
return print(f"Cannot load websocket module {websockets}")
websockets = ws_module
key_file_name = options.get('--ssl-keyfile', None)
key_file_name = options.get("--ssl-keyfile", None)
if key_file_name:
ssl_options = AppOptions(
key_file_name=options.get('--ssl-keyfile', None),
cert_file_name=options.get('--ssl-certfile', None),
passphrase=options.get('--ssl-keyfile-password', None),
ca_file_name=options.get('--ssl-ca-certs', None),
ssl_ciphers=options.get('--ssl-ciphers', None)
key_file_name=options.get("--ssl-keyfile", None),
cert_file_name=options.get("--ssl-certfile", None),
passphrase=options.get("--ssl-keyfile-password", None),
ca_file_name=options.get("--ssl-ca-certs", None),
ssl_ciphers=options.get("--ssl-ciphers", None),
)
else:
ssl_options = None
@ -212,20 +249,30 @@ def execute(args):
def listen_log(config):
if not disable_listen_log:
if uds:
print(f"Listening on {config.domain} {'https' if ssl_options else 'http'}://localhost now\n")
print(
f"Listening on {config.domain} {'https' if ssl_options else 'http'}://localhost now\n"
)
else:
print(f"Listening on {'https' if ssl_options else 'http'}://{config.host if config.host and len(config.host) > 1 else '127.0.0.1' }:{config.port} now\n")
print(
f"Listening on {'https' if ssl_options else 'http'}://{config.host if config.host and len(config.host) > 1 else '127.0.0.1' }:{config.port} now\n"
)
if websockets:
websocket_options = {
'compression': int(1 if options.get('--ws-per-message-deflate', False) else 0),
'max_payload_length': int(options.get('--ws-max-size', 16777216)),
'idle_timeout': int(options.get('--ws-idle-timeout', 20)),
'send_pings_automatically': str_bool(options.get('--ws-auto-ping', True)),
'reset_idle_timeout_on_send': str_bool(options.get('--ws-reset-idle-on-send', True)),
'max_lifetime': int(options.get('--ws-max-lifetime', 0)),
'max_backpressure': int(options.get('--max-backpressure', 16777216)),
'close_on_backpressure_limit': str_bool(options.get('--ws-close-on-backpressure-limit', False))
"compression": int(
1 if options.get("--ws-per-message-deflate", False) else 0
),
"max_payload_length": int(options.get("--ws-max-size", 16777216)),
"idle_timeout": int(options.get("--ws-idle-timeout", 20)),
"send_pings_automatically": str_bool(options.get("--ws-auto-ping", True)),
"reset_idle_timeout_on_send": str_bool(
options.get("--ws-reset-idle-on-send", True)
),
"max_lifetime": int(options.get("--ws-max-lifetime", 0)),
"max_backpressure": int(options.get("--max-backpressure", 16777216)),
"close_on_backpressure_limit": str_bool(
options.get("--ws-close-on-backpressure-limit", False)
),
}
else:
websocket_options = None
@ -238,13 +285,23 @@ def execute(args):
elif is_wsgi(module):
return print("Cannot use WSGI interface as socketify interface")
elif not is_socketify(module):
return print("socketify interface must be callable with 1 parameter def run(app: App)")
return print(
"socketify interface must be callable with 1 parameter def run(app: App)"
)
# run app with the settings desired
def run_app():
fork_app = App(ssl_options, int(options.get("--req-res-factory-maxitems", 0)), int(options.get("--ws-factory-maxitems", 0)), task_factory_maxitems, lifespan)
fork_app = App(
ssl_options,
int(options.get("--req-res-factory-maxitems", 0)),
int(options.get("--ws-factory-maxitems", 0)),
task_factory_maxitems,
lifespan,
)
module(fork_app) # call module factory
if websockets: # if socketify websockets are added using --ws in socketify interface we can set here
if (
websockets
): # if socketify websockets are added using --ws in socketify interface we can set here
websockets.update(websocket_options) # set websocket options
fork_app.ws("/*", websockets)
if uds:
@ -268,11 +325,28 @@ def execute(args):
run_app()
# sigint everything to gracefull shutdown
import signal
for pid in pid_list:
os.kill(pid, signal.SIGINT)
else:
if uds:
Interface(module,options=ssl_options, websocket=websockets, websocket_options=websocket_options, task_factory_max_items=task_factory_maxitems, lifespan=lifespan).listen(AppListenOptions(domain=uds), listen_log).run(workers=workers)
Interface(
module,
options=ssl_options,
websocket=websockets,
websocket_options=websocket_options,
task_factory_max_items=task_factory_maxitems,
lifespan=lifespan,
).listen(AppListenOptions(domain=uds), listen_log).run(workers=workers)
else:
Interface(module,options=ssl_options, websocket=websockets, websocket_options=websocket_options, task_factory_max_items=task_factory_maxitems, lifespan=lifespan).listen(AppListenOptions(port=port, host=host), listen_log).run(workers=workers)
Interface(
module,
options=ssl_options,
websocket=websockets,
websocket_options=websocket_options,
task_factory_max_items=task_factory_maxitems,
lifespan=lifespan,
).listen(AppListenOptions(port=port, host=host), listen_log).run(
workers=workers
)

Wyświetl plik

@ -55,8 +55,14 @@ async def sendfile(res, req, filename):
fd.seek(start)
if start > total_size or size > total_size or size < 0 or start < 0:
if content_type:
return res.cork(lambda res: res.write_header(b"Content-Type", content_type).write_status(416).end_without_body())
return res.cork(lambda res: res.write_status(416).end_without_body())
return res.cork(
lambda res: res.write_header(b"Content-Type", content_type)
.write_status(416)
.end_without_body()
)
return res.cork(
lambda res: res.write_status(416).end_without_body()
)
status = 206
else:
end = size - 1
@ -74,6 +80,7 @@ async def sendfile(res, req, filename):
res.write_header(
b"Content-Range", "bytes %d-%d/%d" % (start, end, total_size)
)
res.cork(send_headers)
pending_size = size
# keep sending until abort or done
@ -104,7 +111,7 @@ def static_route(app, route, directory):
def route_handler(res, req):
url = req.get_url()
res.grab_aborted_handler()
url = url[len(route)::]
url = url[len(route) : :]
if url.endswith("/"):
if url.startswith("/"):
url = url[1:-1]
@ -123,6 +130,7 @@ def static_route(app, route, directory):
route = route[:-1]
app.get("%s/*" % route, route_handler)
def middleware(*functions):
syncs = []
asyncs = []
@ -146,6 +154,7 @@ def middleware(*functions):
# stops if returns Falsy
if not data:
return
async def wrapper(res, req, data):
# cicle to all middlewares
for function in asyncs:
@ -170,7 +179,6 @@ def middleware(*functions):
return optimized_middleware_route
def sync_middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware
def middleware_route(res, req, data=None):
@ -185,6 +193,7 @@ def sync_middleware(*functions):
return middleware_route
def async_middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware
async def middleware_route(res, req, data=None):
@ -211,7 +220,7 @@ def async_middleware(*functions):
class DecoratorRouter:
def __init__(self, app, prefix: str="", *middlewares):
def __init__(self, app, prefix: str = "", *middlewares):
self.app = app
self.middlewares = middlewares
self.prefix = prefix
@ -232,6 +241,7 @@ class DecoratorRouter:
def post(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -244,6 +254,7 @@ class DecoratorRouter:
def options(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -256,6 +267,7 @@ class DecoratorRouter:
def delete(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -268,6 +280,7 @@ class DecoratorRouter:
def patch(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -280,6 +293,7 @@ class DecoratorRouter:
def put(self, path: str):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -292,6 +306,7 @@ class DecoratorRouter:
def head(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -304,6 +319,7 @@ class DecoratorRouter:
def connect(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -316,6 +332,7 @@ class DecoratorRouter:
def trace(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -328,6 +345,7 @@ class DecoratorRouter:
def any(self, path):
path = f"{self.prefix}{path}"
def decorator(handler):
if len(self.middlewares) > 0:
middies = list(*self.middlewares)
@ -338,6 +356,7 @@ class DecoratorRouter:
return decorator
class MiddlewareRouter:
def __init__(self, app, *middlewares):
self.app = app

Wyświetl plik

@ -6,7 +6,9 @@ from .uv import UVLoop
import asyncio
import platform
is_pypy = platform.python_implementation() == 'PyPy'
is_pypy = platform.python_implementation() == "PyPy"
async def task_wrapper(exception_handler, loop, response, task):
try:
return await task
@ -52,6 +54,7 @@ class Loop:
# custom task factory
def pypy_task_factory(loop, coro, context=None):
return create_task(loop, coro, context=context)
self.loop.set_task_factory(pypy_task_factory)
else:
# CPython performs worse using custom create_task, so native create_task is used
@ -60,7 +63,6 @@ class Loop:
self.run_async = self._run_async_cpython
def set_timeout(self, timeout, callback, user_data):
return self.uv_loop.create_timer(timeout, 0, callback, user_data)
@ -110,7 +112,6 @@ class Loop:
# run one step of libuv
self.uv_loop.run_once()
def stop(self):
if self.started:
# Just mark as started = False and wait
@ -121,12 +122,13 @@ class Loop:
def get_native_loop(self):
return self.uv_loop.get_native_loop()
def _run_async_pypy(self, task, response=None):
# this garanties error 500 in case of uncaught exceptions, and can trigger the custom error handler
# using an coroutine wrapper generates less overhead than using add_done_callback
# this is an custom task/future with less overhead
future = self._task_factory(self.loop, task_wrapper(self.exception_handler, self.loop, response, task))
future = self._task_factory(
self.loop, task_wrapper(self.exception_handler, self.loop, response, task)
)
# force asyncio run once to enable req in async functions before first await
self.loop._run_once()
return None # this future maybe already done and reused not safe to await
@ -134,15 +136,19 @@ class Loop:
def _run_async_cpython(self, task, response=None):
# this garanties error 500 in case of uncaught exceptions, and can trigger the custom error handler
# using an coroutine wrapper generates less overhead than using add_done_callback
future = self.loop.create_task(task_wrapper(self.exception_handler, self.loop, response, task))
future = self.loop.create_task(
task_wrapper(self.exception_handler, self.loop, response, task)
)
# force asyncio run once to enable req in async functions before first await
self.loop._run_once()
return None # this future is safe to await but we return None for compatibility, and in the future will be the same behavior as PyPy
def dispose(self):
if self.uv_loop:
self.uv_loop.dispose()
self.uv_loop = None
# if sys.version_info >= (3, 11)
# with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner:
# runner.run(main())

Wyświetl plik

@ -401,5 +401,3 @@ library_path = os.path.join(
lib = ffi.dlopen(library_path)

Wyświetl plik

@ -4,9 +4,6 @@ from http import cookies
import inspect
from io import BytesIO
import json
import mimetypes
import os
import platform
import signal
import uuid
from urllib.parse import parse_qs, quote_plus, unquote_plus
@ -20,8 +17,6 @@ from dataclasses import dataclass
from .helpers import DecoratorRouter
from typing import Union
mimetypes.init()
@ffi.callback("void(const char*, size_t, void*)")
def uws_missing_server_name(hostname, hostname_length, user_data):
@ -1772,7 +1767,7 @@ class AppResponse:
else:
data = self.app._json_serializer.dumps(message).encode("utf-8")
# ignores content_type should always be json here
self.write_header(b"Content-Type", b'application/json')
self.write_header(b"Content-Type", b"application/json")
lib.uws_res_end(
self.app.SSL, self.res, data, len(data), 1 if end_connection else 0
@ -2476,7 +2471,7 @@ class App:
request_response_factory_max_items=0,
websocket_factory_max_items=0,
task_factory_max_items=100_000,
lifespan=True
lifespan=True,
):
socket_options_ptr = ffi.new("struct us_socket_context_options_t *")
socket_options = socket_options_ptr[0]
@ -3128,6 +3123,7 @@ class App:
def listen(self, port_or_options=None, handler=None):
if self.lifespan:
async def task_wrapper(task):
try:
if inspect.iscoroutinefunction(task):
@ -3229,6 +3225,7 @@ class App:
signal.signal(signal.SIGINT, lambda sig, frame: self.close())
self.loop.run()
if self.lifespan:
async def task_wrapper(task):
try:
if inspect.iscoroutinefunction(task):
@ -3240,6 +3237,7 @@ class App:
self.trigger_error(error, None, None)
finally:
return None
# shutdown lifespan
if self._on_shutdown_handler:
self.loop.run_until_complete(task_wrapper(self._on_shutdown_handler))
@ -3276,7 +3274,6 @@ class App:
else:
try:
if inspect.iscoroutinefunction(self.error_handler):
print("coroutine!", error)
self.run_async(
self.error_handler(error, response, request), response
)

Wyświetl plik

@ -89,7 +89,9 @@ class RequestTask:
# status is still pending
_log_destroy_pending = True
def __init__(self, coro, loop, default_done_callback=None, no_register=False, context=None):
def __init__(
self, coro, loop, default_done_callback=None, no_register=False, context=None
):
"""Initialize the future.
The optional event_loop argument allows explicitly setting the event
@ -590,10 +592,12 @@ def create_task_with_factory(task_factory_max_items=100_000):
if len(items) == 0:
return create_task(loop, coro, default_done_callback)
task = items.pop()
def done(f):
if default_done_callback is not None:
default_done_callback(f)
items.append(f)
task._reuse(coro, loop, done)
return task

@ -1 +1 @@
Subproject commit 9b13a6b02886c792189252e948290d2eea9aeda9
Subproject commit 7187fc3d658d4335cdf0c79371eeb8310717b95c

Wyświetl plik

@ -7,6 +7,7 @@ def socketify_generic_handler(data):
(handler, user_data) = ffi.from_handle(data)
handler(user_data)
class UVCheck:
def __init__(self, loop, handler, user_data):
self._handler_data = ffi.new_handle((handler, user_data))

Wyświetl plik

@ -5,11 +5,13 @@ from .asgi import ws_close, ws_upgrade, ws_open, ws_message
from io import BytesIO, BufferedReader
from .native import lib, ffi
import platform
is_pypy = platform.python_implementation() == "PyPy"
from .tasks import create_task, create_task_with_factory
import sys
import logging
@ffi.callback("void(uws_res_t*, const char*, size_t, bool, void*)")
def wsgi_on_data_handler(res, chunk, chunk_length, is_end, user_data):
data_response = ffi.from_handle(user_data)
@ -23,6 +25,7 @@ def wsgi_on_data_handler(res, chunk, chunk_length, is_end, user_data):
data_response._ptr,
)
class WSGIBody:
def __init__(self, buffer):
self.buf = buffer
@ -106,10 +109,11 @@ class WSGIBody:
ret.append(data)
data = b""
else:
line, data = data[:pos + 1], data[pos + 1:]
line, data = data[: pos + 1], data[pos + 1 :]
ret.append(line)
return ret
class WSGIDataResponse:
def __init__(self, app, environ, start_response, aborted, buffer, on_data):
self.buffer = buffer
@ -120,6 +124,7 @@ class WSGIDataResponse:
self.app = app
self.start_response = start_response
@ffi.callback("void(uws_res_t*, void*)")
def wsgi_corked_response_start_handler(res, user_data):
data_response = ffi.from_handle(user_data)
@ -158,6 +163,7 @@ def wsgi(ssl, response, info, user_data, aborted):
headers_set = None
headers_written = False
status_text = None
def write_headers(headers):
nonlocal headers_written, headers_set, status_text
if headers_written or not headers_set:
@ -171,7 +177,6 @@ def wsgi(ssl, response, info, user_data, aborted):
elif isinstance(status_text, bytes):
lib.uws_res_write_status(ssl, response, status_text, len(status_text))
for (key, value) in headers:
if isinstance(key, str):
# this is faster than using .lower()
@ -194,7 +199,6 @@ def wsgi(ssl, response, info, user_data, aborted):
continue # auto
key_data = key
if isinstance(value, str):
value_data = value.encode("utf-8")
elif isinstance(value, bytes):
@ -212,6 +216,7 @@ def wsgi(ssl, response, info, user_data, aborted):
lib.uws_res_write_header(
ssl, response, key_data, len(key_data), value_data, len(value_data)
)
def start_response(status, headers, exc_info=None):
nonlocal headers_set, status_text
if exc_info:
@ -224,7 +229,6 @@ def wsgi(ssl, response, info, user_data, aborted):
elif headers_set:
raise AssertionError("Headers already set!")
headers_set = headers
status_text = status
@ -237,6 +241,7 @@ def wsgi(ssl, response, info, user_data, aborted):
elif isinstance(data, str):
data = data.encode("utf-8")
lib.uws_res_write(ssl, response, data, len(data))
return write
# check for body
@ -249,7 +254,9 @@ def wsgi(ssl, response, info, user_data, aborted):
return
ssl = data_response.app.server.SSL
data_response.environ["CONTENT_LENGTH"] = str(data_response.buffer.getbuffer().nbytes)
data_response.environ["CONTENT_LENGTH"] = str(
data_response.buffer.getbuffer().nbytes
)
app_iter = data_response.app.wsgi(
data_response.environ, data_response.start_response
)
@ -302,11 +309,22 @@ def wsgi(ssl, response, info, user_data, aborted):
write_headers(headers_set)
lib.uws_res_end_without_body(ssl, response, 0)
def is_asgi(module):
return hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 3
return (
hasattr(module, "__call__") and len(inspect.signature(module).parameters) == 3
)
class _WSGI:
def __init__(self, app, options=None, websocket=None, websocket_options=None, task_factory_max_items=100_000):
def __init__(
self,
app,
options=None,
websocket=None,
websocket_options=None,
task_factory_max_items=100_000,
):
self.server = App(options, task_factory_max_items=0)
self.SERVER_HOST = None
self.SERVER_PORT = None
@ -338,23 +356,30 @@ class _WSGI:
def run_task(task):
factory(loop, task)
loop._run_once()
self._run_task = run_task
else:
def run_task(task):
create_task(loop, task)
loop._run_once()
self._run_task = run_task
else:
if sys.version_info >= (3, 8): # name fixed to avoid dynamic name
def run_task(task):
loop.create_task(task, name='socketify.py-request-task')
loop.create_task(task, name="socketify.py-request-task")
loop._run_once()
self._run_task = run_task
else:
def run_task(task):
loop.create_task(task)
loop._run_once()
self._run_task = run_task
# detect ASGI to use as WebSocket as mixed protocol
@ -435,7 +460,7 @@ class _WSGI:
"REMOTE_HOST": "",
"CONTENT_LENGTH": "0",
"CONTENT_TYPE": "",
'wsgi.input_terminated': True
"wsgi.input_terminated": True,
}
)
self.server.listen(port_or_options, handler)
@ -454,7 +479,15 @@ class _WSGI:
# "Public" WSGI interface to allow easy forks/workers
class WSGI:
def __init__(self, app, options=None, websocket=None, websocket_options=None, task_factory_max_items=100_000, lifespan=False):
def __init__(
self,
app,
options=None,
websocket=None,
websocket_options=None,
task_factory_max_items=100_000,
lifespan=False,
):
self.app = app
self.options = options
self.websocket = websocket
@ -470,7 +503,11 @@ class WSGI:
def run(self, workers=1):
def run_app():
server = _WSGI(
self.app, self.options, self.websocket, self.websocket_options, self.task_factory_max_items
self.app,
self.options,
self.websocket,
self.websocket_options,
self.task_factory_max_items,
)
if self.listen_options:
(port_or_options, handler) = self.listen_options