The resource in an aiohttp application¶
ASGIResource is an aiohttp resource. The router keeps
it with the other resources, therefore the ASGI application gets requests from
the same server, the same event loop and the same process.
Register the resource¶
from aiohttp import web
from aiohttp_asgi import ASGIResource
asgi_resource = ASGIResource(asgi_app, root_path="/api")
aiohttp_app.router.register_resource(asgi_resource)
The resource takes a request when the request path starts with root_path.
The ASGI application gets root_path in the scope, so FastAPI and Starlette
match their routes without the prefix: with root_path="/api" the request
GET /api/users/42 reaches the route /users/42.
A path which starts with the prefix but has no ASGI route gives the answer of
the ASGI application, usually 404. The aiohttp router does not continue the
search.
Keep the registration order¶
The default root_path is /, therefore the resource takes every request.
Add the aiohttp routes before such a resource:
aiohttp_app = web.Application()
# First the aiohttp routes.
aiohttp_app.router.add_get("/ping", ping_handler)
# Then the catch-all ASGI resource.
aiohttp_app.router.register_resource(ASGIResource(asgi_app))
Warning
aiohttp reuses the last added resource when its raw_match() accepts the
path. A router.add_get() call after a resource with root_path="/" reaches
the ASGI resource and raises
AttributeError: 'ASGIResource' object has no attribute 'add_route'.
A prefix like root_path="/api" has not this problem, because raw_match()
rejects the other paths.
Send the lifespan events¶
lifespan_mount() appends a cleanup context to
the aiohttp application. The context sends lifespan.startup when the
aiohttp application starts, and lifespan.shutdown when it stops:
asgi_resource.lifespan_mount(aiohttp_app)
Without this call the ASGI application gets no lifespan events, so the startup
and shutdown handlers of FastAPI or Starlette do not run. The adapter logs the
result of each event, and waits at most
SHUTDOWN_TIMEOUT seconds for the lifespan
task after the shutdown event.
WebSockets¶
The resource makes an aiohttp WebSocket response when the request is a
WebSocket handshake, therefore an ASGI WebSocket endpoint works without
additional code:
from starlette.websockets import WebSocket, WebSocketDisconnect
@asgi_app.websocket("/ws")
async def echo(websocket: WebSocket):
await websocket.accept()
while True:
try:
await websocket.send_json(await websocket.receive_json())
except WebSocketDisconnect:
return
The adapter reads the Sec-WebSocket-Protocol header and gives the
subprotocols to the ASGI application in scope["subprotocols"].
Middlewares and signals¶
The resource is part of the aiohttp application, therefore the aiohttp
middlewares, the on_response_prepare signal and the other aiohttp
mechanisms also work for the ASGI requests. For example
normalize_path_middleware redirects to the path which the ASGI application
needs:
aiohttp_app = web.Application(
middlewares=[
web.normalize_path_middleware(remove_slash=True, append_slash=False),
],
)
What the ASGI application receives¶
The adapter builds the scope from the aiohttp request:
Scope key |
Value |
|---|---|
|
|
|
|
|
The request method |
|
The request scheme; |
|
The request path |
|
The not decoded path, as bytes |
|
The |
|
The query string, as bytes |
|
The request headers, as a list of lower case byte pairs |
|
The host and the port of the request URL |
|
The remote address of the connection, and zero as the port |
|
The WebSocket subprotocols, else |