3

I am trying to write a middleware in my FastAPI application, so that requests coming to endpoints matching a particular format will be re-routed to a different URL, but I am unable to find a way to do that since request.url is read-only.

I am also looking for a way to update request headers before re-routing.

Are these things even possible in FastAPI?

Redirection is the best I could do so far:

from fastapi import Request
from fastapi.responses import RedirectResponse

@app.middleware("http")
async def redirect_middleware(request: Request, call_next):
    if matches_certain_format(request.url.path):
        new_url = create_target_url(request.url.path)
        return RedirectResponse(url=new_url)
Chris
  • 18,724
  • 6
  • 46
  • 80
Aniket Tiratkar
  • 798
  • 6
  • 16
  • 1
    Could you just request the other URL from `localhost` as if someone in the browser did it, and return whatever it returns? – Libra Mar 13 '23 at 21:17
  • @Libra, that'd work and I think I will go with that approach for now. I was expecting FastAPI to provide a better way though. – Aniket Tiratkar Mar 14 '23 at 04:27
  • Does this answer your question? [How to create a FastAPI endpoint that can accept either Form or JSON body?](https://stackoverflow.com/questions/74009210/how-to-create-a-fastapi-endpoint-that-can-accept-either-form-or-json-body) – Chris Mar 14 '23 at 05:42

2 Answers2

3

To change the request's URL path—in other words, re-route the request to a different endpoint—one can simply modify the request.scope['path'] value inside the middleware, before processing the request, as demonstrated in Option 3 of this answer. As for updating the request headers, or adding new custom headers to the request, you can follow a similar approach to the one described here, which demonstrates how to modify the request.scope['headers'] value.

Working Example

If you would like to avoid maintaining a list of routes to re-route and performing checks inside the middleware, you could instead mount a sub-application, which will contain only the routes that require re-routing, and add the middleware to that sub-app, similar to Option 3 of this answer.

from fastapi import FastAPI, Request

app = FastAPI()
routes_to_reroute = ['/']

@app.middleware('http')
async def some_middleware(request: Request, call_next):
    if request.url.path in routes_to_reroute:
        request.scope['path'] = '/welcome'
        headers = dict(request.scope['headers'])
        headers[b'custom-header'] = b'my custom header'
        request.scope['headers'] = [(k, v) for k, v in headers.items()]
        
    return await call_next(request)

@app.get('/')
async def main():
    return 'OK'

@app.get('/welcome')
async def welcome(request: Request):
    return {'msg': 'Welcome!', 'headers': request.headers}
Chris
  • 18,724
  • 6
  • 46
  • 80
  • This looks good and I think it would work, but not for my requirement (which is not mentioned in the original question). Could it be because I am trying to re-route the request to a different server? I tried changing the server with this statement `request.scope['server'] = (host, int(port))`, but it is not leaving the server. Do you know what might be wrong here? – Aniket Tiratkar Mar 14 '23 at 08:40
  • For _redirecting/forwarding_ requests to a different server, you could either keep using `RedirectResponse` (but it wouldn't allow you passing headers to the other server - see [this answer](https://stackoverflow.com/a/74292752/17865804), as well as [this answer](https://stackoverflow.com/a/73137093/17865804) and [this answer](https://stackoverflow.com/a/73599289/17865804) for more details; unless the request is issued using JS in the frontend, which would allow you to work around this problem - see [here](https://stackoverflow.com/a/75188418/17865804))... – Chris Mar 14 '23 at 10:39
  • .... or _forward_ the requests to the other server, similar to the approach demonstrated in [this answer](https://stackoverflow.com/a/74556972/17865804). – Chris Mar 14 '23 at 10:39
  • 1
    Thanks a lot for these links, it gave me exactly the information I needed. And I agree with your suggestion about the question, I am going to leave it as is. – Aniket Tiratkar Mar 14 '23 at 20:29
0

Yes, FastAPI applications allow for URL rewriting. However, as you mentioned, the request.url is immutable, so you are unable to update it.

Instead, you can make a new URL object and pass it to the request object with the updated URL and headers. Here is an example middleware that does both URL rewriting and header modification:

from fastapi import Request
from fastapi.responses import RedirectResponse

@app.middleware("http")
async def redirect_middleware(request: Request, call_next):
    if matches_certain_format(request.url.path):
        new_url = create_target_url(request.url.path)
        headers = request.headers.copy()
        headers['new-header'] = 'new-value'
        new_request = Request(request.scope, request.receive, request.app, request.method, URL(new_url), headers=headers)
        return await call_next(new_request)
    else:
        response = await call_next(request)
        return response
SyntaxNavigator
  • 312
  • 3
  • 10
  • It won't work. First, `request.headers.copy()` will fail because headers doesn't have a `copy` method. Let's say we use `copy.copy` here, then `headers['new-header'] = 'new-value'` will fail because it is read-only. Thanks for your answer though. – Aniket Tiratkar Mar 14 '23 at 04:32