From 91c9b1d2ab8bb696192a7ee7b63bcafb982ef665 Mon Sep 17 00:00:00 2001 From: Meno Abels Date: Thu, 16 Jun 2016 13:55:36 +0200 Subject: [PATCH 1/3] * added the wsf protocol to support websocket to run over socket files. --- lib/WebSocketClient.js | 40 ++++++++++++++++- lib/WebSocketRequest.js | 22 +++++----- test/unit/fileSockets.js | 95 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 test/unit/fileSockets.js diff --git a/lib/WebSocketClient.js b/lib/WebSocketClient.js index 4b4abb25..85f902cb 100644 --- a/lib/WebSocketClient.js +++ b/lib/WebSocketClient.js @@ -100,7 +100,7 @@ function WebSocketClient(config) { } this._req = null; - + switch (this.config.webSocketVersion) { case 8: case 13: @@ -112,6 +112,40 @@ function WebSocketClient(config) { util.inherits(WebSocketClient, EventEmitter); +function handleWsfProtocol(url) { + var path = require('path'); + var fs = require('fs'); + var parts = url.href.substring("wsf://".length).split(path.sep); + var sockName = "/"; + if (parts[0] == "." || parts[0] == "..") { + sockName = ""; + } + var found = false; + var sep = ""; + for (var i = 0; i < parts.length; ++i) { + sockName += sep + parts[i]; + // ugly sync but is this really worth change the hole api + var stat = fs.statSync(sockName); + try { + found = stat && stat.isSocket(); + if (found) { + url.socketPath = sockName; + url.host = 'localhost'; + url.hostname = 'localhost'; + url.pathname = "kaputt"; + url.path = '/' + parts.slice(i+1).join('/'); + break; + } + } catch (e) { + // ignore + } + sep = path.sep + } + if (!found) { + throw new Error('no socket found in wsf url:' + url.href); + } +} + WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, headers, extraRequestOptions) { var self = this; if (typeof(protocols) === 'string') { @@ -137,6 +171,9 @@ WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, head if (!this.url.protocol) { throw new Error('You must specify a full WebSocket URL, including protocol.'); } + if (this.url.protocol == "wsf:") { + handleWsfProtocol(this.url); + } if (!this.url.host) { throw new Error('You must specify a full WebSocket URL, including hostname. Relative URLs are not supported.'); } @@ -224,6 +261,7 @@ WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, head // These options are always overridden by the library. The user is not // allowed to specify these directly. extend(requestOptions, { + socketPath: this.url.socketPath, hostname: this.url.hostname, port: this.url.port, method: 'GET', diff --git a/lib/WebSocketRequest.js b/lib/WebSocketRequest.js index f4d96555..62a3b1ef 100644 --- a/lib/WebSocketRequest.js +++ b/lib/WebSocketRequest.js @@ -95,13 +95,13 @@ function WebSocketRequest(socket, httpRequest, serverConfig) { this.remoteAddress = socket.remoteAddress; this.remoteAddresses = [this.remoteAddress]; this.serverConfig = serverConfig; - + // Watch for the underlying TCP socket closing before we call accept this._socketIsClosing = false; this._socketCloseHandler = this._handleSocketCloseBeforeAccept.bind(this); this.socket.on('end', this._socketCloseHandler); this.socket.on('close', this._socketCloseHandler); - + this._resolved = false; } @@ -248,7 +248,7 @@ WebSocketRequest.prototype.parseCookies = function(str) { WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) { this._verifyResolution(); - + // TODO: Handle extensions var protocolFullCase; @@ -286,7 +286,7 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co } if (this.requestedProtocols.indexOf(acceptedProtocol) === -1) { this.reject(500); - throw new Error('Specified protocol was not requested by the client.'); + throw new Error('Specified protocol was not requested by the client:'+acceptedProtocol+":"+this.requestedProtocols); } protocolFullCase = protocolFullCase.replace(headerSanitizeRegExp, ''); @@ -426,21 +426,21 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co // if (negotiatedExtensions) { // response += 'Sec-WebSocket-Extensions: ' + negotiatedExtensions.join(', ') + '\r\n'; // } - + // Mark the request resolved now so that the user can't call accept or // reject a second time. this._resolved = true; this.emit('requestResolved', this); - + response += '\r\n'; var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig); connection.webSocketVersion = this.webSocketVersion; connection.remoteAddress = this.remoteAddress; connection.remoteAddresses = this.remoteAddresses; - + var self = this; - + if (this._socketIsClosing) { // Handle case when the client hangs up before we get a chance to // accept the connection and send our side of the opening handshake. @@ -452,7 +452,7 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co cleanupFailedConnection(connection); return; } - + self._removeSocketCloseListeners(); connection._addSocketEventListeners(); }); @@ -464,12 +464,12 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co WebSocketRequest.prototype.reject = function(status, reason, extraHeaders) { this._verifyResolution(); - + // Mark the request resolved now so that the user can't call accept or // reject a second time. this._resolved = true; this.emit('requestResolved', this); - + if (typeof(status) !== 'number') { status = 403; } diff --git a/test/unit/fileSockets.js b/test/unit/fileSockets.js new file mode 100644 index 00000000..39b40a7b --- /dev/null +++ b/test/unit/fileSockets.js @@ -0,0 +1,95 @@ +#!/usr/bin/env node + +var test = require('tape'); +var http = require('http'); +var WebSocketServer = require('../../lib/WebSocketServer'); +var WebSocketClient = require('../../lib/WebSocketClient'); + + +function serverSide(t, sockFname) { + var server = http.createServer((request, response) => { + response.writeHead(404); + response.end(); + }); + server.listen(sockFname, () => { }); + server.on('error', (e) => { + t.assert(true, false, "errors should not happen"); + }); + + var wsServer = new WebSocketServer({ httpServer: server, autoAcceptConnections: false }); + wsServer.on('request', (request) => { + var connection = request.accept('sockfname-protocol', request.origin); + connection.on('message', function(message) { + //console.log(">>",message); + switch (message.utf8Data) { + case "ping" : + connection.send("pong"); + break; + case "bye" : + connection.close(); + break; + } + }); + connection.on('close', function(reasonCode, description) { + }); + }); + return server; +} + +function clientSide(t, sockFname, cb) { + let client = new WebSocketClient(); + client.on('connectFailed', (error) => { + console.error(error); + t.assert(true, false, "errors should not happen"); + }); + + client.on('connect', (connection) => { + connection.on('error', (error) => { + t.assert(true, false, "errors should not happen"); + }); + connection.on('close', function() { + t.assert(true, true); + cb(); + }); + connection.on('message', function(message) { + switch (message.utf8Data) { + case "pong" : + connection.send("bye"); + } + }); + //console.log("< Date: Fri, 17 Jun 2016 12:34:15 +0200 Subject: [PATCH 2/3] * remove the wsf prefix * if the hostname is in the ipv6 literal form [::1] i allow to write in the inner part the socket name. So the url could look like: ws://[/path/to/socket]/rest * removed the hostname evaluation. There is a host attribute in a parsed url instance. --- lib/WebSocketClient.js | 60 ++++++++++++++-------------------------- test/unit/fileSockets.js | 41 +++++++++++++++++---------- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/lib/WebSocketClient.js b/lib/WebSocketClient.js index 85f902cb..054a5315 100644 --- a/lib/WebSocketClient.js +++ b/lib/WebSocketClient.js @@ -23,6 +23,8 @@ var https = require('https'); var url = require('url'); var crypto = require('crypto'); var WebSocketConnection = require('./WebSocketConnection'); +var path = require('path'); +var fs = require('fs'); var protocolSeparators = [ '(', ')', '<', '>', '@', @@ -112,38 +114,26 @@ function WebSocketClient(config) { util.inherits(WebSocketClient, EventEmitter); -function handleWsfProtocol(url) { - var path = require('path'); - var fs = require('fs'); - var parts = url.href.substring("wsf://".length).split(path.sep); - var sockName = "/"; - if (parts[0] == "." || parts[0] == "..") { - sockName = ""; +function handleWsfProtocol(requestUrl, parsedUrl) { + if (typeof(requestUrl) !== 'string') { + return; } - var found = false; - var sep = ""; - for (var i = 0; i < parts.length; ++i) { - sockName += sep + parts[i]; - // ugly sync but is this really worth change the hole api - var stat = fs.statSync(sockName); - try { - found = stat && stat.isSocket(); - if (found) { - url.socketPath = sockName; - url.host = 'localhost'; - url.hostname = 'localhost'; - url.pathname = "kaputt"; - url.path = '/' + parts.slice(i+1).join('/'); - break; - } - } catch (e) { - // ignore - } - sep = path.sep + var prefix = parsedUrl.protocol+"//["; + if (requestUrl.indexOf(prefix) != 0 || + (parsedUrl.hostname && parsedUrl.hostname.length > 0)) { + // parse found a regular ipv6 literal this is not for us + return; } - if (!found) { - throw new Error('no socket found in wsf url:' + url.href); + var closing = requestUrl.indexOf(']', prefix.length); + if (closing <= 0) { + // the url string is not valid + return; } + var sockName = requestUrl.substring(prefix.length, closing); + var reparseUrl = requestUrl.substring(0, prefix.length) + "dead::beef" + requestUrl.substring(closing); + var reparsedUrl = url.parse(reparseUrl); + Object.assign(parsedUrl, reparsedUrl); + parsedUrl.socketPath = sockName; } WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, headers, extraRequestOptions) { @@ -171,9 +161,7 @@ WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, head if (!this.url.protocol) { throw new Error('You must specify a full WebSocket URL, including protocol.'); } - if (this.url.protocol == "wsf:") { - handleWsfProtocol(this.url); - } + handleWsfProtocol(requestUrl, this.url); if (!this.url.host) { throw new Error('You must specify a full WebSocket URL, including hostname. Relative URLs are not supported.'); } @@ -206,19 +194,13 @@ WebSocketClient.prototype.connect = function(requestUrl, protocols, origin, head } this.base64nonce = nonce.toString('base64'); - var hostHeaderValue = this.url.hostname; - if ((this.url.protocol === 'ws:' && this.url.port !== '80') || - (this.url.protocol === 'wss:' && this.url.port !== '443')) { - hostHeaderValue += (':' + this.url.port); - } - var reqHeaders = headers || {}; extend(reqHeaders, { 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Version': this.config.webSocketVersion.toString(10), 'Sec-WebSocket-Key': this.base64nonce, - 'Host': hostHeaderValue + 'Host': this.url.host }); if (this.protocols.length > 0) { diff --git a/test/unit/fileSockets.js b/test/unit/fileSockets.js index 39b40a7b..5e5cd9f8 100644 --- a/test/unit/fileSockets.js +++ b/test/unit/fileSockets.js @@ -6,12 +6,16 @@ var WebSocketServer = require('../../lib/WebSocketServer'); var WebSocketClient = require('../../lib/WebSocketClient'); -function serverSide(t, sockFname) { +function serverSide(t, sockFname, addr) { var server = http.createServer((request, response) => { response.writeHead(404); response.end(); }); - server.listen(sockFname, () => { }); + if (addr) { + server.listen(sockFname, addr, () => { }); + } else { + server.listen(sockFname, () => { }); + } server.on('error', (e) => { t.assert(true, false, "errors should not happen"); }); @@ -20,7 +24,6 @@ function serverSide(t, sockFname) { wsServer.on('request', (request) => { var connection = request.accept('sockfname-protocol', request.origin); connection.on('message', function(message) { - //console.log(">>",message); switch (message.utf8Data) { case "ping" : connection.send("pong"); @@ -36,7 +39,7 @@ function serverSide(t, sockFname) { return server; } -function clientSide(t, sockFname, cb) { +function clientSide(t, sockFname, addr, cb) { let client = new WebSocketClient(); client.on('connectFailed', (error) => { console.error(error); @@ -57,39 +60,47 @@ function clientSide(t, sockFname, cb) { connection.send("bye"); } }); - //console.log("< Date: Fri, 17 Jun 2016 20:56:35 +0200 Subject: [PATCH 3/3] * test if named pipes are working on windows --- test/unit/fileSockets.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/unit/fileSockets.js b/test/unit/fileSockets.js index 5e5cd9f8..6db1966f 100644 --- a/test/unit/fileSockets.js +++ b/test/unit/fileSockets.js @@ -2,6 +2,8 @@ var test = require('tape'); var http = require('http'); +var path = require('path'); +var os = require('os'); var WebSocketServer = require('../../lib/WebSocketServer'); var WebSocketClient = require('../../lib/WebSocketClient'); @@ -91,7 +93,10 @@ function run(t, socket, adr, finCb) { test('use ws over file sockets', function(t) { t.plan(10 + 1); - var sock = "./S.test."+process.pid; + var sock = './S.test.' + process.pid; + if (os.platform() == 'win32') { + var sock = path.join("\\\\?\\pipe", process.cwd(), "S.test." + process.pid); + } run(t, sock); });