Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added the wsf protocol #240

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions lib/WebSocketClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
'(', ')', '<', '>', '@',
Expand Down Expand Up @@ -100,7 +102,7 @@ function WebSocketClient(config) {
}

this._req = null;

switch (this.config.webSocketVersion) {
case 8:
case 13:
Expand All @@ -112,6 +114,28 @@ function WebSocketClient(config) {

util.inherits(WebSocketClient, EventEmitter);

function handleWsfProtocol(requestUrl, parsedUrl) {
if (typeof(requestUrl) !== 'string') {
return;
}
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;
}
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) {
var self = this;
if (typeof(protocols) === 'string') {
Expand All @@ -137,6 +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.');
}
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.');
}
Expand Down Expand Up @@ -169,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) {
Expand Down Expand Up @@ -224,6 +243,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',
Expand Down
22 changes: 11 additions & 11 deletions lib/WebSocketRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -248,7 +248,7 @@ WebSocketRequest.prototype.parseCookies = function(str) {

WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, cookies) {
this._verifyResolution();

// TODO: Handle extensions

var protocolFullCase;
Expand Down Expand Up @@ -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, '');
Expand Down Expand Up @@ -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.
Expand All @@ -452,7 +452,7 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co
cleanupFailedConnection(connection);
return;
}

self._removeSocketCloseListeners();
connection._addSocketEventListeners();
});
Expand All @@ -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;
}
Expand Down
111 changes: 111 additions & 0 deletions test/unit/fileSockets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env node

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');


function serverSide(t, sockFname, addr) {
var server = http.createServer((request, response) => {
response.writeHead(404);
response.end();
});
if (addr) {
server.listen(sockFname, addr, () => { });
} else {
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) {
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, addr, 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");
}
});
connection.send("ping");
});
if (typeof(sockFname) == "number") {
if (addr.includes(':')) {
addr = "["+addr+"]";
}
var url = 'ws://'+addr+':'+sockFname+'/';
client.connect(url, 'sockfname-protocol');
//client.connect('ws://[::1]:'+sockFname+'/', 'sockfname-protocol');
} else {
client.connect('ws://['+sockFname+']/', 'sockfname-protocol');
}
}

function run(t, socket, adr, finCb) {
var server = serverSide(t, socket, adr);
var i = 0;
var cb = function() {
if (i < 10) {
++i;
clientSide(t, socket, adr, cb);
} else {
server.close();
finCb && finCb();
}
}
clientSide(t, socket, adr, cb);
}

test('use ws over file sockets', function(t) {
t.plan(10 + 1);
var sock = './S.test.' + process.pid;
if (os.platform() == 'win32') {
var sock = path.join("\\\\?\\pipe", process.cwd(), "S.test." + process.pid);
}
run(t, sock);
});

test('use ws over ipv4 tcp sockets', function(t) {
t.plan(10 + 1);
run(t, 4711, "127.0.0.1");
});

test('use ws over ipv6 tcp sockets', function(t) {
t.plan(10 + 1);
run(t, 4711, "::1");
});