channels
channels copied to clipboard
Document long polling
It looks like channel handle long polling via ResponseLater exception but no test or documentation on how to retreive connexion to send data back and close it.
I supose we may use channels.handler.ViewConsumer the same way we would use a WebsocketConsumer, but I didn't find this info in any doc.
I'm testing ResponseLater for one of my views but can't make it work. I have some middleware, and it seems that any middleware will always force a HttpResponse. The ResponseLater exception won't reach the Channels code (in AsgiHandler) ignoring this specific Exception, it will get a HttpResponse object with HTML content describing the error.
The AsgiHandler wraps BaseHandler.get_response() in a try/except and then ignores the ResponseLater exception. But if my view raises the ResponseLater, it is converted to a HttpResponse before it reaches the AsgiHandler. I'm still using MIDDLEWARE_CLASSES, but I'm not sure if this is the reason.
The channels/handler.AsgiHandler relies uses BaseHandler.load_middleware(), which wraps a convert_exception_to_response
around all middleware and around get_response. If this is not somehow monkeypatched the exception won't be bubbled up.
In channels < 0.8 the ResponseLater exception thrown by a Django View was supported through monkeypatching in channels/hacks.py . This was removed in commit #b9464ca149a4f66ebeb8801254cdd6d1b12cd58d . Restoring it doesn't help me either.
Is ResponseLater still supported?
Another design could be to annotate the Response object with an attribute which signifies the ResponseLater intent. Like:
def my_view(request):
response = HttpResponse("", status=200)
response._channels_response_later = True
return response
The channels/handler.AsgiHandler should then check for this attribute. This will work as long as the middleware does not replace the response object, but at least it's a working option.
Yes, ResponseLater does not really interact well with any middleware. It's been replaced in Channels 2 by async HTTP views instead, so you can await
, but of course you give up old-style middleware entirely (channels 2 allows for ASGI middleware, which I need to document, and which works quite like current Django middleware but allows for async stuff)
@andrewgodwin Thanks for your reply. I suspect Channels 2 async requires python3. I would love to have some mechanism supported with middleware to skip responses, at least until we all can move to Channels 2. I think status code 100 (Continue) or some custom header would be more suitable for decorating the response. I've created pull-request #803
Some background; some of my Django views use django database/session data to setup a http-request to a background service (http://example.com/user={request.user.username}
), block and wait only to return the result-content when available. This blocks my workers (which somehow end up using a lot of memory). I'm trying to offload these http-proxy requests to an asyncio worker, which posts the result in the http-response reply-channel. I'll try to share this at https://github.com/ivorbosloper/channels_async so there's an example for using this feature.
Hi @ivorbosloper,
Probably you choose wrong place to hold a request. You shouldn't block channels worker with any kind of waiting process.
Maybe my example can give you some inspiration. Here https://bitbucket.org/proofit404/algont I setup MJPEG stream which stays on Daphne and worker handler lifetime is about few milliseconds to send one frame to that stream.
Thanks @proofit404 , I hadn't found a nice example of how to plug in on interface-worker level before. The point for this fix is to allow me to use existing Django view logic (middleware, orm queries, stuff all Django developers work with) to define a task, and then handle this task asynchronously in an asyncio loop for scaling (celery would fit but doesn't run in an asyncio loop) and then send the result in the http-reply channel.
By plugging in on Django-view level, existing code is reusable and the impact for developers (the API) is minimal/very simple. If you plugin at Channel Message level, you have to (re)write logic to get a session/do database requests, and send some custom response, all in Channel/message semantics.
My main concern about solution suggested by you, that you're trying to store state internally in the worker process. While Channels 1.0 is a synchronous solution all the way down to the message handler in the worker instance.
I think something different can feet:
For example, we have three points in code:
@channel_session
def handle_long_polling_request(message):
try:
request = apply_django_middlewares(make_asgi_request(message))
except Unathorized:
message.reply_channel.send({'status_code': 401})
return
clone_session(request.session, to=channel_session)
channel_session['user_id'] = request.user.pk
store_reply_channel_someway() # for example include it in the Group
# Do not send anithing to user, request will be hold by Daphne, worker will process next message.
@channel_session
def send_polling_response(message):
Channel(stored_reply_channel).send(compose_long_polling_responce(message))
# Daphne will reply to the waiting client, worker will process next message. No blocking at all.
# In the other system part we detect that interesting event
# happened and send it in a message to the long polling
# response handler.
I'm going to leave this open as we still need a good example for channels 2, but it will be easier to do now you can write it as an async consumer (might be good to get #841 done first)
I notice that we don't yet have an async WebsocketConsumer or an async JsonWebsocketConsumer. Would be benificial for applications that need to do a longpoll in response to a websocket noticification.
I also noticed that a few hours ago and so I committed them in: https://github.com/django/channels/commit/de82aaa660e85af6d1945b445bb6ade4896e2f50
They'll be in the next release :)