patx/wred
Document ASGI middleware composition and benchmark results
Commit c8fe3c4 · patx · 2026-10-13T03:18:16-04:00
Comments
No comments yet.
Diff
diff --git a/README.md b/README.md
index d7569fa..16426fe 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
# Wred
-A small ASGI framework for JSON APIs. One file. JSON from `orjson`, server from `granian`.
+Wred handles HTTP routing, requests, and JSON responses. Need CORS, sessions,
+authentication, or compression? Wrap it with existing ASGI middleware.
+No need to reinvent what ASGI already makes possible.
Your API needs to return some JSON. It probably doesn't need a goddamn cathedral.
@@ -19,7 +21,8 @@ Auth, sessions, databases, validation schemas, templates, CORS, CSRF protection,
rate limiting, dependency injection, generated docs, background jobs, WebSockets,
streaming, or a reason to argue about folder structure.
-Those are your problem. You can solve them without making them everyone else's.
+Your application chooses those pieces. Use existing libraries without making
+them everyone else's dependency. Wred doesn't need to know they exist.
## Use it
@@ -53,6 +56,13 @@ uv run granian --interface asginl app:app
`asginl` disables lifespan handling. Wred doesn't implement it.
+The server can run a middleware wrapper around Wred as its `app` too.
+[Starlette's middleware](https://starlette.dev/middleware/#using-middleware-in-other-frameworks)
+works with other ASGI applications: CORS, GZip, TrustedHost, HTTPS redirects,
+and cookie sessions are available without changing Wred. Install only what
+your app needs; Starlette isn't a Wred dependency. Choose standard ASGI wrappers
+rather than integrations that require another framework's application internals.
+
## Routes
Handlers are async. The first argument is the request. `methods` defaults to
@@ -108,7 +118,10 @@ async def search(request):
```
Read a bearer credential with `request.headers.get('authorization', '')`.
-Checking that credential is your job.
+Check it in your application or let authentication middleware populate the ASGI
+scope, then enforce access in your handlers. Middleware can also add session
+data: Starlette's SessionMiddleware exposes it through `request.scope['session']`,
+not a Wred-specific session property. Its cookies are signed, not encrypted.
## Responses
@@ -142,6 +155,8 @@ Other bodies must be bytes: encode text yourself, for example
`return 'hello'.encode(), 'text/plain; charset=utf-8', 200`.
Custom headers can carry cookies, cache policies, CORS rules, or `Retry-After`;
Wred sends them, you decide what they mean.
+For behavior across every route, an ASGI wrapper can intercept requests and
+responses to apply CORS or compression. Keep that plumbing out of your handlers.
## Static files
@@ -183,3 +198,23 @@ uv run granian --interface asginl --workers 1 example_app.app:app
```
Open <http://127.0.0.1:8000>. Demo data lives in memory and disappears on restart.
+
+## Benchmarks
+
+`GET /` returning `{"hello": "world"}`, measured on October 13, 2026.
+Three runs per framework, with the run order rotated each round.
+
+| Framework | Median requests/sec | Min | Max |
+| --- | ---: | ---: | ---: |
+| Wred 0.01 | **272,088** | 262,187 | 277,656 |
+| BlackSheep 2.6.4 | 211,619 | 205,719 | 220,455 |
+| MicroPie 0.32 | 207,720 | 197,520 | 216,010 |
+
+Python 3.14.4, Granian 2.8.4, eight workers, HTTP/1 keep-alive, asyncio,
+and access logging disabled. `wrk -t4 -c256 -d20s --latency`, after a
+five-second warmup for each run. Client and server shared the same machine
+over loopback. All nine measured runs had zero socket errors or non-success responses.
+
+Wred delivered about 31% more requests/sec than MicroPie in this workload.
+It's a tiny JSON response, not your database, auth, or entire application.
+Benchmark your own shit.