fixes, json serializer, wip extensions

pull/75/head
Ciro 2023-01-06 16:11:19 -03:00
rodzic 2f8a0ca6d8
commit 6ae34c3b77
17 zmienionych plików z 981 dodań i 552 usunięć

Wyświetl plik

@ -34,10 +34,11 @@
- Max Backpressure, Max Timeout, Max Payload and Idle Timeout Support - Max Backpressure, Max Timeout, Max Payload and Idle Timeout Support
- Automatic Ping / Pong Support - Automatic Ping / Pong Support
- Per Socket Data - Per Socket Data
- Middlewares - [`Middlewares`](https://docs.socketify.dev/middlewares.html)
- Templates Support (examples with [`Mako`](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_mako.py) and [`Jinja2`](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_jinja2.py)) - [`Templates`](https://docs.socketify.dev/templates.html) Support (examples with [`Mako`](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_mako.py) and [`Jinja2`](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_jinja2.py))
- ASGI Server with pub/sub extension for Falcon - [`ASGI Server`](https://docs.socketify.dev/cli.html)
- WSGI Server - [`WSGI Server`](https://docs.socketify.dev/cli.html)
- [`Plugins/Extensions`](https://docs.socketify.dev/extensions.html)
## :mag_right: Upcoming Features ## :mag_right: Upcoming Features
- In-Memory Cache Tools - In-Memory Cache Tools

Wyświetl plik

@ -8,12 +8,11 @@ class Home:
resp.content_type = falcon.MEDIA_TEXT # Default is JSON, so override resp.content_type = falcon.MEDIA_TEXT # Default is JSON, so override
resp.text = "Hello, World!" resp.text = "Hello, World!"
async def on_post(self, req, resp): async def on_post(self, req, resp):
# curl -d '{"key1":"value1", "key2":"value2"}' -H "Content-Type: application/json" -X POST http://localhost:8000/ # curl -d '{"name":"test"}' -H "Content-Type: application/json" -X POST http://localhost:8000/
raw_data = await req.stream.read() json = await req.media
print("data", raw_data)
resp.status = falcon.HTTP_200 # This is the default status resp.status = falcon.HTTP_200 # This is the default status
resp.content_type = falcon.MEDIA_TEXT # Default is JSON, so override resp.content_type = falcon.MEDIA_TEXT # Default is JSON, so override
resp.text = raw_data resp.text = json.get("name", "")

Wyświetl plik

@ -35,5 +35,6 @@ With no precedents websocket performance and an really fast HTTP server that can
- [GraphiQL](graphiql.md) - [GraphiQL](graphiql.md)
- [WebSockets and Backpressure](websockets-backpressure.md) - [WebSockets and Backpressure](websockets-backpressure.md)
- [SSL](ssl.md) - [SSL](ssl.md)
- [CLI Reference](cli.md) - [Plugins / Extensions](extensions.md)
- [CLI, ASGI and WSGI](cli.md)
- [API Reference](api.md) - [API Reference](api.md)

Wyświetl plik

@ -13,5 +13,6 @@
- [GraphiQL](graphiql.md) - [GraphiQL](graphiql.md)
- [WebSockets and Backpressure](websockets-backpressure.md) - [WebSockets and Backpressure](websockets-backpressure.md)
- [SSL](ssl.md) - [SSL](ssl.md)
- [CLI Reference](cli.md) - [Plugins / Extensions](extensions.md)
- [CLI, ASGI and WSGI](cli.md)
- [API Reference](api.md) - [API Reference](api.md)

Wyświetl plik

@ -4,6 +4,7 @@
class App: class App:
def __init__(self, options=None): def __init__(self, options=None):
def template(self, template_engine): def template(self, template_engine):
def json_serializer(self, json_serializer):
def static(self, route, directory): def static(self, route, directory):
def get(self, path, handler): def get(self, path, handler):
def post(self, path, handler): def post(self, path, handler):
@ -34,7 +35,7 @@ class App:
## AppResponse ## AppResponse
```python ```python
class AppResponse: class AppResponse:
def __init__(self, response, loop, ssl, render=None): def __init__(self, response, app):
def cork(self, callback): def cork(self, callback):
def set_cookie(self, name, value, options={}): def set_cookie(self, name, value, options={}):
def run_async(self, task): def run_async(self, task):
@ -81,7 +82,7 @@ class AppResponse:
## AppRequest ## AppRequest
```python ```python
class AppRequest: class AppRequest:
def __init__(self, request): def __init__(self, request, app):
def get_cookie(self, name): def get_cookie(self, name):
def get_url(self): def get_url(self):
def get_full_url(self): def get_full_url(self):
@ -123,7 +124,7 @@ class AppOptions:
```python ```python
class WebSocket: class WebSocket:
def __init__(self, websocket, ssl, loop): def __init__(self, websocket, app):
# uuid for socket data, used to free data after socket closes # uuid for socket data, used to free data after socket closes
def get_user_data_uuid(self): def get_user_data_uuid(self):

Wyświetl plik

@ -182,6 +182,22 @@ def route_handler(res, req):
res.run_async(sendfile(res, req, "my_text")) res.run_async(sendfile(res, req, "my_text"))
``` ```
## Using ujson, orjson or any custom JSON serializer
socketify by default uses built in `json` module with have great performance on PyPy, but if you wanna to use another module instead of the default you can just register using `app.json_serializer(module)`
```python
from socketify import App
import ujson
app = App()
# set json serializer to ujson
# json serializer must have dumps and loads functions
app.json_serializer(ujson)
app.get("/", lambda res, req: res.end({"Hello":"World!"}))
```
## Raw socket pointer ## Raw socket pointer
If for some reason you need the raw socket pointer you can use `res.get_native_handle()` and will get an CFFI handler. If for some reason you need the raw socket pointer you can use `res.get_native_handle()` and will get an CFFI handler.

55
docs/extensions.md 100644
Wyświetl plik

@ -0,0 +1,55 @@
# Plugins / Extensions
You can add more functionality to request, response, and websocket objects, for this you can use `app.register(extension)` to register an extension.
Be aware that using extensions can have a performance impact and using it with `request_response_factory_max_items`, `websocket_factory_max_items`
or the equivalent on CLI `--req-res-factory-maxitems`, `--ws-factory-maxitems` will reduce this performance impact.
Extensions must follow the signature `def extension(request, response, ws)`, request, response, and ws objects contain `method` decorator that binds a method to an instance,
and also a `property(name: str, default_value: any = None)` that dynamic adds an property to the instance.
```python
from socketify import App, OpCode
app = App()
def extension(request, response, ws):
@request.method
async def get_user(self):
token = self.get_header("token")
return { "name": "Test" } if token else { "name", "Anonymous" }
@response.method
def msgpack(self, value: any):
self.write_header(b'Content-Type', b'application/msgpack')
data = msgpack.packb(value, default=encode_datetime, use_bin_type=True)
return self.end(data)
@ws.method
def send_pm(self, to_username: str, message: str):
user_data = self.get_user_data()
pm_topic = f"pm-{to_username}+{user_data.username}"
# if topic exists just send the message
if app.num_subscribers(pm_topic) > 0:
# send private message
return self.publish(pm_topic, message, OpCode.TEXT)
# if the topic not exists create it and signal the user
# subscribe to the conversation
self.subscribe(pm_topic)
# signal user that you want to talk and create an pm room
# all users must subscribe to signal-{username}
self.publish(f"signal-{to_username}", {
"type": "pm",
"username": user_data.username,
"message": message
}, OpCode.TEXT)
# this property can be used on extension methods and/or middlewares
request.property("cart", [])
# extensions must be registered before routes
app.register(extension)
```
### Next [CLI, ASGI and WSGI](cli.md)

Wyświetl plik

@ -1,5 +1,5 @@
## GraphiQL Support ## GraphiQL Support
In /src/examples/helper/graphiql.py we implemented an helper for using graphiQL with strawberry. In [`/src/examples/helper/graphiql.py`](https://github.com/cirospaciari/socketify.py/blob/main/examples/graphiql.py) we implemented an helper for using graphiQL with strawberry.
### Usage ### Usage
```python ```python

Wyświetl plik

@ -49,4 +49,4 @@ You probably want shared compressor if dealing with larger JSON messages, or 4kb
idle_timeout is roughly the amount of seconds that may pass between messages. Being idle for more than this, and the connection is severed. This means you should make your clients send small ping messages every now and then, to keep the connection alive. The server will automatically send pings in case it needs to. idle_timeout is roughly the amount of seconds that may pass between messages. Being idle for more than this, and the connection is severed. This means you should make your clients send small ping messages every now and then, to keep the connection alive. The server will automatically send pings in case it needs to.
### Next [SSL](ssl.md) ### Next [Plugins / Extensions](extensions.md)

Wyświetl plik

@ -0,0 +1,16 @@
from socketify import App
import ujson
app = App()
# set json serializer to ujson
# json serializer must have dumps and loads functions
app.json_serializer(ujson)
app.get("/", lambda res, req: res.end({"Hello":"World!"}))
app.listen(
3000,
lambda config: print("Listening on port http://localhost:%d now\n" % config.port),
)
app.run()

Wyświetl plik

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "socketify" name = "socketify"
version = "0.0.3" version = "0.0.4"
authors = [ authors = [
{ name="Ciro Spaciari", email="ciro.spaciari@gmail.com" }, { 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( setuptools.setup(
name="socketify", name="socketify",
version="0.0.3", version="0.0.4",
platforms=["any"], platforms=["any"],
author="Ciro Spaciari", author="Ciro Spaciari",
author_email="ciro.spaciari@gmail.com", author_email="ciro.spaciari@gmail.com",

Wyświetl plik

@ -5,7 +5,8 @@ from .socketify import (
OpCode, OpCode,
SendStatus, SendStatus,
CompressOptions, CompressOptions,
Loop Loop,
AppExtension
) )
from .asgi import ( from .asgi import (
ASGI ASGI

Wyświetl plik

@ -374,7 +374,6 @@ def uws_asgi_corked_response_start_handler(res, user_data):
lib.socketify_res_write_int_status(ssl, res, int(status)) lib.socketify_res_write_int_status(ssl, res, int(status))
for name, value in headers: for name, value in headers:
write_header(ssl, res, name, value) write_header(ssl, res, name, value)
write_header(ssl, res, b"Server", b"socketify.py")
@ffi.callback("void(uws_res_t*, void*)") @ffi.callback("void(uws_res_t*, void*)")
@ -384,7 +383,6 @@ def uws_asgi_corked_accept_handler(res, user_data):
lib.socketify_res_write_int_status(ssl, res, int(status)) lib.socketify_res_write_int_status(ssl, res, int(status))
for name, value in headers: for name, value in headers:
write_header(ssl, res, name, value) write_header(ssl, res, name, value)
write_header(ssl, res, b"Server", b"socketify.py")
@ffi.callback("void(uws_res_t*, void*)") @ffi.callback("void(uws_res_t*, void*)")
@ -392,7 +390,6 @@ def uws_asgi_corked_ws_accept_handler(res, user_data):
(ssl, headers) = ffi.from_handle(user_data) (ssl, headers) = ffi.from_handle(user_data)
for name, value in headers: for name, value in headers:
write_header(ssl, res, name, value) write_header(ssl, res, name, value)
write_header(ssl, res, b"Server", b"socketify.py")
@ffi.callback("void(uws_res_t*, void*)") @ffi.callback("void(uws_res_t*, void*)")

Plik diff jest za duży Load Diff

Wyświetl plik

@ -201,9 +201,6 @@ def wsgi(ssl, response, info, user_data, aborted):
lib.uws_res_write_header( lib.uws_res_write_header(
ssl, response, key_data, len(key_data), value_data, len(value_data) ssl, response, key_data, len(key_data), value_data, len(value_data)
) )
lib.uws_res_write_header(
ssl, response, b'Server', 6, b'socketify.py', 12
)
# check for body # check for body
if bool(info.has_content): if bool(info.has_content):

Wyświetl plik

@ -14,13 +14,40 @@ def extension(request, response, ws):
async def get_cart(self): async def get_cart(self):
return [{ "quantity": 10, "name": "T-Shirt" }] return [{ "quantity": 10, "name": "T-Shirt" }]
request.property("token", None) @response.method
def send(self, content: any, content_type: str = b'text/plain', status=200):
self.write_header(b'Content-Type', content_type)
self.write_status(status)
self.end(content)
request.property("token", "testing")
# extensions must be registered before routes
app.register(extension) app.register(extension)
app.get("/", lambda res, req: res.end("Hello World!")) async def home(res, req):
print("token", req.token)
cart = await req.get_cart()
print("cart", cart)
user = await req.get_user()
print("user", user)
print("token", req.token)
res.send("Hello World!")
app.get("/", home)
app.listen( app.listen(
3000, 3000,
lambda config: print("Listening on port http://localhost:%d now\n" % config.port), lambda config: print("Listening on port http://localhost:%d now\n" % config.port),
) )
app.run() app.run()
# uws_websocket_upgrade_handler
# uws_generic_method_handler
# uws_websocket_drain_handler
# uws_websocket_subscription_handler
# uws_websocket_open_handler
# uws_websocket_message_handler
# uws_websocket_pong_handler
# uws_websocket_ping_handler
# uws_websocket_close_handler
# uws_websocket_subscription_handler