DEV Community 👩‍💻👨‍💻

Ciro Spaciari
Ciro Spaciari

Posted on • Updated on

Socketify.py - Maybe the fastest web framework for Python and PyPy

This year i started a journey to bring a really fast framework for Http, Http2, Http3 and WebSockets to Python and PyPy.

The problems with japronto and uvloop

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, Windows or macOS Silicon.
I really needed good support + performance, not only performance.
Almost any solution out there have a really big performance hit when using PyPy3 or are not really fast enougth

The Solution

We discover a really fast, small, and well maintained C++ Library called uNetworking/uWebSockets, but no C API available, so we create and adapt the full C API from uNetworking/uWebSockets and will integrate libuv powered fetch and file IO, this same C API is used by Bun

⚡ Benchmarking

HTTP requests per second (Linux x64)

image

WebSocket messages per second (Linux x64)

image

It's a really an over simplificated microbenchmark, but shows a big difference between TechEmPower benchmarks using wrk and + 16 pipelining with an lua script, and of course will not hit 1 million req/s on my machine, running tfb tools in my machine socketify.py + PyPy3 hits 770k req/s vs 582k from japronto and in WebSockets almost 900k messages/s with PyPy3 and 860k with Python3 the same performance as Bun,
with Falcon 35k messages/s and with Falcon with PyPy3 56k messages/s, node.js manages 192k, and still can get at least 30% or more performance once fully integrated with libuv + asyncio.

See more benchmark info here: https://github.com/cirospaciari/socketify.py#zap-benchmarks

💡 Features

  • WebSocket with pub/sub support
  • Fast and realiable Http/Https
  • Support for Windows, Linux and macOS Silicon & x64
  • Support for PyPy3 and CPython
  • Dynamic URL Routing with Wildcard & Parameter support
  • Sync and Async Function Support
  • Really Simple API
  • Fast and Encrypted TLS 1.3 quicker than most alternative servers can do even unencrypted, cleartext messaging
  • Per-SNI HttpRouter Support
  • Proxy Protocol v2
  • Shared or Dedicated Compression Support
  • Max Backpressure, Max Timeout, Max Payload and Idle Timeout Support
  • Automatic Ping / Pong Support
  • Per Socket Data

🔎 Upcoming Features

  • Fetch like API powered by libuv
  • Async file IO powered by libuv
  • Full asyncio integration with libuv
  • Full Http3 support
  • HPy integration to better support CPython, PyPy and GraalPython
  • Hot Reloading

We created and adapt the full C API from uNetworking/uWebSockets and will integrate libuv powered fetch and file IO, this same C API is used by Bun

📦 Installation

For macOS x64 & Silicon, Linux x64, Windows

pip install git+https://github.com/cirospaciari/socketify.py.git
#or specify PyPy3
pypy3 -m pip install git+https://github.com/cirospaciari/socketify.py.git
#or in editable mode
pypy3 -m pip install -e git+https://github.com/cirospaciari/socketify.py.git@main#egg=socketify
Enter fullscreen mode Exit fullscreen mode

🤔 Usage

Hello world app

from socketify import App

app = App()
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))
app.run()
Enter fullscreen mode Exit fullscreen mode

SSL version sample

from socketify import App, AppOptions

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.listen(3000, lambda config: print("Listening on port http://localhost:%d now\n" % config.port))
app.run()
Enter fullscreen mode Exit fullscreen mode

WebSockets

from socketify import App, AppOptions, OpCode, CompressOptions

def ws_open(ws):
    print('A WebSocket got connected!')
    ws.send("Hello World!", OpCode.TEXT)

def ws_message(ws, message, opcode):
    #Ok is false if backpressure was built up, wait for drain
    ok = ws.send(message, opcode)

app = App()    
app.ws("/*", {
    'compression': CompressOptions.SHARED_COMPRESSOR,
    'max_payload_length': 16 * 1024 * 1024,
    'idle_timeout': 12,
    'open': ws_open,
    'message': ws_message,
    'drain': lambda ws: print('WebSocket backpressure: %i' % ws.get_buffered_amount()),
    'close': lambda ws, code, message: print('WebSocket closed')
})
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()
Enter fullscreen mode Exit fullscreen mode

And click here more than 20 examples

💼 Commercially supported

Socketify is an active project and will be maintained over time with security updates and new features and will be commercial support available

Check our github page: https://github.com/cirospaciari/socketify.py

Top comments (0)

Dream Big


Use any Linode offering to create something unique or silly in the DEV x Linode Hackathon 2022 and win the Wacky Wildcard category.

→ Join the Hackathon <-