Porównaj commity

..

No commits in common. "main" and "v0.0.9" have entirely different histories.
main ... v0.0.9

48 zmienionych plików z 1069 dodań i 2307 usunięć

Wyświetl plik

@ -25,7 +25,7 @@ jobs:
cmake -DCMAKE_BUILD_TYPE=Release -GNinja .. && ninja crypto ssl cmake -DCMAKE_BUILD_TYPE=Release -GNinja .. && ninja crypto ssl
cd ..\..\..\..\ cd ..\..\..\..\
cl /MD /W3 /D /EHsc /Zc:__cplusplus /Ox /DLL /D_WINDLL /LD /D "NOMINMAX" /D "WIN32_LEAN_AND_MEAN" /D "UWS_NO_ZLIB" /D "UWS_WITH_PROXY" /D "LIBUS_USE_LIBUV" /I native/src/ /I uWebSockets/src /I uWebSockets/capi /I uWebSockets/uSockets/boringssl/include /D "LIBUS_USE_OPENSSL" /std:c++20 /I C:\vcpkg\packages\libuv_x64-windows-static-md\include /I uWebSockets/uSockets/src /Felibsocketify_windows_amd64.dll ./native/src/libsocketify.cpp uWebSockets/uSockets/src/*.c uWebSockets/uSockets/src/crypto/*.cpp uWebSockets/uSockets/src/eventing/*.c uWebSockets/uSockets/src/crypto/*.c advapi32.lib uWebSockets/uSockets/boringssl/amd64/ssl/ssl.lib uWebSockets/uSockets/boringssl/amd64/crypto/crypto.lib C:\vcpkg\installed\x64-windows-static-md\lib\libuv.lib iphlpapi.lib userenv.lib psapi.lib user32.lib shell32.lib dbghelp.lib ole32.lib uuid.lib ws2_32.lib cl /MD /W3 /D /EHsc /Zc:__cplusplus /Ox /DLL /D_WINDLL /LD /D "NOMINMAX" /D "WIN32_LEAN_AND_MEAN" /D "UWS_NO_ZLIB" /D "UWS_WITH_PROXY" /D "LIBUS_USE_LIBUV" /I native/src/ /I uWebSockets/src /I uWebSockets/capi /I uWebSockets/uSockets/boringssl/include /D "LIBUS_USE_OPENSSL" /std:c++20 /I C:\vcpkg\packages\libuv_x64-windows-static-md\include /I uWebSockets/uSockets/src /Felibsocketify_windows_amd64.dll ./native/src/libsocketify.cpp uWebSockets/uSockets/src/*.c uWebSockets/uSockets/src/crypto/*.cpp uWebSockets/uSockets/src/eventing/*.c uWebSockets/uSockets/src/crypto/*.c advapi32.lib uWebSockets/uSockets/boringssl/amd64/ssl/ssl.lib uWebSockets/uSockets/boringssl/amd64/crypto/crypto.lib C:\vcpkg\installed\x64-windows-static-md\lib\uv_a.lib iphlpapi.lib userenv.lib psapi.lib user32.lib
git add libsocketify_windows_amd64.dll git add libsocketify_windows_amd64.dll
git config --global user.email "ciro.spaciari@gmail.com" git config --global user.email "ciro.spaciari@gmail.com"

3
.gitignore vendored
Wyświetl plik

@ -5,5 +5,4 @@ __pycache__
*.o *.o
node_modules/ node_modules/
yarn.lock yarn.lock
.vscode .vscode
/venv

Wyświetl plik

@ -1,26 +1,26 @@
# socketify.py # socketify.py
<p align="center"> <p align="center">
<a href="https://github.com/cirospaciari/socketify.py"><img src="https://raw.githubusercontent.com/cirospaciari/socketify.py/main/misc/logo.png" alt="Logo" height=170></a> <a href="https://github.com/cirospaciari/socketify.py"><img src="https://raw.githubusercontent.com/cirospaciari/socketify.py/main/misc/logo.png" alt="Logo" height=170></a>
<br /> <br />
<br /> <br />
<a href="https://github.com/cirospaciari/socketify.py/actions/workflows/linux.yml" target="_blank"><img src="https://github.com/cirospaciari/socketify.py/actions/workflows/linux.yml/badge.svg" /></a>
<a href="https://github.com/cirospaciari/socketify.py/actions/workflows/windows.yml" target="_blank"><img src="https://github.com/cirospaciari/socketify.py/actions/workflows/windows.yml/badge.svg" /></a>
<a href="https://github.com/cirospaciari/socketify.py/actions/workflows/macos.yml" target="_blank"><img src="https://github.com/cirospaciari/socketify.py/actions/workflows/macos.yml/badge.svg" /></a>
<a href="https://github.com/cirospaciari/socketify.py/actions/workflows/macos_arm64.yml" target="_blank"><img src="https://github.com/cirospaciari/socketify.py/actions/workflows/macos_arm64.yml/badge.svg" /></a>
<br/>
<a href='https://github.com/cirospaciari/socketify.py'><img alt='GitHub Clones' src='https://img.shields.io/badge/dynamic/json?color=success&label=Clones&query=count&url=https://gist.githubusercontent.com/cirospaciari/2243d59951f4abe4fd2000f1e20bc561/raw/clone.json&logo=github'></a> <a href='https://github.com/cirospaciari/socketify.py'><img alt='GitHub Clones' src='https://img.shields.io/badge/dynamic/json?color=success&label=Clones&query=count&url=https://gist.githubusercontent.com/cirospaciari/2243d59951f4abe4fd2000f1e20bc561/raw/clone.json&logo=github'></a>
<a href='https://pypi.org/project/socketify/' target="_blank"><img alt='PyPI Downloads' src='https://static.pepy.tech/personalized-badge/socketify?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads'></a>
<a href="https://github.com/sponsors/cirospaciari/" target="_blank"><img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/cirospaciari"/></a> <a href="https://github.com/sponsors/cirospaciari/" target="_blank"><img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/cirospaciari"/></a>
<a href='https://discord.socketify.dev/' target="_blank"><img alt='Discord' src='https://img.shields.io/discord/1042529276219641906?label=Discord'></a>
</p> </p>
<div align="center">
<a href="https://docs.socketify.dev">Documentation</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://discord.socketify.dev/">Discord</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://github.com/cirospaciari/socketify.py/issues">Issues</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://github.com/cirospaciari/socketify.py/tree/main/examples">Examples</a>
<br />
</div>
## :bookmark_tabs: Documentation
See the full docs in [docs.socketify.dev](https://docs.socketify.dev) or in [/docs/README.md](docs/README.md)
Also take a look in the examples in [/examples](https://github.com/cirospaciari/socketify.py/tree/main/examples)
## 💡 Features ## 💡 Features
- WebSocket with pub/sub support - WebSocket with pub/sub support
@ -44,7 +44,6 @@
- [`Plugins/Extensions`](https://docs.socketify.dev/extensions.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
- Fetch like API powered by libuv - Fetch like API powered by libuv
- Async file IO powered by libuv - Async file IO powered by libuv
@ -60,7 +59,6 @@ We created and adapted the full C API from [uNetworking/uWebSockets](https://git
Join Github [`Discussions`](https://github.com/cirospaciari/socketify.py/discussions) or [`Discord`](https://discord.socketify.dev/) for help and have a look at the development progress. Join Github [`Discussions`](https://github.com/cirospaciari/socketify.py/discussions) or [`Discord`](https://discord.socketify.dev/) for help and have a look at the development progress.
## :zap: Benchmarks ## :zap: Benchmarks
Socketify WebFramework HTTP requests per second (Linux x64) Socketify WebFramework HTTP requests per second (Linux x64)
![image](https://raw.githubusercontent.com/cirospaciari/socketify.py/main/misc/bench-bar-graph-general.png) ![image](https://raw.githubusercontent.com/cirospaciari/socketify.py/main/misc/bench-bar-graph-general.png)
@ -77,14 +75,14 @@ WebSocket messages per second (Linux x64)
![image](https://raw.githubusercontent.com/cirospaciari/socketify.py/main/misc/bench-bar-graph-websockets.png) ![image](https://raw.githubusercontent.com/cirospaciari/socketify.py/main/misc/bench-bar-graph-websockets.png)
Http tested with TFB tool plaintext benchmark<br/> Http tested with TFB tool plaintext benchmark<br/>
WebSocket tested with [Bun.sh](https://bun.sh) bench chat-client <br/> WebSocket tested with [Bun.sh](https://bun.sh) bench chat-client <br/>
Source code in [TechEmPower](https://github.com/TechEmpower/FrameworkBenchmarks) and for websockets in [bench](https://github.com/cirospaciari/socketify.py/tree/main/bench)<br/> Source code in [TechEmPower](https://github.com/TechEmpower/FrameworkBenchmarks) and for websockets in [bench](https://github.com/cirospaciari/socketify.py/tree/main/bench)<br/>
Machine OS: Debian GNU/Linux bookworm/sid x86_64 Kernel: 6.0.0-2-amd64 CPU: Intel i7-7700HQ (8) @ 3.800GHz Memory: 32066MiB Machine OS: Debian GNU/Linux bookworm/sid x86_64 Kernel: 6.0.0-2-amd64 CPU: Intel i7-7700HQ (8) @ 3.800GHz Memory: 32066MiB
## 📦 Installation ## 📦 Installation
For macOS x64 & Silicon, Linux x64, Windows For macOS x64 & Silicon, Linux x64, Windows
```bash ```bash
@ -96,11 +94,9 @@ pypy3 -m pip install -e socketify
``` ```
Using install via requirements.txt Using install via requirements.txt
```text ```text
socketify socketify
``` ```
```bash ```bash
pip install -r ./requirements.txt pip install -r ./requirements.txt
#or specify PyPy3 #or specify PyPy3
@ -110,28 +106,19 @@ pypy3 -m pip install -r ./requirements.txt
If you are using linux or macOS, you may need to install libuv and zlib in your system If you are using linux or macOS, you may need to install libuv and zlib in your system
macOS macOS
```bash ```bash
brew install libuv brew install libuv
brew install zlib brew install zlib
``` ```
Linux (Ubuntu/Debian) Linux
```bash ```bash
apt install libuv1 zlib1g apt install libuv1 zlib1g
``` ```
Linux (RHEL/OEL)
```bash
yum install cmake zlib-devel libuv-devel
```
## 🤔 Usage ## 🤔 Usage
Hello world app Hello world app
```python ```python
from socketify import App from socketify import App
@ -142,7 +129,6 @@ app.run()
``` ```
SSL version sample SSL version sample
``` python ``` python
from socketify import App, AppOptions from socketify import App, AppOptions
@ -153,9 +139,8 @@ app.run()
``` ```
WebSockets WebSockets
```python ```python
from socketify import App, OpCode, CompressOptions from socketify import App, AppOptions, OpCode, CompressOptions
def ws_open(ws): def ws_open(ws):
print('A WebSocket got connected!') print('A WebSocket got connected!')
@ -174,7 +159,7 @@ app.ws("/*", {
'message': ws_message, 'message': ws_message,
'drain': lambda ws: print(f'WebSocket backpressure: {ws.get_buffered_amount()}'), 'drain': lambda ws: print(f'WebSocket backpressure: {ws.get_buffered_amount()}'),
'close': lambda ws, code, message: print('WebSocket closed'), 'close': lambda ws, code, message: print('WebSocket closed'),
'subscription': lambda ws, topic, subscriptions, subscriptions_before: print(f'subscribe/unsubscribe on topic {topic} {subscriptions} {subscriptions_before}'), 'subscription': lambda ws, topic, subscriptions, subscriptions_before: print(f'subscription/unsubscription on topic {topic} {subscriptions} {subscriptions_before}'),
}) })
app.any("/", lambda res,req: res.end("Nothing to see here!'")) app.any("/", lambda res,req: res.end("Nothing to see here!'"))
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % (config.port))) app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % (config.port)))
@ -184,7 +169,6 @@ app.run()
We have more than 20 examples [click here](https://github.com/cirospaciari/socketify.py/tree/main/examples) for more We have more than 20 examples [click here](https://github.com/cirospaciari/socketify.py/tree/main/examples) for more
## :hammer: Building from source ## :hammer: Building from source
```bash ```bash
#clone and update submodules #clone and update submodules
git clone https://github.com/cirospaciari/socketify.py.git git clone https://github.com/cirospaciari/socketify.py.git
@ -201,7 +185,6 @@ pypy3 -m pip uninstall socketify
``` ```
## :briefcase: Commercially supported ## :briefcase: Commercially supported
I'm a Brazilian consulting & contracting company dealing with anything related with [socketify.py](https://github.com/cirospaciari/socketify.py) and [socketify.rb](https://github.com/cirospaciari/socketify.rb) I'm a Brazilian consulting & contracting company dealing with anything related with [socketify.py](https://github.com/cirospaciari/socketify.py) and [socketify.rb](https://github.com/cirospaciari/socketify.rb)
Don't hesitate sending a mail if you are in need of advice, support, or having other business inquiries in mind. We'll figure out what's best for both parties. Don't hesitate sending a mail if you are in need of advice, support, or having other business inquiries in mind. We'll figure out what's best for both parties.
@ -209,7 +192,6 @@ Don't hesitate sending a mail if you are in need of advice, support, or having o
Special thank's to [uNetworking AB](https://github.com/uNetworking) to develop [uWebSockets](https://github.com/uNetworking/uWebSockets), [uSockets](https://github.com/uNetworking/uSockets) and allow us to bring this features and performance to Python and PyPy Special thank's to [uNetworking AB](https://github.com/uNetworking) to develop [uWebSockets](https://github.com/uNetworking/uWebSockets), [uSockets](https://github.com/uNetworking/uSockets) and allow us to bring this features and performance to Python and PyPy
## :heart: Sponsors ## :heart: Sponsors
If you like to see this project thrive, you can sponsor us on GitHub too. We need all the help we can get. If you like to see this project thrive, you can sponsor us on GitHub too. We need all the help we can get.
Thank you [`Otavio Augusto`](https://github.com/middlebaws) to be the first sponsor of this project! Thank you [`Otavio Augusto`](https://github.com/middlebaws) to be the first sponsor of this project!
@ -217,17 +199,21 @@ Thank you [`Otavio Augusto`](https://github.com/middlebaws) to be the first spon
<a href="https://github.com/sponsors/cirospaciari/" target="_blank"><img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/cirospaciari"/></a> <a href="https://github.com/sponsors/cirospaciari/" target="_blank"><img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/cirospaciari"/></a>
## :star: Stargazers ## :star: Stargazers
[![Stargazers repo roster for @cirospaciari/socketify.py](https://reporoster.com/stars/dark/cirospaciari/socketify.py)](https://github.com/cirospaciari/socketify.py/stargazers) [![Stargazers repo roster for @cirospaciari/socketify.py](https://reporoster.com/stars/dark/cirospaciari/socketify.py)](https://github.com/cirospaciari/socketify.py/stargazers)
## :wrench: Forkers ## :wrench: Forkers
[![Forkers repo roster for @cirospaciari/socketify.py](https://reporoster.com/forks/dark/cirospaciari/socketify.py)](https://github.com/cirospaciari/socketify.py/network/members) [![Forkers repo roster for @cirospaciari/socketify.py](https://reporoster.com/forks/dark/cirospaciari/socketify.py)](https://github.com/cirospaciari/socketify.py/network/members)
## :grey_question: uvloop
## :question: socketify.py vs japronto
People really want to compare with japronto, but this projects are not really comparable. Socketify is an active project and will be maintained over time with security updates and new features, japronto don't get any github updates since 2020 and don't get any src update since 2018, japronto don't support SSL, WebSockets, [`PyPy3`](https://www.pypy.org/), Windows or macOS Silicon, socketify will support Http3 and a lot more features.
And yes, we can be faster than japronto when all our features and goals are achieved, and we are probably faster than any current maintained solution out there.
## :grey_question: uvloop
We don't use uvloop, because uvloop don't support Windows and PyPy3 at this moment, this can change in the future, but right now we want to implement our own libuv + asyncio solution, and a lot more. We don't use uvloop, because uvloop don't support Windows and PyPy3 at this moment, this can change in the future, but right now we want to implement our own libuv + asyncio solution, and a lot more.
## :dizzy: CFFI vs Cython vs HPy ## :dizzy: CFFI vs Cython vs HPy
Cython performs really well on Python3 but really bad on PyPy3, CFFI are chosen for better support PyPy3 until we got our hands on an stable [`HPy`](https://hpyproject.org/) integration.
Cython performs really well on Python3 but really bad on PyPy3, CFFI are chosen for better support PyPy3 until we got our hands on a stable [`HPy`](https://hpyproject.org/) integration.

Wyświetl plik

@ -15,7 +15,7 @@ class SSGIHttpResponse:
# send chunk of data, can be used to perform with less backpressure than using send # send chunk of data, can be used to perform with less backpressure than using send
# total_size is the sum of all lengths in bytes of all chunks to be sended # total_size is the sum of all lengths in bytes of all chunks to be sended
# connection will end when total_size is met # connection will end when total_size is met
# returns tuple(bool, bool) first bool represents if the chunk is successfully sended, the second if the connection has ended # returns tuple(bool, bool) first bool represents if the chunk is succefully sended, the second if the connection has ended
def send_chunk(self, chunk: Union[str, bytes, bytearray, memoryview], total_size: int = False) -> Awaitable: def send_chunk(self, chunk: Union[str, bytes, bytearray, memoryview], total_size: int = False) -> Awaitable:
pass pass

Wyświetl plik

@ -8,10 +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!"
def on_post(self, req, resp): def on_post(self, req, resp):
raw_data = req.stream.read() raw_data = req.stream.getvalue()
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 = 'Ok' resp.text = raw_data
@ -22,4 +23,4 @@ home = Home()
app.add_route("/", home) app.add_route("/", home)
if __name__ == "__main__": if __name__ == "__main__":
WSGI(app).listen(8000, lambda config: print(f"Listening on port http://localhost:{config.port} now\n")).run(workers=1) WSGI(app).listen(8000, lambda config: print(f"Listening on port http://localhost:{config.port} now\n")).run(workers=8)

Wyświetl plik

@ -1,5 +1,6 @@
from socketify import ASGI from socketify import ASGI
async def app(scope, receive, send): async def app(scope, receive, send):
assert scope['type'] == 'http' assert scope['type'] == 'http'
@ -19,4 +20,4 @@ async def app(scope, receive, send):
if __name__ == "__main__": if __name__ == "__main__":
ASGI(app, lifespan=False).listen(8000, lambda config: print(f"Listening on port http://localhost:{config.port} now\n")).run(1) ASGI(app).listen(8000, lambda config: print(f"Listening on port http://localhost:{config.port} now\n")).run(8)

Wyświetl plik

@ -1,70 +1,8 @@
payload = None from socketify import WSGI
with open("xml.zip", "rb") as file:
payload = file.read()
chunk_size = 64 * 1024
content_length = len(payload)
def app_chunked(environ, start_response):
start_response('200 OK', [('Content-Type', 'application/zip'), ('Transfer-Encoding', 'chunked')])
sended = 0
while content_length > sended:
end = sended + chunk_size
yield payload[sended:end]
sended = end
def app(environ, start_response): def app(environ, start_response):
start_response('200 OK', [('Content-Type', 'application/zip'), ('Content-Length', str(content_length))])
sended = 0
while content_length > sended:
end = sended + chunk_size
yield payload[sended:end]
sended = end
# import gc
# gc.collect()
# gc.set_threshold(50, 3, 3)
# import tracemalloc
# tracemalloc.start()
def app_hello(environ, start_response):
# start_response('200 OK', [('Content-Type', 'text/plain'), ('Content-Length', '13')])
start_response('200 OK', [('Content-Type', 'text/plain')]) start_response('200 OK', [('Content-Type', 'text/plain')])
yield b'Hello, World!\n'
return [ b'Hello, World!']
if __name__ == "__main__": if __name__ == "__main__":
# import fastwsgi WSGI(app).listen(8000, lambda config: print(f"Listening on port http://localhost:{config.port} now\n")).run(8)
# fastwsgi.run(wsgi_app=app_hello, host='127.0.0.1', port=8000, loglevel=0)
# from meinheld import server
# server.listen(("0.0.0.0", 8000))
# server.run(app_hello)
from socketify import WSGI
WSGI(app_hello).listen(8000, lambda config: print(f"Listening on port http://localhost:{config.port} now\n")).run(1)
# def run_app():
# import fastwsgi
# fastwsgi.run(wsgi_app=app_hello, host='127.0.0.1', port=8000)
# import os
# pid_list = []
# # fork limiting the cpu count - 1
# for _ in range(1, 8):
# pid = os.fork()
# # n greater than 0 means parent process
# if not pid > 0:
# run_app()
# break
# pid_list.append(pid)
# run_app() # run app on the main process too :)
# # sigint everything to graceful shutdown
# import signal
# for pid in pid_list:
# os.kill(pid, signal.SIGINT)

Wyświetl plik

@ -1,4 +0,0 @@
while true
do
wrk -t1 -c200 -d1 -H 'Connection: keep-alive' http://127.0.0.1:8000 > /dev/null
done

Wyświetl plik

@ -1,83 +0,0 @@
import sys
import io
import time
import datetime
import socket
import optparse
parser = optparse.OptionParser("usage: %prog [options]", add_help_option=False)
parser.add_option("-h", "--host", dest="host", default='127.0.0.1', type="string")
parser.add_option("-p", "--port", dest="port", default=3000, type="int")
(opt, args) = parser.parse_args()
def get_request(path = r'/', host = '127.0.0.1', port = 3000):
req = f'GET {path}' + r' HTTP/1.1' + '\r\n'
req += f'Host: {host}:{port}\r\n'
req += r'User-Agent: curl/7.66.0' + '\r\n'
req += r'Accept: */*' + '\r\n'
req += '\r\n'
return req
payload_tiny = get_request(host = opt.host, port = opt.port)
payload_tiny = payload_tiny.encode('utf-8')
def create_sock(timeout = 0.001):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
sock.connect((opt.host, opt.port))
return sock
sock = create_sock()
sock.sendall(payload_tiny)
time.sleep(0.020)
resp = sock.recv(4096)
print('====== response ========')
print(resp.decode('utf-8'))
print('========================')
sock.close()
start_time = datetime.datetime.now()
test1_limit = start_time + datetime.timedelta(seconds = 1)
test2_limit = test1_limit + datetime.timedelta(seconds = 10)
sock = create_sock()
while True:
if datetime.datetime.now() >= test1_limit:
break
sock.sendall(payload_tiny)
try:
resp = sock.recv(4096)
except socket.timeout:
pass
print(f'Test 1 completed!')
sock.close()
req_num = 1000*1000
payload_huge = payload_tiny * req_num
#print(len(payload_huge))
print(f'Run test 2 ...')
totalsent = 0
totalresp = b''
sock = create_sock()
while True:
if datetime.datetime.now() >= test2_limit:
print(f'Test 2: Timeout exceeded!')
break
try:
rc = sock.send(payload_huge[totalsent:])
if rc == 0:
#raise RuntimeError("socket connection broken")
pass
totalsent += rc
resp = sock.recv(65*1024)
totalresp += resp
except socket.timeout:
pass
except ConnectionResetError:
print(f'totalsent = {totalsent}, totalrecv = {len(totalresp)}')
print(f'LastResp: {totalresp[-256:]}')
raise
sock.close()
print("==== Test Finish =====")

Wyświetl plik

@ -1,12 +0,0 @@
#!/usr/bin/env python3
from quart import Quart
app = Quart(__name__)
@app.get("/")
async def plaintext():
return "Hello, World!", {"Content-Type": "text/plain"}
# Quart perform really baddly for sure needs more optimizations, but socketify ASGI + PyPy performs better than uvicorn+httptools+gunicorn

Wyświetl plik

@ -2,15 +2,12 @@ from socketify import App
import os import os
import multiprocessing import multiprocessing
import asyncio import asyncio
def run_app(): def run_app():
app = App(request_response_factory_max_items=200_000) app = App(request_response_factory_max_items=200_000)
router = app.router()
@router.get("/")
async def home(res, req): async def home(res, req):
res.send(b"Hello, World!") res.end("Hello, World!")
app.get("/", home)
app.listen( app.listen(
8000, 8000,
lambda config: print( lambda config: print(
@ -29,7 +26,7 @@ def create_fork():
# fork limiting the cpu count - 1 # fork limiting the cpu count - 1
# for i in range(1, multiprocessing.cpu_count()): for i in range(1, multiprocessing.cpu_count()):
# create_fork() create_fork()
run_app() # run app on the main process too :) run_app() # run app on the main process too :)

Wyświetl plik

@ -11,22 +11,12 @@
<a href="https://github.com/cirospaciari/socketify.py/actions/workflows/macos_arm64.yml" target="_blank"><img src="https://github.com/cirospaciari/socketify.py/actions/workflows/macos_arm64.yml/badge.svg" /></a> <a href="https://github.com/cirospaciari/socketify.py/actions/workflows/macos_arm64.yml" target="_blank"><img src="https://github.com/cirospaciari/socketify.py/actions/workflows/macos_arm64.yml/badge.svg" /></a>
<br/> <br/>
<a href='https://github.com/cirospaciari/socketify.py'><img alt='GitHub Clones' src='https://img.shields.io/badge/dynamic/json?color=success&label=Clones&query=count&url=https://gist.githubusercontent.com/cirospaciari/2243d59951f4abe4fd2000f1e20bc561/raw/clone.json&logo=github'></a> <a href='https://github.com/cirospaciari/socketify.py'><img alt='GitHub Clones' src='https://img.shields.io/badge/dynamic/json?color=success&label=Clones&query=count&url=https://gist.githubusercontent.com/cirospaciari/2243d59951f4abe4fd2000f1e20bc561/raw/clone.json&logo=github'></a>
<a href='https://pypi.org/project/socketify/' target="_blank"><img alt='PyPI Downloads' src='https://static.pepy.tech/personalized-badge/socketify?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads'></a>
<a href="https://github.com/sponsors/cirospaciari/" target="_blank"><img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/cirospaciari"/></a> <a href="https://github.com/sponsors/cirospaciari/" target="_blank"><img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/cirospaciari"/></a>
<a href='https://discord.socketify.dev/' target="_blank"><img alt='Discord' src='https://img.shields.io/discord/1042529276219641906?label=Discord'></a>
</p>
<div align="center">
<a href="https://github.com/cirospaciari/socketify.py">Github</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://discord.socketify.dev/">Discord</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://github.com/cirospaciari/socketify.py/issues">Issues</a>
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>
<a href="https://github.com/cirospaciari/socketify.py/tree/main/examples">Examples</a>
<br />
</div>
</p>
<br/> <br/>
Socketify.py is a reliable, high-performance Python web framework for building large-scale app backends and microservices. Socketify.py is a reliable, high-performance Python web framework for building large-scale app backends and microservices.
With no precedents websocket performance and a really fast HTTP server that can delivery encrypted TLS 1.3 quicker than most alternative servers can do even unencrypted, cleartext messaging. With no precedents websocket performance and a really fast HTTP server that can delivery encrypted TLS 1.3 quicker than most alternative servers can do even unencrypted, cleartext messaging.
@ -48,4 +38,3 @@ With no precedents websocket performance and a really fast HTTP server that can
- [SSL](ssl.md) - [SSL](ssl.md)
- [CLI, ASGI and WSGI](cli.md) - [CLI, ASGI and WSGI](cli.md)
- [API Reference](api.md) - [API Reference](api.md)
- [Examples](examples.md)

Wyświetl plik

@ -16,4 +16,3 @@
- [SSL](ssl.md) - [SSL](ssl.md)
- [CLI, ASGI and WSGI](cli.md) - [CLI, ASGI and WSGI](cli.md)
- [API Reference](api.md) - [API Reference](api.md)
- [Examples](examples.md)

Wyświetl plik

@ -38,12 +38,11 @@ class App:
``` ```
## Response ## AppResponse
```python ```python
class Response: class AppResponse:
def __init__(self, response, app): def __init__(self, response, app):
def cork(self, callback): def cork(self, callback):
def close(self):
def set_cookie(self, name, value, options={}): def set_cookie(self, name, value, options={}):
def run_async(self, task): def run_async(self, task):
async def get_form_urlencoded(self, encoding="utf-8"): async def get_form_urlencoded(self, encoding="utf-8"):
@ -62,7 +61,7 @@ class Response:
def get_proxied_remote_address_bytes(self): def get_proxied_remote_address_bytes(self):
def get_proxied_remote_address(self): def get_proxied_remote_address(self):
def cork_send(self, message: any, content_type: str = b'text/plain', status : str | bytes | int = b'200 OK', headers=None, end_connection=False): def cork_send(self, message: any, content_type: str = b'text/plain', status : str | bytes | int = b'200 OK', headers=None, end_connection=False):
def send(self, message: any = b"", content_type: str = b'text/plain', status : str | bytes | int = b'200 OK', headers=None, end_connection=False): def send(self, message: any, content_type: str = b'text/plain', status : str | bytes | int = b'200 OK', headers=None, end_connection=False):
def end(self, message, end_connection=False): def end(self, message, end_connection=False):
def pause(self): def pause(self):
def resume(self): def resume(self):
@ -88,9 +87,9 @@ class Response:
def __del__(self): def __del__(self):
``` ```
## Request ## AppRequest
```python ```python
class Request: class AppRequest:
def __init__(self, request, app): def __init__(self, request, app):
def get_cookie(self, name): def get_cookie(self, name):
def get_url(self): def get_url(self):
@ -229,6 +228,4 @@ class MiddlewareRouter:
def connect(self, path, handler): def connect(self, path, handler):
def trace(self, path, handler): def trace(self, path, handler):
def any(self, path, handler): def any(self, path, handler):
``` ```
### Next [Examples](examples.md)

Wyświetl plik

@ -30,7 +30,7 @@ Options:
--ws-close-on-backpressure-limit BOOLEAN Close connections that hits maximum backpressure [default: False] --ws-close-on-backpressure-limit BOOLEAN Close connections that hits maximum backpressure [default: False]
--lifespan [auto|on|off] Lifespan implementation. [default: auto] --lifespan [auto|on|off] Lifespan implementation. [default: auto]
--interface [auto|asgi|asgi3|wsgi|ssgi|socketify] Select ASGI (same as ASGI3), ASGI3, WSGI or SSGI as the application interface. [default: auto] --interface [auto|asgi|asgi3|wsgi|ssgi|socketify] Select ASGI (same as ASGI3), ASGI3, WSGI or SSGI as the application interface. [default: auto]
--disable-listen-log BOOLEAN Disable log when start listening [default: False] --disable-listen-log BOOLEAN Disable log when start listenning [default: False]
--version or -v Display the socketify.py version and exit. --version or -v Display the socketify.py version and exit.
--ssl-keyfile TEXT SSL key file --ssl-keyfile TEXT SSL key file
--ssl-certfile TEXT SSL certificate file --ssl-certfile TEXT SSL certificate file

Wyświetl plik

@ -1,81 +0,0 @@
## 📚 Examples
All examples are located in the [`examples`](https://github.com/cirospaciari/socketify.py/tree/main/examples) directory.
### 🚀 Getting Started
- [`hello_world.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/hello_world.py) - Basic HTTP server setup
- [`hello_world_cli.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/hello_world_cli.py) - Command-line interface example
- [`hello_world_cli_ws.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/hello_world_cli_ws.py) - CLI with WebSocket support
- [`hello_world_unix_domain.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/hello_world_unix_domain.py) - Unix domain socket example
### 🔒 Security & HTTPS
- [`https.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/https.py) - HTTPS server with SSL/TLS configuration
### 🌐 WebSocket Examples
- [`websockets.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/websockets.py) - Basic WebSocket implementation
- [`ws_close_connection.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/ws_close_connection.py) - WebSocket connection management
- [`chat/`](https://github.com/cirospaciari/socketify.py/tree/main/examples/chat) - Real-time chat application
- [`broadcast.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/broadcast.py) - Broadcasting messages to multiple clients
- [`backpressure.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/backpressure.py) - Handling WebSocket backpressure
### ⚙️ Middleware & Routing
- [`middleware.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/middleware.py) - Basic middleware implementation
- [`middleware_async.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/middleware_async.py) - Asynchronous middleware
- [`middleware_sync.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/middleware_sync.py) - Synchronous middleware
- [`middleware_router.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/middleware_router.py) - Router-based middleware
- [`router_and_basics.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/router_and_basics.py) - Routing fundamentals
### 🔄 Async/Sync Programming
- [`async.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/async.py) - Asynchronous request handling
- [`upgrade.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/upgrade.py) - Protocol upgrade examples
- [`upgrade_async.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/upgrade_async.py) - Asynchronous protocol upgrades
### 📁 File Handling & Static Content
- [`static_files.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/static_files.py) - Serving static files
- [`file_stream.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/file_stream.py) - File streaming capabilities
- [`upload_or_post.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/upload_or_post.py) - File uploads and POST data handling
### 🎨 Template Engines
- [`template_jinja2.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_jinja2.py) - Jinja2 template integration
- [`template_mako.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/template_mako.py) - Mako template integration
- [`templates/`](https://github.com/cirospaciari/socketify.py/tree/main/examples/templates) - Template examples and resources
### 🛠️ Advanced Features
- [`custom_json_serializer.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/custom_json_serializer.py) - Custom JSON serialization
- [`http_request_cache.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/http_request_cache.py) - HTTP request caching
- [`proxy.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/proxy.py) - Proxy server implementation
- [`automatic_port_selection.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/automatic_port_selection.py) - Dynamic port selection
### 🔧 Server Configuration
- [`listen_options.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/listen_options.py) - Server listening options
- [`graceful_shutdown.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/graceful_shutdown.py) - Graceful server shutdown
- [`forks.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/forks.py) - Multi-process server setup
### 📊 GraphQL Integration
- [`graphiql.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/graphiql.py) - GraphiQL interface setup
- [`graphiql_raw.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/graphiql_raw.py) - Raw GraphQL implementation
### 🐳 Development & Deployment
- [`docker/`](https://github.com/cirospaciari/socketify.py/tree/main/examples/docker) - Docker containerization examples
- [`requirements.txt`](https://github.com/cirospaciari/socketify.py/tree/main/examples/requirements.txt) - Example dependencies
### 🛡️ Error Handling & Logging
- [`error_handler.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/error_handler.py) - Error handling strategies
- [`better_logging.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/better_logging.py) - Advanced logging setup
- [`not_found.py`](https://github.com/cirospaciari/socketify.py/tree/main/examples/not_found.py) - Custom 404 error pages
### 🔨 Utilities & Helpers
- [`helpers/`](https://github.com/cirospaciari/socketify.py/tree/main/examples/helpers) - Utility functions and helper modules

Wyświetl plik

@ -8,14 +8,10 @@ Hello world app
```python ```python
from socketify import App from socketify import App
def make_app(app: App): app = App()
app.get("/", lambda res, req: res.end("Hello World socketify from Python!")) app.get("/", lambda res, req: res.end("Hello World socketify from Python!"))
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
if __name__ == "__main__": app.run()
app = App()
make_app(app)
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
app.run()
``` ```
> This example just show how intuitive is to start an simple hello world app. > This example just show how intuitive is to start an simple hello world app.
@ -23,13 +19,10 @@ SSL version sample
``` python ``` python
from socketify import App, AppOptions from socketify import App, AppOptions
def make_app(app): app = App(AppOptions(key_file_name="./misc/key.pem", cert_file_name="./misc/cert.pem", passphrase="1234"))
app.get("/", lambda res, req: res.end("Hello World socketify from Python!")) app.get("/", lambda res, req: res.end("Hello World socketify from Python!"))
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
if __name__ == "__main__": app.run()
app = App(AppOptions(key_file_name="./misc/key.pem", cert_file_name="./misc/cert.pem", passphrase="1234"))
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
app.run()
``` ```
> We have a lot of SSL options, but this is the most common you can see all the options in the [API Reference](api.md) > We have a lot of SSL options, but this is the most common you can see all the options in the [API Reference](api.md)
@ -45,25 +38,20 @@ def ws_open(ws):
def ws_message(ws, message, opcode): def ws_message(ws, message, opcode):
#Ok is false if backpressure was built up, wait for drain #Ok is false if backpressure was built up, wait for drain
ok = ws.send(message, opcode) ok = ws.send(message, opcode)
def make_app(app): app = App()
app.ws("/*", { app.ws("/*", {
'compression': CompressOptions.SHARED_COMPRESSOR, 'compression': CompressOptions.SHARED_COMPRESSOR,
'max_payload_length': 16 * 1024 * 1024, 'max_payload_length': 16 * 1024 * 1024,
'idle_timeout': 12, 'idle_timeout': 12,
'open': ws_open, 'open': ws_open,
'message': ws_message, 'message': ws_message,
'drain': lambda ws: print('WebSocket backpressure: %i' % ws.get_buffered_amount()), 'drain': lambda ws: print('WebSocket backpressure: %i' % ws.get_buffered_amount()),
'close': lambda ws, code, message: print('WebSocket closed') 'close': lambda ws, code, message: print('WebSocket closed')
}) })
app.any("/", lambda res,req: res.end("Nothing to see here!'")) app.any("/", lambda res,req: res.end("Nothing to see here!'"))
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % (config.port)))
app.run()
if __name__ == "__main__":
app = App()
make_app(app)
app.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % (config.port)))
app.run()
``` ```
> We can have multiple routes for WebSockets, but in this example we just get one for anything we need, adding an option of compression using SHARED_COMPRESSOR, max_payload_length of 1mb and an idle timeout of 12s just to show some most commonly used features you can see all these options in the [API Reference](api.md) > We can have multiple routes for WebSockets, but in this example we just get one for anything we need, adding an option of compression using SHARED_COMPRESSOR, max_payload_length of 1mb and an idle timeout of 12s just to show some most commonly used features you can see all these options in the [API Reference](api.md)
@ -71,4 +59,4 @@ if __name__ == "__main__":
If you just wanna to see some more examples you can go to our [examples folder](https://github.com/cirospaciari/socketify.py/tree/main/examples) for more than 25 quick examples. If you just wanna to see some more examples you can go to our [examples folder](https://github.com/cirospaciari/socketify.py/tree/main/examples) for more than 25 quick examples.
### Next [Corking Concept](corking.md) ### Next [Corking Concept](corking.md)

Wyświetl plik

@ -102,7 +102,7 @@ async def async_xablau(res, req):
# this can be async no problems # this can be async no problems
def on_error(error, res, req): def on_error(error, res, req):
# here you can log properly the error and do a pretty response to your clients # here you can log properly the error and do a pretty response to your clients
print("Something goes %s" % str(error)) print("Somethind goes %s" % str(error))
# response and request can be None if the error is in an async function # response and request can be None if the error is in an async function
if res != None: if res != None:
# if response exists try to send something # if response exists try to send something
@ -164,4 +164,4 @@ app.remove_server_name("*.google.*")
``` ```
### Next [Middlewares](middlewares.md) ### Next [Middlewares](middlewares.md)

Wyświetl plik

@ -3,52 +3,6 @@ WebSocket "routes" are registered similarly, but not identically.
Every websocket route has the same pattern and pattern matching as for Http, but instead of one single callback you have a whole set of them, here's an example: Every websocket route has the same pattern and pattern matching as for Http, but instead of one single callback you have a whole set of them, here's an example:
Configuration details, notes:
- *idle_timeout*: number of seconds of inactivity before client is disconnected. If set to 0, no policy is enforced (connections can be stale).
- *open*: callback function for websocket connection being open
```python
def on_open(ws : WebSocket):
"""
ws: WebSocket - websocket connection
"""
...
```
- *close*: callback function for websocket connection closed
```python
def on_close(ws: WebSocket, code: int, msg: Union[bytes, str]):
"""
ws: WebSocket
websocket connection
code: int
exit code from client
msg: byte, str
exit message
"""
...
```
- *upgrade*: callback function to upgrade socket connection details
```python
def on_upgrade(res: Response, req: Request, socket_context):
"""
res: Response
req: Request
"""
...
```
- *message*: callback function for websocket message received
```python
def on_message(ws: WebSocket, msg: Union[bytes, str], opcode: OpCode):
"""
ws: WebSocket
msg: bytes, str
opcode: OpCode
"""
```
- *drain*: in the event of backpressure, policy to drain ws buffer
```python
def on_drain(ws: WebSocket):
...
```
```python ```python
app = App() app = App()
app.ws( app.ws(
@ -57,11 +11,10 @@ app.ws(
"compression": CompressOptions.SHARED_COMPRESSOR, "compression": CompressOptions.SHARED_COMPRESSOR,
"max_payload_length": 16 * 1024 * 1024, "max_payload_length": 16 * 1024 * 1024,
"idle_timeout": 12, "idle_timeout": 12,
"open": on_open, "open": ws_open,
"message": on_message, "message": ws_message,
"close": on_close, 'drain': lambda ws: print(f'WebSocket backpressure: {ws.get_buffered_amount()}'),
"upgrade": on_upgrade, "close": lambda ws, code, message: print("WebSocket closed"),
'drain': on_drain,
"subscription": lambda ws, topic, subscriptions, subscriptions_before: print(f'subscription/unsubscription on topic {topic} {subscriptions} {subscriptions_before}'), "subscription": lambda ws, topic, subscriptions, subscriptions_before: print(f'subscription/unsubscription on topic {topic} {subscriptions} {subscriptions_before}'),
}, },
) )
@ -71,57 +24,6 @@ You should use the provided user data feature to store and attach any per-socket
If you want to create something more elaborate you could have the user data hold a pointer to some dynamically allocated memory block that keeps a boolean whether the WebSocket is still valid or not. Sky is the limit here. If you want to create something more elaborate you could have the user data hold a pointer to some dynamically allocated memory block that keeps a boolean whether the WebSocket is still valid or not. Sky is the limit here.
In order to do so, use the `upgrade` callback configuration in the `app.ws` settings.
Example:
```python
from socketify import App, WebSocket, OpCode
app = App()
ID = 0
def on_open(ws: WebSocket):
user_data = ws.get_user_data()
print('ws %s connected' % user_data['user_id'])
ws.send('Hello, world!')
def on_upgrade(res, req, socket_context):
global ID
ID += 1
key = req.get_header("sec-websocket-key")
protocol = req.get_header("sec-websocket-protocol")
extensions = req.get_header("sec-websocket-extensions")
user_data=dict(user_id=ID)
res.upgrade(key, protocol, extensions, socket_context, user_data)
def on_message(ws: WebSocket, msg: str, opcode: OpCode):
user_data = ws.get_user_data()
print('ws %s: %s' % (user_data['user_id'], msg))
def on_close(ws, code, msg):
user_data = ws.get_user_data()
print('ws %s closed' % user_data['user_id'])
def on_drain(ws: WebSocket):
user_data = ws.get_user_data()
print('ws %s backpressure: %s' % (user_data['user_id'], ws.get_buffered_amount()))
app.ws(
"/*",
{
"compression": CompressOptions.SHARED_COMPRESSOR,
"max_payload_length": 16 * 1024 * 1024,
"idle_timeout": 12,
"open": on_open,
"message": on_message,
"close": on_close,
"upgrade": on_upgrade,
"drain": on_drain,
"subscription": lambda ws, topic, subscriptions, subscriptions_before: print(f'subscription/unsubscription on topic {topic} {subscriptions} {subscriptions_before}'),
}
)
```
## WebSockets are valid from open to close ## WebSockets are valid from open to close
All given WebSocket are guaranteed to live from open event (where you got your WebSocket) until close event is called. All given WebSocket are guaranteed to live from open event (where you got your WebSocket) until close event is called.
Message events will never emit outside of open/close. Calling ws.close or ws.end will immediately call the close handler. Message events will never emit outside of open/close. Calling ws.close or ws.end will immediately call the close handler.

Wyświetl plik

@ -2,6 +2,7 @@ from socketify import App
import os import os
import multiprocessing import multiprocessing
def run_app(): def run_app():
app = App() app = App()
app.get("/", lambda res, req: res.end("Hello, World!")) app.get("/", lambda res, req: res.end("Hello, World!"))
@ -15,19 +16,15 @@ def run_app():
app.run() app.run()
pid_list = [] def create_fork():
# fork limiting the cpu count - 1 n = os.fork()
for _ in range(1, multiprocessing.cpu_count()):
pid = os.fork()
# n greater than 0 means parent process # n greater than 0 means parent process
if not pid > 0: if not n > 0:
run_app() run_app()
break
pid_list.append(pid)
# fork limiting the cpu count - 1
for i in range(1, multiprocessing.cpu_count()):
create_fork()
run_app() # run app on the main process too :) run_app() # run app on the main process too :)
# sigint everything to graceful shutdown
import signal
for pid in pid_list:
os.kill(pid, signal.SIGINT)

Wyświetl plik

@ -1,21 +0,0 @@
from streaming_form_data import StreamingFormDataParser
from socketify import Response
def get_formdata(res: Response, parser: StreamingFormDataParser):
_dataFuture = res.app.loop.create_future()
def is_aborted(res):
res.aborted = True
try:
if not _dataFuture.done():
_dataFuture.set_result(parser)
except:
pass
def get_chunks(res, chunk, is_end):
parser.data_received(chunk)
if is_end:
_dataFuture.set_result(parser)
res.on_aborted(is_aborted)
res.on_data(get_chunks)
return _dataFuture

Wyświetl plik

@ -1,6 +1,6 @@
import strawberry import strawberry
import strawberry.utils.graphiql import strawberry.utils.graphiql
from io import BytesIO
def graphiql_from(Query, Mutation=None): def graphiql_from(Query, Mutation=None):
if Mutation: if Mutation:
@ -8,45 +8,32 @@ def graphiql_from(Query, Mutation=None):
else: else:
schema = strawberry.Schema(Query) schema = strawberry.Schema(Query)
def post(res, req): async def post(res, req):
# we can pass whatever we want to context, query, headers or params, cookies etc # we can pass whatever we want to context, query, headers or params, cookies etc
context_value = req.preserve() context_value = req.preserve()
buffer = BytesIO()
def on_data(res, chunk, is_end):
buffer.write(chunk)
if is_end:
try:
body = res.app._json_serializer.loads(buffer.getvalue().decode("utf-8"))
res.run_async(graph_ql(res, body, context_value))
except Exception as err:
res.app.trigger_error(err, res, None)
res.grab_aborted_handler()
res.on_data(on_data)
async def graph_ql(res, body, context_value): # get all incoming data and parses as json
query = body["query"] body = await res.get_json()
variables = body.get("variables", None)
root_value = body.get("rootValue", None)
operation_name = body.get("operationName", None)
data = await schema.execute(
query,
variables,
context_value,
root_value,
operation_name,
)
res.cork_send( query = body["query"]
{ variables = body.get("variables", None)
"data": (data.data), root_value = body.get("root_value", None)
**({"errors": data.errors} if data.errors else {}), operation_name = body.get("operation_name", None)
**({"extensions": data.extensions} if data.extensions else {}),
} data = await schema.execute(
) query,
variables,
context_value,
root_value,
operation_name,
)
res.cork_end(
{
"data": (data.data),
**({"errors": data.errors} if data.errors else {}),
**({"extensions": data.extensions} if data.extensions else {}),
}
)
return post return post

Wyświetl plik

@ -9,10 +9,9 @@ app = App(
) )
app.get("/", lambda res, req: res.end("Hello World socketify from Python!")) app.get("/", lambda res, req: res.end("Hello World socketify from Python!"))
app.listen( app.listen(
54321, 3000,
lambda config: print("Listening on port https://localhost:%d now\n" % config.port), lambda config: print("Listening on port https://localhost:%d now\n" % config.port),
) )
app.run() app.run()
# mkdir misc
# openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -passout pass:1234 -keyout ./misc/key.pem -out ./misc/cert.pem # openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -passout pass:1234 -keyout ./misc/key.pem -out ./misc/cert.pem

Wyświetl plik

@ -6,7 +6,7 @@ from socketify import App
def middleware(*functions): def middleware(*functions):
def middleware_route(res, req): def middleware_route(res, req):
data = None data = None
# circle to all middlewares # cicle to all middlewares
for function in functions: for function in functions:
# call middlewares # call middlewares
data = function(res, req, data) data = function(res, req, data)

Wyświetl plik

@ -6,8 +6,8 @@
# using oha -c 400 -z 5s http://localhost:3000/ # using oha -c 400 -z 5s http://localhost:3000/
# nginx - try_files - 77630.15 req/s # nginx - try_files - 77630.15 req/s
# pypy3 - socketify static - 16797.30 req/s # pypy3 - socketify static - 15839.22 req/s
# python3 - socketify static - 10140.19 req/s # python3 - socketify static - 8294.96 req/s
# node.js - @fastify/static - 5437.16 req/s # node.js - @fastify/static - 5437.16 req/s
# node.js - express.static - 4077.49 req/s # node.js - express.static - 4077.49 req/s
# python3 - socketify static_aiofile - 2390.96 req/s # python3 - socketify static_aiofile - 2390.96 req/s

Wyświetl plik

@ -78,69 +78,6 @@ async def upload_multiple(res, req):
# We respond when we are done # We respond when we are done
res.cork_end("Thanks for the data!") res.cork_end("Thanks for the data!")
def upload_formdata(res, req):
# using streaming_form_data package for parsing
from streaming_form_data import StreamingFormDataParser
from streaming_form_data.targets import ValueTarget, FileTarget
print(f"Posted to {req.get_url()}")
parser = StreamingFormDataParser(headers=req.get_headers())
name = ValueTarget()
parser.register('name', name)
file = FileTarget('/tmp/file')
file2 = FileTarget('/tmp/file2')
parser.register('file', file)
parser.register('file2', file2)
def on_data(res, chunk, is_end):
parser.data_received(chunk)
if is_end:
res.cork(on_finish)
def on_finish(res):
print(name.value)
print(file.multipart_filename)
print(file.multipart_content_type)
print(file2.multipart_filename)
print(file2.multipart_content_type)
res.end("Thanks for the data!")
res.on_data(on_data)
async def upload_formhelper(res, req):
# using streaming_form_data package for parsing + helper
from streaming_form_data import StreamingFormDataParser
from streaming_form_data.targets import ValueTarget, FileTarget
from helpers.form_data import get_formdata
print(f"Posted to {req.get_url()}")
parser = StreamingFormDataParser(headers=req.get_headers())
name = ValueTarget()
parser.register('name', name)
file = FileTarget('/tmp/file')
file2 = FileTarget('/tmp/file2')
parser.register('file', file)
parser.register('file2', file2)
await get_formdata(res, parser)
print(name.value)
print(file.multipart_filename)
print(file.multipart_content_type)
print(file2.multipart_filename)
print(file2.multipart_content_type)
res.cork_end("Thanks for the data!")
app = App() app = App()
app.post("/", upload) app.post("/", upload)
@ -149,8 +86,6 @@ app.post("/json", upload_json)
app.post("/text", upload_text) app.post("/text", upload_text)
app.post("/urlencoded", upload_urlencoded) app.post("/urlencoded", upload_urlencoded)
app.post("/multiple", upload_multiple) app.post("/multiple", upload_multiple)
app.post("/formdata", upload_formdata)
app.post("/formdata2", upload_formhelper)
app.any("/*", lambda res, _: res.write_status(404).end("Not Found")) app.any("/*", lambda res, _: res.write_status(404).end("Not Found"))
app.listen( app.listen(

Wyświetl plik

@ -4,14 +4,13 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "socketify" name = "socketify"
version = "0.0.31" version = "0.0.9"
dynamic = ["dependencies"]
authors = [ authors = [
{ name="Ciro Spaciari", email="ciro.spaciari@gmail.com" }, { name="Ciro Spaciari", email="ciro.spaciari@gmail.com" },
] ]
description = "Bringing WebSockets, Http/Https High Performance servers for PyPy3 and Python3" description = "Bringing WebSockets, Http/Https High Performance servers for PyPy3 and Python3"
readme = "README.md" readme = "README.md"
requires-python = ">=3.8" requires-python = ">=3.7"
classifiers = [ classifiers = [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",

Wyświetl plik

@ -1,8 +1,8 @@
import sys import sys
vi = sys.version_info vi = sys.version_info
if vi < (3, 8): if vi < (3, 7):
raise RuntimeError("socketify requires Python 3.8 or greater") raise RuntimeError("socketify requires Python 3.7 or greater")
# if sys.platform in ('win32', 'cygwin', 'cli'): # if sys.platform in ('win32', 'cygwin', 'cli'):
# raise RuntimeError('socketify does not support Windows at the moment') # raise RuntimeError('socketify does not support Windows at the moment')
@ -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.31", version="0.0.9",
platforms=["any"], platforms=["any"],
author="Ciro Spaciari", author="Ciro Spaciari",
author_email="ciro.spaciari@gmail.com", author_email="ciro.spaciari@gmail.com",
@ -88,7 +88,7 @@ setuptools.setup(
"./native/*/*/*", "./native/*/*/*",
] ]
}, },
python_requires=">=3.8", python_requires=">=3.7",
install_requires=["cffi>=1.0", "setuptools>=58.1.0"], install_requires=["cffi>=1.0", "setuptools>=58.1.0"],
has_ext_modules=lambda: True, has_ext_modules=lambda: True,
cmdclass={}, # cmdclass={'sdist': Prepare, 'build_ext': Makefile}, cmdclass={}, # cmdclass={'sdist': Prepare, 'build_ext': Makefile},

Wyświetl plik

@ -1,9 +1,9 @@
import asyncio import asyncio
from .dataclasses import AppListenOptions, AppOptions
from .tasks import TaskFactory, create_task, RequestTask
from .socketify import ( from .socketify import (
App, App,
AppOptions,
AppListenOptions,
OpCode, OpCode,
SendStatus, SendStatus,
CompressOptions, CompressOptions,

Wyświetl plik

@ -1,7 +1,7 @@
from socketify import App, OpCode from socketify import App, OpCode, Loop
from queue import SimpleQueue from queue import SimpleQueue
from .native import lib, ffi from .native import lib, ffi
from .tasks import create_task, TaskFactory from .tasks import create_task, create_task_with_factory
import os import os
import platform import platform
import sys import sys
@ -10,15 +10,7 @@ import uuid
import asyncio import asyncio
is_pypy = platform.python_implementation() == "PyPy" is_pypy = platform.python_implementation() == "PyPy"
@ffi.callback("void(uws_res_t*, void*)")
def asgi_on_abort_handler(res, user_data):
ctx = ffi.from_handle(user_data)
ctx.aborted = True
ctx.loop.is_idle = False
if ctx.abort_future is not None:
ctx.abort_future.set_result(True)
ctx.abort_future = None
async def task_wrapper(task): async def task_wrapper(task):
try: try:
@ -33,6 +25,7 @@ async def task_wrapper(task):
EMPTY_RESPONSE = {"type": "http.request", "body": b"", "more_body": False} EMPTY_RESPONSE = {"type": "http.request", "body": b"", "more_body": False}
@ffi.callback("void(uws_websocket_t*, const char*, size_t, uws_opcode_t, void*)") @ffi.callback("void(uws_websocket_t*, const char*, size_t, uws_opcode_t, void*)")
def ws_message(ws, message, length, opcode, user_data): def ws_message(ws, message, length, opcode, user_data):
socket_data = ffi.from_handle(user_data) socket_data = ffi.from_handle(user_data)
@ -57,11 +50,10 @@ def ws_open(ws, user_data):
@ffi.callback( @ffi.callback(
"void(int, uws_res_t*, socketify_asgi_ws_data, uws_socket_context_t* socket, void*)" "void(int, uws_res_t*, socketify_asgi_ws_data, uws_socket_context_t* socket, void*, bool*)"
) )
def ws_upgrade(ssl, response, info, socket_context, user_data): def ws_upgrade(ssl, response, info, socket_context, user_data, aborted):
app = ffi.from_handle(user_data) app = ffi.from_handle(user_data)
app.server.loop.is_idle = False
headers = [] headers = []
next_header = info.header_list next_header = info.header_list
while next_header != ffi.NULL: while next_header != ffi.NULL:
@ -91,7 +83,6 @@ def ws_upgrade(ssl, response, info, socket_context, user_data):
extensions = ffi.unpack(info.extensions, info.extensions_size).decode("utf8") extensions = ffi.unpack(info.extensions, info.extensions_size).decode("utf8")
compress = app.ws_compression compress = app.ws_compression
ws = ASGIWebSocket(app.server.loop) ws = ASGIWebSocket(app.server.loop)
lib.uws_res_on_aborted(ssl, response, asgi_on_abort_handler, ws._ptr)
scope = { scope = {
"type": "websocket", "type": "websocket",
@ -107,7 +98,7 @@ def ws_upgrade(ssl, response, info, socket_context, user_data):
"root_path": "", "root_path": "",
"path": url.decode("utf8"), "path": url.decode("utf8"),
"raw_path": url, "raw_path": url,
"query_string": ffi.unpack(info.query_string, info.query_string_size)[1:], "query_string": ffi.unpack(info.query_string, info.query_string_size),
"headers": headers, "headers": headers,
"subprotocols": [protocol] if protocol else [], "subprotocols": [protocol] if protocol else [],
"extensions": { "extensions": {
@ -118,9 +109,8 @@ def ws_upgrade(ssl, response, info, socket_context, user_data):
} }
async def send(options): async def send(options):
if ws.aborted: if bool(aborted[0]):
return False return False
ws.loop.is_idle = False
type = options["type"] type = options["type"]
if type == "websocket.send": if type == "websocket.send":
data = options.get("bytes", None) data = options.get("bytes", None)
@ -248,7 +238,6 @@ def ws_upgrade(ssl, response, info, socket_context, user_data):
@ffi.callback("void(uws_res_t*, const char*, size_t, bool, void*)") @ffi.callback("void(uws_res_t*, const char*, size_t, bool, void*)")
def asgi_on_data_handler(res, chunk, chunk_length, is_end, user_data): def asgi_on_data_handler(res, chunk, chunk_length, is_end, user_data):
data_response = ffi.from_handle(user_data) data_response = ffi.from_handle(user_data)
data_response.loop.is_idle = False
data_response.is_end = bool(is_end) data_response.is_end = bool(is_end)
more_body = not data_response.is_end more_body = not data_response.is_end
result = { result = {
@ -270,22 +259,6 @@ class ASGIDataQueue:
self.is_end = False self.is_end = False
self.next_data_future = loop.create_future() self.next_data_future = loop.create_future()
class ASGIContext:
def __init__(self, ssl, response, loop):
self._ptr = ffi.new_handle(self)
self.aborted = False
self.sended_empty = False
self.data_queue = None
self.ssl = ssl
self.response = response
self.loop = loop
self.abort_future = None
async def wait_disconnect(self):
if not self.aborted:
if self.abort_future is None:
self.abort_future = self.loop.create_future()
await self.abort_future
class ASGIWebSocket: class ASGIWebSocket:
def __init__(self, loop): def __init__(self, loop):
@ -300,14 +273,6 @@ class ASGIWebSocket:
self._message = None self._message = None
self._ptr = ffi.new_handle(self) self._ptr = ffi.new_handle(self)
self.unregister = None self.unregister = None
self.aborted = False
self.abort_future = None
async def wait_disconnect(self):
if not self.aborted:
if self.abort_future is None:
self.abort_future = self.loop.create_future()
await self.abort_future
def accept(self): def accept(self):
self.accept_future = self.loop.create_future() self.accept_future = self.loop.create_future()
@ -440,11 +405,10 @@ def uws_asgi_corked_403_handler(res, user_data):
lib.uws_res_end_without_body(ssl, res, 0) lib.uws_res_end_without_body(ssl, res, 0)
@ffi.callback("void(int, uws_res_t*, socketify_asgi_data, void*)") @ffi.callback("void(int, uws_res_t*, socketify_asgi_data, void*, bool*)")
def asgi(ssl, response, info, user_data): def asgi(ssl, response, info, user_data, aborted):
app = ffi.from_handle(user_data) app = ffi.from_handle(user_data)
app.server.loop.is_idle = False
headers = [] headers = []
next_header = info.header_list next_header = info.header_list
while next_header != ffi.NULL: while next_header != ffi.NULL:
@ -463,7 +427,7 @@ def asgi(ssl, response, info, user_data):
"http_version": "1.1", "http_version": "1.1",
"server": (app.SERVER_HOST, app.SERVER_PORT), "server": (app.SERVER_HOST, app.SERVER_PORT),
"client": ( "client": (
None if info.remote_address == ffi.NULL else ffi.unpack(info.remote_address, info.remote_address_size).decode("utf8"), ffi.unpack(info.remote_address, info.remote_address_size).decode("utf8"),
None, None,
), ),
"scheme": app.SERVER_SCHEME, "scheme": app.SERVER_SCHEME,
@ -471,25 +435,18 @@ def asgi(ssl, response, info, user_data):
"root_path": "", "root_path": "",
"path": url.decode("utf8"), "path": url.decode("utf8"),
"raw_path": url, "raw_path": url,
"query_string": ffi.unpack(info.query_string, info.query_string_size)[1:], "query_string": ffi.unpack(info.query_string, info.query_string_size),
"headers": headers, "headers": headers,
} }
loop = app.server.loop
ctx = ASGIContext(ssl, response, loop)
if bool(info.has_content): if bool(info.has_content):
data_queue = ASGIDataQueue(loop) data_queue = ASGIDataQueue(app.server.loop)
lib.uws_res_on_data(ssl, response, asgi_on_data_handler, data_queue._ptr) lib.uws_res_on_data(ssl, response, asgi_on_data_handler, data_queue._ptr)
ctx.data_queue = data_queue else:
data_queue = None
lib.uws_res_on_aborted(ssl, response, asgi_on_abort_handler, ctx._ptr)
async def receive(): async def receive():
if ctx.aborted: if bool(aborted[0]):
return {"type": "http.disconnect"} return {"type": "http.disconnect"}
ctx.loop.is_idle = False
data_queue = ctx.data_queue
if data_queue: if data_queue:
if data_queue.queue.empty(): if data_queue.queue.empty():
if not data_queue.is_end: if not data_queue.is_end:
@ -501,24 +458,13 @@ def asgi(ssl, response, info, user_data):
else: else:
return data_queue.queue.get(False) # consume queue return data_queue.queue.get(False) # consume queue
# no more body, just EMPTY RESPONSE # no more body, just empty
if not ctx.sended_empty: return EMPTY_RESPONSE
ctx.sended_empty = True
return EMPTY_RESPONSE
# already sended empty body so wait for aborted request
if not ctx.aborted:
await ctx.wait_disconnect()
return {"type": "http.disconnect"}
async def send(options): async def send(options):
if ctx.aborted: if bool(aborted[0]):
return False return False
ctx.loop.is_idle = False
type = options["type"] type = options["type"]
ssl = ctx.ssl
response = ctx.response
if type == "http.response.start": if type == "http.response.start":
# can also be more native optimized to do it in one GIL call # can also be more native optimized to do it in one GIL call
# try socketify_res_write_int_status_with_headers and create and socketify_res_cork_write_int_status_with_headers # try socketify_res_write_int_status_with_headers and create and socketify_res_cork_write_int_status_with_headers
@ -547,12 +493,6 @@ def asgi(ssl, response, info, user_data):
elif isinstance(message, str): elif isinstance(message, str):
data = message.encode("utf-8") data = message.encode("utf-8")
lib.socketify_res_cork_end(ssl, response, data, len(data), 0) lib.socketify_res_cork_end(ssl, response, data, len(data), 0)
if ctx.abort_future is not None:
ctx.aborted = True
ctx.abort_future.set_result(False)
ctx.abort_future = None
return True return True
return False return False
@ -573,8 +513,8 @@ class _ASGI:
self.server = App(options, task_factory_max_items=0) self.server = App(options, task_factory_max_items=0)
self.SERVER_PORT = None self.SERVER_PORT = None
self.SERVER_HOST = "" self.SERVER_HOST = ""
self.SERVER_SCHEME = "https" if options and options.cert_file_name is not None else "http" self.SERVER_SCHEME = "https" if self.server.options else "http"
self.SERVER_WS_SCHEME = "wss" if options and options.cert_file_name is not None else "ws" self.SERVER_WS_SCHEME = "wss" if self.server.options else "ws"
self.task_factory_max_items = task_factory_max_items self.task_factory_max_items = task_factory_max_items
self.lifespan = lifespan self.lifespan = lifespan
@ -583,25 +523,38 @@ class _ASGI:
# internally will still use custom task factory for pypy because of Loop # internally will still use custom task factory for pypy because of Loop
if is_pypy: if is_pypy:
if task_factory_max_items > 0: if task_factory_max_items > 0:
factory = TaskFactory(task_factory_max_items) factory = create_task_with_factory(task_factory_max_items)
def run_task(task): def run_task(task):
factory(loop, task_wrapper(task)) factory(loop, task_wrapper(task))
loop._run_once()
self._run_task = run_task self._run_task = run_task
else: else:
def run_task(task): def run_task(task):
future = create_task(loop, task_wrapper(task)) create_task(loop, task_wrapper(task))
future._log_destroy_pending = False loop._run_once()
self._run_task = run_task self._run_task = run_task
else: else:
if sys.version_info >= (3, 8): # name fixed to avoid dynamic name
def run_task(task): def run_task(task):
future = create_task(loop, task_wrapper(task)) future = loop.create_task(
task_wrapper(task), name="socketify.py-request-task"
)
future._log_destroy_pending = False future._log_destroy_pending = False
loop._run_once()
self._run_task = run_task
else:
def run_task(task):
future = loop.create_task(task_wrapper(task))
future._log_destroy_pending = False
loop._run_once()
self._run_task = run_task self._run_task = run_task
@ -688,13 +641,12 @@ class _ASGI:
asgi_app = self asgi_app = self
self.is_starting = True self.is_starting = True
self.is_stopped = False self.is_stopped = False
self.status = 0 # 0 starting, 1 ok, 2 error, 3 stopping, 4 stopped, 5 stopped with error, 6 no lifespan self.status = 0 # 0 starting, 1 ok, 2 error, 3 stoping, 4 stopped, 5 stopped with error, 6 no lifespan
self.status_message = "" self.status_message = ""
self.stop_future = self.server.loop.create_future() self.stop_future = self.server.loop.create_future()
async def send(options): async def send(options):
nonlocal asgi_app nonlocal asgi_app
asgi_app.server.loop.is_idle = False
type = options["type"] type = options["type"]
asgi_app.status_message = options.get("message", "") asgi_app.status_message = options.get("message", "")
if type == "lifespan.startup.complete": if type == "lifespan.startup.complete":
@ -712,7 +664,6 @@ class _ASGI:
async def receive(): async def receive():
nonlocal asgi_app nonlocal asgi_app
asgi_app.server.loop.is_idle = False
while not asgi_app.is_stopped: while not asgi_app.is_stopped:
if asgi_app.is_starting: if asgi_app.is_starting:
asgi_app.is_starting = False asgi_app.is_starting = False
@ -735,7 +686,7 @@ class _ASGI:
asgi_app.server.listen(port_or_options, handler) asgi_app.server.listen(port_or_options, handler)
finally: finally:
return None return None
self.server.loop.is_idle = False
# start lifespan # start lifespan
self.server.loop.ensure_future(task_wrapper(self.app(scope, receive, send))) self.server.loop.ensure_future(task_wrapper(self.app(scope, receive, send)))
self.server.run() self.server.run()
@ -773,13 +724,10 @@ class _ASGI:
return self return self
def __del__(self): def __del__(self):
try: if self.asgi_http_info:
if self.asgi_http_info: lib.socketify_destroy_asgi_app_info(self.asgi_http_info)
lib.socketify_destroy_asgi_app_info(self.asgi_http_info) if self.asgi_ws_info:
if self.asgi_ws_info: lib.socketify_destroy_asgi_ws_app_info(self.asgi_ws_info)
lib.socketify_destroy_asgi_ws_app_info(self.asgi_ws_info)
except:
pass
# "Public" ASGI interface to allow easy forks/workers # "Public" ASGI interface to allow easy forks/workers
@ -800,26 +748,12 @@ class ASGI:
self.listen_options = None self.listen_options = None
self.task_factory_max_items = task_factory_max_items self.task_factory_max_items = task_factory_max_items
self.lifespan = lifespan self.lifespan = lifespan
self.server = None
self.pid_list = None
def listen(self, port_or_options, handler=None): def listen(self, port_or_options, handler=None):
self.listen_options = (port_or_options, handler) self.listen_options = (port_or_options, handler)
return self return self
def close(self): def run(self, workers=1):
# always wait a sec so forks can start properly if close is called too fast
import time
time.sleep(1)
if self.server is not None:
self.server.close()
if self.pid_list is not None:
import signal
for pid in self.pid_list:
os.kill(pid, signal.SIGINT)
def run(self, workers=1, block=True):
def run_task(): def run_task():
server = _ASGI( server = _ASGI(
self.app, self.app,
@ -832,27 +766,17 @@ class ASGI:
if self.listen_options: if self.listen_options:
(port_or_options, handler) = self.listen_options (port_or_options, handler) = self.listen_options
server.listen(port_or_options, handler) server.listen(port_or_options, handler)
self.server = server server.run()
server.run()
pid_list = [] def create_fork():
start = 1 if block else 0 n = os.fork()
# fork limiting the cpu count - 1
for _ in range(block, workers):
pid = os.fork()
# n greater than 0 means parent process # n greater than 0 means parent process
if not pid > 0: if not n > 0:
run_task() run_task()
break
pid_list.append(pid)
self.pid_list = pid_list
if block: # fork limiting the cpu count - 1
run_task() # run app on the main process too :) for _ in range(1, workers):
# sigint everything to graceful shutdown create_fork()
import signal
for pid in pid_list:
os.kill(pid, signal.SIGINT)
run_task() # run app on the main process too :)
return self return self

Wyświetl plik

@ -28,7 +28,7 @@ Options:
--ws-close-on-backpressure-limit BOOLEAN Close connections that hits maximum backpressure [default: False] --ws-close-on-backpressure-limit BOOLEAN Close connections that hits maximum backpressure [default: False]
--lifespan [auto|on|off] Lifespan implementation. [default: auto] --lifespan [auto|on|off] Lifespan implementation. [default: auto]
--interface [auto|asgi|asgi3|wsgi|ssgi|socketify] Select ASGI (same as ASGI3), ASGI3, WSGI or SSGI as the application interface. [default: auto] --interface [auto|asgi|asgi3|wsgi|ssgi|socketify] Select ASGI (same as ASGI3), ASGI3, WSGI or SSGI as the application interface. [default: auto]
--disable-listen-log BOOLEAN Disable log when start listening [default: False] --disable-listen-log BOOLEAN Disable log when start listenning [default: False]
--version or -v Display the socketify.py version and exit. --version or -v Display the socketify.py version and exit.
--ssl-keyfile TEXT SSL key file --ssl-keyfile TEXT SSL key file
--ssl-certfile TEXT SSL certificate file --ssl-certfile TEXT SSL certificate file
@ -188,10 +188,10 @@ def execute(args):
elif interface == "ssgi": elif interface == "ssgi":
# if not is_ssgi(module): # if not is_ssgi(module):
# return print("SSGI is in development yet but is coming soon") # return print("SSGI is in development yet but is comming soon")
# from . import SSGI as Interface # from . import SSGI as Interface
# interface = "ssgi" # interface = "ssgi"
return print("SSGI is in development yet but is coming soon") return print("SSGI is in development yet but is comming soon")
elif interface != "socketify": elif interface != "socketify":
return print(f"{interface} interface is not supported yet") return print(f"{interface} interface is not supported yet")
@ -310,20 +310,22 @@ def execute(args):
fork_app.listen(AppListenOptions(port=port, host=host), listen_log) fork_app.listen(AppListenOptions(port=port, host=host), listen_log)
fork_app.run() fork_app.run()
pid_list = [] # now we can start all over again
# fork limiting the cpu count - 1 def create_fork(_):
for _ in range(1, workers): n = os.fork()
pid = os.fork()
# n greater than 0 means parent process # n greater than 0 means parent process
if not pid > 0: if not n > 0:
run_app() run_app()
break return n
pid_list.append(pid)
run_app() # run app on the main process too :) # run in all forks
pid_list = list(map(create_fork, range(1, workers)))
# sigint everything to graceful shutdown # run in this process
run_app()
# sigint everything to gracefull shutdown
import signal import signal
for pid in pid_list: for pid in pid_list:
os.kill(pid, signal.SIGINT) os.kill(pid, signal.SIGINT)
else: else:

Wyświetl plik

@ -1,52 +0,0 @@
from dataclasses import dataclass
@dataclass
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
cert_file_name: str = None
passphrase: str = None
dh_params_file_name: str = None
ca_file_name: str = None
ssl_ciphers: str = None
ssl_prefer_low_memory_usage: int = 0
def __post_init__(self):
NoneType = type(None)
if not isinstance(self.key_file_name, (NoneType, str)):
raise RuntimeError("key_file_name must be a str if specified")
if not isinstance(self.cert_file_name, (NoneType, str)):
raise RuntimeError("cert_file_name must be a str if specified")
if not isinstance(self.passphrase, (NoneType, str)):
raise RuntimeError("passphrase must be a str if specified")
if not isinstance(self.dh_params_file_name, (NoneType, str)):
raise RuntimeError("dh_params_file_name must be a str if specified")
if not isinstance(self.ca_file_name, (NoneType, str)):
raise RuntimeError("ca_file_name must be a str if specified")
if not isinstance(self.ssl_ciphers, (NoneType, str)):
raise RuntimeError("ssl_ciphers must be a str if specified")
if not isinstance(self.ssl_prefer_low_memory_usage, int):
raise RuntimeError("ssl_prefer_low_memory_usage must be an int")

Wyświetl plik

@ -70,7 +70,7 @@ async def sendfile(res, req, filename):
def send_headers(res): def send_headers(res):
res.write_status(status) res.write_status(status)
# tells the browser the last modified date # tells the broswer the last modified date
res.write_header(b"Last-Modified", last_modified) res.write_header(b"Last-Modified", last_modified)
# tells the browser that we support range # tells the browser that we support range
@ -147,7 +147,7 @@ def middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware # we use Optional data=None at the end so you can use and middleware inside a middleware
def optimized_middleware_route(res, req, data=None): def optimized_middleware_route(res, req, data=None):
# circle to all middlewares # cicle to all middlewares
for function in syncs: for function in syncs:
# call middlewares # call middlewares
data = function(res, req, data) data = function(res, req, data)
@ -156,7 +156,7 @@ def middleware(*functions):
return return
async def wrapper(res, req, data): async def wrapper(res, req, data):
# circle to all middlewares # cicle to all middlewares
for function in asyncs: for function in asyncs:
# detect if is coroutine or not # detect if is coroutine or not
if inspect.iscoroutinefunction(function): if inspect.iscoroutinefunction(function):
@ -182,7 +182,7 @@ def middleware(*functions):
def sync_middleware(*functions): def sync_middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware # we use Optional data=None at the end so you can use and middleware inside a middleware
def middleware_route(res, req, data=None): def middleware_route(res, req, data=None):
# circle to all middlewares # cicle to all middlewares
for function in functions: for function in functions:
# call middlewares # call middlewares
data = function(res, req, data) data = function(res, req, data)
@ -198,7 +198,7 @@ def async_middleware(*functions):
# we use Optional data=None at the end so you can use and middleware inside a middleware # we use Optional data=None at the end so you can use and middleware inside a middleware
async def middleware_route(res, req, data=None): async def middleware_route(res, req, data=None):
some_async_as_run = False some_async_as_run = False
# circle to all middlewares # cicle to all middlewares
for function in functions: for function in functions:
# detect if is coroutine or not # detect if is coroutine or not
if inspect.iscoroutinefunction(function): if inspect.iscoroutinefunction(function):
@ -363,61 +363,61 @@ class MiddlewareRouter:
self.middlewares = middlewares self.middlewares = middlewares
def get(self, path, handler): def get(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.get(path, middleware(*middies)) self.app.get(path, middleware(*middies))
return self return self
def post(self, path, handler): def post(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.post(path, middleware(*middies)) self.app.post(path, middleware(*middies))
return self return self
def options(self, path, handler): def options(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.options(path, middleware(*middies)) self.app.options(path, middleware(*middies))
return self return self
def delete(self, path, handler): def delete(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.delete(path, middleware(*middies)) self.app.delete(path, middleware(*middies))
return self return self
def patch(self, path, handler): def patch(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.patch(path, middleware(*middies)) self.app.patch(path, middleware(*middies))
return self return self
def put(self, path, handler): def put(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.put(path, middleware(*middies)) self.app.put(path, middleware(*middies))
return self return self
def head(self, path, handler): def head(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.head(path, middleware(*middies)) self.app.head(path, middleware(*middies))
return self return self
def connect(self, path, handler): def connect(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.connect(path, middleware(*middies)) self.app.connect(path, middleware(*middies))
return self return self
def trace(self, path, handler): def trace(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.trace(path, middleware(*middies)) self.app.trace(path, middleware(*middies))
return self return self
def any(self, path, handler): def any(self, path, handler):
middies = list(self.middlewares) middies = list(*self.middlewares)
middies.append(handler) middies.append(handler)
self.app.any(path, middleware(*middies)) self.app.any(path, middleware(*middies))
return self return self

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -1,6 +1,6 @@
import asyncio import asyncio
import logging import logging
from .tasks import create_task, TaskFactory from .tasks import create_task, create_task_with_factory
from .uv import UVLoop from .uv import UVLoop
import asyncio import asyncio
@ -8,6 +8,7 @@ import platform
is_pypy = platform.python_implementation() == "PyPy" is_pypy = platform.python_implementation() == "PyPy"
async def task_wrapper(exception_handler, loop, response, task): async def task_wrapper(exception_handler, loop, response, task):
try: try:
return await task return await task
@ -25,18 +26,16 @@ async def task_wrapper(exception_handler, loop, response, task):
class Loop: class Loop:
def __init__(self, exception_handler=None, task_factory_max_items=0, idle_relaxation_time=0.01): def __init__(self, exception_handler=None, task_factory_max_items=0):
# get the current running loop or create a new one without warnings # get the current running loop or create a new one without warnings
self.loop = asyncio._get_running_loop() self.loop = asyncio._get_running_loop()
self._idle_count = 0
self.is_idle = False
self.idle_relaxation_time = idle_relaxation_time
if self.loop is None: if self.loop is None:
self.loop = asyncio.new_event_loop() self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
self.uv_loop = UVLoop() self.uv_loop = UVLoop()
if hasattr(exception_handler, "__call__"): if hasattr(exception_handler, "__call__"):
self.exception_handler = exception_handler self.exception_handler = exception_handler
self.loop.set_exception_handler( self.loop.set_exception_handler(
@ -48,27 +47,20 @@ class Loop:
self.started = False self.started = False
if is_pypy: # PyPy async Optimizations if is_pypy: # PyPy async Optimizations
if task_factory_max_items > 0: # Only available in PyPy for now if task_factory_max_items > 0: # Only available in PyPy for now
self._task_factory = TaskFactory(task_factory_max_items) self._task_factory = create_task_with_factory(task_factory_max_items)
else: else:
self._task_factory = create_task self._task_factory = create_task
self.run_async = self._run_async_pypy self.run_async = self._run_async_pypy
# custom task factory
# TODO: check if any framework breaks without current_task(loop) support
# custom task factory for other tasks
def pypy_task_factory(loop, coro, context=None): def pypy_task_factory(loop, coro, context=None):
return create_task(loop, coro, context=context) return create_task(loop, coro, context=context)
self.loop.set_task_factory(pypy_task_factory) self.loop.set_task_factory(pypy_task_factory)
else: else:
# CPython performs worse using custom create_task, so native create_task is used
# TODO: check if any framework breaks without current_task(loop) support # but this also did not allow the use of create_task_with_factory :/
# custom task factory for other tasks # native create_task do not allow to change context, callbacks, state etc
def cpython_task_factory(loop, coro, context=None):
return create_task(loop, coro, context=context)
self.loop.set_task_factory(cpython_task_factory)
# CPython performs equals or worse using TaskFactory
self.run_async = self._run_async_cpython self.run_async = self._run_async_cpython
def set_timeout(self, timeout, callback, user_data): def set_timeout(self, timeout, callback, user_data):
@ -79,71 +71,15 @@ class Loop:
def _keep_alive(self): def _keep_alive(self):
if self.started: if self.started:
relax = False self.uv_loop.run_once()
if not self.is_idle: self.loop.call_soon(self._keep_alive)
self._idle_count = 0
elif self._idle_count < 10000:
self._idle_count += 1
else:
relax = True
self.is_idle = True
if relax:
self.uv_loop.run_nowait()
if self._idle_count < 15000:
self._idle_count += 1
# we are idle not for long, wait 5s until next relax mode
self.loop.call_later(0.001, self._keep_alive)
else:
# we are really idle now lets use less CPU
self.loop.call_later(self.idle_relaxation_time, self._keep_alive)
else:
self.uv_loop.run_nowait()
# be more aggressive when needed
self.loop.call_soon(self._keep_alive)
def create_task(self, *args, **kwargs): def create_task(self, *args, **kwargs):
# this is not using optimized create_task yet # this is not using optimized create_task yet
return self.loop.create_task(*args, **kwargs) return self.loop.create_task(*args, **kwargs)
def ensure_future(self, task): def ensure_future(self, task):
return asyncio.ensure_future(task, loop=self.loop) return asyncio.ensure_future(task, loop=self.loop)
def set_event_loop(self, loop):
needs_start = False
if self.loop.is_running():
self.stop()
self.loop = loop
if self.exception_handler is not None:
self.loop.set_exception_handler(
lambda loop, context: self.exception_handler(loop, context, None)
)
if is_pypy: # PyPy async Optimizations
# TODO: check if any framework breaks without current_task(loop) support
# custom task factory for other tasks
def pypy_task_factory(loop, coro, context=None):
return create_task(loop, coro, context=context)
self.loop.set_task_factory(pypy_task_factory)
else:
# TODO: check if any framework breaks without current_task(loop) support
# custom task factory for other tasks
def cpython_task_factory(loop, coro, context=None):
return create_task(loop, coro, context=context)
self.loop.set_task_factory(cpython_task_factory)
if needs_start:
self.run()
def create_background_task(self, bg_task):
def next_tick():
self.ensure_future(bg_task())
self.loop.call_soon(next_tick)
def run_until_complete(self, task=None): def run_until_complete(self, task=None):
self.started = True self.started = True
@ -171,15 +107,8 @@ class Loop:
def run_once(self): def run_once(self):
# run one step of asyncio # run one step of asyncio
# if loop._run_once is not available use loop.run_forever + loop.call_soon(loop.stop) self.loop._stopping = True
# this is useful when using uvloop or custom loops self.loop._run_once()
try:
self.loop._stopping = True
self.loop._run_once()
except Exception:
# this can be _StopError with means we should not call run_forever, but we can ignore it
self.loop.call_soon(self.loop.stop)
self.loop.run_forever()
# run one step of libuv # run one step of libuv
self.uv_loop.run_once() self.uv_loop.run_once()
@ -194,19 +123,24 @@ class Loop:
return self.uv_loop.get_native_loop() return self.uv_loop.get_native_loop()
def _run_async_pypy(self, task, response=None): def _run_async_pypy(self, task, response=None):
# this guarantees error 500 in case of uncaught exceptions, and can trigger the custom error handler # this garanties error 500 in case of uncaught exceptions, and can trigger the custom error handler
# using an coroutine wrapper generates less overhead than using add_done_callback # using an coroutine wrapper generates less overhead than using add_done_callback
# this is an custom task/future with less overhead and that calls the first step # this is an custom task/future with less overhead
future = self._task_factory( future = self._task_factory(
self.loop, task_wrapper(self.exception_handler, self.loop, response, task) self.loop, task_wrapper(self.exception_handler, self.loop, response, task)
) )
# force asyncio run once to enable req in async functions before first await
self.loop._run_once()
return None # this future maybe already done and reused not safe to await return None # this future maybe already done and reused not safe to await
def _run_async_cpython(self, task, response=None): def _run_async_cpython(self, task, response=None):
# this guarantees error 500 in case of uncaught exceptions, and can trigger the custom error handler # this garanties error 500 in case of uncaught exceptions, and can trigger the custom error handler
# using an coroutine wrapper generates less overhead than using add_done_callback # using an coroutine wrapper generates less overhead than using add_done_callback
# this is an custom task/future with less overhead and that calls the first step future = self.loop.create_task(
future = create_task(self.loop, task_wrapper(self.exception_handler, self.loop, response, task)) task_wrapper(self.exception_handler, self.loop, response, task)
)
# force asyncio run once to enable req in async functions before first await
self.loop._run_once()
return None # this future is safe to await but we return None for compatibility, and in the future will be the same behavior as PyPy return None # this future is safe to await but we return None for compatibility, and in the future will be the same behavior as PyPy
def dispose(self): def dispose(self):

Wyświetl plik

@ -187,7 +187,7 @@ void uws_add_server_name_with_options(int ssl, uws_app_t *app, const char *hostn
void uws_missing_server_name(int ssl, uws_app_t *app, uws_missing_server_handler handler, void *user_data); void uws_missing_server_name(int ssl, uws_app_t *app, uws_missing_server_handler handler, void *user_data);
void uws_filter(int ssl, uws_app_t *app, uws_filter_handler handler, void *user_data); void uws_filter(int ssl, uws_app_t *app, uws_filter_handler handler, void *user_data);
void uws_res_close(int ssl, uws_res_t *res);
void uws_res_end(int ssl, uws_res_t *res, const char *data, size_t length, bool close_connection); void uws_res_end(int ssl, uws_res_t *res, const char *data, size_t length, bool close_connection);
void uws_res_pause(int ssl, uws_res_t *res); void uws_res_pause(int ssl, uws_res_t *res);
void uws_res_resume(int ssl, uws_res_t *res); void uws_res_resume(int ssl, uws_res_t *res);
@ -215,7 +215,7 @@ size_t uws_res_get_proxied_remote_address_as_text(int ssl, uws_res_t *res, const
bool uws_req_is_ancient(uws_req_t *res); bool uws_req_is_ancient(uws_req_t *res);
bool uws_req_get_yield(uws_req_t *res); bool uws_req_get_yield(uws_req_t *res);
void uws_req_set_yield(uws_req_t *res, bool yield); void uws_req_set_field(uws_req_t *res, bool yield);
size_t uws_req_get_url(uws_req_t *res, const char **dest); size_t uws_req_get_url(uws_req_t *res, const char **dest);
size_t uws_req_get_method(uws_req_t *res, const char **dest); size_t uws_req_get_method(uws_req_t *res, const char **dest);
size_t uws_req_get_case_sensitive_method(uws_req_t *res, const char **dest); size_t uws_req_get_case_sensitive_method(uws_req_t *res, const char **dest);
@ -352,21 +352,21 @@ typedef struct {
socketify_header* header_list; socketify_header* header_list;
} socketify_asgi_ws_data; } socketify_asgi_ws_data;
typedef void (*socketify_asgi_method_handler)(int ssl, uws_res_t *response, socketify_asgi_data request, void *user_data); typedef void (*socketify_asgi_method_handler)(int ssl, uws_res_t *response, socketify_asgi_data request, void *user_data, bool* aborted);
typedef struct { typedef struct {
int ssl; int ssl;
uws_app_t* app; uws_app_t* app;
socketify_asgi_method_handler handler; socketify_asgi_method_handler handler;
void * user_data; void * user_data;
} socksocketify_asgi_app_info; } socksocketify_asgi_app_info;
typedef void (*socketify_asgi_ws_method_handler)(int ssl, uws_res_t *response, socketify_asgi_ws_data request, uws_socket_context_t* socket, void *user_data); typedef void (*socketify_asgi_ws_method_handler)(int ssl, uws_res_t *response, socketify_asgi_ws_data request, uws_socket_context_t* socket, void *user_data, bool* aborted);
typedef struct { typedef struct {
int ssl; int ssl;
uws_app_t* app; uws_app_t* app;
socketify_asgi_ws_method_handler handler; socketify_asgi_ws_method_handler handler;
uws_socket_behavior_t behavior; uws_socket_behavior_t behavior;
void * user_data; void * user_data;
} socketify_asgi_ws_app_info; } socksocketify_asgi_ws_app_info;
socketify_asgi_data socketify_asgi_request(int ssl, uws_req_t *req, uws_res_t *res); socketify_asgi_data socketify_asgi_request(int ssl, uws_req_t *req, uws_res_t *res);
void socketify_destroy_headers(socketify_header* headers); void socketify_destroy_headers(socketify_header* headers);
@ -376,8 +376,8 @@ socketify_asgi_ws_data socketify_asgi_ws_request(int ssl, uws_req_t *req, uws_re
bool socketify_res_write_int_status(int ssl, uws_res_t* res, int code); bool socketify_res_write_int_status(int ssl, uws_res_t* res, int code);
socksocketify_asgi_app_info* socketify_add_asgi_http_handler(int ssl, uws_app_t* app, socketify_asgi_method_handler handler, void* user_data); socksocketify_asgi_app_info* socketify_add_asgi_http_handler(int ssl, uws_app_t* app, socketify_asgi_method_handler handler, void* user_data);
void socketify_destroy_asgi_app_info(socksocketify_asgi_app_info* app); void socketify_destroy_asgi_app_info(socksocketify_asgi_app_info* app);
socketify_asgi_ws_app_info* socketify_add_asgi_ws_handler(int ssl, uws_app_t* app, uws_socket_behavior_t behavior, socketify_asgi_ws_method_handler handler, void* user_data); socksocketify_asgi_ws_app_info* socketify_add_asgi_ws_handler(int ssl, uws_app_t* app, uws_socket_behavior_t behavior, socketify_asgi_ws_method_handler handler, void* user_data);
void socketify_destroy_asgi_ws_app_info(socketify_asgi_ws_app_info* app); void socketify_destroy_asgi_ws_app_info(socksocketify_asgi_ws_app_info* app);
void socketify_res_cork_write(int ssl, uws_res_t *response, const char* data, size_t length); void socketify_res_cork_write(int ssl, uws_res_t *response, const char* data, size_t length);
void socketify_res_cork_end(int ssl, uws_res_t *response, const char* data, size_t length, bool close_connection); void socketify_res_cork_end(int ssl, uws_res_t *response, const char* data, size_t length, bool close_connection);
@ -385,12 +385,6 @@ void socketify_ws_cork_send(int ssl, uws_websocket_t *ws, const char* data, size
void socketify_ws_cork_send_with_options(int ssl, uws_websocket_t *ws, const char* data, size_t length, uws_opcode_t opcode, bool compress, bool close_connection); void socketify_ws_cork_send_with_options(int ssl, uws_websocket_t *ws, const char* data, size_t length, uws_opcode_t opcode, bool compress, bool close_connection);
void socketify_res_send_int_code(int ssl, uws_res_t *res, const char* content_data, size_t content_data_size, int code, const char *content_type, size_t content_type_size, bool close_connection);
void socketify_res_send(int ssl, uws_res_t *res, const char *content_data, size_t content_data_size, const char *status_code, size_t status_code_size, const char *content_type, size_t content_type_size, bool close_connection);
void socketify_res_cork_send_int_code(int ssl, uws_res_t *res, const char* content_data, size_t content_data_size, int code, const char *content_type, size_t content_type_size, bool close_connection);
void socketify_res_cork_send(int ssl, uws_res_t *res, const char *content_data, size_t content_data_size, const char *status_code, size_t status_code_size, const char *content_type, size_t content_type_size, bool close_connection);
""" """
) )

Wyświetl plik

@ -1,7 +1,7 @@
LIBRARY_NAME := libsocketify LIBRARY_NAME := libsocketify
UWS_LIBRARY_NAME := libuwebsockets UWS_LIBRARY_NAME := libuwebsockets
CC ?= clang CC := clang
CXX ?= clang++ CXX := clang++
ARCH := amd64 ARCH := amd64
ifeq ($(PLATFORM), arm64) ifeq ($(PLATFORM), arm64)
@ -72,14 +72,17 @@ macos:
# build boringssl # build boringssl
cd ../uWebSockets/uSockets/boringssl && mkdir -p amd64 && cd amd64 && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 .. && make crypto ssl cd ../uWebSockets/uSockets/boringssl && mkdir -p amd64 && cd amd64 && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 .. && make crypto ssl
# build lsquic
cd ../uWebSockets/uSockets/lsquic && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBORINGSSL_DIR=../boringssl -DCMAKE_BUILD_TYPE=Release -DLSQUIC_BIN=Off . && make lsquic
# build uWebSockets # build uWebSockets
cd ../uWebSockets/uSockets && $(CC) -mmacosx-version-min=10.14 -I src -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -pthread -fPIC -std=c11 -O3 -c src/*.c src/eventing/*.c src/crypto/*.c cd ../uWebSockets/uSockets && $(CC) -mmacosx-version-min=10.14 -I src -I lsquic/include -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -DLIBUS_USE_QUIC -pthread -fPIC -std=c11 -O3 -c src/*.c src/eventing/*.c src/crypto/*.c
cd ../uWebSockets/uSockets && $(CXX) -stdlib=libc++ -mmacosx-version-min=10.14 -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -pthread -fPIC -std=c++17 -O3 -c src/crypto/*.cpp cd ../uWebSockets/uSockets && $(CXX) -stdlib=libc++ -mmacosx-version-min=10.14 -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -DLIBUS_USE_QUIC -pthread -fPIC -std=c++17 -O3 -c src/crypto/*.cpp
cd ../uWebSockets/uSockets && $(AR) rvs uSockets_darwin_amd64.a *.o cd ../uWebSockets/uSockets && $(AR) rvs uSockets_darwin_amd64.a *.o
# build CAPI + libsocketify # build CAPI + libsocketify
$(CXX) -stdlib=libc++ -mmacosx-version-min=10.14 -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp $(CXX) -stdlib=libc++ -mmacosx-version-min=10.14 -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/lsquic/include -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp
$(CXX) -stdlib=libc++ -mmacosx-version-min=10.14 -shared -undefined dynamic_lookup -o ../$(LIBRARY_NAME)_darwin_amd64.so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_darwin_amd64.a ../uWebSockets/uSockets/boringssl/amd64/ssl/libssl.a ../uWebSockets/uSockets/boringssl/amd64/crypto/libcrypto.a -flto -fPIC -lz -luv $(CXX) -stdlib=libc++ -mmacosx-version-min=10.14 -shared -undefined dynamic_lookup -o ../$(LIBRARY_NAME)_darwin_amd64.so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_darwin_amd64.a ../uWebSockets/uSockets/boringssl/amd64/ssl/libssl.a ../uWebSockets/uSockets/boringssl/amd64/crypto/libcrypto.a ../uWebSockets/uSockets/lsquic/src/liblsquic/liblsquic.a -flto -fPIC -lz -luv
linux: linux:
$(MAKE) clean $(MAKE) clean
@ -87,26 +90,29 @@ linux:
# build boringssl # build boringssl
cd ../uWebSockets/uSockets/boringssl && mkdir -p $(ARCH) && cd $(ARCH) && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release .. && make crypto ssl cd ../uWebSockets/uSockets/boringssl && mkdir -p $(ARCH) && cd $(ARCH) && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release .. && make crypto ssl
# build lsquic
cd ../uWebSockets/uSockets/lsquic && cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBORINGSSL_DIR=../boringssl -DCMAKE_BUILD_TYPE=Release -DLSQUIC_BIN=Off . && make lsquic
# build uWebSockets # build uWebSockets
cd ../uWebSockets/uSockets && $(CC) -I src -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -pthread -fPIC -std=c11 -O3 -c src/*.c src/eventing/*.c src/crypto/*.c cd ../uWebSockets/uSockets && $(CC) -I src -I lsquic/include -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -DLIBUS_USE_QUIC -pthread -fPIC -std=c11 -O3 -c src/*.c src/eventing/*.c src/crypto/*.c
cd ../uWebSockets/uSockets && $(CXX) -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -pthread -fPIC -std=c++17 -O3 -c src/crypto/*.cpp cd ../uWebSockets/uSockets && $(CXX) -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -DLIBUS_USE_QUIC -pthread -fPIC -std=c++17 -O3 -c src/crypto/*.cpp
cd ../uWebSockets/uSockets && $(AR) rvs uSockets_linux_$(ARCH).a *.o cd ../uWebSockets/uSockets && $(AR) rvs uSockets_linux_$(ARCH).a *.o
# build CAPI + libsocketify # build CAPI + libsocketify
$(CXX) -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp $(CXX) -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/lsquic/include -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp
$(CXX) -shared -static-libstdc++ -static-libgcc -s -o ../$(LIBRARY_NAME)_linux_$(ARCH).so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_linux_$(ARCH).a ../uWebSockets/uSockets/boringssl/$(ARCH)/ssl/libssl.a ../uWebSockets/uSockets/boringssl/$(ARCH)/crypto/libcrypto.a -flto -fPIC -lz -luv $(CXX) -shared -static-libstdc++ -static-libgcc -s -o ../$(LIBRARY_NAME)_linux_$(ARCH).so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_linux_$(ARCH).a ../uWebSockets/uSockets/boringssl/$(ARCH)/ssl/libssl.a ../uWebSockets/uSockets/boringssl/$(ARCH)/crypto/libcrypto.a ../uWebSockets/uSockets/lsquic/src/liblsquic/liblsquic.a -flto -fPIC -lz -luv
linux-uws-socketify: linux-uws-socketify:
# build uWebSockets # build uWebSockets
cd ../uWebSockets/uSockets && $(CC) -I src -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -pthread -fPIC -std=c11 -O3 -c src/*.c src/eventing/*.c src/crypto/*.c cd ../uWebSockets/uSockets && $(CC) -I src -I lsquic/include -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -DLIBUS_USE_QUIC -pthread -fPIC -std=c11 -O3 -c src/*.c src/eventing/*.c src/crypto/*.c
cd ../uWebSockets/uSockets && $(CXX) -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -pthread -fPIC -std=c++17 -O3 -c src/crypto/*.cpp cd ../uWebSockets/uSockets && $(CXX) -I boringssl/include -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -DLIBUS_USE_QUIC -pthread -fPIC -std=c++17 -O3 -c src/crypto/*.cpp
cd ../uWebSockets/uSockets && $(AR) rvs uSockets_linux_$(ARCH).a *.o cd ../uWebSockets/uSockets && $(AR) rvs uSockets_linux_$(ARCH).a *.o
# build CAPI + libsocketify # build CAPI + libsocketify
$(CXX) -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp $(CXX) -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/lsquic/include -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp
$(CXX) -shared -static-libstdc++ -static-libgcc -s -o ../$(LIBRARY_NAME)_linux_$(ARCH).so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_linux_$(ARCH).a ../uWebSockets/uSockets/boringssl/$(ARCH)/ssl/libssl.a ../uWebSockets/uSockets/boringssl/$(ARCH)/crypto/libcrypto.a -flto -fPIC -lz -luv $(CXX) -shared -static-libstdc++ -static-libgcc -s -o ../$(LIBRARY_NAME)_linux_$(ARCH).so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_linux_$(ARCH).a ../uWebSockets/uSockets/boringssl/$(ARCH)/ssl/libssl.a ../uWebSockets/uSockets/boringssl/$(ARCH)/crypto/libcrypto.a ../uWebSockets/uSockets/lsquic/src/liblsquic/liblsquic.a -flto -fPIC -lz -luv
linux-socketify-only: linux-socketify-only:
# build CAPI + libsocketify # build CAPI + libsocketify
$(CXX) -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp $(CXX) -I ./src -I ../uWebSockets/src -I ../uWebSockets/uSockets/src -I ../uWebSockets/capi -I ../uWebSockets/uSockets/lsquic/include -I ../uWebSockets/uSockets/boringssl/include -DUWS_WITH_PROXY -pthread -fPIC -std=c++17 -c -O3 ./src/$(LIBRARY_NAME).cpp
$(CXX) -shared -static-libstdc++ -static-libgcc -s -o ../$(LIBRARY_NAME)_linux_$(ARCH).so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_linux_$(ARCH).a ../uWebSockets/uSockets/boringssl/$(ARCH)/ssl/libssl.a ../uWebSockets/uSockets/boringssl/$(ARCH)/crypto/libcrypto.a -flto -fPIC -lz -luv $(CXX) -shared -static-libstdc++ -static-libgcc -s -o ../$(LIBRARY_NAME)_linux_$(ARCH).so $(LIBRARY_NAME).o ../uWebSockets/uSockets/uSockets_linux_$(ARCH).a ../uWebSockets/uSockets/boringssl/$(ARCH)/ssl/libssl.a ../uWebSockets/uSockets/boringssl/$(ARCH)/crypto/libcrypto.a ../uWebSockets/uSockets/lsquic/src/liblsquic/liblsquic.a -flto -fPIC -lz -luv

Wyświetl plik

@ -8,144 +8,136 @@
extern "C" extern "C"
{ {
#endif #endif
DLL_EXPORT typedef void (*socketify_prepare_handler)(void *user_data); DLL_EXPORT typedef void (*socketify_prepare_handler)(void* user_data);
DLL_EXPORT typedef void (*socketify_timer_handler)(void *user_data); DLL_EXPORT typedef void (*socketify_timer_handler)(void* user_data);
DLL_EXPORT typedef enum { DLL_EXPORT typedef enum {
SOCKETIFY_RUN_DEFAULT = 0, SOCKETIFY_RUN_DEFAULT = 0,
SOCKETIFY_RUN_ONCE, SOCKETIFY_RUN_ONCE,
SOCKETIFY_RUN_NOWAIT SOCKETIFY_RUN_NOWAIT
} socketify_run_mode; } socketify_run_mode;
DLL_EXPORT typedef struct DLL_EXPORT typedef struct {
{ void* uv_prepare_ptr;
void *uv_prepare_ptr;
socketify_prepare_handler on_prepare_handler; socketify_prepare_handler on_prepare_handler;
void *on_prepare_data; void* on_prepare_data;
void *uv_loop; void* uv_loop;
} socketify_loop; } socketify_loop;
DLL_EXPORT typedef struct DLL_EXPORT typedef struct{
{ void* uv_timer_ptr;
void *uv_timer_ptr;
socketify_timer_handler handler; socketify_timer_handler handler;
void *user_data; void* user_data;
} socketify_timer; } socketify_timer;
DLL_EXPORT typedef struct
{
const char *name; DLL_EXPORT typedef struct {
const char *value;
size_t name_size; const char* name;
size_t value_size; const char* value;
size_t name_size;
size_t value_size;
void* next;
} socketify_header;
void *next;
} socketify_header;
DLL_EXPORT typedef struct DLL_EXPORT typedef struct {
{
const char *full_url; const char* full_url;
const char *url; const char* url;
const char *query_string; const char* query_string;
const char *method; const char* method;
const char *remote_address; const char* remote_address;
size_t full_url_size; size_t full_url_size;
size_t url_size; size_t url_size;
size_t query_string_size; size_t query_string_size;
size_t method_size; size_t method_size;
size_t remote_address_size; size_t remote_address_size;
socketify_header *header_list; socketify_header* header_list;
bool has_content; bool has_content;
} socketify_asgi_data; } socketify_asgi_data;
DLL_EXPORT typedef struct DLL_EXPORT typedef struct {
{
const char *full_url; const char* full_url;
const char *url; const char* url;
const char *query_string; const char* query_string;
const char *method; const char* method;
const char *remote_address; const char* remote_address;
size_t full_url_size; size_t full_url_size;
size_t url_size; size_t url_size;
size_t query_string_size; size_t query_string_size;
size_t method_size; size_t method_size;
size_t remote_address_size; size_t remote_address_size;
const char *protocol; const char* protocol;
const char *key; const char* key;
const char *extensions; const char* extensions;
size_t protocol_size; size_t protocol_size;
size_t key_size; size_t key_size;
size_t extensions_size; size_t extensions_size;
socketify_header *header_list; socketify_header* header_list;
} socketify_asgi_ws_data; } socketify_asgi_ws_data;
DLL_EXPORT typedef void (*socketify_asgi_method_handler)(int ssl, uws_res_t *response, socketify_asgi_data request, void *user_data); DLL_EXPORT typedef void (*socketify_asgi_method_handler)(int ssl, uws_res_t *response, socketify_asgi_data request, void *user_data, bool* aborted);
DLL_EXPORT typedef void (*socketify_asgi_ws_method_handler)(int ssl, uws_res_t *response, socketify_asgi_ws_data request, uws_socket_context_t *socket, void *user_data); DLL_EXPORT typedef void (*socketify_asgi_ws_method_handler)(int ssl, uws_res_t *response, socketify_asgi_ws_data request, uws_socket_context_t* socket, void *user_data, bool* aborted);
DLL_EXPORT typedef struct DLL_EXPORT typedef struct {
{ int ssl;
int ssl; uws_app_t* app;
uws_app_t *app; socketify_asgi_method_handler handler;
socketify_asgi_method_handler handler; void * user_data;
void *user_data; } socksocketify_asgi_app_info;
} socksocketify_asgi_app_info;
DLL_EXPORT typedef struct DLL_EXPORT typedef struct {
{ int ssl;
int ssl; uws_app_t* app;
uws_app_t *app; socketify_asgi_ws_method_handler handler;
socketify_asgi_ws_method_handler handler; uws_socket_behavior_t behavior;
uws_socket_behavior_t behavior; void * user_data;
void *user_data; } socksocketify_asgi_ws_app_info;
} socketify_asgi_ws_app_info;
DLL_EXPORT socketify_loop *socketify_create_loop();
DLL_EXPORT bool socketify_constructor_failed(socketify_loop *loop);
DLL_EXPORT bool socketify_on_prepare(socketify_loop *loop, socketify_prepare_handler handler, void *user_data);
DLL_EXPORT bool socketify_prepare_unbind(socketify_loop *loop);
DLL_EXPORT void socketify_destroy_loop(socketify_loop *loop);
DLL_EXPORT void *socketify_get_native_loop(socketify_loop *loop);
DLL_EXPORT int socketify_loop_run(socketify_loop *loop, socketify_run_mode mode); DLL_EXPORT socketify_loop * socketify_create_loop();
DLL_EXPORT void socketify_loop_stop(socketify_loop *loop); DLL_EXPORT bool socketify_constructor_failed(socketify_loop* loop);
DLL_EXPORT bool socketify_on_prepare(socketify_loop* loop, socketify_prepare_handler handler, void* user_data);
DLL_EXPORT bool socketify_prepare_unbind(socketify_loop* loop);
DLL_EXPORT void socketify_destroy_loop(socketify_loop* loop);
DLL_EXPORT void* socketify_get_native_loop(socketify_loop* loop);
DLL_EXPORT socketify_timer *socketify_create_timer(socketify_loop *loop, uint64_t timeout, uint64_t repeat, socketify_timer_handler handler, void *user_data); DLL_EXPORT int socketify_loop_run(socketify_loop* loop, socketify_run_mode mode);
DLL_EXPORT void socketify_timer_destroy(socketify_timer *timer); DLL_EXPORT void socketify_loop_stop(socketify_loop* loop);
DLL_EXPORT void socketify_timer_set_repeat(socketify_timer *timer, uint64_t repeat);
DLL_EXPORT socketify_timer *socketify_create_check(socketify_loop *loop, socketify_timer_handler handler, void *user_data); DLL_EXPORT socketify_timer* socketify_create_timer(socketify_loop* loop, uint64_t timeout, uint64_t repeat, socketify_timer_handler handler, void* user_data);
DLL_EXPORT void socketify_check_destroy(socketify_timer *timer); DLL_EXPORT void socketify_timer_destroy(socketify_timer* timer);
DLL_EXPORT void socketify_timer_set_repeat(socketify_timer* timer, uint64_t repeat);
DLL_EXPORT socketify_asgi_data socketify_asgi_request(int ssl, uws_req_t *req, uws_res_t *res); DLL_EXPORT socketify_timer* socketify_create_check(socketify_loop* loop, socketify_timer_handler handler, void* user_data);
DLL_EXPORT void socketify_destroy_headers(socketify_header *headers); DLL_EXPORT void socketify_check_destroy(socketify_timer* timer);
DLL_EXPORT bool socketify_res_write_int_status_with_headers(int ssl, uws_res_t *res, int code, socketify_header *headers);
DLL_EXPORT void socketify_res_write_headers(int ssl, uws_res_t *res, socketify_header *headers);
DLL_EXPORT bool socketify_res_write_int_status(int ssl, uws_res_t *res, int code);
DLL_EXPORT socketify_asgi_ws_data socketify_asgi_ws_request(int ssl, uws_req_t *req, uws_res_t *res);
DLL_EXPORT socksocketify_asgi_app_info *socketify_add_asgi_http_handler(int ssl, uws_app_t *app, socketify_asgi_method_handler handler, void *user_data); DLL_EXPORT socketify_asgi_data socketify_asgi_request(int ssl, uws_req_t *req, uws_res_t *res);
DLL_EXPORT void socketify_destroy_asgi_app_info(socksocketify_asgi_app_info *app); DLL_EXPORT void socketify_destroy_headers(socketify_header* headers);
DLL_EXPORT bool socketify_res_write_int_status_with_headers(int ssl, uws_res_t* res, int code, socketify_header* headers);
DLL_EXPORT void socketify_res_write_headers(int ssl, uws_res_t* res, socketify_header* headers);
DLL_EXPORT bool socketify_res_write_int_status(int ssl, uws_res_t* res, int code);
DLL_EXPORT socketify_asgi_ws_data socketify_asgi_ws_request(int ssl, uws_req_t *req, uws_res_t *res);
DLL_EXPORT void socketify_res_cork_write(int ssl, uws_res_t *response, const char *data, size_t length); DLL_EXPORT socksocketify_asgi_app_info* socketify_add_asgi_http_handler(int ssl, uws_app_t* app, socketify_asgi_method_handler handler, void* user_data);
DLL_EXPORT void socketify_res_cork_end(int ssl, uws_res_t *response, const char *data, size_t length, bool close_connection); DLL_EXPORT void socketify_destroy_asgi_app_info(socksocketify_asgi_app_info* app);
DLL_EXPORT socketify_asgi_ws_app_info *socketify_add_asgi_ws_handler(int ssl, uws_app_t *app, uws_socket_behavior_t behavior, socketify_asgi_ws_method_handler handler, void *user_data); DLL_EXPORT void socketify_res_cork_write(int ssl, uws_res_t *response, const char* data, size_t length);
DLL_EXPORT void socketify_destroy_asgi_ws_app_info(socketify_asgi_ws_app_info *app); DLL_EXPORT void socketify_res_cork_end(int ssl, uws_res_t *response, const char* data, size_t length, bool close_connection);
DLL_EXPORT void socketify_ws_cork_send(int ssl, uws_websocket_t *ws, const char *data, size_t length, uws_opcode_t opcode);
DLL_EXPORT void socketify_ws_cork_send_with_options(int ssl, uws_websocket_t *ws, const char *data, size_t length, uws_opcode_t opcode, bool compress, bool fin); DLL_EXPORT socksocketify_asgi_ws_app_info* socketify_add_asgi_ws_handler(int ssl, uws_app_t* app, uws_socket_behavior_t behavior, socketify_asgi_ws_method_handler handler, void* user_data);
DLL_EXPORT void socketify_res_send_int_code(int ssl, uws_res_t *res, const char *content_data, size_t content_data_size, int code, const char *content_type, size_t content_type_size, bool close_connection); DLL_EXPORT void socketify_destroy_asgi_ws_app_info(socksocketify_asgi_ws_app_info* app);
DLL_EXPORT void socketify_res_send(int ssl, uws_res_t *res, const char *content_data, size_t content_data_size, const char *status_code, size_t status_code_size, const char *content_type, size_t content_type_size, bool close_connection); DLL_EXPORT void socketify_ws_cork_send(int ssl, uws_websocket_t *ws, const char* data, size_t length, uws_opcode_t opcode);
DLL_EXPORT void socketify_res_cork_send_int_code(int ssl, uws_res_t *res, const char *content_data, size_t content_data_size, int code, const char *content_type, size_t content_type_size, bool close_connection); DLL_EXPORT void socketify_ws_cork_send_with_options(int ssl, uws_websocket_t *ws, const char* data, size_t length, uws_opcode_t opcode, bool compress, bool fin);
DLL_EXPORT void socketify_res_cork_send(int ssl, uws_res_t *res, const char *content_data, size_t content_data_size, const char *status_code, size_t status_code_size, const char *content_type, size_t content_type_size, bool close_connection);
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus
} }

Wyświetl plik

@ -25,7 +25,7 @@ class SSGIHttpResponse:
# send chunk of data, can be used to perform with less backpressure than using send # send chunk of data, can be used to perform with less backpressure than using send
# total_size is the sum of all lengths in bytes of all chunks to be sended # total_size is the sum of all lengths in bytes of all chunks to be sended
# connection will end when total_size is met # connection will end when total_size is met
# returns tuple(bool, bool) first bool represents if the chunk is successfully sended, the second if the connection has ended # returns tuple(bool, bool) first bool represents if the chunk is succefully sended, the second if the connection has ended
def send_chunk(self, chunk: Union[bytes, bytearray, memoryview], total_size: int) -> Awaitable: def send_chunk(self, chunk: Union[bytes, bytearray, memoryview], total_size: int) -> Awaitable:
return self.res.send_chunk(chunk, total_size) return self.res.send_chunk(chunk, total_size)

Wyświetl plik

@ -6,7 +6,6 @@ from asyncio import (
futures, futures,
_register_task, _register_task,
_enter_task, _enter_task,
current_task,
_leave_task, _leave_task,
_unregister_task, _unregister_task,
) )
@ -37,9 +36,7 @@ class RequestTask:
Differences: Differences:
- This class do not support current_task - This class is only used by socketify.py loop.run_async
- This class executes the first step like node.js Promise
- This class is not thread-safe. - This class is not thread-safe.
@ -92,7 +89,6 @@ class RequestTask:
# status is still pending # status is still pending
_log_destroy_pending = True _log_destroy_pending = True
_parent_task = None
def __init__( def __init__(
self, coro, loop, default_done_callback=None, no_register=False, context=None self, coro, loop, default_done_callback=None, no_register=False, context=None
): ):
@ -119,12 +115,8 @@ class RequestTask:
self._log_destroy_pending = False self._log_destroy_pending = False
if self._loop.get_debug(): if self._loop.get_debug():
self._source_traceback = format_helpers.extract_stack(sys._getframe(1)) self._source_traceback = format_helpers.extract_stack(sys._getframe(1))
self._loop.call_soon(self.__step, context=self._context)
_register_task(self) _register_task(self)
if loop.is_running():
self.__step()
else:
self._loop.call_soon(self.__step, context=self._context)
def _reuse(self, coro, loop, default_done_callback=None): def _reuse(self, coro, loop, default_done_callback=None):
"""Reuse an future that is not pending anymore.""" """Reuse an future that is not pending anymore."""
@ -155,12 +147,8 @@ class RequestTask:
self._fut_waiter = None self._fut_waiter = None
self._coro = coro self._coro = coro
self._loop.call_soon(self.__step, context=self._context)
_register_task(self) _register_task(self)
# if current_task():
# self._loop.call_soon(self.__step, context=self._context)
# else:
self.__step()
def __repr__(self): def __repr__(self):
return base_tasks._task_repr(self) return base_tasks._task_repr(self)
@ -499,11 +487,7 @@ class RequestTask:
self._must_cancel = False self._must_cancel = False
coro = self._coro coro = self._coro
self._fut_waiter = None self._fut_waiter = None
_parent_task = current_task(self._loop)
if _parent_task is not None:
_leave_task(self._loop, _parent_task)
self._parent_task = _parent_task
_enter_task(self._loop, self) _enter_task(self._loop, self)
# Call either coro.throw(exc) or coro.send(None). # Call either coro.throw(exc) or coro.send(None).
try: try:
@ -575,9 +559,6 @@ class RequestTask:
self._loop.call_soon(self.__step, new_exc, context=self._context) self._loop.call_soon(self.__step, new_exc, context=self._context)
finally: finally:
_leave_task(self._loop, self) _leave_task(self._loop, self)
if self._parent_task is not None:
_enter_task(self._loop, self._parent_task)
self._parent_task = None
self = None # Needed to break cycles when an exception occurs. self = None # Needed to break cycles when an exception occurs.
def __wakeup(self, future): def __wakeup(self, future):
@ -599,29 +580,29 @@ class RequestTask:
__iter__ = __await__ # make compatible with 'yield from'. __iter__ = __await__ # make compatible with 'yield from'.
async def factory_task_wrapper(task, dispose): def create_task_with_factory(task_factory_max_items=100_000):
try: items = []
await task for _ in range(0, task_factory_max_items):
finally: task = RequestTask(None, None, None, True)
dispose() if task._source_traceback:
del task._source_traceback[-1]
items.append(task)
class TaskFactory: def factory(loop, coro, default_done_callback=None):
def __init__(self, task_factory_max_items=100_000): if len(items) == 0:
self.items = [] return create_task(loop, coro, default_done_callback)
for _ in range(0, task_factory_max_items): task = items.pop()
task = RequestTask(None, None, None, True)
if task._source_traceback:
del task._source_traceback[-1]
self.items.append(task)
def __call__(self, loop, coro): def done(f):
if len(self.items) == 0: if default_done_callback is not None:
return create_task(loop, coro) default_done_callback(f)
task = self.items.pop() items.append(f)
task._reuse(factory_task_wrapper(coro, lambda : self.items.append(task)), loop) task._reuse(coro, loop, done)
return task return task
return factory
def create_task(loop, coro, default_done_callback=None, context=None): def create_task(loop, coro, default_done_callback=None, context=None):
"""Schedule a coroutine object. """Schedule a coroutine object.

@ -1 +1 @@
Subproject commit f33291d10a2051ba0f10c18761c030fb00390fdf Subproject commit 7187fc3d658d4335cdf0c79371eeb8310717b95c

Wyświetl plik

@ -1,4 +1,3 @@
from .native import ffi, lib from .native import ffi, lib
@ -91,7 +90,7 @@ class UVLoop:
def run(self): def run(self):
if self._loop != ffi.NULL: if self._loop != ffi.NULL:
return lib.socketify_loop_run(self._loop, lib.SOCKETIFY_RUN_DEFAULT) return lib.socketify_loop_run(self._loop, lib.SOCKETIFY_RUN_DEFAULT)
def run_once(self): def run_once(self):
if self._loop != ffi.NULL: if self._loop != ffi.NULL:
return lib.socketify_loop_run(self._loop, lib.SOCKETIFY_RUN_ONCE) return lib.socketify_loop_run(self._loop, lib.SOCKETIFY_RUN_ONCE)

Wyświetl plik

@ -7,16 +7,14 @@ from .native import lib, ffi
import platform import platform
is_pypy = platform.python_implementation() == "PyPy" is_pypy = platform.python_implementation() == "PyPy"
from .tasks import create_task, TaskFactory from .tasks import create_task, create_task_with_factory
import sys import sys
import logging import logging
import uuid
@ffi.callback("void(uws_res_t*, const char*, size_t, bool, void*)") @ffi.callback("void(uws_res_t*, const char*, size_t, bool, void*)")
def wsgi_on_data_handler(res, chunk, chunk_length, is_end, user_data): def wsgi_on_data_handler(res, chunk, chunk_length, is_end, user_data):
data_response = ffi.from_handle(user_data) data_response = ffi.from_handle(user_data)
data_response.app.server.loop.is_idle = False
if chunk != ffi.NULL: if chunk != ffi.NULL:
data_response.buffer.write(ffi.unpack(chunk, chunk_length)) data_response.buffer.write(ffi.unpack(chunk, chunk_length))
if bool(is_end): if bool(is_end):
@ -27,87 +25,6 @@ def wsgi_on_data_handler(res, chunk, chunk_length, is_end, user_data):
data_response._ptr, data_response._ptr,
) )
@ffi.callback("void(uws_res_t*, void*)")
def wsgi_on_data_ref_abort_handler(res, user_data):
data_retry = ffi.from_handle(user_data)
data_retry.aborted = True
data_retry.server.loop.is_idle = False
if data_retry.id is not None:
data_retry.app._data_refs.pop(data_retry.id, None)
@ffi.callback("bool(uws_res_t*, uintmax_t, void*)")
def wsgi_on_writable_handler(res, offset, user_data):
data_retry = ffi.from_handle(user_data)
if data_retry.aborted:
return False
chunks = data_retry.chunks
last_sended_offset = data_retry.last_offset
server = data_retry.app.server
ssl = server.SSL
server.loop.is_idle = False
content_length = data_retry.content_length
data = chunks[0]
data_size = len(data)
last_offset = int(lib.uws_res_get_write_offset(ssl, res))
if last_sended_offset != last_offset:
offset = last_offset - last_sended_offset
data = data[offset:data_size]
data_size = len(data)
if data_size == 0:
chunks.pop(0)
if len(chunks) == 0:
logging.error(AssertionError("Content-Length do not match sended content"))
lib.uws_res_close(
ssl,
res
)
if data_retry.id is not None:
data_retry.app._data_refs.pop(data_retry.id, None)
return True
data = chunks[0]
result = lib.uws_res_try_end(
ssl,
res,
data,
data_size,
content_length,
0,
)
has_responded = bool(result.has_responded)
ok = bool(result.ok)
data_retry.last_offset = int(lib.uws_res_get_write_offset(ssl, res))
if ok:
chunks.pop(0)
if not has_responded and len(chunks) == 0:
logging.error(AssertionError("Content-Length do not match sended content"))
lib.uws_res_close(
ssl,
res
)
if data_retry.id is not None:
data_retry.app._data_refs.pop(data_retry.id, None)
elif has_responded and data_retry.id is not None:
data_retry.app._data_refs.pop(data_retry.id, None)
elif not has_responded and len(chunks) == 0:
logging.error(AssertionError("Content-Length do not match sended content"))
lib.uws_res_close(
ssl,
res
)
if data_retry.id is not None:
data_retry.app._data_refs.pop(data_retry.id, None)
elif has_responded and data_retry.id is not None:
data_retry.app._data_refs.pop(data_retry.id, None)
return ok
class WSGIBody: class WSGIBody:
def __init__(self, buffer): def __init__(self, buffer):
@ -198,25 +115,14 @@ class WSGIBody:
class WSGIDataResponse: class WSGIDataResponse:
def __init__(self, app, environ, start_response, buffer, on_data): def __init__(self, app, environ, start_response, aborted, buffer, on_data):
self.buffer = buffer self.buffer = buffer
self.aborted = aborted
self._ptr = ffi.new_handle(self) self._ptr = ffi.new_handle(self)
self.on_data = on_data self.on_data = on_data
self.environ = environ self.environ = environ
self.app = app self.app = app
self.start_response = start_response self.start_response = start_response
self.id = None
self.aborted = False
class WSGIRetryDataSend:
def __init__(self, app, chunks, content_length, last_offset):
self.chunks = chunks
self._ptr = ffi.new_handle(self)
self.app = app
self.content_length = content_length
self.last_offset = last_offset
self.id = None
self.aborted = False
@ffi.callback("void(uws_res_t*, void*)") @ffi.callback("void(uws_res_t*, void*)")
@ -225,12 +131,9 @@ def wsgi_corked_response_start_handler(res, user_data):
data_response.on_data(data_response, res) data_response.on_data(data_response, res)
@ffi.callback("void(int, uws_res_t*, socketify_asgi_data request, void*, bool*)")
@ffi.callback("void(int, uws_res_t*, socketify_asgi_data request, void*)") def wsgi(ssl, response, info, user_data, aborted):
def wsgi(ssl, response, info, user_data):
app = ffi.from_handle(user_data) app = ffi.from_handle(user_data)
app.server.loop.is_idle = False
# reusing the dict is slower than cloning because we need to clear HTTP headers # reusing the dict is slower than cloning because we need to clear HTTP headers
environ = dict(app.BASIC_ENVIRON) environ = dict(app.BASIC_ENVIRON)
@ -238,7 +141,7 @@ def wsgi(ssl, response, info, user_data):
environ["PATH_INFO"] = ffi.unpack(info.url, info.url_size).decode("utf8") environ["PATH_INFO"] = ffi.unpack(info.url, info.url_size).decode("utf8")
environ["QUERY_STRING"] = ffi.unpack( environ["QUERY_STRING"] = ffi.unpack(
info.query_string, info.query_string_size info.query_string, info.query_string_size
).decode("utf8")[1:] ).decode("utf8")
if info.remote_address != ffi.NULL: if info.remote_address != ffi.NULL:
environ["REMOTE_ADDR"] = ffi.unpack( environ["REMOTE_ADDR"] = ffi.unpack(
info.remote_address, info.remote_address_size info.remote_address, info.remote_address_size
@ -260,13 +163,11 @@ def wsgi(ssl, response, info, user_data):
headers_set = None headers_set = None
headers_written = False headers_written = False
status_text = None status_text = None
is_chunked = False
content_length = -1
def write_headers(headers): def write_headers(headers):
nonlocal headers_written, headers_set, status_text, content_length, is_chunked, app nonlocal headers_written, headers_set, status_text
if headers_written or not headers_set: if headers_written or not headers_set:
return return
app.server.loop.is_idle = False
headers_written = True headers_written = True
@ -282,33 +183,20 @@ def wsgi(ssl, response, info, user_data):
if ( if (
key == "content-length" key == "content-length"
or key == "Content-Length" or key == "Content-Length"
): or key == "Transfer-Encoding"
content_length = int(value)
continue # auto generated by try_end
if (
key == "Transfer-Encoding"
or key == "transfer-encoding" or key == "transfer-encoding"
): ):
is_chunked = str(value) == "chunked" continue # auto
if is_chunked:
continue
key_data = key.encode("utf-8") key_data = key.encode("utf-8")
elif isinstance(key, bytes): elif isinstance(key, bytes):
# this is faster than using .lower() # this is faster than using .lower()
if ( if (
key == b"content-length" key == b"content-length"
or key == b"Content-Length" or key == b"Content-Length"
): or key == b"Transfer-Encoding"
content_length = int(value)
continue # auto
if (
key == b"Transfer-Encoding"
or key == b"transfer-encoding" or key == b"transfer-encoding"
): ):
is_chunked = str(value) == "chunked" continue # auto
if is_chunked:
continue
key_data = key key_data = key
if isinstance(value, str): if isinstance(value, str):
@ -328,14 +216,9 @@ def wsgi(ssl, response, info, user_data):
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)
) )
# no content-length
if content_length < 0:
is_chunked = True
content_length = ffi.cast("uintmax_t", content_length)
def start_response(status, headers, exc_info=None): def start_response(status, headers, exc_info=None):
nonlocal headers_set, status_text, app nonlocal headers_set, status_text
app.server.loop.is_idle = False
if exc_info: if exc_info:
try: try:
if headers_written: if headers_written:
@ -350,13 +233,9 @@ def wsgi(ssl, response, info, user_data):
status_text = status status_text = status
def write(data): def write(data):
nonlocal is_chunked, app
app.server.loop.is_idle = False
if not headers_written: if not headers_written:
write_headers(headers_set) write_headers(headers_set)
# will allow older frameworks only with is_chunked
is_chunked = True
if isinstance(data, bytes): if isinstance(data, bytes):
lib.uws_res_write(ssl, response, data, len(data)) lib.uws_res_write(ssl, response, data, len(data))
elif isinstance(data, str): elif isinstance(data, str):
@ -365,69 +244,32 @@ def wsgi(ssl, response, info, user_data):
return write return write
# check for body # check for body
if bool(info.has_content): if bool(info.has_content):
WSGI_INPUT = BytesIO() WSGI_INPUT = BytesIO()
environ["wsgi.input"] = WSGIBody(WSGI_INPUT) environ["wsgi.input"] = WSGIBody(WSGI_INPUT)
def on_data(data_response, response): def on_data(data_response, response):
last_offset = -1 if bool(data_response.aborted[0]):
data_retry = None
failed_chunks = None
if data_response.aborted:
return return
data_response.app.server.loop.is_idle = False
ssl = data_response.app.server.SSL ssl = data_response.app.server.SSL
data_response.environ["CONTENT_LENGTH"] = str( data_response.environ["CONTENT_LENGTH"] = str(
data_response.buffer.getbuffer().nbytes data_response.buffer.getbuffer().nbytes
) )
if data_response.id is not None:
data_response.app._data_refs.pop(data_response.id, None)
app_iter = data_response.app.wsgi( app_iter = data_response.app.wsgi(
data_response.environ, data_response.start_response data_response.environ, data_response.start_response
) )
try: try:
for data in app_iter: for data in app_iter:
if data: if data and not headers_written:
if not headers_written: write_headers(headers_set)
write_headers(headers_set)
if isinstance(data, bytes):
if is_chunked: lib.uws_res_write(ssl, response, data, len(data))
if isinstance(data, bytes): elif isinstance(data, str):
lib.uws_res_write(ssl, response, data, len(data)) data = data.encode("utf-8")
elif isinstance(data, str): lib.uws_res_write(ssl, response, data, len(data))
data = data.encode("utf-8")
lib.uws_res_write(ssl, response, data, len(data))
else:
if isinstance(data, str):
data = data.encode("utf-8")
if failed_chunks:
failed_chunks.append(data)
else:
last_offset = int(lib.uws_res_get_write_offset(ssl, response))
result = lib.uws_res_try_end(
ssl,
response,
data,
len(data),
content_length,
0,
)
# this should be very very rare for HTTP
if not bool(result.ok):
failed_chunks = []
# just mark the chunks
failed_chunks.append(data)
# add on writable handler
data_retry = WSGIRetryDataSend(
app, failed_chunks, content_length, last_offset
)
except Exception as error: except Exception as error:
logging.exception(error) logging.exception(error)
@ -436,76 +278,27 @@ def wsgi(ssl, response, info, user_data):
app_iter.close() app_iter.close()
if not headers_written: if not headers_written:
write_headers(headers_set) write_headers(headers_set)
if is_chunked: lib.uws_res_end_without_body(ssl, response, 0)
lib.uws_res_end_without_body(ssl, response, 0)
elif data_retry is not None:
_id = uuid.uuid4()
app._data_refs[_id] = data_retry
lib.uws_res_on_aborted(ssl, response, wsgi_on_data_ref_abort_handler, data_retry._ptr)
lib.uws_res_on_writable(ssl, response, wsgi_on_writable_handler, data_retry._ptr)
elif result is None or (not bool(result.has_responded) and bool(result.ok)): # not reaches Content-Length
logging.error(AssertionError("Content-Length do not match sended content"))
lib.uws_res_close(
ssl,
response
)
data_response = WSGIDataResponse( data_response = WSGIDataResponse(
app, environ, start_response, WSGI_INPUT, on_data app, environ, start_response, aborted, WSGI_INPUT, on_data
) )
_id = uuid.uuid4()
data_response.id = _id
app._data_refs[_id] = data_response
lib.uws_res_on_aborted(ssl, response, wsgi_on_data_ref_abort_handler, data_response._ptr)
lib.uws_res_on_data(ssl, response, wsgi_on_data_handler, data_response._ptr) lib.uws_res_on_data(ssl, response, wsgi_on_data_handler, data_response._ptr)
else: else:
failed_chunks = None environ["wsgi.input"] = None
last_offset = -1
data_retry = None
# Django do not check for None with is lame
# we use the same empty for everyone to avoid extra allocations
environ["wsgi.input"] = app.EMPTY_WSGI_BODY
# we also set CONTENT_LENGTH to 0 so if Django is lame again its covered
environ["CONTENT_LENGTH"] = "0"
app_iter = app.wsgi(environ, start_response) app_iter = app.wsgi(environ, start_response)
result = None
try: try:
for data in app_iter: for data in app_iter:
if data: if data and not headers_written:
if not headers_written: write_headers(headers_set)
write_headers(headers_set)
if is_chunked:
if isinstance(data, bytes):
lib.uws_res_write(ssl, response, data, len(data))
elif isinstance(data, str):
data = data.encode("utf-8")
lib.uws_res_write(ssl, response, data, len(data))
else:
if isinstance(data, str):
data = data.encode("utf-8")
if failed_chunks: # if failed once, will fail again later
failed_chunks.append(data)
else:
last_offset = int(lib.uws_res_get_write_offset(ssl, response))
result = lib.uws_res_try_end(
ssl,
response,
data,
len(data),
content_length,
0,
)
# this should be very very rare for HTTP
if not bool(result.ok):
failed_chunks = []
# just mark the chunks
failed_chunks.append(data)
# add on writable handler
data_retry = WSGIRetryDataSend(
app, failed_chunks, content_length, last_offset
)
if isinstance(data, bytes):
lib.uws_res_write(ssl, response, data, len(data))
elif isinstance(data, str):
data = data.encode("utf-8")
lib.uws_res_write(ssl, response, data, len(data))
except Exception as error: except Exception as error:
logging.exception(error) logging.exception(error)
finally: finally:
@ -513,21 +306,8 @@ def wsgi(ssl, response, info, user_data):
app_iter.close() app_iter.close()
if not headers_written: if not headers_written:
write_headers(headers_set) write_headers(headers_set)
if is_chunked: lib.uws_res_end_without_body(ssl, response, 0)
lib.uws_res_end_without_body(ssl, response, 0)
elif data_retry is not None:
_id = uuid.uuid4()
data_retry.id = _id
app._data_refs[_id] = data_retry
lib.uws_res_on_aborted(ssl, response, wsgi_on_data_ref_abort_handler, data_retry._ptr)
lib.uws_res_on_writable(ssl, response, wsgi_on_writable_handler, data_retry._ptr)
elif result is None or (not bool(result.has_responded) and bool(result.ok)): # not reaches Content-Length
logging.error(AssertionError("Content-Length do not match sended content"))
lib.uws_res_close(
ssl,
response
)
def is_asgi(module): def is_asgi(module):
@ -548,12 +328,11 @@ class _WSGI:
self.server = App(options, task_factory_max_items=0) self.server = App(options, task_factory_max_items=0)
self.SERVER_HOST = None self.SERVER_HOST = None
self.SERVER_PORT = None self.SERVER_PORT = None
self.SERVER_WS_SCHEME = "wss" if self.server._options else "ws" self.SERVER_WS_SCHEME = "wss" if self.server.options else "ws"
self.wsgi = app self.wsgi = app
self.EMPTY_WSGI_BODY = WSGIBody(BytesIO())
self.BASIC_ENVIRON = dict(os.environ) self.BASIC_ENVIRON = dict(os.environ)
self.ws_compression = False self.ws_compression = False
self._data_refs = {}
self._ptr = ffi.new_handle(self) self._ptr = ffi.new_handle(self)
self.asgi_http_info = lib.socketify_add_asgi_http_handler( self.asgi_http_info = lib.socketify_add_asgi_http_handler(
self.server.SSL, self.server.app, wsgi, self._ptr self.server.SSL, self.server.app, wsgi, self._ptr
@ -572,17 +351,18 @@ class _WSGI:
# internally will still use custom task factory for pypy because of Loop # internally will still use custom task factory for pypy because of Loop
if is_pypy: if is_pypy:
if task_factory_max_items > 0: if task_factory_max_items > 0:
factory = TaskFactory(task_factory_max_items) factory = create_task_with_factory(task_factory_max_items)
def run_task(task): def run_task(task):
factory(loop, task) factory(loop, task)
loop._run_once()
self._run_task = run_task self._run_task = run_task
else: else:
def run_task(task): def run_task(task):
future = create_task(loop, task) create_task(loop, task)
future._log_destroy_pending = False loop._run_once()
self._run_task = run_task self._run_task = run_task
@ -590,15 +370,15 @@ class _WSGI:
if sys.version_info >= (3, 8): # name fixed to avoid dynamic name if sys.version_info >= (3, 8): # name fixed to avoid dynamic name
def run_task(task): def run_task(task):
future = create_task(loop, task) loop.create_task(task, name="socketify.py-request-task")
future._log_destroy_pending = False loop._run_once()
self._run_task = run_task self._run_task = run_task
else: else:
def run_task(task): def run_task(task):
future = create_task(loop, task) loop.create_task(task)
future._log_destroy_pending = False loop._run_once()
self._run_task = run_task self._run_task = run_task
@ -671,7 +451,7 @@ class _WSGI:
"wsgi.errors": sys.stderr, "wsgi.errors": sys.stderr,
"wsgi.version": (1, 0), "wsgi.version": (1, 0),
"wsgi.run_once": False, "wsgi.run_once": False,
"wsgi.url_scheme": "https" if self.server._options and self.server._options.cert_file_name is not None else "http", "wsgi.url_scheme": "https" if self.server.options else "http",
"wsgi.multithread": False, "wsgi.multithread": False,
"wsgi.multiprocess": False, "wsgi.multiprocess": False,
"wsgi.file_wrapper": None, # No file wrapper support for now "wsgi.file_wrapper": None, # No file wrapper support for now
@ -691,13 +471,10 @@ class _WSGI:
return self return self
def __del__(self): def __del__(self):
try: if self.asgi_http_info:
if self.asgi_http_info: lib.socketify_destroy_asgi_app_info(self.asgi_http_info)
lib.socketify_destroy_asgi_app_info(self.asgi_http_info) if self.asgi_ws_info:
if self.asgi_ws_info: lib.socketify_destroy_asgi_ws_app_info(self.asgi_ws_info)
lib.socketify_destroy_asgi_ws_app_info(self.asgi_ws_info)
except:
pass
# "Public" WSGI interface to allow easy forks/workers # "Public" WSGI interface to allow easy forks/workers
@ -717,28 +494,14 @@ class WSGI:
self.websocket_options = websocket_options self.websocket_options = websocket_options
self.listen_options = None self.listen_options = None
self.task_factory_max_items = task_factory_max_items self.task_factory_max_items = task_factory_max_items
self.server = None
self.pid_list = None
# lifespan is not supported in WSGI # lifespan is not supported in WSGI
def listen(self, port_or_options, handler=None): def listen(self, port_or_options, handler=None):
self.listen_options = (port_or_options, handler) self.listen_options = (port_or_options, handler)
return self return self
def close(self): def run(self, workers=1):
# always wait a sec so forks can start properly if close is called too fast def run_app():
import time
time.sleep(1)
if self.server is not None:
self.server.close()
if self.pid_list is not None:
import signal
for pid in self.pid_list:
os.kill(pid, signal.SIGINT)
def run(self, workers=1, block=True):
def run_task():
server = _WSGI( server = _WSGI(
self.app, self.app,
self.options, self.options,
@ -749,28 +512,17 @@ class WSGI:
if self.listen_options: if self.listen_options:
(port_or_options, handler) = self.listen_options (port_or_options, handler) = self.listen_options
server.listen(port_or_options, handler) server.listen(port_or_options, handler)
self.server = server
server.run() server.run()
pid_list = [] def create_fork():
n = os.fork()
start = 1 if block else 0
# fork limiting the cpu count - 1
for _ in range(start, workers):
pid = os.fork()
# n greater than 0 means parent process # n greater than 0 means parent process
if not pid > 0: if not n > 0:
run_task() run_app()
break
pid_list.append(pid)
self.pid_list = pid_list # fork limiting the cpu count - 1
for i in range(1, workers):
if block: create_fork()
run_task() # run app on the main process too :)
# sigint everything to graceful shutdown
import signal
for pid in pid_list:
os.kill(pid, signal.SIGINT)
run_app() # run app on the main process too :)
return self return self