How to send event to client from any place of server using django channels and websockets

Question:

Now I’m building a website using django and angular, And I want to add websocket communication between client and server.
I followed the instructions of django channel documentation.( https://channels.readthedocs.io/ )
I want to send any event from any place of django server code. But when multiple users connect via socket, the data sent from server always goes to the last connected user.
And here’s what I did.
First in consumers.py, I defined my ChatConsumer class as follows and added a new handler named "tweet_send" function

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        await self.accept()
    ...

    # here's what I added a new tweet send handler
    async def tweet_send(self, event):
        content = event['content']
        content["group_name"] = self.room_group_name

        # Send message to WebSocket
        await self.send(text_data=json.dumps({
            "type": "MESSAGE",
            "data": content
        }))

And somewhere of django server side(in serializers.py), when a new tweet is created, I send a new tweet data to all users using django.channel_layer

...
def create(self, validated_data):
    ...
    new_tweet.save()

    # send message via channels
    channel_layer = get_channel_layer()
    all_users = list(Member.objects.filter(is_active = True).values_list('id', flat = True))
    for user_id in all_users:
        async_to_sync(channel_layer.group_send)(
            "chat_{}".format(user_id),            # this is the group name of group name of individual users.
            { "type": "chat_message", "message": "tweet created" }
        )

And in Angular websocket service

init(userID: number): void{
  if(this.ws){
    this.ws.close();
  }

  this.ws = new WebSocket(`${environment.WEBSOCKET_URL}/chat/${userID}/`);
  this.ws.onopen = () => {
    this.currentWebSocketSubject.subscribe(data => {
      if (data) {
        this.ws?.send(JSON.stringify(data));
      }
    });
  };

  this.ws.onmessage = (event) => {
    console.log("message arrived");
    console.log(event);
    if (event.data) {
      const tempData: WS = JSON.parse(event.data) as WS;
      switch (tempData.type) {
        case 'MESSAGE':
          console.log("message arrived");
          console.log(tempData);
          if (tempData.data) {
            this.unreadMessages++;
            this.messageSubject.next(tempData);
          }
          break;

      }
    }
  };
}

When I test those codes, I used two clients, two clients logged in, and connected to the django server via websockets successfully.
When I create a tweet, django server sends event to every channel using channel_layer.group_send function, and in ChatConsumer class the added "tweet_send" function sends the event to the user.
And these events are meant to different users respectively.
But they are all sent to lastly websocket-connected user.
I don’t know why. Please help me.

Asked By: Metalgear

||

Answers:

I think you must be using channels 3.0.0 and this error occurs. I suggest you should upgrade channels version to 3.0.1 or 3.0.2.
Then the error will disappear.

Answered By: terminator

I can see this question has been here for a long time. There is one problem with your code, and that problem is that the tweet send handler in your consumer (tweet_send) is not matching the message type you’re sending to the group from your serializers.py.

So to fix this problem, you have to rename your tweet send handler method to match the message type you’re sending to the group, which in this case is chat_message. So your tweet send handler becomes:

# tweet send handler
async def chat_message(self, event):
    content = event['content']
    content["group_name"] = self.room_group_name

    # Send message to WebSocket
    await self.send(text_data=json.dumps({
        "type": "MESSAGE",
        "data": content
    }))

I hope this help.

Answered By: Algebra