-
Describe the bug To Reproduce
Run the application with Expected behavior Environment:
Additional context
|
Beta Was this translation helpful? Give feedback.
Replies: 24 comments 6 replies
-
works fine for me copy-pasting the exact same thing, only had to comment
|
Beta Was this translation helpful? Give feedback.
-
That's exactly the point, the print statement, or more exactly the ``àwait``` inside blocks the application. The question is why ? |
Beta Was this translation helpful? Give feedback.
-
I think the middleware is unnecessary, should you want to get your payload just do:
|
Beta Was this translation helpful? Give feedback.
-
But i don't want to just get the payload of one request, i want to log the payloads of EVERY endpoints of my application. I don't want for that to add code in every endpoint, that's the purpose of a middleware. |
Beta Was this translation helpful? Give feedback.
-
@wyfo It appears this is sort of a Starlette problem -- if you try to access You can reproduce the issue in the pure starlette example if you try to print the json contents inside your starlette endpoint:
FastAPI grabs the request.json() as part of it's request handling, which is why you run into the issue even without explicitly trying to if using FastAPI. In order for this to be handled properly, I think it would require fixes in starlette; I feel like I've seen this discussed in starlette issues, but I'm not sure. I'll reference it if I find it. Even if you weren't using FastAPI, you'd face this issue if you didn't do something funny with to store the json after the first time you read it. On the other hand, I think there is a neat workaround for this use case (that actually requires FastAPI), since you don't need your middleware to modify anything before the next handler receives it: from typing import Mapping
from starlette.requests import Request
from fastapi import FastAPI, APIRouter, Depends
app = FastAPI()
api_router = APIRouter()
@api_router.post("/")
def read_root(arg: Mapping[str, str]):
return {"Hello": "World"}
async def log_json(request: Request):
print(await request.json())
# the trick here is including log_json in the dependencies:
app.include_router(api_router, dependencies=[Depends(log_json)]) This requires you to add all endpoints to This works because no new |
Beta Was this translation helpful? Give feedback.
-
@wyfo For what it's worth, it looks like consuming the body inside of middleware is somewhat broadly discouraged -- encode/starlette#495 (comment) (by the creator of starlette):
So the "workaround" I posted above may actually be a better way to handle this than middleware anyway. |
Beta Was this translation helpful? Give feedback.
-
@dmontagu |
Beta Was this translation helpful? Give feedback.
-
Thanks for your help @euri10 and @dmontagu. Agreed with all of @dmontagu's explanation and suggestion. Thanks @wyfo for reporting back and closing the issue. |
Beta Was this translation helpful? Give feedback.
-
I guess, it's useful to use custom Request or Route in some cases.
Proof me wrong |
Beta Was this translation helpful? Give feedback.
-
@dmontagu I used your method, and I get a import logging
import fastapi
import routers
logger = logging.getLogger(__name__)
app = fastapi.FastAPI()
async def log_request_json(request: fastapi.Request):
logger.debug("request parameters: %s", await request.json())
app.include_router(routers.api_router, dependencies=[fastapi.Depends(log_request_json)]) |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
How to use two await keyword within a function to log all the endpoint,if I use so my application does not working. |
Beta Was this translation helpful? Give feedback.
-
@PrasannaVpk Do you mean health-check? |
Beta Was this translation helpful? Give feedback.
-
I cant able to log body json for all post request. Can you please suggest a alternative how to log the request json. Right now it goes to infinite loop and not responding.
|
Beta Was this translation helpful? Give feedback.
-
@karthick-optisol Check out this doc https://fastapi.tiangolo.com/advanced/custom-request-and-route/#accessing-the-request-body-in-an-exception-handler. |
Beta Was this translation helpful? Give feedback.
-
what is "Message" there? |
Beta Was this translation helpful? Give feedback.
-
from starlette.types import Message |
Beta Was this translation helpful? Give feedback.
-
await set_body(request, request.body()) -> await set_body(request, await request.body()) Looks like await should be added. |
Beta Was this translation helpful? Give feedback.
-
For anyone who is looking for class-based middleware: class LoggingMiddleware(BaseHTTPMiddleware):
def __init__(self, app):
super().__init__(app)
async def set_body(self, request: Request):
receive_ = await request._receive()
async def receive() -> Message:
return receive_
request._receive = receive
async def dispatch(self, request, call_next):
await self.set_body(request)
body = await request.body()
json_body = await request.json()
print(body)
print(json_body)
response = await call_next(request)
return response Many thanks for @liukelin |
Beta Was this translation helpful? Give feedback.
-
This workaround works as expected many thanks @king-peanut. The unique condition is that this should be placed as the last middleware (the first to be loaded into the app, take care about the reverse ordering), if not it will freeze. |
Beta Was this translation helpful? Give feedback.
-
If you're using StreamingResponse in your response. receive_ = await request._receive()
async def receive() -> Message:
return receive_
request._receive = receive Does not work =( But you can write async def get_request_body(request: Request) -> bytes:
body = await request.body()
request._receive = ReceiveProxy(receive=request.receive, cached_body=body)
return body
@dataclasses.dataclass
class ReceiveProxy:
"""Proxy to starlette.types.Receive.__call__ with caching first receive call."""
receive: starlette.types.Receive
cached_body: bytes
_is_first_call: ClassVar[bool] = True
async def __call__(self):
# First call will be for getting request body => returns cached result
if self._is_first_call:
self._is_first_call = False
return {"type": "http.request", "body": self.cached_body, "more_body": False}
return await self.receive() |
Beta Was this translation helpful? Give feedback.
-
@king-peanut Your solution solves the hanging issue. However, whenever I do |
Beta Was this translation helpful? Give feedback.
-
You can add a dependent function in the router, and put the value of request.json into request.state in the dependent function. Now we consider how to get this value. Since the execution order of the program when the request comes is to execute the middleware first. Then execute the dependent function, we can get request.json from the request when the middleware gets the response async def log_json(request: Request):
param_json = await request.json()
request.state.param_json = param_json
# register route
app.include_router(main_router, dependencies=[Depends(log_json)])
# in the middleware
response: StreamingResponse = await call_next(request)
param_json = request.state.param_json |
Beta Was this translation helpful? Give feedback.
-
I'm locking this issue to avoid confusion. This was solved on FastAPI 0.108.0. See what I said here for more details. |
Beta Was this translation helpful? Give feedback.
I'm locking this issue to avoid confusion.
This was solved on FastAPI 0.108.0.
See what I said here for more details.