{{ message }}
Repository navigation
Conversation
When a path operation returns content with status 205 (set via `status_code=205` or the `Response` parameter), FastAPI empties `response.body`, but the response class had already computed Content-Length from the original content (e.g. 4 for `null`). Servers like Uvicorn then fail with "Response content shorter than Content-Length" and the client gets a truncated response. Set Content-Length to 0 when the body of a 205 response is dropped. Only 205 is changed: per RFC 9112 section 6.3, 1xx, 204 and 304 responses are terminated after the header section regardless of Content-Length (Starlette doesn't set it for them), while a 205 is still delimited by it. A Content-Length set by the user on a 304, allowed by RFC 9110 section 8.6, is left untouched.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
A path operation that uses status
205 Reset Contentand returns anything (evenNone) produces a response whoseContent-Lengthheader doesn't match its (empty) body. Under Uvicorn every such request fails and the client receives a truncated response.This PR sets
Content-Length: 0when FastAPI drops the body of a 205 response. No other status code is affected.Reproduction
Run it with
uvicorn main:appand send a request:Uvicorn logs the following for every request:
The same happens when the status is set dynamically through the
Responseparameter:Here the header is
content-length: 28while the body is empty.Expected: a valid 205 response with
content-length: 0and an empty body, so the connection can be reused.Actual:
content-lengthis the length of the serialized return value, the body is empty, the server errors and the connection is dropped.Root cause
In
get_request_handler()(fastapi/routing.py), the response is built first and the body is dropped afterwards for status codes that must not have one:By the time
response.bodyis cleared, the response class has already computedContent-Lengthfrom the original content (e.g.4fornull), so the header goes stale.This only causes a mismatch for 205. Starlette's
Response.init_headers()already skipsContent-Lengthfor status codes below 200 and for 204 and 304, so for those there is no header to go stale. 205 is the only status FastAPI treats as "no body" (is_body_allowed_for_status_code()returnsFalsefor< 200, 204, 205 and 304) that Starlette still adds aContent-Lengthfor.The fix
The change runs before the headers from the
Responseparameter are appended, so it only corrects the value computed by the response class and never overrides a header set explicitly by the user.Why only 205 (re: #16386)
#16386 tried to fix the same bug and was closed with the concern that the change might break something else, specifically 304 responses, which may legitimately carry a
Content-Length. This PR is scoped to address that:Content-Lengthdoesn't affect framing, and they are left untouched.Content-Length. If the header says 4 and no bytes follow, the message is malformed. That's exactly the error Uvicorn reports.Content-Length: 0the response is correct and the connection stays reusable.Content-Lengthin a 304 with the size of the representation that a 200 would have returned. This PR does not touch 304, and a test confirms that aContent-Lengthset by the user on a 304 is preserved.Why
0rather than removing the header: withoutContent-LengthorTransfer-Encoding: chunked, an HTTP/1.1 client has to read until the connection closes to know where the response ends, which breaks keep-alive. An explicitContent-Length: 0is the unambiguous way to frame an empty 205.Tests
New file
tests/test_response_code_no_body_content_length.py:status_code=205, returning a dictcontent-length: 280status_code=205, returningNonecontent-length: 40Responseparametercontent-length: 280Content-Length: 17Also checked:
test_response_code_no_body.pyand the tutorial tests.ruff check,ruff format --checkandmypypass on the changed files.content-length: 0, no error is logged and the connection stays open.Scope and compatibility
fastapi/routing.py, only on the code path where FastAPI already drops the body.Responseinstance) are not affected, since they don't go through this path.