-
-
Notifications
You must be signed in to change notification settings - Fork 946
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
feat: Add route-level middleware #1286
Conversation
A more common use case of that is to apply a specific middleware to /api, /admin, and / scopes. Admin may require session, API wants token authentication, and landing page needs none of them. |
May I also ask you to add middleware to Mount and Host classes? That would be awesome! |
I think that'd be cool, and should be doable, but maybe let's leave it for another PR, depending on the outcome of this one? I'd like to get an initial review / buy in from the Starlette team before making any more changes |
Sorry to jump in, but there's any news about this? It would be useful in my projects |
It is waiting for review, there is no ETA. But, if you really needed to, you could just wrap the class in your app to add the functionality given that .app is a public attribute |
Okay, ya know what - it's a pretty nice simple implementation, and even though I'm not super keen on route-based middlewares (just because constraints are not a bad thing) I think we could go with this. Here's what I'd like to see in order to merge this...
After that then I reckon it's a thumbs up from me. Good stuff! |
Hey @tomchristie, thank you for looking at this! I added the same implementation into What are your thoughts on the other route types? I reckon this can also be added to WebSocketRoute and Host, but maybe it's better to wait until someone actually requests that feature. I added a blurb in the docs, it's relatively short but I think it gets the point across. |
Co-authored-by: Marcelo Trylesinski <[email protected]>
@tomchristie looks like I was able to implement your requested changes and get all tests passing. I just wanted to highlight this question before we move forward with this:
Also, like we were discussing w/ @Kludex on Gitter, I'm open to being added as a maintainer so I can merge PRs after approval (given that I have several open across Starlette and httpx). |
starlette/routing.py
Outdated
@@ -340,16 +348,22 @@ def __init__( | |||
self.path = path.rstrip("/") | |||
if app is not None: | |||
self.app: ASGIApp = app | |||
routes = getattr(self.app, "routes", []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this variable used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good catch, it is not being used. It's leftover from a previous implementation.
starlette/routing.py
Outdated
return getattr(self.app, "routes", []) | ||
# we dynamically grab the routes so that if this is a Starlette router | ||
# it can have routes added to it after it is mounted | ||
return getattr(self._user_app, "routes", []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomchristie how would you feel about just grabbing a reference to .routes
in __init__
instead of doing a getattr here? I suppose that would break if the mounted router replaces its .routes
with a new list, but otherwise it should be fine and it would avoid having to add this _user_app
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I like it more. I went ahead and implemented it that way: e9ff903
If you prefer it as it was doing a getattr
in self.routes
, I'm happy to revert it
a0a0b1d
to
e9ff903
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All seems pretty reasonable yup. I'm personally still not that wild about route-level middleware, but I'm happy to defer this one to the rest of the team. 🤣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were two possible solutions proposed by Tom on the issue that motivated this PR.
- We could potentially add some public API on the application so that it's easier to premptively resolve the request routing? Ideally we ensure that can be cached, so that the lookup only occurs once.
- We could add the ability to have some kinds of middleware run after the request routing. (Probably not super keen on that.)
I took my time to think about the first solution, and how that would look like... I think what we can do is to add a parameter on the Middleware
class:
app = Starlette(
middleware=[
Middleware(PotatoMiddleware, path_prefix="/potato") # or `path_prefixes`
]
)
Considering the above, we stick with the middleware concept being only at the app level.
Thoughts? @alex-oleshkevich @adriangb
Co-authored-by: Marcelo Trylesinski <[email protected]>
How would that be implemented? Is |
FYI for those subscribed to this issue, I opened #1464 which I think solves this problem more elegantly |
Yes |
In case you reconsider #685 (comment) @tomchristie
I think this would be a useful feature. I often see middleware parameters that are "a regex pattern to match routes" because the middleware has to determine what routes to interact with before routing is done. Here and here are two examples. This also closes fastapi/fastapi#1174
Aside from being a pain to build some complex regex, there are performance implications: imagine (in the absurd scenario) that you have 100 endpoints and 100 middleware, but each middleware applies only to 1 endpoint. Now you have 99 middleware executing and doing regex matching when they could have just been bypassed to begin with if they executed after routing.
That said, re-using the existing Middleware construct for this use has it's cons. For example, it is obvious that the middleware cannot modify the path. I can't think of any other major limitations, but I there may be others.