fastapi
fastapi copied to clipboard
Response content longer than Content-Length error for DELETE and NoContent
First Check
- [X] I added a very descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the FastAPI documentation, with the integrated search.
- [X] I already searched in Google "How to X in FastAPI" and didn't find any information.
- [X] I already read and followed all the tutorial in the docs and didn't find an answer.
- [X] I already checked if it is not related to FastAPI but to Pydantic.
- [X] I already checked if it is not related to FastAPI but to Swagger UI.
- [X] I already checked if it is not related to FastAPI but to ReDoc.
Commit to Help
- [X] I commit to help with one of those options 👆
Example Code
from fastapi import FastAPI, status
app = FastAPI()
@app.delete("/", status_code=status.HTTP_204_NO_CONTENT)
def read_root():
return None
Description
Upon requesting above code I got expected response but my logs shows that there is an error in uvicorn. The problem exists for DELETE method and NoContent response status code (for HEAD there is no such problem)
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 372, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/fastapi/applications.py", line 269, in __call__
await super().__call__(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/applications.py", line 124, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 184, in __call__
raise exc
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 93, in __call__
raise exc
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
await self.app(scope, receive, sender)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
raise e
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
await self.app(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/routing.py", line 670, in __call__
await route.handle(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/routing.py", line 266, in handle
await self.app(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/routing.py", line 68, in app
await response(scope, receive, send)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/responses.py", line 162, in __call__
await send({"type": "http.response.body", "body": self.body})
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/exceptions.py", line 79, in sender
await send(message)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in _send
await send(message)
File "/home/user/PycharmProjects/sample_app/venv/lib/python3.9/site-packages/uvicorn/protocols/http/httptools_impl.py", line 501, in send
raise RuntimeError("Response content longer than Content-Length")
RuntimeError: Response content longer than Content-Length```
### Operating System
Linux
### Operating System Details
_No response_
### FastAPI Version
0.78.0
### Python Version
3.10
### Additional Context
_No response_
This should help: https://github.com/tiangolo/fastapi/issues/717#issuecomment-583826657
This works as well:
from fastapi import FastAPI, status, Response
app = FastAPI()
@app.delete("/", status_code=status.HTTP_204_NO_CONTENT, response_class=Response)
def read_root():
return None
Thanks @aboubacs! I was having the same issue returning 204 as a POST response. @tiangolo This worked as recently as 0.75.2 without a special response class. IIRC, we had this same problem a long time ago before I contributed the special code for recognizing 204 as a no-content response. My opinion is that 204 as the declared status code should still work without the response_class
annotation, and that this is a regression. If I have time I will look at contributing a fix.
OK, so I looked into this a bit and the issue looks to be related to a recent change in Starlette (see this discussion for a repro without FastAPI in the mix). It may be that Starlette used to suppress the content return on a JSONResponse if the response code was 204, but it no longer does.
I notice that, historically, there are a number of times when people have proposed adding code to FastAPI's router to set the response class explicitly (to Response
rather than JSONResponse
) in case the response code is one of the STATUS_CODES_WITH_NO_BODY
. Given the amount of back-and-forth in the Starlette code around this, perhaps @tiangolo it's worth FastAPI making this change? If you agree I'm happy to take a shot at it. If not, how about explicit documentation in FastAPI that if you plan to return no content you need to specify the response class.
It's worth noting that it's the 3rd issue related to this topic created this week, so the current state of things is a loss of time for many users
Does FastAPI not have any regression tests? How is it that this wasn’t picked up when the starlette version was bumped?
This appears to be fixed somewhat in 0.79.0, as now if the status code is one that doesn't allow content, it will explicitly set the response body to b""
, though in cases where you're using another status code but want it to return no content, you still have to explicitly set the response_class
to Response
.
hi I get the same error when doing:
raise HTTPException(status_code=HTTP_304_NOT_MODIFIED)
I am running fastapi 0.79.0
I think #4952 is a fix for this
Thanks for the report here @myslak71! :cake:
And thanks for the discussion everyone. :coffee:
It seems this was handled in https://github.com/tiangolo/fastapi/pull/5145, available since FastAPI 0.79.0
.
If that solves it, you could close the issue. Thanks! :cake:
This seems to be an issue for me still on fastapi 0.82.0. It can be reproduced like this:
from fastapi import FastAPI, HTTPException
import uvicorn
app = FastAPI()
@app.get("/")
async def root():
raise HTTPException(204)
if __name__ == '__main__':
uvicorn.run(app, debug=True)
Visiting the page results in:
RuntimeError: Response content longer than Content-Length
Any updates on this?
I cannot reproduce with the example of @gazpachoking . This is my cURL:
[jarro@MBP-van-Jarro ~ % curl localhost:8000 -v
* Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 204 No Content
< date: Sun, 18 Sep 2022 20:50:13 GMT
< server: uvicorn
<
* Connection #0 to host localhost left intact
Which seems correct. My logs are clean as well:
INFO: Started server process [28938]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:51631 - "GET / HTTP/1.1" 204 No Content
Running on Python 3.10.5, uvicorn 0.18.2, FastAPI 0.83:
Package Version
------------------ ---------
anyio 3.6.1
attrs 22.1.0
black 22.6.0
certifi 2022.6.15
charset-normalizer 2.1.0
click 8.1.3
coverage 6.4.3
fastapi 0.83.0
h11 0.13.0
idna 3.3
iniconfig 1.1.1
mypy-extensions 0.4.3
packaging 21.3
pathspec 0.9.0
pip 22.0.4
platformdirs 2.5.2
pluggy 1.0.0
py 1.11.0
pydantic 1.9.1
pyparsing 3.0.9
pytest 7.1.2
pytest-cov 3.0.0
python-multipart 0.0.5
requests 2.28.1
setuptools 58.1.0
six 1.16.0
sniffio 1.2.0
starlette 0.19.1
tomli 2.0.1
typing_extensions 4.3.0
urllib3 1.26.11
uvicorn 0.18.2
FWIW, I don't believe the behavior reported by @gazpachoking, even if it's reproducible, is a bug. 204 is an "OK" status response with no body. All request error (400-series) and server-error (500-series) responses are expected to have a body which explains the problem, and the HTTPException
response is designed for these responses. So producing an HTTPException response with no body feels like a logic bug that neither FastAPI nor Starlette really needs to guard against.
This was discussed and handled in #4949 and #5365.
FWIW, I don't believe the behavior reported by @gazpachoking, even if it's reproducible, is a bug. 204 is an "OK" status response with no body. All request error (400-series) and server-error (500-series) responses are expected to have a body which explains the problem, and the
HTTPException
response is designed for these responses. So producing an HTTPException response with no body feels like a logic bug that neither FastAPI nor Starlette really needs to guard against.
Hmm, that's a good point. It isn't really an exception, although I still think it's nice to be able to short circuit the view function the same way you can for an actual exception.
It isn't really an exception, although I still think it's nice to be able to short circuit the view function the same way you can for an actual exception.
I completely agree, and I agree that raise
is very handy for this, so I'm glad this now works!
I ran into this issue when using a custom response class that was returning a string with unicode characters instead of returning bytes. For example to reproduce:
class CustomResponse(Response):
media_type = "application/json"
def render(self, content: Any) -> bytes:
return '{"password":"•••••••","connect_timeout":10,"created_at":"2023-01-06T05:39:34.285672"}'