v3.10.0
Highlights
Trailing-slash redirects. /users/ now 307-redirects to /users (and vice versa) instead of 404ing, preserving the method and query string. The most common routing footgun, gone. Strict matchers can opt out:
api = responder.API(redirect_slashes=False)Request size limits. Close the unbounded-upload DoS vector with one parameter — oversized bodies get 413, whether read via await req.content, req.media(), or streamed:
api = responder.API(max_request_size=10 * 1024 * 1024) # 10 MBFail-fasts on Content-Length when present, enforces cumulatively for chunked uploads, and negotiates JSON error bodies for JSON clients.
Automatic ETags. API(auto_etag=True) gives every GET response a content-hash ETag with full 304 Not Modified handling — zero per-route code. An explicit resp.etag always wins.
After-response background tasks. Defer work until the client has the response:
@api.route("/signup", methods=["POST"])
async def signup(req, resp):
resp.media = {"ok": True}
resp.background(send_welcome_email, "user@example.com")Cache-Control helper. resp.cache_control(public=True, max_age=3600) — pairs with 3.9.1's conditional requests to round out the caching story.
Changed
- Trailing-slash redirects are on by default (previously such requests were 404s).
Fixed
- A
413raised while reading the body duringrequest_modelvalidation is no longer swallowed into a422.
Full changelog: https://github.com/kennethreitz/responder/blob/main/CHANGELOG.md