I am trying to set up an app which uses django2.0.2 and channels2.1.1. What I would like to achieve is using a background/worker task to perform some work that will produce data, that should dynamically appear on the website. My problem, related primarily to channels, is: how do I correctly establish communication between the worker, and the consumer connected to a websocket?
Below is a minimal example highlighting the issue: The idea is that the user triggers the worker, the worker produces some data and sends it, via the channel layer, to a consumer that is connected to the websocket.
#routing.py
from channels.routing import ChannelNameRouter, ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from django.urls import path
from testApp.consumers import *
application = ProtocolTypeRouter({
"websocket":AuthMiddlewareStack(
URLRouter([
path("wspath",TestConsumer),
]),
),
"channel":ChannelNameRouter({
"test_worker": TestWorker,
}),
})
The consumers:
#consumers.py
from channels.consumer import SyncConsumer
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
class TestConsumer(WebsocketConsumer):
def websocket_connect(self,message):
async_to_sync(self.channel_layer.group_add)("testGroup",self.channel_name)
self.connect()
#I understand this next part is a bit weird, but I figured it
#is the most concise way to explain my problem
async_to_sync(self.channel_layer.group_send)(
"testGroup",
{
'type':"echo_msg",
'msg':"sent from WebsocketConsumer",
})
def echo_msg(self, message):
print("Message to WebsocketConsumer", message)
class TestWorker(SyncConsumer):
def triggerWorker(self, message):
async_to_sync(self.channel_layer.group_add)("testGroup",self.channel_name)
async_to_sync(self.channel_layer.group_send)(
"testGroup",
{
'type':"echo_msg",
'msg':"sent from worker",
})
def echo_msg(self, message):
print("Message to worker ", message)
The view
#views.py
from django.shortcuts import render
import channels.layers
from asgiref.sync import async_to_sync
def index(request):
if request.method == "POST":
channel_layer = channels.layers.get_channel_layer()
async_to_sync(channel_layer.send)('test_worker',{
'type':'triggerWorker',
})
return render(
request,
"index.html",
{})
and the html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script>
console.log('ws://' + window.location.host)
var socket = new WebSocket(
'ws://' + window.location.host + "/wspath"
);
</script>
</head>
<div>Click to run worker</div>
<body>
<form action="" method="POST">
{% csrf_token %}
<button type="submit">Start</button>
</form>
</body>
Now, when I run this by executing (in separate consoles)
python3 manage.py runserver
and
python3 manage.py runworker test_worker
and then trigger the worker, the runserver console outputs:
Message to WebsocketConsumer {'type': 'echo_msg', 'msg': 'sent from WebsocketConsumer'}
where as the runworker console outputs:
Message to worker {'type': 'echo_msg', 'msg': 'sent from worker'}
Message to worker {'type': 'echo_msg', 'msg': 'sent from WebsocketConsumer'}
So I can send stuff worker -> worker, WebsocketConsumer -> WebsocketConsumer, WebsocketConsumer -> worker.
From what I understand (which is evidently wrong) there should have also been a message worker -> WebsocketConsumer, because I added both to the "testGroup".
So, my question is why didn't the WebsocketConsumer receive anything from the worker (which is what I am interested in, to eventually establish communication with the javaScript)? Or, in other words, why can I only send stuff from the WebsocketConsumer to the worker, and not vice versa?
I'm running your code. I can see everything is working.
You have that initial message going on POST - that is working - you add it to the group. When the websocket connects it sends a message to the worker. You can see that being received in your runworker terminal. That is bouncing it back to your consumer and is being printed out in the terminal where you run runserver. To get it back to your web browser you need to write:
def echo_msg(self, message):
print("Message to WebsocketConsumer", message)
self.send(json.dumps(message))
Open your developer tools in Chrome to see it coming back. Go to networking > select the websocket connection > then click frames.
BTW You don't need to add test worker to the same group over and over again. The worker is always running.
Another BTW: If your groups are going to be named the same for all users, you don't need to add your worker to a group. You can send messages directly to your worker by it's route name (send instead of group_send). The worker can send messages back to the group without it being added to the group. You only need to add the websocket consumers to groups.
Also, if you don't want multiple users seeing the same messages you don't need groups at all. Just send the messages to the worker with a channel name (self.channel_name) to send it back to.
Also you'll probably want to work with json consumers rather than parsing the messages yourself but that is up to you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With