Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to set request headers before path operation is executed #2727

Closed
2 tasks
satishdash opened this issue Jan 30, 2021 · 10 comments
Closed
2 tasks

How to set request headers before path operation is executed #2727

satishdash opened this issue Jan 30, 2021 · 10 comments

Comments

@satishdash
Copy link

First check

  • [ ✓] I added a very descriptive title to this issue.
  • [ ✓] I used the GitHub search to find a similar issue and didn't find it.
  • [ ✓] I searched the FastAPI documentation, with the integrated search.
  • [✓ ] I already searched in Google "How to X in FastAPI" and didn't find any information.
  • [✓ ] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [ ✓] I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.
  • [✓ ] After submitting this, I commit to one of:
    • Read open issues with questions until I find 2 issues where I can help someone and add a comment to help there.
    • I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future.
    • Implement a Pull Request for a confirmed bug.

Example

request.state.someid = someid

Description

Tried implementing call in a class whose init method takes scope, receive and send params
tried mutating scope ['headers'] adding additional key, value pairs utf8 encoded but the request object in the path operation still doesn't have those headers.

Invoking api through test client invokes the middleware but the headers that are mutated still isn't visible in the request object received by the path operation.

@tiangolo suggested in one of the issues to create a new request object but have no idea how to do it.

Environment

  • OS: [e.g. Linux / Windows / macOS]:macos catalina
  • FastAPI Version [e.g. 0.3.0]: 0.63
  • Python: 3.8.6
  • Uvicorn: 0.13.3

To know the FastAPI version use:

python -c "import fastapi; print(fastapi.__version__)"
  • Python version:

To know the Python version use:

python --version

Additional comments

Can anybody please suggest or recommend it's important.

@satishdash satishdash added the question Question or problem label Jan 30, 2021
@ycd
Copy link
Contributor

ycd commented Jan 30, 2021

tried mutating scope ['headers'] adding additional key, value pairs utf8 encoded but the request object in the path operation still doesn't have those headers.

In Starlette Request is exposed as immutable multi-dict.

But fortunately we can monkey-patch everything in Python.

You can create your own router like this, also you can do the same thing with a middleware, but i like this approach more.

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

from typing import Callable,  Tuple
from uuid import uuid4



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

        async def custom_route_handler(request: Request) -> Response:

            id_header: Tuple[bytes] = "x-request-id".encode(), str(uuid4()).encode()

            request.headers.__dict__["_list"].append(id_header)

            response: Response = await original_route_handler(request)

            return response

        return custom_route_handler


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


@router.post("/dummy")
async def dummy(request: Request):
    return request.headers["x-request-id"] 


app.include_router(router)

@falkben
Copy link
Sponsor Contributor

falkben commented Jan 30, 2021

Very nice @ycd

This is shown in the docs here: https://fastapi.tiangolo.com/advanced/custom-request-and-route/

@dashsatish
Copy link

dashsatish commented Feb 1, 2021

Thanks @ycd. Like this approach. Is there a way where we can use a similar approach with a middleware __call__ method?

@Kludex
Copy link
Sponsor Collaborator

Kludex commented Feb 1, 2021

from fastapi import FastAPI

app = FastAPI()

class Middleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        assert scope["type"] == "http"
        headers = dict(scope["headers"])
        headers[b"x-request-id"] = b'1' # generate the way you want
        scope["headers"] = [(k, v) for k, v in headers.items()]
        await self.app(scope, receive, send)

app.add_middleware(Middleware)

@app.get("/")
def home():
    return "Hello World!"

Check also: https://pypi.org/project/starlette-context/

@dashsatish
Copy link

Thanks @Kludex . This is also an awesome solution. Was trying to the do the same but was missing async/await keywords in the function definition. But really appreciate for the prompt reply.

@mnahinkhan
Copy link

mnahinkhan commented Nov 21, 2021

Another way is to set headers using the middleware decorator that's similar to the way described in the documentation:

https://fastapi.tiangolo.com/tutorial/middleware/

@app.middleware("http")
async def create_auth_header(
    request: Request,
    call_next,
):
    """
    Check if there are cookies set for authorization. If so, construct the
    Authorization header and modify the request (unless the header already
    exists!)
    """
    if (
        "Authorization" not in request.headers
        and "access_token_payload" in request.cookies
        and "access_token_signature" in request.cookies
    ):
        access_token_payload = request.cookies["access_token_payload"]
        access_token_signature = request.cookies["access_token_signature"]
        access_token = f"{access_token_payload}.{access_token_signature}"
        request.headers.__dict__["_list"].append(
            (
                "authorization".encode(),
                f"Bearer {access_token}".encode(),
            )
        )

    response = await call_next(request)
    return response

Edit: It's really important for the header key to be lower case when encoding like above (so use authorization instead of Authorization, for example.

@Magnati
Copy link

Magnati commented Feb 27, 2022

I cannot say for all suggestions but I am pretty sure that at least some approaches are

  • not updating the Headers object index (aka requests.headers[<my-new-member>] would fail)
  • not updating the scope attribute accordingly (aka requests.scope.get(headers) != requests.headers)

Suggestion:

        # REPLACE:
        # request.headers.__dict__["_list"].append(
        #     (
        #        "authorization".encode(),
        #         f"Bearer {access_token}".encode(),
        #     )
        # )
        new_headers = request.headers.mutablecopy()
        new_headers.append(
            "authorization".encode(), 
            f"Bearer {access_token}".encode()
        )
        request._headers = new_headers  # solves first point
        request.scope.update(headers=request.headers.raw)  # solves second point
        

I am pretty sure that the proposals of @mnahinkhan and @ycd are missing both points.
@Kludex approach solves I guess the scope issue, but the first point (request.headers[<new-entry>]) might still be unsettled.

@TheBubblePopped
Copy link

This isn't working @Magnati. I recommend #3027 (comment)

@tiangolo
Copy link
Owner

tiangolo commented Nov 7, 2022

Thanks for the help here everyone! ☕

If that solves your use case, you can close the issue @satishdash 🍰

@satishdash
Copy link
Author

Solved!

@tiangolo tiangolo reopened this Feb 27, 2023
Repository owner locked and limited conversation to collaborators Feb 27, 2023
@tiangolo tiangolo converted this issue into discussion #6996 Feb 27, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

9 participants