Borrow Starlette’s middleware
ASGI middleware wraps an ASGI app. Wred is an ASGI app. No adapter ceremony, secret handshake, or framework conversion experience required.
Install what you use
python -m pip install 'wred[server]' starlette itsdangerousitsdangerous is needed for the session recipe below. Wred’s request is still a Wred request after wrapping; middleware-provided data is available in its ASGI scope.
Build an explicit stack
Save this as starlette_app.py and run weft starlette_app:app. Register routes on api; export the completed middleware stack as app.
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.errors import ServerErrorMiddleware
from starlette.middleware.gzip import GZipMiddleware
from starlette.middleware.trustedhost import TrustedHostMiddleware
from starlette.responses import JSONResponse
from wred import App
api = App()
@api.route('/api/hello')
async def hello(request):
return {'hello': 'world'}
async def server_error(request, exc):
return JSONResponse({'error': 'Internal server error'}, status_code=500)
# Requests enter the last wrapper first.
app = GZipMiddleware(api, minimum_size=1000)
app = TrustedHostMiddleware(app, allowed_hosts=['localhost', '127.0.0.1', 'api.example.com'])
app = ServerErrorMiddleware(app, handler=server_error, debug=False)
app = CORSMiddleware(
app,
allow_origins=['http://localhost:5173'],
allow_methods=['GET', 'POST', 'PATCH', 'DELETE'],
allow_headers=['Authorization', 'Content-Type'],
expose_headers=['Location'],
)The last wrapper receives the request first. Responses travel back through the layers. The wrapper accepting the request is the object your ASGI server must import. Exporting the inner api skips every layer, a very efficient way to misconfigure middleware.
CORS belongs outside the error handler
This sample allows a frontend at http://localhost:5173. CORS is outermost so generated 500 responses can carry its headers. It also answers browser preflight requests before they reach Wred.
For cookie credentials, set allow_credentials=True and explicit origins, methods, and headers. CORS grants browser access; it does not authenticate the caller.
See Starlette’s CORS documentation and implementation.
curl -i -X OPTIONS http://127.0.0.1:8000/api/hello \
-H 'Origin: http://localhost:5173' \
-H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: Content-Type'Expect a successful preflight with access-control-allow-origin: http://localhost:5173. The actual sample endpoint is GET-only; a successful preflight does not create a POST handler. The route list still has a job.
Hosts, compression, and errors
TrustedHostMiddleware checks the Host header. It does not decide which proxy IPs to trust. Gzip compresses eligible responses above the configured threshold. Our tiny hello JSON is below 1,000 bytes, so it remains tiny hello JSON.
ServerErrorMiddleware supplies a JSON 500 response while allowing the exception to be logged. Debug mode stays off here; a public traceback is a surprisingly detailed résumé for your server.
See the middleware reference. If your reverse proxy handles compression, choose one compression layer. Configure HTTPS redirects only after the app receives the correct forwarded scheme; otherwise you may build a redirect loop with excellent uptime.
A separate signed-cookie session recipe
This is an alternative example, not a replacement for the notebook’s database-backed logins. Save as session_app.py.
import os
from starlette.middleware.sessions import SessionMiddleware
from wred import App
api = App()
@api.route('/visits')
async def visits(request):
session = request.scope['session']
session['visits'] = session.get('visits', 0) + 1
return {'visits': session['visits']}
app = SessionMiddleware(
api,
secret_key=os.environ['SESSION_SECRET'],
same_site='lax',
https_only=os.environ.get('COOKIE_SECURE', '1') == '1',
)export SESSION_SECRET="$(python -c 'import secrets; print(secrets.token_hex(32))')"
# Local HTTP only. Leave COOKIE_SECURE unset for HTTPS deployments.
COOKIE_SECURE=0 weft session_app:appIn a second terminal:
curl -sS -c visits.cookies http://127.0.0.1:8000/visits
curl -sS -b visits.cookies -c visits.cookies http://127.0.0.1:8000/visits
# {"visits":2}Keep the secret stable across workers and restarts. Session data is signed, not encrypted: store a harmless preference or counter, not a password. Wred reads it through request.scope["session"]. There is no request.session property waiting to be discovered.
