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 :)