A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/encode/starlette/issues/869 below:

Handling trailing slashes properly with mounted routes · Issue #869 · encode/starlette · GitHub

Hello there!

In the documentation, there is this example about large applications. If we make it a little more complete, we can get something like this:

import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route


async def homepage(request):
    return JSONResponse(status_code=200)


async def users(request):
    return JSONResponse([{"username": "bob"}, {"username": "alice"}])


async def user(request):
    username = request.path_params["username"]
    if username == "bob":
        return JSONResponse({"username": "bob"})
    if username == "alice":
        return JSONResponse({"username": "alice"})
    return JSONResponse(status_code=404)


routes = [
    Route('/', homepage),
    Mount('/users', routes=[
        Route('/', users, methods=['GET', 'POST']),
        Route('/{username}', user),
    ])
]

app = Starlette(routes=routes)
uvicorn.run(app)

But the problem with this example is that all routes are behaving differently:

So one route answers as expected whether you give it a trailing slash or not, another one only answers directly if you give it the trailing slash, and another one only answers directly if you do not give it the trailing slash.

Of course, the fact that the homepage is answering with or without slash is only due to how HTTP works, because you can't send a GET without a route, and all HTTP clients will convert this to GET /.

At this point I'm just paraphrasing #823, where the discussion ended with:

That's intentional, because a mount point should always be in the form /path-to-mount/{...}

Alright, let's take that for a fact, but then... what if I want all my mounted routes to answer without a trailing slash? This is particularly important for API development, where you want to provide consistent endpoints.

I started by trying to give an empty route, i.e. Route('', users, methods=['GET', 'POST']), but that simply does not work: AssertionError: Routed paths must start with '/'.

Reconsidering the answer in #823, I then tried to mount on / directly, i.e. doing this, thinking that it would work around my problem:

routes = [
    Route('/', homepage),
    Mount('/', routes=[
        Route('/users', users, methods=['GET', 'POST']),
        Route('/users/{username}', user),
    ])
]

That seems weird here, because you could simply add the routes at the top level, but in my real use-case, I'm using routers that I import and mount. And that works great... until you add another Mount, that will never be resolved, because the routing stops at the first mount point with a matching prefix, as explained in #380 (comment).

Then I saw #633 (comment), and tried to add /? everywhere, just like this:

routes = [
    Route('/', homepage),
    Mount('/users', routes=[
        Route('/?', users, methods=['GET', 'POST']),
        Route('/{username}/?', user),
    ])
]

This is not user-friendly at all, but it helped on the /users/{username} route, which now answers as expected whether you give it a trailing slash or not. Alas, the /users route still emits a 307. So basically, at this point, I just don't know how to handle this, and I'm pretty frustrated by spending so much time on such a trivial thing.

What I ended up doing, since I'm using routers, is defining the whole path in my routes, with a big hack at the application level:

routes = (
    users_router.routes,
    + other_router.routes
)

If there isn't something obvious that I missed and would resolve my problem in a simpler way, please consider one or more of the following (by my preference order):

Sorry for the long text, I couldn't find a simpler way to expose the whole problem and thinking behind it.

Important

Cozmo25, shaung, joegester, iot-resister, zqbake and 33 more


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4