Fastapi: [QUESTION] How to get Response Body from middleware

Created on 7 Feb 2020  路  15Comments  路  Source: tiangolo/fastapi

Hi, I working on FastAPI. I would like to get or access the attribute from Response object. As i know It's StreamingResponse type. It's look like async_generator. I've read the document from https://www.starlette.io/responses/#streamingresponse. This instruction look like writing on the Response object. It's don't have a instruction about getting body object from response. Then i trying to find the solution from https://github.com/tiangolo/fastapi/issues/623. but now the link for solution has been expired.
So anyone have idea or example ?
Thank you.

answered question

Most helpful comment

@ronanfernandes @tiangolo is has actually pointed in the right direction, maybe not being explicit enough.

There is not reliable way to access response from middleware, but does it actually need to be read in the middleware?

You need to create a CustomAPI route, because the response body in the middleware can be a StreamingResponse or could be gzipped or many other things.

The only safe way to access it is by wrapping the execution of the api route.

This example is at the bottom of the page @tiangolo has linked, and shows you exactly how you can access the response.

import time
from typing import Callable

from fastapi import APIRouter, FastAPI, Request, Response
from fastapi.routing import APIRoute


class TimedRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            before = time.time()
            response: Response = await original_route_handler(request)
            duration = time.time() - before
            response.headers["X-Response-Time"] = str(duration)
            print(f"route duration: {duration}")
            print(f"route response: {response}")
            print(f"route response headers: {response.headers}")
            return response

        return custom_route_handler


app = FastAPI()
router = APIRouter(route_class=TimedRoute)


@app.get("/")
async def not_timed():
    return {"message": "Not timed"}


@router.get("/timed")
async def timed():
    return {"message": "It's the time of my life"}


app.include_router(router)

All 15 comments

Is this one ?

Is this one ?

What section in article?
I had seen that before, It's no mention on Response Body, just only response header.

@bozzlab Sorry, the docs got reorganized and the link moved. It is fixed in that issue now, but it is also here for reference: https://fastapi.tiangolo.com/advanced/custom-request-and-route/

@bozzlab Sorry, the docs got reorganized and the link moved. It is fixed in that issue now, but it is also here for reference: https://fastapi.tiangolo.com/advanced/custom-request-and-route/

Thank you for the documents, But now i'm working in this style

app.middleware('http')
async def middleware(request: Request, call_next) -> Response:    
    response = await call_next(request)  # response has been StreamingResponse Type     
    #do something() instead that line to convert Request -> JSONResponse
    return response

So, If i would like to get Response Body using this way. Is possible ?

So... did you solve your issue @bozzlab ? If that's the case, you can close the issue.

So... did you solve your issue @bozzlab ? If that's the case, you can close the issue.

Not yet, I'm still finding solution for this.

I ran into a similar situation, for my use case I created a subclass of the response type I needed and added the extra parameter I would have added inside the middleware.

class SnakeCaseResponse(UJSONResponse):
    def render(self, content: typing.Any) -> bytes:
        content['is_success'] = 'error' not in content
        return super().render(convert_to_camel_case(content))

same problem, I am trying to get the body of a request and response in middleware/custom router.

Getting the request body in a middleware can be problematic and in some cases not possible, as the request's body is "consumed".

But to solve those use cases where you are sure your request's body won't be gigantic (or infinite), for example, it's a simple JSON, and you want to access the JSON body in something like a middleware and also in the path operations, you can apply the ideas from this section, let me paste the link again: https://fastapi.tiangolo.com/advanced/custom-request-and-route/#accessing-the-request-body-in-an-exception-handler

Please read the docs there. That should help you solve your use cases.

@tiangolo Would it be possible to attach raw content to the Response object?

class Response:
    media_type = None
    charset = "utf-8"

    def __init__(
        self,
        content: typing.Any = None,
        status_code: int = 200,
        headers: dict = None,
        media_type: str = None,
        background: BackgroundTask = None,
    ) -> None:
        self.body = self.render(content) # <-- self.content = content
        self.status_code = status_code
        if media_type is not None:
            self.media_type = media_type
        self.background = background
        self.init_headers(headers)

I want to log the content of a JSONResponse as formatted json, but the only way to access it is in a custom APIRoute handler wrapper is after it has been transformed into json bytes.

Now I have to do json.load(response.body) with small perf impact.

Is there a reason why raw content is not exposed or is a bad idea?

I guess I could make my own custom response class...although this might be a bit tricky.

Getting the request body in a middleware can be problematic and in some cases not possible, as the request's body is "consumed".

But to solve those use cases where you are sure your request's body won't be gigantic (or infinite), for example, it's a simple JSON, and you want to access the JSON body in something like a middleware and also in the _path operations_, you can apply the ideas from this section, let me paste the link again: https://fastapi.tiangolo.com/advanced/custom-request-and-route/#accessing-the-request-body-in-an-exception-handler

Please read the docs there. That should help you solve your use cases.

Hi @tiangolo, thanks for your help and for all the efforts in this project!

But I think in this case you get it wrong, @bozzlab is looking for response body, not request body...

@ronanfernandes @tiangolo is has actually pointed in the right direction, maybe not being explicit enough.

There is not reliable way to access response from middleware, but does it actually need to be read in the middleware?

You need to create a CustomAPI route, because the response body in the middleware can be a StreamingResponse or could be gzipped or many other things.

The only safe way to access it is by wrapping the execution of the api route.

This example is at the bottom of the page @tiangolo has linked, and shows you exactly how you can access the response.

import time
from typing import Callable

from fastapi import APIRouter, FastAPI, Request, Response
from fastapi.routing import APIRoute


class TimedRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            before = time.time()
            response: Response = await original_route_handler(request)
            duration = time.time() - before
            response.headers["X-Response-Time"] = str(duration)
            print(f"route duration: {duration}")
            print(f"route response: {response}")
            print(f"route response headers: {response.headers}")
            return response

        return custom_route_handler


app = FastAPI()
router = APIRouter(route_class=TimedRoute)


@app.get("/")
async def not_timed():
    return {"message": "Not timed"}


@router.get("/timed")
async def timed():
    return {"message": "It's the time of my life"}


app.include_router(router)

thank you, it麓s clearer now.

@vjpr you could probably create a sub-class of JSONResponse with your custom implementation, overriding its initialization and storing the data you need in a custom attribute.

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

Was this page helpful?
1 / 5 - 1 ratings