kopia lustrzana https://github.com/cirospaciari/socketify.py
fixes, json serializer, wip extensions
rodzic
2f8a0ca6d8
commit
6ae34c3b77
|
@ -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
|
||||
|
|
|
@ -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", "")
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
|
@ -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" },
|
||||
]
|
||||
|
|
2
setup.py
2
setup.py
|
@ -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",
|
||||
|
|
|
@ -5,7 +5,8 @@ from .socketify import (
|
|||
OpCode,
|
||||
SendStatus,
|
||||
CompressOptions,
|
||||
Loop
|
||||
Loop,
|
||||
AppExtension
|
||||
)
|
||||
from .asgi import (
|
||||
ASGI
|
||||
|
|
|
@ -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
|
@ -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):
|
||||
|
|
31
src/tests.py
31
src/tests.py
|
@ -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
|
Ładowanie…
Reference in New Issue