Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions channels/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from django.urls import Resolver404


class RequestAborted(Exception):
"""
Raised when the incoming request tells us it's aborted partway through
Expand Down Expand Up @@ -63,3 +66,12 @@ class StopConsumer(Exception):
"""

pass


class RouterResolver404(Resolver404, ValueError):
"""
Raised when a router cannot resolve a path.
Backwards compatible, in future ValueError will be deprecated in favor of Resolver404
"""

pass
10 changes: 6 additions & 4 deletions channels/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from django.urls.exceptions import Resolver404
from django.urls.resolvers import RegexPattern, RoutePattern, URLResolver

from channels.exceptions import RouterResolver404

"""
All Routing instances inside this file are also valid ASGI applications - with
new Channels routing, whatever you end up with as the top level object is just
Expand Down Expand Up @@ -99,7 +101,7 @@ async def __call__(self, scope, receive, send):
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)
raise RouterResolver404("No route found for path %r." % path)
path = path[len(root_path) :]

# Remove leading / to match Django's handling
Expand Down Expand Up @@ -127,13 +129,13 @@ async def __call__(self, scope, receive, send):
receive,
send,
)
except Resolver404:
except RouterResolver404:
pass
else:
if "path_remaining" in scope:
raise Resolver404("No route found for path %r." % path)
raise RouterResolver404("No route found for path %r." % path)
# We are the outermost URLRouter
raise ValueError("No route found for path %r." % path)
raise RouterResolver404("No route found for path %r." % path)


class ChannelNameRouter:
Expand Down
24 changes: 24 additions & 0 deletions tests/test_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.urls import path, re_path

from channels.exceptions import RouterResolver404
from channels.routing import ChannelNameRouter, ProtocolTypeRouter, URLRouter


Expand Down Expand Up @@ -312,3 +313,26 @@ def test_invalid_routes():
URLRouter([path("", include([]))])

assert "include() is not supported in URLRouter." in str(exc)


@pytest.mark.asyncio
async def test_non_existent_route_returns_not_found():
"""
Test URLRouter route validation on non existent routes
"""
router = URLRouter([path("ws/existing", MockApplication(return_value=1))])

with pytest.raises(RouterResolver404):
await router(
{"type": "websocket", "path": "/", "root_path": "/"},
None,
None,
)

expected_result = await router(
{"type": "websocket", "path": "/ws/existing"},
None,
None,
)
assert expected_result == 1
# todo check nested routing