Skip to content

Commit

Permalink
Option to disable routing in ASGIApp (Fixes #345)
Browse files Browse the repository at this point in the history
  • Loading branch information
rodja authored and miguelgrinberg committed Feb 9, 2024
1 parent 159361f commit 1dbd573
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 10 deletions.
27 changes: 17 additions & 10 deletions src/engineio/async_drivers/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ class ASGIApp:
:param other_asgi_app: A separate ASGI app that receives all other traffic.
:param engineio_path: The endpoint where the Engine.IO application should
be installed. The default value is appropriate for
most cases.
most cases. With a value of ``None``, all incoming
traffic is directed to the Engine.IO server, with the
assumption that routing, if necessary, is handled by
a different layer. When this option is set to
``None``, ``static_files`` and ``other_asgi_app`` are
ignored.
:param on_startup: function to be called on application startup; can be
coroutine
:param on_shutdown: function to be called on application shutdown; can be
Expand All @@ -44,24 +49,26 @@ def __init__(self, engineio_server, other_asgi_app=None,
self.engineio_server = engineio_server
self.other_asgi_app = other_asgi_app
self.engineio_path = engineio_path
if not self.engineio_path.startswith('/'):
self.engineio_path = '/' + self.engineio_path
if not self.engineio_path.endswith('/'):
self.engineio_path += '/'
if self.engineio_path is not None:
if not self.engineio_path.startswith('/'):
self.engineio_path = '/' + self.engineio_path
if not self.engineio_path.endswith('/'):
self.engineio_path += '/'
self.static_files = static_files or {}
self.on_startup = on_startup
self.on_shutdown = on_shutdown

async def __call__(self, scope, receive, send):
if scope['type'] in ['http', 'websocket'] and \
scope['path'].startswith(self.engineio_path):
if scope['type'] == 'lifespan':
await self.lifespan(scope, receive, send)
elif scope['type'] in ['http', 'websocket'] and (
self.engineio_path is None
or scope['path'].startswith(self.engineio_path)):
await self.engineio_server.handle_request(scope, receive, send)
else:
static_file = get_static_file(scope['path'], self.static_files) \
if scope['type'] == 'http' and self.static_files else None
if scope['type'] == 'lifespan':
await self.lifespan(scope, receive, send)
elif static_file and os.path.exists(static_file['filename']):
if static_file and os.path.exists(static_file['filename']):
await self.serve_static_file(static_file, receive, send)
elif self.other_asgi_app is not None:
await self.other_asgi_app(scope, receive, send)
Expand Down
27 changes: 27 additions & 0 deletions tests/async/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,30 @@ def test_make_response_websocket_reject_no_payload(self):
environ['asgi.send'].mock.assert_called_with(
{'type': 'websocket.close'}
)

def test_sub_app_routing(self):

class ASGIDispatcher:
def __init__(self, routes):
self.routes = routes

async def __call__(self, scope, receive, send):
path = scope['path']
for prefix, app in self.routes.items():
if path.startswith(prefix):
await app(scope, receive, send)
return
assert False, 'No route found'

other_app = AsyncMock()
mock_server = mock.MagicMock()
mock_server.handle_request = AsyncMock()
eio_app = async_asgi.ASGIApp(mock_server, engineio_path=None)
root_app = ASGIDispatcher({'/foo': other_app, '/eio': eio_app})
scope = {'type': 'http', 'path': '/foo/bar'}
_run(root_app(scope, 'receive', 'send'))
other_app.mock.assert_called_once_with(scope, 'receive', 'send')
scope = {'type': 'http', 'path': '/eio/'}
_run(root_app(scope, 'receive', 'send'))
eio_app.engineio_server.handle_request.mock.assert_called_once_with(
scope, 'receive', 'send')

0 comments on commit 1dbd573

Please sign in to comment.