I want to send a message over a channel using Django Channels. This is how I'm doing.
I create a consumer first. I'm able to echo back the received messages. However, not able to send messages to a specific channel/group.
class Consumer(AsyncJsonWebsocketConsumer):
"""Consumer."""
def _get_connection_id(self):
return ''.join(e for e in self.channel_name if e.isalnum())
async def connect(self):
scope = self.scope
user_id = str(scope['user'].user_id)
connection_id = self._get_connection_id()
# Adding connection to DB.
obj = UserConnection.add(connection_id=connection_id, user_id=user_id)
# Accept the connection
await self.accept()
# Adding current to group.
await self.channel_layer.group_add(
user_id,
connection_id,
)
async def disconnect(self, close_code):
"""Remove the connection and decrement connection_count in DB."""
connection_id = self._get_connection_id()
user_id = str(self.scope['user'].user_id)
UserConnection.drop(connection_id=connection_id)
# Dropping from group.
await self.channel_layer.group_discard(
user_id,
connection_id,
)
async def receive_json(self, data, **kwargs):
"""Receive messages over socket."""
resp = data
# I'm able to echo back the received message after some processing.
await self.send(json.dumps(resp, default=str))
# This does not works.
def send_to_connection(connection_id, data):
"""Send the data to the connected socket id."""
return get_channel_layer().group_send(connection_id, data)
Now when I try to send a message, the connected socket does not receives the message.
>>> connection_id = UserConnection.objects.get(user_id=user_id).connection_id
>>> send_to_connection(connection_id, {'a':1})
# returns <coroutine object RedisChannelLayer.group_send at 0x109576d40>
What's the issue in the code?
There is a bit of a misunderstanding on how Channel layers work. Let me try to clear it up. When a client connects to the Channels server, a consumer instance or channel is created for that client. If you add the channel to a group, Django Channels stores that information in the channel layer. If you want to send a message to all clients in a group, first you send it to their connections/channels via the channel layer, then the channels will send it downstream to the connected clients.
So, in your case, when you call group_send
, it is not sending the message to the client application as it has no information about the websocket connection, but to the client application's consumer instance. That consumer instance then needs to pick up the message and forward it to the client.
Following the example in the docs, this is what you need to do:
async def receive_json(self, data, **kwargs):
"""Receive messages over socket."""
resp = data
# I'm able to echo back the received message after some processing.
await self.send(json.dumps(resp, default=str))
# catches group messages from channel layer and forwards downstream to client
async def forward_group_message(self, event):
await self.send(json.dumps(event['data'], default=str))
# Sends message to all channels in a group cia the channel layer
def send_to_connection(connection_id, data):
"""Send the data to the connected socket id."""
return get_channel_layer().group_send(
connection_id,
{"type": "forward_group_message", "data": data}
)
Note the type
key in the event you're sending to the channel layer. That is how Django Channel knows which method/handler of the consumer to route the channel layer event to. You can also use the dot notation employed in the docs and Django Channels will still find the handler. So you can use "type": "forward.group.message"