-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
✨ Add ignore trailing slashes option to FastAPI
class
#12145
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
base: master
Are you sure you want to change the base?
Conversation
FastAPI
classFastAPI
class
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.
Thanks for the PR! We'll get this reviewed and will get back to you 🙏
Also FYI - the CI is currently failing due to an unrelated issue - we're looking into it. Update: The CI has been fixed by merging in the latest from |
The code-coverage is at 100% now. I added the tests before removing code duplication to check that all |
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.
Thanks again for the PR!
I've now had a more detailed look at the proposal here.
From going through old issues and discussions, it does seem like a lot of users are looking for ways to unify the behaviour between paths with and without slashes. It looks like this used to be possible with /?
syntax but that isn't supported anymore.
You can still obtain this behaviour by registering the path in the router
twice, once with slash and once without, but I can see how that gets unweildy for large applications.
In the current implementation on master
, suppose you have something like
@router.get('/hello')
def hello_page() -> str:
return 'Hello, World!'
Then when accessing http://127.0.0.1:8000/hello/
you would get a
"GET /hello/ HTTP/1.1" 307 Temporary Redirect
However if you've specified ignore_trailing_slash=True
with this PR, you'd get
"GET /hello HTTP/1.1" 200 OK
i.e. the trailing slash is already removed before it hits the server & the logs and the behaviour is indeed exactly the same.
One issue with the current implementation is that users wouldn't be warned if the app does register two variants of the path, e.g.
@router.get('/hello')
def hello_page() -> str:
return 'Hello, World!'
@router.get('/hello/')
def hello_page() -> str:
return 'Hello, Slashed World!'
With FastAPI(ignore_trailing_slash=True)
, the second path /hello/
would never get called.
So to recap, I can see how this ignore_trailing_slash=True
behaviour could be preferred in some applications, but it does add some complexity and potential confusion on top of the existing functionality with redirect_slashes
etc. If we do want this feature, we need to add some documentation to it as well, to make sure the consequences of using this flag are crystal clear.
I'm going to leave the final decision up to the project's BDFL, Mister Tiangolo 😁
Thanks for your detailed feedback on the pull request. Perhaps a quick note that the behavior of I added a bit more documentation to provide clarity on how the proposed flag influences the behavior when multiple variants of the same path are registered. @tiangolo , what do you think? |
It would be interesting to see how this will work in combination with another PR #5595 that validates duplicate paths. |
Good point, I just looked at PR #5595. It looks to me like it will integrate well with this one. We just have to be careful to strip the trailing slashes before calculating the route hashes. |
@tiangolo Any updates on this? |
I just want to add a comment as to where this becomes vitally important from what I encountered. When I'm using Cloudflare forwarding to Traefik as a reverse proxy to forward to FastAPI there is a huge fundamental difference in the way IP's are passed for HTTPS and HTTP: the 307 redirect breaks this and causes things to be very much more complicated when trying to put in place rate limits and other IP specific things. This is fixed outside of FastAPI with
or
But trailing slashes and 307 redirects are definitely an issue. |
The entire web community are waiting for this PR to be merged a new version gets released ! Thank you |
+1 for this. Getting this merged would really help! CC: @tiangolo |
+1 it would be perfect 😄 cc @tiangolo |
Hi all! Thanks for the interest in this PR 🙏 It's better to just "upvote" the original post and/or show it some love with a 👍, 🎉 or ❤️. Just commenting "+1" and tagging maintainers adds quite a bit of noise to our notifications, actually slowing down maintenance on the OS repos rather than helping 😉 That said, of course detailed reviews and comments are always welcome. And just to recap - we've got this internally on our queue for Tiangolo to review. Thanks for your patience! |
First i want to say thanks to @Synrom for the contribution and to @svlandeg to follow this PR. This is my personal opinion on the PR: while on one hand, I approve of the choice and elegance of using middleware to simplify this kind of operation, I believe that the approach of integrating it directly as an option in FastAPI is wrong for a number of reasons.
What i propose: Make the RemovingTrailingSlashMiddleware available through the middlewares defined or exported by FastAPI, create dedicated documentation for the middleware, and use it as a reference for an example context as requested by many. At the end to solve the current 'issue' ( that for me is more an utility that an issue) just few line of code are required. class
6D40
span> RemoveTrailingSlashMiddleware:
def __init__(self, app: ASGIApp):
self.app = app
async def __call__(self, scope, receive, send):
# Ensure we are working with an HTTP or WebSocket request
if scope["type"] in {"http", "websocket"}:
path = scope["path"]
# Remove trailing slash for all paths except the root "/"
if path != "/" and path.endswith("/"):
scope["path"] = path.rstrip("/")
# Call the next middleware or app in the stack
await self.app(scope, receive, send) And add the middleware to the app with the disired order.
|
Hey @luzzodev, thanks for the reply and feedback. Your criticism about using a middleware is valid. Now about your proposal of just introducing a app = FastAPI()
app.add_middleware(RemoveTrailingSlashMiddleware)
app.get("/auth/")
def return_msg():
return {"msg": "This is unreachable"} In this scenario, both I believe this issue has captured a lot of interest because many users share a similar experience: their FastAPI application runs perfectly in a local environment, but once it's deployed behind a complex proxy setup, things break due to temporary redirects. |
Hi @Synrom just to clarify some points:
What i'm bringing on the table are just example from a different prospective and just confirm how bad was the choise to to add redirect_slashes in starlette to True by default. The real point here is that treat In conclusion, i like your solution and your work but fixing the middleware execution position, not reusable middleware and possible confusion with already implemented redirect_slashes for me a problems that need to be solved. |
In FastAPI, Our problem is how the equivalency is solved at runtime. Redirect helps but it fails in a number of situations including when FastAPI is behind a (simple) proxy (that handles TLS and only forwards non-HTTPS requests to the server). Redirect has it own problems as well, e.g. latency. More redirects = Higher latency. It gets worse if commonly used paths have to go through this issue. So, instead of redirects, it's much better to just ignore trailing slashes and resolve both
I don't understand how
We arrived at the same situation even for the right path. I guess we require |
Dear @tusharsnx, with your response, you are just giving me more evidence of how much confusion there is.
First of all FastApi has nothing to do with redirect, is a Starlette implementation instead. Fastapi just wrap over Starlette and, for consistency, expose the same arguments keeping default unchanged. The evidence are really the opposite of the ones you are describing, because indeed a redirect is called instead of a direct execution of the new path and even in the match phase of a route, there is no trace of considering the two path equivalent. This is not only related to Fastapi/Starlette but is how it works in the majority of the cases (Web Server, Proxies, etc.).
I could partially agree with it, in a lot of implementations, proxy, webserver etc. this is default behavior but in a context where you have direct control over path definition could be a little tricky. The problem about failing locally and in production could be for sure addressable to other things like having the follow redirect enabled by default on most http client.
Your problem is the same of many others that are dealing with trailing slash and do not want to handle redirect. In conclusion if you want more flexibility then go with redirect and let What i mean for consistency? Define the presence, format, and semantics of a path component in URIs. But maybe even this approach is not enough because be consistent is too strict and in reality you, and many others, want to process all routes with a king of "resolve_internal_redirect" option. As i said, this could be easily achieved by using a middleware that, of course, is not the one i wrote and it was not intended for it.
What i described is just one simple way that you can use to achieve the result of treat equally uri with and without trailing slash but of course keeping consistency and your example you were not consistent. |
Dear @luzzodev, I really like the idea of having a flag to resolve redirects internally. That said, I believe this functionality would be better implemented directly in Starlette rather than in FastAPI. FastAPI relies on the Starlette Router under the hood, and it’s the Router that issues the redirect responses. A simple solution would be to introduce a flag in the Router class to enable internal resolution of redirects. I've started a discussion on the Starlette repository about this—feel free to check it out! 😄 |
@Synrom I agree with it being in Starlette. But I think at the very least, this should be called out in the documentation for those unfamiliar with the default behaviour. I just spent hours chasing down HTTPS issues on the client side because fastapi was doing this redirect silently. |
Add a boolean flag
ignore_trailing_slash
to FastAPI class that decides whether to ignore trailing slashes at the end of URIs.For example, by setting
ignore_trailing_slash
to True, requests to/auth
and/auth/
will have the same behaviour.By default (
ignore_trailing_slash
is False), the two requests are treated differently and one of them will result in a 307-redirect.Using the flag is as easy as:
This change provides a simple solution to discussions #9328 and #7298