starlette icon indicating copy to clipboard operation
starlette copied to clipboard

Handling of `scope["root_path"]` and `scope["path"]` differs from ASGI spec

Open silane opened this issue 4 years ago • 2 comments

Checklist

  • [x] The bug is reproducible against the latest release and/or master.
  • [x] There are no similar issues or pull requests to fix it yet.

Describe the bug

Accoring to https://github.com/django/asgiref/issues/229#issuecomment-765583185, scope["path"] includes the scope["root_path"]. So if root_path is /foo and request for /foo/bar has come, the upstream application servers (such as uvicorn) are passing scope["path"] == "/foo/bar" and scope["root_path"] == "/foo", which is correct behavior.

But starlette does not conform this. It assumes scope["path"] is a remainder of stripping scope["root_path"] from original request path. In this case, scope["path"] == "/bar" and scope["root_path"] == "/foo". This ASGI incompatible assumption can be seen here or here and many other places.

The following "To reproduce" section shows just one aspect of this wrong assumption.

To reproduce

  1. Save main.py as follows.
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route, Mount


class Bar:
    async def __call__(self, scope, receive, send):
        await PlainTextResponse(f"bar {scope['root_path']=} {scope['path']=}")(scope, receive, send)

class FooBar:
    async def __call__(self, scope, receive, send):
        await PlainTextResponse(f"foobar {scope['root_path']=} {scope['path']=}")(scope, receive, send)

routes =[
    Route('/bar', endpoint=Bar()),
    Mount('/foo', routes=[
        Route('/bar', endpoint=FooBar())
    ])
]
app = Starlette(routes=routes)
  1. Run uvicorn main:app --port 8000 --root-path /foo
  2. Access by curl http://localhost:8000/foo/bar

Expected behavior

Receives

bar scope['root_path']='/foo' scope['path']='/foo/bar'

Actual behavior

Receives

foobar scope['root_path']='/foo/foo' scope['path']='/bar'

Some points here are,

  1. scope['path'] does not include scope['root_path'] , which is ASGI incompatible.
  2. FooBar ASGI handler is called. It does not take scope['root_path'] into account when routing.

Environment

  • OS: Linux
  • Python version: 3.8.2
  • Starlette version: 0.17.0

Additional context

This may be a root cause of this issue in fastapi

silane avatar Nov 15 '21 05:11 silane

Hello @Kludex :wave: I would be interested to help tackling this issue; if you need a hand, let me know :)

frankie567 avatar Jul 22 '22 09:07 frankie567

Take in consideration that I'll need a lot of references to follow on this, and to understand the implications for our users.

But yeah, help is appreciated. 🙏

Kludex avatar Jul 22 '22 09:07 Kludex