from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.middleware.base import BaseHTTPMiddleware
class SampleMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
_ = await request.form()
return await call_next(request)
app = Starlette()
@app.route('/test', methods=['POST'])
async def test(request):
_ = await request.form() # blocked, because middleware already consumed request body
return PlainTextResponse('Hello, world!')
app.add_middleware(SampleMiddleware)
$ uvicorn test:app --reload
$ curl -d "a=1" http://127.0.0.1:8000/test
# request is blocked
Coming from FastAPI issue referenced above.
@tomchristie I don’t understand the issue about consuming the request in a middleware, could you explain this point ? In fact, I have the need (which is current where I work) to log every requests received by my production server. Is there a better place than a middleware to do it and avoid duplicating code in every endpoint ? (I would like something like this https://github.com/Rhumbix/django-request-logging)
For now, I found this workaround (but that’s not very pretty):
I kind of disagree with your example. In fact, stream data is not stored by default, but stream metadata (is the stream closed) are; there will be an understandable error raised if someone try to stream twice, and that is enough imho. That’s why if the body is cached, the stream consumption has to be cached too.
“Consume request body in middleware is problematic”
Indeed. Consuming request data in middleware is problematic. Not just to Starlette, but generally, everywhere.
On the whole you should avoid doing so if at all possible.
There’s some work we could do to make it work better, but there will always be cases where it can’t work (eg. if you stream the request data, then it’s just not going to be available anymore later down the line).
There are plenty use cases like @wyfo mention. In my case i’m using JWT Signature to check all the data integrity, so i decode the token and then compare the decoded result with the body + query + path params of the request. I don’t know any better way of doing this
is there an update for this? it seems like this is something a lot of people need for very legit use cases ( mainly logging it seems)
is there a plan to allow consuming the body in a middleware ? i see the body is cached on the request object _body, is it possible to cache it on the scope so it accessible from everywhere after it is read?
any other solution would also be ok, but i do feel this i needed
@JHBalaji how are you logging the request body to context, are you not running into the same issue when trying to access the body in the incoming request middleware?
I had the same problem but I also needed to consume the response. If anyone has this problem here is a solution I used:
I’m posting in this 4-year old issue this with the hope that someone will see it and it will lead to a better understanding of the problem and ultimately a fix, because I have run into this problem many times, and because it doesn’t throw an error, it just hangs the program it always takes me too long to figure out what’s going on.
I think there is some confusion about what the problem is. I don’t believe people are confused about how to wrap
receivewith a function that consumes the request body, nor do I think people are misguided in needing to consume the full request body in a middleware. Sure, it removes the benefits of a streaming architecture, but that’s an acceptable trade-off in many cases. For example, in the case of HMAC signature verification: The first thing I want to do when my application receives a request is verify the signature. If the signature is bad, I want the request to be rejected before it uses anymore resources (database connections, logging, etc.).If I only wrap the
receivefunction in another function that does signature verification, then the signature won’t be checked until the request has gotten all the way to the endpoint handler where Starlette begins to consume the request body.The problem comes when you try to consume the request body (i.e., call
receive) directly inside of the middleware, like this:receiveis now exhausted, and this line hangs because it’s waiting for something that will never come.For me, all that’s needed is a tiny, undocumented property on
statethat can signal to Starlette that the body has been consumed and it shouldn’t wait for anything else:And in
Request.body:Sure, it’s hacky. But it’s just one line of code, doesn’t need to be documented (i.e. comes with the understanding that using it is risky), adds virtually no request overhead, but will save me and probably thousands of other developers from having to refactor their middleware stack or routes, fork this repository, or use another library.
If someone needs a fix now, you can do something like this to trick Starlette (this is similar to other workarounds already mentioned above). Note that this code isn’t production-ready:
Currently, this is one of the better options: https://fastapi.tiangolo.com/advanced/custom-request-and-route/#accessing-the-request-body-in-an-exception-handler but unfortunately there still isn’t a great way to do this as far as I’m aware.
After reading through this, it seems the issue isn’t with consuming the entire body, starlette actually will pump that async generator and save it to the request for subsequent requests: https://github.com/encode/starlette/blob/e45c5793611bfec606cd9c5d56887ddc67f77c38/starlette/requests.py#L225
The issue comes when getting form data. That function uses the underlying stream, not the body that may or may not be cached already: https://github.com/encode/starlette/blob/e45c5793611bfec606cd9c5d56887ddc67f77c38/starlette/requests.py#L246
A call to
json()properly uses the cached data, butform()doesn’t.Give this a shot in a controller or middleware:
and I bet that works fine. Toss in a
await request.form()and it will give you thatStream consumederrorMy work around that I’m using in conjunction with FastAPI:
Then installed into FastAPI like:
@JivanRoquet yes but the FastAPI documentation does have what you might be looking for.
@tomchristie we’re currently using the above workaround using the code below:
this is working great for us. What would you think about maybe adding some kind of mixin class to starlette? maybe like a
RequestLoggingMiddlewareclass, so that it makes it easier to log request payloads from the API, even though, I agree with you that in general, this is problematic and should be avoided?I think we can do it in this way:
Request(scope, receive, send)it will set self instance intoscope.__new__). This says we will always have the sameRequestobject.