Search code examples
djangoredisdjango-rest-frameworkdjango-channelsasgi

Django Channels consumer consuming 1 call twice


I am using a combination of DRF 3.11.0 and Channels 2.4.0 to implement a backend, and it is hosted on Heroku on 1 dyno with a Redis resource attached. I have a socket on my React frontend that successfully sends/received from the backend server.

I am having an issues where any message sent back to the front end over the socket is being sent twice. I have confirmed through console.log that the front end is only pinging the back end once. I can confirm through print() inside of the API call that the function is only calling async_to_sync(channel_layer.group_send) once as well. The issue is coming from my consumer - when I use print(self.channel_name) inside of share_document_via_videocall(), I can see that two instances with different self.channel_names are being called (specific.AOQenhTn!fUybdYEsViaP and specific.AOQenhTn!NgtWxuiHtHBw. It seems like the consumer has connected to two separate channels, but I'm not sure why. When I put print() statements in my connect() I only see it go through the connect process once.

How do I ensure that I am only connected to one channel?

in settings.py:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            #"hosts": [('127.0.0.1', 6379)],
            "hosts": [(REDIS_HOST)],
        },
    },
}

Consumer:

import json
from asgiref.sync import async_to_sync
from channels.db import database_sync_to_async

from channels.generic.websocket import AsyncWebsocketConsumer
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from .exceptions import ClientError
import datetime
from django.utils import timezone

class HeaderConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        print("connecting")

        await self.accept()
        print("starting")
        print(self.channel_name)
        await self.send("request_for_token")


    async def continue_connect(self):
        print("continuing")
        print(self.channel_name)

        await self.get_user_from_token(self.scope['token'])

        await self.channel_layer.group_add(
            "u_%d" % self.user['id'],
            self.channel_name,
        )
        #... more stuff


    async def disconnect(self, code):
        await self.channel_layer.group_discard(
            "u_%d" % self.user['id'],
            self.channel_name,
        )


    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        if 'token' in text_data_json:
            self.scope['token'] = text_data_json['token']
            await self.continue_connect()


    async def share_document_via_videocall(self, event):

        # Send a message down to the client
        print("share_document received")
        print(event)
        print(self.channel_name)
        print(self.user['id'])

        await self.send(text_data=json.dumps(
            {
                "type": event['type'],
                "message": event["message"],
            },
        ))

    @database_sync_to_async
    def get_user_from_token(self, t):
        try:
            print("trying token" + t)
            token = Token.objects.get(key=t)
            self.user = token.user.get_profile.json()
        except Token.DoesNotExist:
            print("failed")
            self.user = AnonymousUser()

REST API call:

class ShareViaVideoChat(APIView):
    permission_classes = (permissions.IsAuthenticated,)

    def post(self, request, format=None):
        data = request.data
        recipient_list = data['recipient_list']

        channel_layer = get_channel_layer()
        for u in recipient_list:
            if u['id'] != None:
                print("sending to:")
                print('u_%d' % u['id'])
                async_to_sync(channel_layer.group_send)(
                    'u_%d' % u['id'],
                    {'type': 'share_document_via_videocall',
                     'message': {
                             'document': {'data': {}},
                             'sender': {'name': 'some name'}
                         }
                     }
                )

        return Response()

Solution

  • with respect to you getting to calls with different channel names are you sure your frontend has not connected twice to the consumer? Check in the debug console in your browser.