-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
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 use different middleware for different routes/path #1174
Comments
I haven't use this way, but you can have a try. |
Thank you @Dustyposa this looks exactly like what I was looking for. Here is a example implementation in case someone else stumble upon the same problem # app1.py
from fastapi import FastAPI
app1 = FastAPI(openapi_prefix="/app1")
@app1.get("path1")
async def path1():
return { "message": "app1"} # app2.py
from fastapi import FastAPI
app2 = FastAPI(openapi_prefix="/app2")
@app1.get("path2")
async def path2():
return { "message": "app2"} # main.py
from fastapi import FastAPI
from app1 import app1
from app2 import app2
app1.add_middleware(
CORSMiddleware,
allow_origins=['myfrontend.com'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# here we can add middlewares that are only for app2 and not executed on paths of app1
# the CORS policy is different for app2. (allows '*' instead of 'myfrontend.com')
app2.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app = FastAPI()
app.mount("/app1", app1) # if does not work try: app.mount(app1, "/app1")
app.mount("/app2", app2) # if does not work try: app.mount(app2, "/app2")
@app.get("/")
async def root():
return {"message": "alive"} |
Thanks for your help here @Dustyposa 🙇♂️ 🍰 And thanks for reporting back and closing the issue @philippegirard 🚀 |
This is a fine approach for some use cases. But having route based middleware is still a requirement for many projects I work on. I understand starlette itself cannot yet do this, so perhaps this is the wrong github page to bring it up. I will see what Tom Cristie thinks about adding route based middleware to starlette. Then perhaps fastapi can impliment it in the route decorators |
I'm facing a quite similar issue : I want to add custom 'Content-Security-Policy' header the easiest way to do so is to add a middleware : @Api.middleware("http")
However this CSP block the Swagger UI and thus it would be great to only apply the middleware to a subset of routes. nb1 : For those who are wondering, i removed the middleware and i had to add a response headers to all routes (ie functions) except for the swaggers (not very clean) |
+1 for router and/or route level middleware |
Thanks @philippegirard. It's helped me, but in your example: app.mount(app1, "/app1")
app.mount(app2, "/app2") Not working. Checking the documentation, I realized that the correct way is swapping parameters: app.mount("/app1", app1)
app.mount("/app2", app2) |
An interesting alternative is to use customization of the I wonder if we could wrap an existing middleware into an router = APIRouter(route_class=SomeWrapper(middleware=[Middleware1, Middleware2])) |
And... here's a super quick & dirty implementation + 1 test: from typing import List, Optional, Type
from starlette.middleware.base import BaseHTTPMiddleware
from fastapi import APIRouter, FastAPI, Response
from fastapi.routing import APIRoute
from starlette.middleware import Middleware
from starlette.types import ASGIApp
# define a vanilla middleware
class CustomHeaderMiddleware(BaseHTTPMiddleware):
def __init__(self, app: ASGIApp, header_value: str):
super().__init__(app)
self.header_value = header_value
async def dispatch(self, request, call_next):
response: Response = await call_next(request)
response.headers['X-Custom'] = self.header_value
return response
# bind the middleware to an APIRoute subclass
def MiddlewareWrapper(middleware: Optional[List[Middleware]] = None) -> Type[APIRoute]:
class CustomAPIRoute(APIRoute):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
app = self.app
for cls, options in reversed(middleware or []):
app = cls(app, **options)
self.app = app
return CustomAPIRoute
app = FastAPI()
router = APIRouter(route_class=MiddlewareWrapper(middleware=[Middleware(CustomHeaderMiddleware, header_value="test")]))
@router.get("/")
async def root():
...
app.include_router(router)
from fastapi.testclient import TestClient
client = TestClient(app)
assert client.get("/").headers["X-Custom"] == "test" Obviously some things will be broken: these middleware can't modify the path (this would silently fail) and there's probably other stuff that won't work 🤷 , but for a lot of things this is probably fine. As far as I can tell, this isn't accessing any private attributes or doing other breakable stuff. I'm sure the class closure thing can be cleaned up in some way. This would be even easier if we were able to edit parameters on |
I opened encode/starlette#1286 to see if we can upstream #1174 (comment) into Starlette (and then get it for free in FastAPI). The implementation there is 4 LOC. |
encode/starlette#1286 is implemented at the app = FastAPI()
@app.get("/", middleware=[...]) As well as the original request: router = APIRouter(middleware=[...])
@router.get("/") Even combining both (although we'd have to determine the order in which the sequence of middlewares is combined). |
@philippegirard which middleware would execute first by default? Can we control the order of execution of the middlewares? |
@abhaynayak24 I am not sure. In this example there's is only one middleware per route. Therefore, there exists no routes which executes two middleware in the same request this example. Experiment to answer your question: Add two middleware to the same route with two different logs and check in the console which one executes first. You can report back on this issue the result of your experiment. Thank :) ! |
It is not the cleanest thing in the world but it occurred to me to create a list with the paths that I want to go through the middleware, and within the middleware a routing condition
In this way, if your middleware does validations, they do not block the other routes such as the api / docs for example |
Hi, the document has moved to a new link . |
I feel like having to use sub apps is not the optimal choice, I would like to be able to set in a route or path level which middlewares and their order should be running there. Is this still not possible in fastapi? |
It won't be possible until encode/starlette#1286 gets merged and FastAPI updates it's Starlette version. Or just copy the implementation in encode/starlette#1286 to a custom subclass of FastAPI's APIRouter if you need it today. |
Any news here? I have a need for route specific middleware. |
Also would love to see route level middleware. EDIT: Current work on this is pending, here: Starlette PR#1464 |
The best way to push this forward would be to express your support in encode/starlette#1464 via a 👍. Maybe also consider donating or having your employer donate to https://github.com/encode/ so that maintainers can spend more time working on Starlette (to the benefit of all downstream projects, including FastAPI) and less time worrying about being a person in a world run by purchasing power. |
A bit late to the party but, Recently I wanted to have an authentication middleware on some of my FastAPI routers, just like you'd do in the Express.JS project. Here's my StackOverflow answer to a similar question: https://stackoverflow.com/a/73233603/10123687 As for the CORS use case, it won't be possible to use the middlewares provided by Starlette, but you can easily write your own and add/modify the response headers. something like this should work: async def CORS(request: Request, response: Response):
response.headers['Access-Control-Allow-Origin'] = 'http://localhost:8000'
response.headers['Access-Control-Allow-Methods'] = 'OPTIONS,GET,POST'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
router = APIRouter(dependencies=[
Depends(CORS)
])
# ... Your routes as usual
@router.get('/xyz')
# ... |
@adriangb it seems like per-route middlewares would be unblocked as the latest pulls in starlette 0.25 which has your changes. Would a PR adding it to FastAPI be likely to get merged? Having separate routers sort of works but for some use cases where you need some route specific configuration in the middleware it gets very clunky. |
I think that yes a PR to FastAPI would be accepted |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
First check
Description
Is it possible to use different middleware for different routes/path?
Additional context
In my case my need comes from CORS. But, I am sure there is other cases than CORS requirements that someone would need different middlewares for different paths.
myapi.com/path1
to allow origins of calls frommyfrontend.com
myapi.com/path2
to allow origins of calls from anywhere ('*') since it is a public facing api.I checked if it was possible to add a middleware at a router level and did not find any documentation about it.
Code example
The text was updated successfully, but these errors were encountered: