Steinar H. Gunderson


Yesterday, I had the problem that while socket.io from the browser would work just fine against a given server endpoint (which I do not control), talking to the same server from Node.js would just give hangs and/or inscrutinable “7:::1” messages (which I later learned meant “handshake missing”).

To skip six hours of debugging, the server set a cookie in the initial HTTP handshake, and expected to get it back when opening a WebSocket, presumably to steer the connection to the same backend that got the handshake. (Chrome didn't show the cookie in the WS debugging, but Firefox did.) So we need to keep track of chose cookies. While still remaining on socket.io 0.9.5 (for stupid reasons). No fear, we add this incredibly elegant bit of code:

var io = require('socket.io-client');
// Hook into XHR to pick out the cookie when we receive it.
var my_cookie;
io.util.request = function() { var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; var xhr = new XMLHttpRequest(); xhr.setDisableHeaderCheck(true); const old_send = xhr.send; xhr.send = function() { // Add our own readyStateChange hook in front, to get the cookie if we don't have it. xhr.old_onreadystatechange = xhr.onreadystatechange; xhr.onreadystatechange = function() { if (xhr.readyState == xhr.HEADERS_RECEIVED) { const cookie = xhr.getResponseHeader('set-cookie'); if (cookie) { my_cookie = cookie[0].split(';')[0]; } } xhr.old_onreadystatechange.call(xhr, arguments); }; // Set the cookie if we have it. if (my_cookie) { xhr.setRequestHeader("Cookie", my_cookie); } return old_send.call(this, arguments); }; return xhr;
};
;
// Now override the socket.io WebSockets transport to include our header.
io.Transport['websocket'].prototype.open = function() { const query = io.util.query(this.socket.options.query); const WebSocket = require('ws'); // Include our cookie. let options = {}; if (my_cookie) { options['headers'] = { 'Cookie': my_cookie }; } this.websocket = new WebSocket(this.prepareUrl() + query, options); // The rest is just repeated from the existing function. const self = this; this.websocket.onopen = function () { self.onOpen(); self.socket.setBuffer(false); }; this.websocket.onmessage = function (ev) { self.onData(ev.data); }; this.websocket.onclose = function () { self.onClose(); self.socket.setBuffer(true); }; this.websocket.onerror = function (e) { self.onError(e); }; return this;
};
// And now, finally!
var socket = io.connect('https://example.com', { transports: ['websocket'] });

It's a reminder that talking HTTP and executing JavaScript does not make you into a (headless) browser. And that you shouldn't let me write JavaScript. :-)

(Apologies for the lack of blank lines; evidently, they confuse Markdown.)