Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 893
easy_mungo Avatar asked Nov 01 '18 17:11

easy_mungo


People also ask

What is the Sec-WebSocket-Accept header?

The Sec-WebSocket-Accept header falls under the response-type headers category. It is used by the server to intimate the client that it understood. It was a WebSocket connection and it is ready to open connection.

What is the handshake in WebSockets?

The handshake is the "Web" in WebSockets. It's the bridge from HTTP to WebSockets. In the handshake, details of the connection are negotiated, and either party can back out before completion if the terms are unfavorable. The server must be careful to understand everything the client asks for,...

What is a handshake in Python?

So handshake is basically HTTP response with a header containg SHA1 of the key and magic-string and key client sent and those same two headers. Here’s how it’s done in python:

Is there a way to pass a list of WebSocket-client headers?

It seems that websocket-client was updated to include websocket headers since this question was asked. Now you can simply pass a list of header parameters as strings: If you are interested in the complete list of valid headers, see the websocket RFC6455 document: http://tools.ietf.org/html/rfc6455#section-4.3


1 Answers

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

like image 173
pzrq Avatar answered Oct 26 '22 14:10

pzrq