Weft is an ASGI and WSGI server for FastAPI, Flask and the rest. It is written in Rust on Tokio and runs inside the Python thread that runs your application: there is no second runtime thread, no queue between Rust and Python, and no handoff per request. The HTTP server and asyncio share one thread, one event loop, and one wakeup.
weft app:app
weft app:app --workers 4
weft app:app --workers 4 --worker-mode thread # free-threaded Python (3.14t)
import weft
weft.run(app, host='0.0.0.0', port=8000)HTTP/1.1, HTTP/2, HTTP/3, WebSocket and WebTransport. TLS, static files, compression, a response cache, Prometheus, and IETF resumable uploads (draft-ietf-httpbis-resumable-upload). ACME is left to Caddy.
Further reading: ARCHITECTURE.md — how it is built. BENCHMARKS.md — Weft, Peregrine and Granian on the-benchmarker contract. RELEASE.md — what changed. bench/README.md — the closed-loop FastAPI matrix.
- Tokio is driven from asyncio's selector.
WeftEventLoopis aSelectorEventLoopwhose selector is a TokioLocalRuntime. Everyselect()runs the runtime until a Python-side file descriptor is ready, a timer is due, or the application has work to do. asyncio's own sockets, subprocesses andcall_soon_threadsafekeep working; they are registered with the same Tokio driver. The GIL is released only while Tokio parks. - The ASGI interface is C, not Python.
sendandreceiveare native vectorcall objects. The scope is a copy of a prebuilt dict with interned keys. Header names are cached.await send(...)writes to the socket before it returns, and hands back an already-completed awaitable unless the client is slow to read (then a real future provides backpressure). A body that has already arrived is returned byawait receive()without suspending. - HTTP/1.1 in Rust:
httparsefor heads, keep-alive and pipelining, content-length and chunked bodies both ways,Expect: 100-continue, HEAD/204/304 semantics, request smuggling checks (TE+CL, invalid lengths), header timeouts, a head size limit, and graceful shutdown that finishes in-flight requests. - WSGI too. A two-argument application (Flask, Django's WSGI handler,
anything PEP 3333) is detected and served the same way;
--protocol wsgiorasgioverrides the guess. The application is called inline once its body has been read;start_responseis a native object that doubles aswrite(), and the response iterable is sent block by block, parking the connection on the socket between blocks so a slow client holds up only its own generator.--wsgi-threads Nruns the application on N threads per worker instead, for blocking handlers; the worker thread still does all the socket work. - Operations, in the server: an access log (
--access-log, text or--access-log-format json) buffered per worker and written in batches;--health-check-pathanswered without Python;--drain-delayfor load balancers;--max-connections;--forwarded-allow-ipsforX-Forwarded-*andForwarded;--request-id,--trace-contextand--request-start-headerfor tracing and queue-time APMs;--rate-limit N/scounted across every worker;--unix/--uds;--reloadandSIGHUPreplace process workers one at a time;--venv/--no-auto-venv;--no-lifespan;--static-dir PREFIX=DIR;--compress/--compress-static;--cache-sizeshared by every worker;--tls-cert/--tls-key(SNI when repeated),--hsts,--redirect-http; HTTP/2 by default (--no-http2,--http2-only); HTTP/3 over QUIC (--http3,--quic-port) and WebTransport (ASGIwebtransport.*: streams and datagrams); Prometheus on--metrics-port/--metrics-host. - Workers: one process by default;
--workers Nstarts N processes that share the listening socket, or N threads with--worker-mode thread, which on free-threaded Python run in parallel in one process. Each worker is a complete, independent loop.
the-benchmarker contract, 256 connections, 4 workers, zrk open-loop ramp,
CPython 3.14.7. Median of three 15 s runs, every request 2xx.
How that was measured.
| app | Weft | Peregrine 1.1.7 | Granian 2.8.3 |
|---|---|---|---|
| raw ASGI | 140,570 | 141,791 | 74,026 |
| raw WSGI | 151,752 | 200,227 | 87,733 |
| FastAPI 0.141 | 34,551 | 36,433 | 23,061 |
Weft is 1.5–1.9× Granian on this machine. Raw ASGI and FastAPI match Peregrine; WSGI does not — Peregrine's WSGI path has no asyncio.
Closed loop on Windows 11, one worker, 64 connections
(python bench/matrix.py --set fastapi), requests per second:
| scenario | uvicorn + httptools | weft | weft, 3.14t |
|---|---|---|---|
GET / (JSON) |
5,150 | 9,190 | 9,450 |
| path parameter + response model | 4,300 | 7,350 | 7,170 |
| 50-item response model list | 2,760 | 3,630 | 3,130 |
POST JSON body |
4,080 | 6,360 | 6,570 |
| 64 KB response | 4,490 | 7,550 | 7,890 |
asyncio.sleep(0.01), 256 connections |
3,630 | 6,080 | 6,420 |
Each server uses one core. With four worker processes (--set scaling),
GET / reaches 35,200 requests per second against uvicorn's 24,300, and
the path parameter endpoint 26,800 against 20,100.
pip install weft-server
Distribution name: weft-server (the PyPI name weft belongs to an unrelated
project). Import package weft, CLI weft. CPython 3.10+; wheels for Linux,
macOS and Windows. Tested on 3.14 and free-threaded 3.14t.
From source:
uv venv --python 3.14 .venv
uv pip install maturin
maturin develop --release
Supported: ASGI 3.0 HTTP (spec 2.4), WebSocket and lifespan; WSGI (PEP
3333); HTTP/1.0, 1.1, HTTP/2 (h2c prior knowledge, ALPN over TLS) and
HTTP/3 over QUIC; WebTransport (ASGI webtransport.*); TCP and unix
sockets; TLS (rustls). ACME is left to the reverse proxy (Caddy).
weft.contrib.ResumableUploads is the IETF resumable upload protocol
(interop 9): a 104 before the body is read, HEAD for the offset, PATCH to
append, DELETE to cancel. A client that does not send Upload-Complete
gets an ordinary upload. weft.contrib also has the framework helpers a
WebTransport or HTTP/3 app usually writes once: WebTransportRouter
(Starlette {name} / Django <int:pk> paths), WebTransportEndpoint,
AltSvcMiddleware, and weft.webtransport.WebTransportSession.
Starlette, FastAPI and Django serve HTTP/3 unchanged; the routers exist
because those frameworks assert on scope["type"] before they would see
a webtransport session.
pytest tests
The suite runs raw ASGI and WSGI apps and a FastAPI app over real sockets, in process, thread and inline worker modes, on both the standard and the free-threaded interpreter.