Skip to content

Commit

Permalink
Make handling of 'root_path' in dispatcher middleware adhere to ASGI …
Browse files Browse the repository at this point in the history
…spec

Before this change, the dispatcher middleware didn't do anything with
'root_path'. It did, however, modify 'path'.

The problem with this behavior, is that the child ASGI app has no way to
determine what its prefix it. And if it can't determine its prefix, it
doesn't know how to construct URLs.

The ASGI spec isn't super clear on the expected behavior. However, some
resources to review are:

* https://asgi.readthedocs.io/en/latest/specs/www.html#wsgi-compatibility
* encode/starlette#2400
* django/asgiref#229

Based on the above, I believe that the correct behavior is that
"root_path" should be updated by the dispatcher middleware but that
"path" should not be modified.

In addition to the above change, I also updated the tests. And I also
added a new test case where the dispatcher middleware is nested inside
of itself.
  • Loading branch information
DaGenix committed Sep 29, 2024
1 parent 84d06b8 commit fe0dbd6
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 8 deletions.
5 changes: 3 additions & 2 deletions src/hypercorn/middleware/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ async def __call__(self, scope: Scope, receive: Callable, send: Callable) -> Non
if scope["type"] == "lifespan":
await self._handle_lifespan(scope, receive, send)
else:
relative_path = scope["path"][len(scope["root_path"]):]
for path, app in self.mounts.items():
if scope["path"].startswith(path):
scope["path"] = scope["path"][len(path) :] or "/"
if relative_path.startswith(path):
scope["root_path"] += path
return await app(scope, receive, send)
await send(
{
Expand Down
23 changes: 17 additions & 6 deletions tests/middleware/test_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def __init__(self, name: str) -> None:

async def __call__(self, scope: Scope, receive: Callable, send: Callable) -> None:
scope = cast(HTTPScope, scope)
response = f"{self.name}-{scope['path']}"
response = f"{self.name}-{scope['path']}-{scope['root_path']}"
await send(
{
"type": "http.response.start",
Expand All @@ -27,7 +27,15 @@ async def __call__(self, scope: Scope, receive: Callable, send: Callable) -> Non
await send({"type": "http.response.body", "body": response.encode()})

app = AsyncioDispatcherMiddleware(
{"/api/x": EchoFramework("apix"), "/api": EchoFramework("api")}
{
"/api/x": EchoFramework("apix"),
"/api/nested": AsyncioDispatcherMiddleware(
{
"/path": EchoFramework("nested"),
}
),
"/api": EchoFramework("api"),
}
)

sent_events = []
Expand All @@ -38,12 +46,15 @@ async def send(message: dict) -> None:

await app({**http_scope, **{"path": "/api/x/b"}}, None, send) # type: ignore
await app({**http_scope, **{"path": "/api/b"}}, None, send) # type: ignore
await app({**http_scope, **{"path": "/api/nested/path/x"}}, None, send) # type: ignore
await app({**http_scope, **{"path": "/"}}, None, send) # type: ignore
assert sent_events == [
{"type": "http.response.start", "status": 200, "headers": [(b"content-length", b"7")]},
{"type": "http.response.body", "body": b"apix-/b"},
{"type": "http.response.start", "status": 200, "headers": [(b"content-length", b"6")]},
{"type": "http.response.body", "body": b"api-/b"},
{"type": "http.response.start", "status": 200, "headers": [(b"content-length", b"20")]},
{"type": "http.response.body", "body": b"apix-/api/x/b-/api/x"},
{"type": "http.response.start", "status": 200, "headers": [(b"content-length", b"15")]},
{"type": "http.response.body", "body": b"api-/api/b-/api"},
{"type": "http.response.start", "status": 200, "headers": [(b"content-length", b"42")]},
{"type": "http.response.body", "body": b"nested-/api/nested/path/x-/api/nested/path"},
{"type": "http.response.start", "status": 404, "headers": [(b"content-length", b"0")]},
{"type": "http.response.body"},
]
Expand Down

0 comments on commit fe0dbd6

Please sign in to comment.