Skip to content

Instantly share code, notes, and snippets.

@pshc
Created April 14, 2011 22:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pshc/920729 to your computer and use it in GitHub Desktop.
Save pshc/920729 to your computer and use it in GitHub Desktop.
Mongrel2 WebSockets testcase
var crypto = require('crypto'),
util = require('util'),
zeromq = require('zeromq');
var pullSocket = zeromq.createSocket('pull');
var publishSocket = zeromq.createSocket('pub');
pullSocket.connect('ipc://node.push.request.sock');
publishSocket.connect('ipc://node.sub.response.sock');
var myUUID = 'nodejs';
var webSocketHeaders = ['key', 'origin', 'protocol', 'version'];
var webSocketSalt = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
var webSocketSubProtocol = 'whiterabbit';
pullSocket.on('message', function (buffer) {
console.log('request');
var req = parseRequest(buffer);
try {
webSocketHandshake(req);
}
catch (e) {
if (e.type == 'wsprotocol')
sendHttpResponse(req, 400, {}, e.message);
else if (e.type == 'mongrel2')
sendHttpResponse(req, 500, {}, e.message);
else
throw e;
}
});
function webSocketHandshake(req) {
var ws = {};
webSocketHeaders.forEach(function (key) {
var fullKey = 'sec-websocket-' + key;
if (!req.headers[fullKey])
throw new WSProtocolError('No ' + fullKey);
ws[key] = req.headers[fullKey];
});
// Should check Origin too
if (parseInt(ws.version) != 6)
throw new WSProtocolError('Version not supported.');
var protocols = ws.protocol.toLowerCase().trim().split(/,\s*/);
if (protocols.indexOf(webSocketSubProtocol) < 0)
throw new WSProtocolError('Sub-protocol not supported.');
// Server handshake
var sig = crypto.createHash('SHA1').update(ws.key).update(webSocketSalt).digest('base64');
var headers = {
Upgrade: 'websocket',
Connection: 'Upgrade',
'Sec-WebSocket-Accept': sig,
'Sec-WebSocket-Protocol': webSocketSubProtocol
};
sendHttpResponse(req, 101, headers, null);
}
var statusCodeNames = {
101: 'Switching Protocols',
400: 'Bad Request',
500: 'Internal Server Error',
};
function sendHttpResponse(req, code, headers, body) {
// Headers assumed ASCII
var frame = myUUID + ' ' + makeNetString(req.listenerID) + ' ';
if (body !== null)
headers['Content-Length'] = Buffer.byteLength(body, 'UTF-8');
var headerArray = [];
for (var h in headers)
headerArray.push(h + ': ' + headers[h] + '\r\n');
var head = 'HTTP/1.1 ' + code + ' ' + statusCodeNames[code] + '\r\n' +
headerArray.join('') + '\r\n';
if (body !== null) {
publishSocket.send(frame + head + body);
publishSocket.send(frame); // done
}
else {
// Keep connection open
publishSocket.send(frame + head);
setTimeout(function () {
publishSocket.send(frame + 'hi');
}, 1000);
}
}
function parseRequest(buf) {
// Format: sender-UUID req-number path (netstring)*
var info = {};
var start = 0;
var end = bufferIndexOf(buf, 32, start);
if (end < 0)
throw new Mongrel2Error('No sender UUID.');
info.senderUUID = buf.toString('ascii', start, end);
start = end + 1;
end = bufferIndexOf(buf, 32, start);
if (end < 0)
throw new Mongrel2Error('No request number.');
info.listenerID = buf.toString('ascii', start, end);
start = end + 1;
end = bufferIndexOf(buf, 32, start);
if (end < 0)
throw new Mongrel2Error('No request path.');
info.path = buf.toString('ascii', start, end);
start = end + 1;
var headers = parseNetString(buf.slice(start, buf.length));
info.headers = JSON.parse(headers);
// Ignoring body for now
return info;
}
function parseNetString(buf) {
var colon = bufferIndexOf(buf, 58);
if (colon < 0)
throw new Mongrel2Error('No netstring length prefix.');
var len = parseInt(buf.toString('ascii', 0, colon));
var end = colon + 1 + len;
if (buf[end] != 44)
throw new Mongrel2Error('Netstring does not end in comma.');
return buf.toString('UTF-8', colon + 1, end);
}
function makeNetString(str) {
return str.length + ':' + str + ',';
}
function bufferIndexOf(buffer, byte, startIndex) {
var len = buffer.length;
var i = parseInt(startIndex);
if (!i || i < 0)
i = 0;
for (; i < len; i++)
if (buffer[i] == byte)
return i;
return -1;
};
function WSProtocolError(message) {
this.message = message;
this.type = 'wsprotocol';
}
util.inherits(WSProtocolError, Error);
function Mongrel2Error(message) {
this.message = message;
this.type = 'mongrel';
}
util.inherits(Mongrel2Error, Error);
ws_handler = Handler(send_spec='ipc://node.push.request.sock',
send_ident='ws_handler',
recv_spec='ipc://node.sub.response.sock', recv_ident='')
routes = {
'/ws/': ws_handler
}
main = Server(
uuid="f400bf85-4538-4f7a-8908-67e313d515c2",
access_log="/logs/access.log",
error_log="/logs/error.log",
chroot="./",
pid_file="/run/mongrel2.pid",
default_host="localhost",
name="main",
port=6767,
hosts=[Host(name='localhost', routes=routes)]
)
servers = [main]
var net = require('net');
var client = net.createConnection(6767, 'localhost');
var headers = {
Host: 'localhost',
//Connection: 'Upgrade',
//Upgrade: 'something',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Origin': 'http://localhost/',
'Sec-WebSocket-Protocol': 'whiterabbit',
'Sec-WebSocket-Version': '6',
};
var headerArray = [];
for (var h in headers)
headerArray.push(h + ': ' + headers[h] + '\r\n');
client.write('GET / HTTP/1.1\r\n' + headerArray.join('') + '\r\n');
client.on('data', function (data) {
process.stdout.write(data.toString('UTF-8'));
});
client.once('data', function (data) {
client.write('Sending some raw data!');
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment