Have a global signalling mechanism for request/response lifecycle
See original GitHub issueCore Django, Flask and some other web frameworks have a way to register a callback whenever a request is handled or a response is sent out. This registry exists globally and independently of application state. It’s invaluable to monitoring tools like Newrelic and Sentry, as their SDKs/Agents can just subscribe to signals at any point in time without requiring the user to register a middleware or really do any other kind of configuration (example). For frameworks/libraries where such signals don’t exist, there is still the possibility of monkeypatching (example). I am affiliated with Sentry, as I work on the Sentry SDK for Python.
Channels offers no possibility to safely hook into the request/response lifecycle of the ASGI application it exports. Like most frameworks it has no global signals, but the routing mechanism is left up to the user in a way that makes it very hard to wrap the outermost ASGI application even through monkeypatching. Django also allows the user to wrap the Django-exported WSGI application in arbitrarily many layers of WSGI middleware that we cannot instrument, but Channels makes it a common pattern for the user to use ASGI middleware while it’s uncommon enough in Django to ignore the issue there.
What I would like to see is the following:
- Modify
channels.routing.get_default_application()
to wrap the discovered ASGI application in a middlewareChannelsApp
that does nothing but forward to the inner application. This already allows us to monkeypatch to achieve our goal. - (optional) Add new global signals that are emitted by this middleware. We are fine with using internal APIs to instrument channels, but official APIs are always better.
Even just the first change enables Sentry and similar tools to hook into channels before Django settings have been loaded. This point is crucial because:
- Sentry’s SDK is initialized as part of loading settings in the first place
- NewRelic’s Agent attempts to register signals and monkeypatch shortly after process startup
The status-quo solution to this problem is to do one of the following:
-
Patch
AsgiHandler
exclusively if you only care about HTTP -
Patch
ProtocolTypeRouter
under the assumption that most people use this type to wrap everything else -
Use some sort of trampoline to look into Django settings to discover
ASGI_APPLICATION
, and then patch that object. This opens two more choices:- Patch Django to know when settings are loaded
- Do it on the first request where you can be sure that settings are loaded
This is a bit involved because
ASGI_APPLICATION
can be any kind of object that might be callable without providing you a settable__call__
. For example it might be a primitive function, in which case patchingfunc_code
/__code__
might do the trick.
Would love to know what you think about this. I am happy to prepare the patch for this.
Django’s upcoming ASGI support seems to do much better in this regard, as there only ASGIHandler
needs to be patched.
Issue Analytics
- State:
- Created 4 years ago
- Comments:12 (6 by maintainers)
OK, I understand.
In that case, no, this isn’t something I’d want to add. Given the choice between explicitly wrapping the ASGI application or opaquely monkey patching in, I would always opt for the former. I think we do no-one any real favours otherwise.
Explicit beats implicit, and ASGI’s one obvious way to do things is wrapping applications in this manner.
Sorry.
The third choice is to have global signals like Flask. I mentioned this possibility four times now. Honestly it’s frustrating to engage with someone who doesn’t bother to read anything that I write (and doesn’t ask for clarification either in case my comments were too vague).
Be that as it may, Django 3 will have the signals we need, so I guess time will solve this.