How to set the "Sec-WebSocket-Protocol" header in python websocket server handshake response?
Asked Answered
C

1

6

I have a python websocket server and a nodejs client and I am not able to implement the websocket's Protocol Handshake.

Code of python server

The following minimal websocket server makes use of Flask-sockets (which uses gevent-websocket). File name is ws_server.py:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
from flask import Flask, request, Response
from flask_sockets import Sockets

app = Flask(__name__)
sockets = Sockets(app)

@sockets.route('/')
def echo_socket(ws):
    # print("type request: ",type(request))
    # print("dir  request: ", dir(request))
    print("request.headers: ", request.headers)
    # print("type ws: ",type(ws))
    # print("dir  ws: ",dir(ws))

    if hasattr(request, "Sec-Websocket-Protocol"):
        print(request.headers["Sec-Websocket-Protocol"])
    else:
        print("INFO: No protocol specified")

    if request.headers["Sec-Websocket-Protocol"] == "aProtocol":
        print("INFO: protocol is OK")
    else:
        print("INFO: protocol not accepted: closing connection")
        ws.close()

    while not ws.closed:
        message = ws.receive()
        if message:
            print("received: "+message)
            ws.send(message)

    if ws.closed:
        print("INFO: connection has been closed")

if __name__ == "__main__":
    from gevent import pywsgi
    from geventwebsocket.handler import WebSocketHandler
    server = pywsgi.WSGIServer(('', 5001), app, handler_class=WebSocketHandler)
    server.serve_forever()

Code of nodejs client

The following minimal websocket client makes use of websocket library. File name is app.js:

'use strict'

var WebSocketClient = require('websocket').client;
var client = new WebSocketClient();

function connectToServer(uri,protocol){
    client.on('connectFailed', function (error) {
        console.log('Connect Error: ' + error.toString());
    });

    client.on('connect', function (connection) {
        console.log('WebSocket Client Connected');

        connection.on('error', function (error) {
            console.log("Connection Error: " + error.toString());
        });

        connection.on('close', function () {
            console.log('echo-protocol Connection Closed');
        });

        connection.on('ping', () => {
            connection.pong();
        });

        connection.on('message', function (message) {
            if (message.type === 'utf8') {
                console.log("Received message is: '" + message.utf8Data + "'");
            }
        });
        console.log("sending SOMETHING");
        connection.sendUTF("SOMETHING");
    });
    client.connect(uri, protocol);
}

const wsHostAndPort = process.env.WSHSTPRT || "ws://echo.websocket.org:80";
const wsProtocol = process.env.WSPRTCL || []; // [] for no protocol

console.log("connecting to: ",wsHostAndPort,"with protocol",wsProtocol);
connectToServer(wsHostAndPort,wsProtocol);

Connection from nodejs as client

Start python ws server:

$ python3 ws_server.py

Connect from nodejs ws client:

$ WSHSTPRT=ws://localhost:5001/ WSPRTCL="aProtocol" node app.js

Output of client terminal is

connecting to:  ws://localhost:5001/ with protocol aProtocol
Connect Error: Error: Expected a Sec-WebSocket-Protocol header.

Output of server terminal is:

request.headers:  Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Version: 13
Sec-Websocket-Key: +QjH5xejDI+OQZQ0OZcWEQ==
Host: localhost:5001
Sec-Websocket-Protocol: aProtocol


INFO: No protocol specified
INFO: protocol is OK
INFO: connection has been closed

It seems to me that the python server needs to set a header named "Sec-WebSocket-Protocol" with the same value it has received from the client. But I don't know how to do it. I have searched the internet (mainly the flask-sockets and gevent-websockets forums and issue trackers) without any luck so far.

I tried another simple client, websocat. I invoked it like this: $ websocat ws://localhost:5001 --protocol aProtocol I interactively prompted some messages, and they were echoed correctly by the python server. It works because, i think, websocat (unlike nodejs' websocket) does not expect a "Sec-WebSocket-Protocol header" in the handshake with the server.

But I need to use the nodejs client which expects the header. So my question is: how can I incorporate the "Sec-WebSocket-Protocol" header in the python server handshake response?

Coan answered 1/11, 2018 at 17:7 Comment(2)
Any luck with this?Connie
No luck unfortunately. Also, I am migrating my server-side software to nodejs.Coan
B
3

I know this was over a year ago and it looks like you've moved on, though it looks like you were almost there, just aProtocol needed to be replaced with the subprotocol that the server expects.

Or alternately, omitted entirely as per the underlying gevent-websocket that flask-websockets uses:

// Subprotocol (2nd parameter) often is not required at all
var ws = new WebSocket('ws://localhost:5001/api');

For future me or anyone else who stumbles here, I hope the following helps.

In JavaScript connecting a browser to the Node.JS apollo-server subscriptions, I needed the following:

// Pasted into Firefox console
const token = '...';  // A bearer token, if auth is needed
const subprotocol = 'graphql-ws';
w = new WebSocket('ws://localhost:5000/graphql', subprotocol);
w.send(JSON.stringify({"type":"connection_init","payload":{"authToken":token}}));

Then I'd see Sec-Websocket-Protocol header applied: Sec-Websocket-Protocol header Message sent and ack received

Otherwise if no subprotocol is applied, Sec-Websocket-Protocol header not sent and Control messages visible, I'd see Connection Closed: 1002 and naturally be unable to send messages to the websocket:

Connection Closed: 1002

Bifacial answered 12/6, 2020 at 6:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.