Menu Close

Playing with Django-Channels

Django-channels is a library touted to bring websockets to django in an incredibly simple / easy-to-implement fashion. I’ve listened to presentations, read some documentation and started exploring the code samples. One of the very first example apps provided by the project maintainers is a chat application – it’s a rudimentary implementation of live chat for authenticated users. It works well, but it’s far from feature-rich. To deep-dive and learn more about django-channels, I wanted to make the app more feature rich by implementing a “user count”. One would think this should be simple – just count the number of open websocket connections associated with a given chat room! And while the premise is simple, the implementation doesn’t really exist yet.

I researched the issue and submitted a ticket on the GitHub project here: https://github.com/django/channels/issues/388

Currently, retrieving this count requires getting the RedisChannelLayer instance and asking for all of the key/value pairs from the Group zset, then getting the length of the returned list:

len(Room.objects.first().websocket_group.channel_layer.channel_layer.group_channels('room-1'))

This also works:

    from channels import channel_layers
    len(channel_layers.backends['default'].channel_layer.group_channels('room-1'))

It feels dirty to retrieve all the data when you just need the count. When using redis as a backend, we can issue the zcard command to retrieve row count for a given zset, i.e:

zcard asgi::group:room-1

Redis Screenshot

My knowledge/research falls short at this point. Could we extend Group class this enhancement be generic enough to implement for all backends? The hacky way can be implemented easily:

class Group(object):
    """
    A group of channels that can be messaged at once, and that expire out
    of the group after an expiry time (keep re-adding to keep them in).
    """
    def __len__(self):
        return len(self.channel_layer.group_channels(self.name))

    def count(self):
        return self.__len__()

We can use the native connection in a backend-specific (`asgi_redis.core.RedisChannelLayer`) fashion:

    from channels import channel_layers
    rcl = channel_layers.backends['default'].channel_layer.group_channels.__self__
    rcl.connection(0).zcard('asgi::group:room-1')

 4,284 total views,  1 views today

Leave a Reply

Your email address will not be published. Required fields are marked *