[django-channels] How to close websocket connection from server?

2,686 views
Skip to first unread message

krnrrr

unread,
Apr 6, 2020, 6:06:59 AM4/6/20
to Django users
we have django 2.0.4 and channels 2.1.7 in production.
And now users must have only open 1 session allowed. (when a user logs in the second time, the first open websocket connection must be closed). I'm out of ideas on how to do it.

AFAIK, `websocket.disconnect` must be sent from the client, and it is not what I want (what's the point to tell the client "hey, tell me that you want to disconnect, because I say so..." if I can close it myself?). I've read this chapter and the related issue in Github and also would like to see a code snippet how to do this.

I also read about a similar issue (54518717 at SO) which is about a "kick out" user from a specific group. but what if I need to kick him out of all his possible groups/channels?

i noticed, that after a user logs in ha connects to a bunch of groups:

1) "asgi::group:room__36"         <--- group for his UserGroup(36)
2) "asgi::group:238"              <--- his personal group, for User(238)
3) "asgi::group:notifications.36" <--- another group for his UserGroup(36)

so, one group can be discarded as a whole, and from others... he must be ZREM'ed somehow?

So, any help or direction would be very appreciated.
Thank you.

krnrrr

unread,
Apr 6, 2020, 6:08:59 AM4/6/20
to Django users
or maybe any example of "superuser with groups: he can list all connections and close them if he likes"-type of application. All i found was some types of chats, but none of them included some kind of "chat-admin" with management of the rooms and visitors

Roger Gammans

unread,
Apr 6, 2020, 6:36:46 AM4/6/20
to django...@googlegroups.com
Don't you just have to send a message to an appropriate group which contains just that users/sessions connections/consumers and in the message handler on the consumer call 'self.close()' and remove the consumer from any subscriptions. 

If I understand the docs, in the disconnect method is still called so can do it there, and should be anyway. (Although it doesn't hurt to do it twice group_discard() is defined a s NOOP if the consumer's unique channel isn't in the group)

Or have I missed something,?

On Mon, 2020-04-06 at 03:08 -0700, krnrrr wrote:
or maybe any example of "superuser with groups: he can list all connections and close them if he likes"-type of application. All i found was some types of chats, but none of them included some kind of "chat-admin" with management of the rooms and visitors

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/ad5f38cd-0d4e-4187-a2c2-d092df869f8e%40googlegroups.com.

krnrrr

unread,
Apr 6, 2020, 7:07:32 AM4/6/20
to Django users
well, how I see it:

- a user can subscribe to any amount of groups during the lifecycle of his JWT
- how can I get a list these groups in the first place? so to be able to "send a message to an appropriate group"?

I thought there's a way to decode JWT, get user ID from it and then somehow:

channels = get_channel_layer():
for ws in channels.get_connections_for_user(user_id):
    ws.close()


but i couldn't find a way

Roger Gammans

unread,
Apr 6, 2020, 7:28:44 AM4/6/20
to django...@googlegroups.com
I think you are looking at it from the wrong direction.

Think about it in terms of consumers/connections, not in terms of users,and in terms of send receiving events.

When in your consumers connect method, you subscribe (eg self.channel_layer.group_add() ) to add appropriate group to send user global messages. One of those messages is close. Then you just need to send the close message, and make sure you have code to close the connection at each subscriber. The group doesn't matter you had use any which doesn't conflict, you might even be able to use one of your exisiting groups. Although are they the Redis names? I would avoid delving under the hood with channel_layers and poking redis directly - unless your creating you own keys.

The channel dos say you should be able to get the users out of self.scope, exactly where will depend on your auth middleware, but probably self.scope['user']

self.channel_layer.group_send(group, {type: 'close'}) , almost does the same as your loop to all the consumers in the group, but it might be better to call your own sub method (if for no other reason than close() doesn't normally take a event object as a argument)
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.

krnrrr

unread,
Apr 6, 2020, 8:36:59 AM4/6/20
to Django users
thank you for the help.
I need to try it out. i think it's more down the "tell-clients-to-tell-me-to-close" route which i wanted to escape.

however, i still don't understand how i can get ALL connections of a user knowing only his token.

i mean, when i have an event in the system, i want to notify a user about - i KNOW which group I must send it to, so it's up to client to consume this message. I did all I should - sent it. And now it's like vice versa - I must learn which group to send message to, knowing only a recipient ID.

I imagined it as like in django admin view there could be a page at UserAdmin: "close all websockets for this user" - in such case we have a manage.py command, or any random view which knows nothing about clients-communicatiors. only about a user, whose connections must be closed.

maybe, you're right, and i'm thinking about it the wrong way....

Roger Gammans

unread,
Apr 6, 2020, 9:57:31 AM4/6/20
to django...@googlegroups.com
You don't have to send the messages all the way to the client (browser), you can handle them directly in the consumer still on the serverside.

As far as I know you'll have to keep track of metadata like all users connections yourself, but you have the connect(), disconnect() method pair to put your tracking code into.
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.

krnrrr

unread,
Apr 19, 2020, 11:54:58 AM4/19/20
to Django users
i gave it another shot and still don't get it.

when I connect the first time i have:

- browser A which serves as a source of "connect" command
- AsyncJsonWebsocketConsumer which handles group_add, accepting incoming socket and setting the "connection A"

when I have a call from the other browser:

- browser B which sends another "connect"
- another consumer. how does it know about "connection A" to close it? it handles it's own connection B

and all the time browser is the initiator of commands. I still don't understand what call i should make when I return a newly create JWT to close connection A.

i still don't get it. I saw this code and this in the sources, but how shou;d a layer know which of thousands websockets to close? I tried to send

async_to_sync(channel_layer.group_discard)("notifications.36", "specific.WrFJFDXq!rIkVZzwQtBCC")

async_to_sync(channel_layer.group_send)("notifications.36", {"type": "websocket_close"})

and neither of these calls succeeded - browser still receives messages sent from notification (the last one succeeds actually, but it closes room for ALL participants, not to specific one. and they promptly reconnect). And I'm not surprised, when the call is made from specific ws consumer, the channel layers knows what to close (as far as I understand). and when it's done simply from the shell...

krnrrr

unread,
Apr 19, 2020, 1:31:34 PM4/19/20
to Django users
another thing I managed to think of is send specific message to a specific room of a user, like:

async_to_sync(channel_layer.group_send)("238", {"type": "send_text", "text": "close"})

and check in consumer's handler:

    async def send_text(self, event):
        if event == "close":
            await self.send(text_data="closed", close=True)

or 

async_to_sync(channel_layer.group_discard)("room__36", "specific.aEuecdIR!DTiuBsxYfldN")

when we want to drop him from one specific room and it requires to keep his channel name in some persistent storage.
Reply all
Reply to author
Forward
0 new messages