I have a python websocket server and a nodejs client and I am not able to implement the websocket's Protocol Handshake.
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()
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);
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?
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.
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,...
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:
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
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:
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With