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
- Automatic Ping / Pong Support
- Per Socket Data
- Middlewares
- 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))
- ASGI Server with pub/sub extension for Falcon
- WSGI Server
- [`Middlewares`](https://docs.socketify.dev/middlewares.html)
- [`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`](https://docs.socketify.dev/cli.html)
- [`WSGI Server`](https://docs.socketify.dev/cli.html)
- [`Plugins/Extensions`](https://docs.socketify.dev/extensions.html)
## :mag_right: Upcoming Features
- 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.text = "Hello, World!"
async def on_post(self, req, resp):
# curl -d '{"key1":"value1", "key2":"value2"}' -H "Content-Type: application/json" -X POST http://localhost:8000/
raw_data = await req.stream.read()
print("data", raw_data)
# curl -d '{"name":"test"}' -H "Content-Type: application/json" -X POST http://localhost:8000/
json = await req.media
resp.status = falcon.HTTP_200 # This is the default status
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)
- [WebSockets and Backpressure](websockets-backpressure.md)
- [SSL](ssl.md)
- [CLI Reference](cli.md)
- [Plugins / Extensions](extensions.md)
- [CLI, ASGI and WSGI](cli.md)
- [API Reference](api.md)

Wyświetl plik

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

Wyświetl plik

@ -4,6 +4,7 @@
class App:
def __init__(self, options=None):
def template(self, template_engine):
def json_serializer(self, json_serializer):
def static(self, route, directory):
def get(self, path, handler):
def post(self, path, handler):
@ -34,7 +35,7 @@ class App:
## AppResponse
```python
class AppResponse:
def __init__(self, response, loop, ssl, render=None):
def __init__(self, response, app):
def cork(self, callback):
def set_cookie(self, name, value, options={}):
def run_async(self, task):
@ -81,7 +82,7 @@ class AppResponse:
## AppRequest
```python
class AppRequest:
def __init__(self, request):
def __init__(self, request, app):
def get_cookie(self, name):
def get_url(self):
def get_full_url(self):
@ -123,7 +124,7 @@ class AppOptions:
```python
class WebSocket:
def __init__(self, websocket, ssl, loop):
def __init__(self, websocket, app):
# uuid for socket data, used to free data after socket closes
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"))
```
## 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
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
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
```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.
### 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]
name = "socketify"
version = "0.0.3"
version = "0.0.4"
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.3",
version="0.0.4",
platforms=["any"],
author="Ciro Spaciari",
author_email="ciro.spaciari@gmail.com",

Wyświetl plik

@ -5,7 +5,8 @@ from .socketify import (
OpCode,
SendStatus,
CompressOptions,
Loop
Loop,
AppExtension
)
from .asgi import (
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))
for name, value in headers:
write_header(ssl, res, name, value)
write_header(ssl, res, b"Server", b"socketify.py")
@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))
for name, value in headers:
write_header(ssl, res, name, value)
write_header(ssl, res, b"Server", b"socketify.py")
@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)
for name, value in headers:
write_header(ssl, res, name, value)
write_header(ssl, res, b"Server", b"socketify.py")
@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(
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
if bool(info.has_content):

Wyświetl plik

@ -14,13 +14,40 @@ def extension(request, response, ws):
async def get_cart(self):
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.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(
3000,
lambda config: print("Listening on port http://localhost:%d now\n" % config.port),
)
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