Now with more WebSockets.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3197 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
2016-04-11 00:09:21 +00:00
parent e6aad326c5
commit 2c8bea27a0
5 changed files with 510 additions and 246 deletions

View File

@ -1,4 +1,7 @@
"use strict";
var gHandlers = [];
var gSocketHandlers = [];
function logError(error) {
print("ERROR " + error);
@ -27,6 +30,14 @@ function all(prefix, handler) {
});
}
function registerSocketHandler(prefix, handler) {
gSocketHandlers.push({
owner: this,
path: prefix,
invoke: handler,
});
}
function Request(method, uri, version, headers, body, client) {
this.method = method;
var index = uri.indexOf("?");
@ -56,8 +67,21 @@ function findHandler(request) {
return matchedHandler;
}
function findSocketHandler(request) {
var matchedHandler = null;
for (var name in gSocketHandlers) {
var handler = gSocketHandlers[name];
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
matchedHandler = handler;
break;
}
}
return matchedHandler;
}
function Response(request, client) {
var kStatusText = {
101: "Switching Protocols",
200: 'OK',
303: 'See other',
403: 'Forbidden',
@ -160,6 +184,136 @@ function handleRequest(request, response) {
}
}
function handleWebSocketRequest(request, response, client) {
var buffer = "";
var frame = "";
var frameOpCode = 0x0;
var handler = findSocketHandler(request);
if (!handler) {
client.close();
return;
}
response.send = function(message, opCode) {
if (opCode === undefined) {
opCode = 0x2;
}
var fin = true;
var packet = String.fromCharCode((fin ? (1 << 7) : 0) | (opCode & 0xf));
var mask = false;
if (message.length < 126) {
packet += String.fromCharCode((mask ? (1 << 7) : 0) | message.length);
} else if (message.length < (1 << 16)) {
packet += String.fromCharCode((mask ? (1 << 7) : 0) | 126);
packet += String.fromCharCode((message.length >> 8) & 0xff);
packet += String.fromCharCode(message.length & 0xff);
} else {
packet += String.fromCharCode((mask ? (1 << 7) : 0) | 127);
packet += String.fromCharCode((message.length >> 24) & 0xff);
packet += String.fromCharCode((message.length >> 16) & 0xff);
packet += String.fromCharCode((message.length >> 8) & 0xff);
packet += String.fromCharCode(message.length & 0xff);
}
packet += message;
return client.write(packet);
}
response.onMessage = null;
handler.invoke(request, response);
client.read(function(data) {
if (data) {
buffer += data;
if (buffer.length >= 2) {
var bits0 = buffer.charCodeAt(0);
var bits1 = buffer.charCodeAt(1);
if (bits1 & (1 << 7) == 0) {
// Unmasked message.
client.close();
}
var opCode = bits0 & 0xf;
var fin = bits0 & (1 << 7);
var payloadLength = bits1 & 0x7f;
var maskStart = 2;
if (payloadLength == 126) {
payloadLength = 0;
for (var i = 0; i < 2; i++) {
payloadLength <<= 8;
payloadLength |= buffer.charCodeAt(2 + i);
}
maskStart = 4;
} else if (payloadLength == 127) {
payloadLength = 0;
for (var i = 0; i < 8; i++) {
payloadLength <<= 8;
payloadLength |= buffer.charCodeAt(2 + i);
}
maskStart = 10;
}
var havePayload = buffer.length >= payloadLength + 2 + 4;
if (havePayload) {
var mask = buffer.substring(maskStart, maskStart + 4);
var dataStart = maskStart + 4;
var decoded = "";
var payload = buffer.substring(dataStart, dataStart + payloadLength);
buffer = buffer.substring(dataStart + payloadLength);
for (var i = 0; i < payloadLength; i++) {
decoded += String.fromCharCode(payload.charCodeAt(i) ^ mask.charCodeAt(i % 4));
}
frame += decoded;
if (opCode) {
frameOpCode = opCode;
}
if (fin) {
if (response.onMessage) {
response.onMessage({
data: frame,
opCode: frameOpCode,
});
}
frame = "";
}
}
}
}
});
client.onError(function(error) {
logError(client.peerName + " - - [" + new Date() + "] " + error);
});
response.writeHead(101, {
"Upgrade": "websocket",
"Connection": "Upgrade",
"Sec-WebSocket-Accept": webSocketAcceptResponse(request.headers["sec-websocket-key"]),
});
}
function webSocketAcceptResponse(key) {
var kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
var kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var hex = require("sha1").hash(key + kMagic)
var binary = "";
for (var i = 0; i < hex.length; i += 6) {
var characters = hex.substring(i, i + 6);
if (characters.length < 6) {
characters += "0".repeat(6 - characters.length);
}
var value = parseInt(characters, 16);
for (var bit = 0; bit < 8 * 3; bit += 6) {
if (i * 8 / 2 + bit >= 8 * hex.length / 2) {
binary += kAlphabet.charAt(64);
} else {
binary += kAlphabet.charAt((value >> (18 - bit)) & 63);
}
}
}
return binary;
}
function handleConnection(client) {
var inputBuffer = "";
var request;
@ -207,6 +361,12 @@ function handleConnection(client) {
lineByLine = false;
body = "";
return true;
} else if (headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
&& headers["upgrade"].toLowerCase() == "websocket") {
var requestObject = new Request(request[0], request[1], request[2], headers, body, client);
var response = new Response(requestObject, client);
handleWebSocketRequest(requestObject, response, client);
return false;
} else {
finish();
return false;
@ -291,3 +451,4 @@ if (privateKey && certificate) {
}
exports.all = all;
exports.registerSocketHandler = registerSocketHandler;