From 82c26f2585bca8b76d8f72f4a971095c8f899606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20R=2E=20Sede=C3=B1o?= Date: Wed, 3 Apr 2024 09:02:44 -0400 Subject: [PATCH] Fixed URLRouter root_path handling. (#1954) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add tests for handling root_path in URLRouting * Handle root_path in URLRouter If we have a scope["root_path"], raise if it does not prefix scope["path"], and strip it from scope["path"] if it does. Do this only if we're in an outermost URLRouter. Signed-off-by: Alejandro R. Sedeño Signed-off-by: Alejandro R Sedeño --- channels/routing.py | 9 +++++++++ tests/test_routing.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/channels/routing.py b/channels/routing.py index 07b5bab7..d09d5ffe 100644 --- a/channels/routing.py +++ b/channels/routing.py @@ -107,6 +107,15 @@ async def __call__(self, scope, receive, send): path = scope.get("path_remaining", scope.get("path", None)) if path is None: raise ValueError("No 'path' key in connection scope, cannot route URLs") + + if "path_remaining" not in scope: + # We are the outermost URLRouter, so handle root_path if present. + root_path = scope.get("root_path", "") + if root_path and not path.startswith(root_path): + # If root_path is present, path must start with it. + raise ValueError("No route found for path %r." % path) + path = path[len(root_path) :] + # Remove leading / to match Django's handling path = path.lstrip("/") # Run through the routes we have until one matches diff --git a/tests/test_routing.py b/tests/test_routing.py index b41f652a..99c76790 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -107,6 +107,23 @@ async def test_url_router(): "args": tuple(), "kwargs": {"default": 42}, } + # Valid root_path in scope + assert ( + await router( + {"type": "http", "path": "/root/", "root_path": "/root"}, None, None + ) + == 1 + ) + assert ( + await router( + {"type": "http", "path": "/root/foo/", "root_path": "/root"}, None, None + ) + == 2 + ) + + # Unmatched root_path in scope + with pytest.raises(ValueError): + await router({"type": "http", "path": "/", "root_path": "/root"}, None, None) # Invalid matches with pytest.raises(ValueError): @@ -261,6 +278,29 @@ async def test_path_remaining(): == 2 ) + assert ( + await outermost_router( + {"type": "http", "path": "/root/prefix/stuff/", "root_path": "/root"}, + None, + None, + ) + == 2 + ) + + with pytest.raises(ValueError): + await outermost_router( + {"type": "http", "path": "/root/root/prefix/stuff/", "root_path": "/root"}, + None, + None, + ) + + with pytest.raises(ValueError): + await outermost_router( + {"type": "http", "path": "/root/prefix/root/stuff/", "root_path": "/root"}, + None, + None, + ) + def test_invalid_routes(): """