My Falcon based ASGI app is executed via Daphne and works fine when it is run locally and accessed via localhost. The app is packaged in a container and run in a K8s cluster behind an Ingress. On K8s the app is not running at the root of the domain, but in a /sub/folder. This breaks the routing of Falcon. I could not find an existing middleware for this, so I implemented one:
class StripRootPath:
async def process_request(self, req, resp):
root_path = req.root_path
if root_path:
req.path = req.path[len(root_path) :]
This works fine if I call Daphne like this
daphne --root-path /sub/path ...
and as long as I'm only using "normal" HTTP requests. Working with websockets fails. The error messages suggested that paths could not be found, so I assumed that my stripping of the root path did not work. I figured out that I was right. There is a special
async def process_request_ws(self, req, ws):
for websocket connections. I tried to implement this method exactly as process_request
but req.root_path
is empty. I did not find any value in req
that would allow me to strip the prefix.
Now I wonder if I'm doing something completely wrong or if this is a bug and root_path
is supposed to be set!?
This seems to be a bug in Daphne, the request scope for WebSocket does not contain the root_path
key/value.
process_request_ws
worked fine to redirect internally by modifying req.path
, however, I simply hardcoded the prefix in my proof-of-concept test.py
:
import falcon
import falcon.asgi
class InternalRedirect:
async def process_request(self, req, resp):
print(f'req.path={req.path}; req.root_path={req.root_path}')
if req.path.startswith('/root/'):
req.path = req.path.split('/root', 1)[1]
async def process_request_ws(self, req, ws):
print(f'req.path={req.path}; req.root_path={req.root_path}')
if req.path.startswith('/root/'):
req.path = req.path.split('/root', 1)[1]
class HelloResource:
async def on_get(self, req, resp, name):
resp.media = {'message': f'Hello, {name}!'}
async def on_websocket(self, req, ws, name):
try:
await ws.accept()
await ws.send_media({'message': f'Hello, {name}'})
while True:
payload = await ws.receive_text()
print(f'Received: [{payload}])')
except falcon.WebSocketDisconnected:
pass
app = falcon.asgi.App(middleware=[InternalRedirect()])
app.add_route('/hello/{name}', HelloResource())
I tried running the same app in Uvicorn, and root_path
is present both for ordinary GET
requests, and when upgraded to a WebSocket:
$ uvicorn --root-path /root test:app
...
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
req.path=/root/hello/robot; req.root_path=/root
INFO: ('127.0.0.1', 33776) - "WebSocket /root/root/hello/robot" [accepted]
INFO: connection open
INFO: connection closed
OTOH, even Uvicorn's log illustrates (NB the double /root/root
) the confusion around root_path
in ASGI; contrary to WSGI's SCRIPT_NAME
, it is unclear whether ASGI path
should include it or not.
See also:
Despite the confusion, Uvicorn should solve your problem at hand. Moreover, it's one the most popular and performant ASGI app servers out there, so my suggested solution is to simply try swapping out Daphne to Uvicorn.