Here's a self-contained, minimal, reproducible, example with my use case:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import uvicorn
app = FastAPI()
class MyModel(BaseModel):
foo: str
bar: int
RESPONSE = {"message": "Hello there!"}
@app.get("/get1")
def get1():
return RESPONSE
@app.get("/get2", status_code=204)
def get2():
return RESPONSE
@app.post("/post1")
def post1():
return RESPONSE
@app.post("/post2")
def post2(data: MyModel):
return RESPONSE
@app.post("/post3", status_code=204)
def post3():
return RESPONSE
@app.post("/post4", status_code=204)
def post4(data: MyModel):
return RESPONSE
@app.post("/post5")
def post5(data: MyModel, status_code=204):
return JSONResponse(
content=RESPONSE,
status_code=204 # must define status_code here
)
if __name__ == "__main__":
uvicorn.run(app, port=5000)
On the given code, the endpoints /get2, /post3 and /post4 fail, raising this exception:
INFO: 127.0.0.1:46692 - "POST /post4 HTTP/1.1" 204 No Content
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 389, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/fastapi/applications.py", line 179, in __call__
await super().__call__(scope, receive, send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
raise exc from None
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
raise exc from None
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
await self.app(scope, receive, sender)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/routing.py", line 566, in __call__
await route.handle(scope, receive, send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
await self.app(scope, receive, send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/routing.py", line 44, in app
await response(scope, receive, send)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/responses.py", line 139, in __call__
await send({"type": "http.response.body", "body": self.body})
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/exceptions.py", line 68, in sender
await send(message)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/starlette/middleware/errors.py", line 156, in _send
await send(message)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 483, in send
output = self.conn.send(event)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/h11/_connection.py", line 469, in send
data_list = self.send_with_data_passthrough(event)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/h11/_connection.py", line 502, in send_with_data_passthrough
writer(event, data_list.append)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/h11/_writers.py", line 78, in __call__
self.send_data(event.data, write)
File "/home/david/.miniconda3/envs/fastapi+pydantic/lib/python3.8/site-packages/h11/_writers.py", line 98, in send_data
raise LocalProtocolError("Too much data for declared Content-Length")
h11._util.LocalProtocolError: Too much data for declared Content-Length
It seems that this fails when returning a dict as response (without using JSONResponse), AND setting a status_code for the endpoint.
HTTP 204 No Content cannot contain a message body: https://tools.ietf.org/html/rfc7230#section-3.3.3
"Any response to a HEAD request and any response with a 1xx (Informational), 204 (No Content), or 304 (Not Modified) status code is always terminated by the first empty line after the header fields, regardless of the header fields present in the message, and thus cannot contain a message body."
content-length forced in h11 to be equal to 0 (h11._connection.py:75) so when you return something in this endpoints there is exception raised
To make valid endpoint that returns 204 status code you should use Response object:
@app.post("/post5")
def post5(data: MyModel, status_code=HTTPStatus.NO_CONTENT):
return Response(status_code=HTTPStatus.NO_CONTENT.value)
@SirTelemak Thanks for the clarification! Big facepalm for me haha :facepalm:
Most helpful comment
To make valid endpoint that returns 204 status code you should use Response object: