Created
April 14, 2011 22:32
-
-
Save pshc/920729 to your computer and use it in GitHub Desktop.
Mongrel2 WebSockets testcase
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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