From b6f25723723e8e5b1db862a3906f1ef831c37fb9 Mon Sep 17 00:00:00 2001 From: Ciro Date: Sat, 28 May 2022 10:47:26 -0300 Subject: [PATCH] rename --- .gitignore | 1 + README.md | 12 ++-- libuv.py | 57 +++++++++++++++++++ original.py | 20 +++++++ selector.py | 119 +++++++++++++++++++++++++++++++++++++++ main.py => socketify.py | 48 +--------------- tests.py | 121 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 326 insertions(+), 52 deletions(-) create mode 100644 .gitignore create mode 100644 libuv.py create mode 100644 original.py create mode 100644 selector.py rename main.py => socketify.py (94%) create mode 100644 tests.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/README.md b/README.md index b8ae3a0..441f21c 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# uWebSockets.py +# socketify.py Fast WebSocket and Http/Https server using CFFI with C API from [uNetworking/uWebSockets](https://github.com/uNetworking/uWebSockets) > This project will adapt the full capi from uNetworking/uWebSockets but for now it's just this. ### Overly simple hello world app ```python -from "uws" import App +from socketify import App app = App() -app.get("/", lambda res, req: res.end("Hello World uWS from Python!")) +app.get("/", lambda res, req: res.end("Hello World socketify from Python!")) app.listen(3000, lambda socket, config: print("Listening on port http://localhost:%d now\n" % config.port)) app.run() ``` @@ -16,7 +16,7 @@ app.run() ### pip (working progress) ```bash -pip install git+https://github.com/cirospaciari/uWebSockets.py.git +pip install git+https://github.com/cirospaciari/socketify.py.git ``` ### Run @@ -26,10 +26,10 @@ pypy3 ./hello_world.py ### SSL version sample ``` python -from "uws" import App, AppOptions +from socketify import App, AppOptions app = App(AppOptions(key_file_name="./misc/key.pem", cert_file_name="./misc/cert.pem", passphrase="1234")) -app.get("/", plaintext) +app.get("/", lambda res, req: res.end("Hello World socketify from Python!")) app.listen(3000, lambda socket, config: print("Listening on port http://localhost:%d now\n" % config.port)) app.run() ``` \ No newline at end of file diff --git a/libuv.py b/libuv.py new file mode 100644 index 0000000..f95c91b --- /dev/null +++ b/libuv.py @@ -0,0 +1,57 @@ + +import cffi +import os +# from ctypes.util import find_library +# print("libuv1: %s" % find_library('uv')) + +ffi = cffi.FFI() +ffi.cdef(""" + + +typedef struct uv_handle_t uv_handle_t; +typedef struct uv_timer_s uv_timer_t; +typedef void(*uv_timer_cb)(uv_timer_t* handle); + +struct uv_timer_s { + uv_handle_t* next_closing; \ + unsigned int flags; + uv_timer_cb timer_cb; \ + void* heap_node[3]; \ + uint64_t timeout; \ + uint64_t repeat; \ + uint64_t start_id; +}; +typedef struct uv_poll_t uv_poll_t; + + +typedef struct uv_loop_t uv_loop_t; + +typedef struct uv_os_sock_t uv_os_sock_t; +typedef struct uv_poll_cb uv_poll_cb; + +typedef void (*uv_close_cb)(uv_handle_t* handle); + +typedef enum { + UV_RUN_DEFAULT = 0, + UV_RUN_ONCE, + UV_RUN_NOWAIT +} uv_run_mode; + +int uv_run(uv_loop_t*, uv_run_mode mode); +void uv_stop(uv_loop_t*); +int uv_poll_init(uv_loop_t* loop, uv_poll_t* handle, int fd); +int uv_poll_init_socket(uv_loop_t* loop, uv_poll_t* handle, uv_os_sock_t socket); +int uv_poll_start(uv_poll_t* handle, int events, uv_poll_cb cb); +int uv_poll_stop(uv_poll_t* handle); +void uv_close(uv_handle_t* handle, uv_close_cb close_cb); +uv_loop_t* uv_handle_get_loop(const uv_handle_t* handle); +int uv_timer_init(uv_loop_t*, uv_timer_t* handle); +int uv_timer_start(uv_timer_t* handle, + uv_timer_cb cb, + uint64_t timeout, + uint64_t repeat); +int uv_timer_stop(uv_timer_t* handle); +uv_loop_t* uv_default_loop(void); +""") + +lib = ffi.dlopen("uv") diff --git a/original.py b/original.py new file mode 100644 index 0000000..dd57016 --- /dev/null +++ b/original.py @@ -0,0 +1,20 @@ +import uws +import asyncio + +# Integrate with asyncio +# asyncio.set_event_loop(uws.Loop()) + +app = uws.App() + +def getHandler(res, req): + res.end("Hello Python!") + +app.get("/*", getHandler) + +def listenHandler(): + print("Listening to port 3000") + +app.listen(3000, listenHandler) +app.run() +# Run asyncio event loop +# asyncio.get_event_loop().run_forever() \ No newline at end of file diff --git a/selector.py b/selector.py new file mode 100644 index 0000000..91cbcd5 --- /dev/null +++ b/selector.py @@ -0,0 +1,119 @@ +from libuv import lib, ffi +import signal +import asyncio +import selectors +# loop = lib.uv_default_loop() +# print(loop) +# lib.uv_run(loop, lib.UV_RUN_ONCE) + + +@ffi.callback("void(uv_timer_t*)") +def selector_timer_handler(timer): + global selector + selector.tick = True + +class Selector(selectors.BaseSelector): + def __init__(self): + self.tick = False + self.list = [] + self.pools = dict() + + self.loop = lib.uv_default_loop() + self.timer = ffi.new("uv_timer_t *") + lib.uv_timer_init(self.loop, self.timer) + signal.signal(signal.SIGINT, lambda sig,frame: self.sigint()) + # super().__init__(self) + + def sigint(self): + self.interrupted = True + pass + def register(self, fileobj, events, data=None): + fd = -1 + if isinstance(fileobj, int): + fd = fileobj + else: + fd = fileobj.fileno() + + pass + def unregister(self, fileobj): + fd = -1 + if isinstance(fileobj, int): + fd = fileobj + else: + fd = fileobj.fileno() + + try: + pool = self.pools[fd] + lib.uv_poll_stop(pool) + del self.pools[fd] + except: + pass + None + def modify(self, fileobj, events, data=None): + # fd = -1 + # if isinstance(fileobj, int): + # fd = fileobj + # else: + # fd = fileobj.fileno() + + # try: + # del self.pools[fd] + # except: + # pass + None + def select(timeout=None): + self.interrupted = False + + if timeout != None: + lib.uv_timer_stop(self.timer) + lib.uv_timer_start(self.timer, selector_timer_handler, ffi.cast("uint64_t", int(timeout)), ffi.cast("uint64_t", 0)) + + while True: + if timeout != None and timeout <= 0: + lib.uv_run(self.loop, lib.UV_RUN_NOWAIT) + break + keep_going = int(lib.uv_run(self.loop, lib.UV_RUN_ONCE)) + if not keep_going: + break + + if self.interrupted: + raise KeyboardInterrupt + + if self.tick: + self.tick = False + break + if len(self.list): + break + + + return slice(self.list, 0 , len(self.list)) + def get_key(self, fileobj): + fd = -1 + if isinstance(fileobj, int): + fd = fileobj + else: + fd = fileobj.fileno() + + try: + pool = self.pools[fd] + return fd + except: + return None + + def get_map(self): + None + def close(self): + None + def tick(self): + self.tick = True + # def call_soon(self, *args, **kwargs): + # self.tick() + # return super().call_soon(*args, **kwargs) + # def call_at(self, *args, **kwargs): + # self.tick() + # return super().call_at(*args, **kwargs) +selector = Selector() +# loop = asyncio.SelectorEventLoop(selector) +# asyncio.set_event_loop(loop) + +print(selector.loop, selector.timer) \ No newline at end of file diff --git a/main.py b/socketify.py similarity index 94% rename from main.py rename to socketify.py index 503f7bb..c5d67db 100644 --- a/main.py +++ b/socketify.py @@ -1,9 +1,4 @@ import cffi -import threading -from datetime import datetime -import time -import os - ffi = cffi.FFI() ffi.cdef(""" @@ -56,6 +51,7 @@ struct us_listen_socket_t { unsigned int socket_ext_size; }; void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls); +struct us_loop_t *uws_get_loop(); typedef enum { @@ -107,8 +103,6 @@ typedef struct int options; } uws_app_listen_config_t; - - struct uws_app_s; struct uws_req_s; struct uws_res_s; @@ -499,42 +493,4 @@ class AppOptions: self.dh_params_file_name = dh_params_file_name self.ca_file_name = ca_file_name self.ssl_ciphers = ssl_ciphers - self.ssl_prefer_low_memory_usage = ssl_prefer_low_memory_usage - - -current_http_date = datetime.utcnow().isoformat() + "Z" -stopped = False -def time_thread(): - while not stopped: - global current_http_date - current_http_date = datetime.utcnow().isoformat() + "Z" - time.sleep(1) - -def plaintext(res, req): - res.write_header("Date", current_http_date) - res.write_header("Server", "uws.py") - res.write_header("Content-Type", "text/plain") - res.end("Hello, World!") - -def run_app(): - timing = threading.Thread(target=time_thread, args=()) - timing.start() - app = App(AppOptions(key_file_name="./misc/key.pem", cert_file_name="./misc/cert.pem", passphrase="1234")) - app.get("/", plaintext) - # app.listen(3000, lambda config: print("Listening on port http://localhost:%s now\n" % str(config.port))) - app.listen(AppListenOptions(port=3000, host="0.0.0.0"), lambda config: print("Listening on port http://%s:%d now\n" % (config.host, config.port))) - app.run() - -def create_fork(): - n = os.fork() - # n greater than 0 means parent process - if not n > 0: - run_app() - -for index in range(1): - create_fork() - -run_app() -#pip install git+https://github.com/inducer/pycuda.git (submodules are cloned recursively) -#https://stackoverflow.com/questions/1754966/how-can-i-run-a-makefile-in-setup-py -#https://packaging.python.org/en/latest/tutorials/packaging-projects/ \ No newline at end of file + self.ssl_prefer_low_memory_usage = ssl_prefer_low_memory_usage \ No newline at end of file diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..a64e658 --- /dev/null +++ b/tests.py @@ -0,0 +1,121 @@ +import threading +from datetime import datetime +import time +import os +import asyncio +#import uvloop +from json import dumps as json +from socketify import App, AppOptions, AppListenOptions +# from ujson import dumps as json +# from zzzjson import stringify as json #is too slow with CFFI +# from orjson import dumps +# def json(data): +# return dumps(data).decode("utf-8") +# from rapidjson import dumps as json + +# import cysimdjson +# parser = cysimdjson.JSONParser() + + +# def parse(value): +# return parser.loads(value) + +# def json(value): +# if isinstance(value, dict): +# for key in value: +# return "{\"%s\":\"%s\"}" % (key, value[key]) + + #only supports dict + # return None + + +current_http_date = datetime.utcnow().isoformat() + "Z" +stopped = False +def time_thread(): + while not stopped: + global current_http_date + current_http_date = datetime.utcnow().isoformat() + "Z" + time.sleep(1) + +def plaintext(res, req): + res.write_header("Date", current_http_date) + res.write_header("Server", "socketify") + res.write_header("Content-Type", "text/plain") + res.end("Hello, World!") + +def applicationjson(res, req): + res.write_header("Date", current_http_date) + res.write_header("Server", "socketify") + res.write_header("Content-Type", "application/json") + res.end(json({"message":"Hello, World!"})) + + +def async_route(handler): + return lambda res,req: asyncio.run(handler(res, req)) + +async def plaintext_async(res, req): + # await asyncio.sleep(1) + res.write_header("Date", current_http_date) + res.write_header("Server", "socketify") + res.write_header("Content-Type", "text/plain") + res.end("Hello, World!") + +async def test(): + await asyncio.sleep(1) + print("Hello!") + +def run_app(): + timing = threading.Thread(target=time_thread, args=()) + timing.start() + app = App(AppOptions(key_file_name="./misc/key.pem", cert_file_name="./misc/cert.pem", passphrase="1234")) + app.get("/", plaintext) + # app.get("/json", applicationjson) + # app.get("/plaintext", plaintext) + # app.get("/", async_route(plaintext_async)) + app.listen(3000, lambda config: print("Listening on port http://localhost:%s now\n" % str(config.port))) + # app.listen(AppListenOptions(port=3000, host="0.0.0.0"), lambda config: print("Listening on port http://%s:%d now\n" % (config.host, config.port))) + + # loop = uvloop.new_event_loop() + # asyncio.set_event_loop(loop) + # print(loop) + # asyncio.run(test()) + app.run() + + # app.run() + # asyncio.get_event_loop().run_forever() +def create_fork(): + n = os.fork() + # n greater than 0 means parent process + if not n > 0: + run_app() + +# for index in range(3): +# create_fork() + +# async def main(): +# print('Hello ...') +# await asyncio.sleep(1) +# print('... World!') + +# asyncio.run(main()) +# asyncio.get_event_loop().run_forever() + +run_app() + + +# print(parse("{\"message\":\"Hello, World\"}")["message"]) +# print(json({ "array": [1, 5.5, True, False, None, "{\"message\":\"Hello, World\"}"], "yes": True, "nop": False, "none": None, "int": 1, "float": 5.5, "e": 1.2123123123123124e+37, "string": "{\"message\":\"Hello, World\"}" })) + +# print(json(AppOptions(key_file_name="./misc/key.pem", cert_file_name="./misc/cert.pem", passphrase="1234").__dict__)) + +#pip install git+https://github.com/inducer/pycuda.git (submodules are cloned recursively) +#https://stackoverflow.com/questions/1754966/how-can-i-run-a-makefile-in-setup-py +#https://packaging.python.org/en/latest/tutorials/packaging-projects/ +#pypy3 -m pip install uvloop (not working with pypy) +#apt install pypy3-dev +#pypy3 -m pip install ujson (its slow D=) +#pypy3 -m pip install orjson (dont support pypy) +#pypy3 -m pip install cysimdjson (uses simdjson) is parse only +#pypy3 -m pip install rapidjson (not working with pypy) +#https://github.com/MagicStack/uvloop/issues/380 +#https://foss.heptapod.net/pypy/pypy/-/issues/3740 \ No newline at end of file