static cork and set_cookie fixes, some cli progress

pull/75/head
Ciro 2022-12-09 11:12:22 -03:00
rodzic 47f4627527
commit 509ff05ad4
6 zmienionych plików z 126 dodań i 46 usunięć

Wyświetl plik

@ -0,0 +1,11 @@
from socketify import App, AppListenOptions
app = App()
app.get("/", lambda res, req: res.end("Hello World!"))
app.listen(
AppListenOptions(domain="/tmp/test.sock"),
lambda config: print("Listening on port %s http://localhost/ now\n" % config.domain),
)
app.run()
# you can test with curl -GET --unix-socket /tmp/test.sock http://localhost/

Wyświetl plik

@ -6,8 +6,8 @@
# using oha -c 400 -z 5s http://localhost:3000/
# nginx - try_files - 77630.15 req/s
# pypy3 - socketify static - 10245.82 req/s
# python3 - socketify static - 8273.71 req/s
# pypy3 - socketify static - 15839.22 req/s
# python3 - socketify static - 8294.96 req/s
# node.js - @fastify/static - 5437.16 req/s
# node.js - express.static - 4077.49 req/s
# python3 - socketify static_aiofile - 2390.96 req/s
@ -21,8 +21,8 @@
# Conclusions:
# With PyPy3 only static is really usable gunicorn/uvicorn, aiofiles and aiofile are realy slow on PyPy3 maybe this changes with HPy
# Python3 with any option will be faster than gunicorn/uvicorn but with PyPy3 with static we got 2x (or almost this in case of fastify) performance of node.js
# But even PyPy3 + socketify static is 7x+ slower than NGINX
# Python3 with any option will be faster than gunicorn/uvicorn but with PyPy3 with static we got almost 4x (or almost 3x this in case of fastify) performance of node.js
# But even PyPy3 + socketify static is 5x+ slower than NGINX
# Anyway we really recommends using NGINX or similar + CDN for production like everybody else
# Gunicorn production recommendations: https://docs.gunicorn.org/en/latest/deploy.html#deploying-gunicorn

Wyświetl plik

@ -34,7 +34,7 @@ Options:
--req-res-factory-maxitems INT Pre allocated instances of Response and Request objects for socketify interface [default: 0]
--ws-factory-maxitems INT Pre allocated instances of WebSockets objects for socketify interface [default: 0]
--uds TEXT Bind to a UNIX domain socket.
--uds TEXT Bind to a UNIX domain socket, this options disables --host or -h and --port or -p.
--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.
@ -153,12 +153,16 @@ 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))))
if workers < 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)
disable_listen_log = options.get("--disable-listen-log", False)
websockets = options.get("--ws", "auto")
@ -197,7 +201,10 @@ def execute(args):
def listen_log(config):
if not disable_listen_log:
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 uds:
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")
if websockets:
websocket_options = {
@ -230,7 +237,10 @@ def execute(args):
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)
fork_app.listen(AppListenOptions(port=port, host=host), listen_log)
if uds:
fork_app.listen(AppListenOptions(domain=uds), listen_log)
else:
fork_app.listen(AppListenOptions(port=port, host=host), listen_log)
fork_app.run()
# now we can start all over again
@ -251,5 +261,29 @@ def execute(args):
for pid in pid_list:
os.kill(pid, signal.SIGINT)
else:
#Generic WSGI, ASGI, SSGI Interface
Interface(module,options=ssl_options, websocket=websockets, websocket_options=websocket_options).listen(AppListenOptions(port=port, host=host), listen_log).run(workers=workers)
# def on_change():
# auto_reload
def create_app():
#Generic WSGI, ASGI, SSGI Interface
if uds:
app = Interface(module,options=ssl_options, websocket=websockets, websocket_options=websocket_options).listen(AppListenOptions(domain=uds), listen_log)
else:
app = Interface(module,options=ssl_options, websocket=websockets, websocket_options=websocket_options).listen(AppListenOptions(port=port, host=host), listen_log)
return app
if auto_reload:
force_reload = False
app = None
while auto_reload:
app = create_app()
app.run()
if not force_reload:
auto_reload = False
else:
app = create_app()
app.run(workers=workers)

Wyświetl plik

@ -9,6 +9,7 @@ mimetypes.init()
# We have an version of this using aiofile and aiofiles
# This is an sync version without any dependencies is normally much faster in CPython and PyPy3
# In production we highly recomend to use CDN like CloudFlare or/and NGINX or similar for static files
# TODO: this should be optimized entire in C++
async def sendfile(res, req, filename):
# read headers before the first await
if_modified_since = req.get_header("if-modified-since")
@ -26,7 +27,7 @@ async def sendfile(res, req, filename):
exists = path.exists(filename)
# not found
if not exists:
return res.write_status(404).end(b"Not Found")
return res.cork(lambda res: res.write_status(404).end(b"Not Found"))
# get size and last modified date
stats = os.stat(filename)
@ -38,17 +39,13 @@ async def sendfile(res, req, filename):
# check if modified since is provided
if if_modified_since == last_modified:
return res.write_status(304).end_without_body()
# tells the broswer the last modified date
res.write_header(b"Last-Modified", last_modified)
return res.cork(lambda res: res.write_status(304).end_without_body())
# add content type
(content_type, encoding) = mimetypes.guess_type(filename, strict=True)
if content_type and encoding:
res.write_header(b"Content-Type", "%s; %s" % (content_type, encoding))
elif content_type:
res.write_header(b"Content-Type", content_type)
content_type = "%s; %s" % (content_type, encoding)
with open(filename, "rb") as fd:
# check range and support it
if start > 0 or not end == -1:
@ -57,17 +54,27 @@ async def sendfile(res, req, filename):
size = end - start + 1
fd.seek(start)
if start > total_size or size > total_size or size < 0 or start < 0:
return res.write_status(416).end_without_body()
res.write_status(206)
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())
status = 206
else:
end = size - 1
res.write_status(200)
status = 200
# tells the browser that we support range
res.write_header(b"Accept-Ranges", b"bytes")
res.write_header(
b"Content-Range", "bytes %d-%d/%d" % (start, end, total_size)
)
def send_headers(res):
res.write_status(status)
# tells the broswer the last modified date
res.write_header(b"Last-Modified", last_modified)
# tells the browser that we support range
if content_type:
res.write_header(b"Content-Type", content_type)
res.write_header(b"Accept-Ranges", b"bytes")
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
while not res.aborted:
@ -81,7 +88,7 @@ async def sendfile(res, req, filename):
break
except Exception as error:
res.write_status(500).end("Internal Error")
res.cork(lambda res: res.write_status(500).end("Internal Error"))
def in_directory(file, directory):

Wyświetl plik

@ -148,6 +148,7 @@ typedef struct {
} uws_try_end_result_t;
typedef void (*uws_listen_handler)(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void *user_data);
typedef void (*uws_listen_domain_handler)(struct us_listen_socket_t *listen_socket, const char* domain, size_t domain_length, int options, void *user_data);
typedef void (*uws_method_handler)(uws_res_t *response, uws_req_t *request, void *user_data);
typedef void (*uws_filter_handler)(uws_res_t *response, int, void *user_data);
typedef void (*uws_missing_server_handler)(const char *hostname, size_t hostname_length, void *user_data);
@ -171,6 +172,8 @@ void uws_app_run(int ssl, uws_app_t *);
void uws_app_listen(int ssl, uws_app_t *app, int port, uws_listen_handler handler, void *user_data);
void uws_app_listen_with_config(int ssl, uws_app_t *app, uws_app_listen_config_t config, uws_listen_handler handler, void *user_data);
void uws_app_listen_domain(int ssl, uws_app_t *app, const char *domain, size_t domain_length, uws_listen_domain_handler handler, void *user_data);
void uws_app_listen_domain_with_options(int ssl, uws_app_t *app, const char *domain,size_t domain_length, int options, uws_listen_domain_handler handler, void *user_data);
bool uws_constructor_failed(int ssl, uws_app_t *app);
unsigned int uws_num_subscribers(int ssl, uws_app_t *app, const char *topic, size_t topic_length);
bool uws_publish(int ssl, uws_app_t *app, const char *topic, size_t topic_length, const char *message, size_t message_length, uws_opcode_t opcode, bool compress);

Wyświetl plik

@ -22,10 +22,6 @@ from dataclasses import dataclass
mimetypes.init()
is_python = platform.python_implementation() == "CPython"
@ffi.callback("void(const char*, size_t, void*)")
def uws_missing_server_name(hostname, hostname_length, user_data):
if user_data != ffi.NULL:
@ -529,6 +525,25 @@ def uws_generic_method_handler(res, req, user_data):
app.trigger_error(err, response, request)
@ffi.callback("void(struct us_listen_socket_t*, const char*, size_t,int, void*)")
def uws_generic_listen_domain_handler(listen_socket, domain, length, _options, user_data):
domain = ffi.unpack(domain, length).decode("utf8")
if listen_socket == ffi.NULL:
raise RuntimeError("Failed to listen on domain %s" % domain)
if user_data != ffi.NULL:
app = ffi.from_handle(user_data)
if hasattr(app, "_listen_handler") and hasattr(app._listen_handler, "__call__"):
app.socket = listen_socket
app._listen_handler(
AppListenOptions(
domain=domain,
options=int(_options)
)
)
@ffi.callback("void(struct us_listen_socket_t*, uws_app_listen_config_t, void*)")
def uws_generic_listen_handler(listen_socket, config, user_data):
if listen_socket == ffi.NULL:
@ -1283,7 +1298,9 @@ class AppResponse:
self._cork_handler = callback
lib.uws_res_cork(self.SSL, self.res, uws_generic_cork_handler, self._ptr)
def set_cookie(self, name, value, options={}):
def set_cookie(self, name, value, options):
if options is None:
options = {}
if self._write_jar is None:
self._write_jar = cookies.SimpleCookie()
self._write_jar[name] = quote_plus(value)
@ -2230,18 +2247,22 @@ class App:
self.SSL, self.app, options, uws_generic_listen_handler, self._ptr
)
else:
native_options = ffi.new("uws_app_listen_config_t *")
options = native_options[0]
options.port = ffi.cast("int", port_or_options.port)
options.host = (
ffi.NULL
if port_or_options.host is None
else ffi.new("char[]", port_or_options.host.encode("utf-8"))
)
options.options = ffi.cast("int", port_or_options.options)
self.native_options_listen = native_options # Keep alive native_options
lib.uws_app_listen_with_config(
self.SSL, self.app, options, uws_generic_listen_handler, self._ptr
if port_or_options.domain:
domain = port_or_options.domain.encode('utf8')
lib.uws_app_listen_domain_with_options(self.SSL, self.app, domain, len(domain), int(port_or_options.options), uws_generic_listen_domain_handler, self._ptr)
else:
native_options = ffi.new("uws_app_listen_config_t *")
options = native_options[0]
options.port = ffi.cast("int", port_or_options.port)
options.host = (
ffi.NULL
if port_or_options.host is None
else ffi.new("char[]", port_or_options.host.encode("utf-8"))
)
options.options = ffi.cast("int", port_or_options.options)
self.native_options_listen = native_options # Keep alive native_options
lib.uws_app_listen_with_config(
self.SSL, self.app, options, uws_generic_listen_handler, self._ptr
)
return self
@ -2320,16 +2341,20 @@ class AppListenOptions:
port: int = 0
host: str = None
options: int = 0
domain: str = None
def __post_init__(self):
if not isinstance(self.port, int):
raise RuntimeError("port must be an int")
if not isinstance(self.host, (type(None), str)):
raise RuntimeError("host must be a str if specified")
if not isinstance(self.domain, (type(None), str)):
raise RuntimeError("domain must be a str if specified")
if not isinstance(self.options, int):
raise RuntimeError("options must be an int")
if self.domain and (self.host or self.port != 0):
raise RuntimeError("if domain is specified, host and port will be no effect")
@dataclass
class AppOptions:
key_file_name: str = None,