简体   繁体   中英

Django Channels. How to respond to a WebSocket open request with a subprotocol?

In JavaScript a browser can specify a sub-protocol as the second parameter in a WebSocket creation:

socket=new WebSocket(url, subprotocol)

Experimenting with Chrome, this is correctly sent to the server as a Sec-WebSocket-Protocol element in the header.

Using Django channels, a simple consumer

def ws_add(message):
    message.reply_channel.send({"accept": True,})

gives the error

WebSocket connection to 'xxx' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received.

What is the correct way to accept that connection request in Django channels' ws_add function?

You have to specify the subprotocol to use in the websocket.accept message. For example, if you subclass channels.generic.websocket.WebsocketConsumer (also works with SyncConsumer ) and using a Sec-WebSocket-Protocol of my-protocol :

class MyProtocolConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        self.base_send({"type": "websocket.accept", "subprotocol": "my-protocol"})

I was having the same problem. The Websocket spec says that if a client asks for a subprotocol then the server must respond to let the client know it supports it. In my case the subprotocol was "graphql-ws"

After digging around in the graphene code it eventually transpired that it is a simple case of adding the following to the settings:

CHANNELS_WS_PROTOCOLS = ["graphql-ws"]

So, just replace the list of protocols with whatever you want to support. Of course once you've done this you actually need to then implement the subprotocol on the server.

If anybody stumbles upon this issue now like I did, here is how I solved it:

You need to add the websocket protocol you are using on the frontend (Token in my case) as the subprotocol parameter when you call self.accept() on the connect method of your consumer like this:

This is how I create a websocket connection on the frontend:

const websocket = new WebSocket('ws://127.0.0.1:8000/ws/', ['Token', 'user_secret_token'])

This is how my consumers.py looks:

class MyConsumer(JsonWebsocketConsumer):
    def connect(self):
        self.room_group_name = 'example_room'

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

        # incorrect
        # self.accept()

        # correct
        self.accept('Token')

Django Channels 2.4.0 Django 3.1.2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM