Use picohttpparser. No more messing around.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4094 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
2022-12-31 16:47:10 +00:00
parent ae4c2aef69
commit ca6d042ed6
18 changed files with 2098 additions and 174 deletions

View File

@ -2,9 +2,9 @@ import * as sha1 from './sha1.js';
"use strict";
var gHandlers = [];
var gSocketHandlers = [];
var gBadRequests = {};
let gHandlers = [];
let gSocketHandlers = [];
let gBadRequests = {};
const kRequestTimeout = 15000;
const kStallTimeout = 60000;
@ -17,8 +17,8 @@ function logError(error) {
}
function addHandler(handler) {
var added = false;
for (var i in gHandlers) {
let added = false;
for (let i in gHandlers) {
if (gHandlers[i].path == handler.path) {
gHandlers[i] = handler;
added = true;
@ -49,7 +49,7 @@ function registerSocketHandler(prefix, handler) {
function Request(method, uri, version, headers, body, client) {
this.method = method;
var index = uri.indexOf("?");
let index = uri.indexOf("?");
if (index != -1) {
this.uri = uri.slice(0, index);
this.query = uri.slice(index + 1);
@ -65,9 +65,9 @@ function Request(method, uri, version, headers, body, client) {
}
function findHandler(request) {
var matchedHandler = null;
for (var name in gHandlers) {
var handler = gHandlers[name];
let matchedHandler = null;
for (let name in gHandlers) {
let handler = gHandlers[name];
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
matchedHandler = handler;
break;
@ -77,9 +77,9 @@ function findHandler(request) {
}
function findSocketHandler(request) {
var matchedHandler = null;
for (var name in gSocketHandlers) {
var handler = gSocketHandlers[name];
let matchedHandler = null;
for (let name in gSocketHandlers) {
let handler = gSocketHandlers[name];
if (request.uri == handler.path || request.uri.slice(0, handler.path.length + 1) == handler.path + '/') {
matchedHandler = handler;
break;
@ -89,7 +89,7 @@ function findSocketHandler(request) {
}
function Response(request, client) {
var kStatusText = {
let kStatusText = {
101: "Switching Protocols",
200: 'OK',
303: 'See other',
@ -99,17 +99,17 @@ function Response(request, client) {
404: 'File not found',
500: 'Internal server error',
};
var _started = false;
var _finished = false;
var _keepAlive = false;
var _chunked = false;
let _started = false;
let _finished = false;
let _keepAlive = false;
let _chunked = false;
return {
writeHead: function(status) {
if (_started) {
throw new Error("Response.writeHead called multiple times.");
}
var reason;
var headers;
let reason;
let headers;
if (arguments.length == 3) {
reason = arguments[1];
headers = arguments[2];
@ -117,11 +117,11 @@ function Response(request, client) {
reason = kStatusText[status];
headers = arguments[1];
}
var lowerHeaders = {};
var requestVersion = request.version.split("/")[1].split(".");
var responseVersion = (requestVersion[0] >= 1 && requestVersion[0] >= 1) ? "1.1" : "1.0";
var headerString = "HTTP/" + responseVersion + " " + status + " " + reason + "\r\n";
for (var i in headers) {
let lowerHeaders = {};
let requestVersion = request.version.split("/")[1].split(".");
let responseVersion = (requestVersion[0] >= 1 && requestVersion[0] >= 1) ? "1.1" : "1.0";
let headerString = "HTTP/" + responseVersion + " " + status + " " + reason + "\r\n";
for (let i in headers) {
headerString += i + ": " + headers[i] + "\r\n";
lowerHeaders[i.toLowerCase()] = headers[i];
}
@ -172,13 +172,13 @@ function Response(request, client) {
}
function handleRequest(request, response) {
var handler = findHandler(request);
let handler = findHandler(request);
print(request.client.peerName + " - - [" + new Date() + "] " + request.method + " " + request.uri + " " + request.version + " \"" + request.headers["user-agent"] + "\"");
if (handler) {
try {
var promise = handler.invoke(request, response);
let promise = handler.invoke(request, response);
if (promise) {
promise.catch(function(error) {
response.reportError(error);
@ -196,11 +196,11 @@ function handleRequest(request, response) {
}
function handleWebSocketRequest(request, response, client) {
var buffer = new Uint8Array(0);
var frame = new Uint8Array(0);
var frameOpCode = 0x0;
let buffer = new Uint8Array(0);
let frame = new Uint8Array(0);
let frameOpCode = 0x0;
var handler = findSocketHandler(request);
let handler = findSocketHandler(request);
if (!handler) {
client.close();
return;
@ -213,9 +213,9 @@ function handleWebSocketRequest(request, response, client) {
if (opCode == 0x1 && (typeof message == "string" || message instanceof String)) {
message = utf8Encode(message);
}
var fin = true;
var packet = [(fin ? (1 << 7) : 0) | (opCode & 0xf)];
var mask = false;
let fin = true;
let packet = [(fin ? (1 << 7) : 0) | (opCode & 0xf)];
let mask = false;
if (message.length < 126) {
packet.push((mask ? (1 << 7) : 0) | message.length);
} else if (message.length < (1 << 16)) {
@ -223,8 +223,8 @@ function handleWebSocketRequest(request, response, client) {
packet.push((message.length >> 8) & 0xff);
packet.push(message.length & 0xff);
} else {
var high = 0; //(message.length / (1 ** 32)) & 0xffffffff;
var low = message.length & 0xffffffff;
let high = 0; //(message.length / (1 ** 32)) & 0xffffffff;
let low = message.length & 0xffffffff;
packet.push((mask ? (1 << 7) : 0) | 127);
packet.push((high >> 24) & 0xff);
packet.push((high >> 16) & 0xff);
@ -236,7 +236,7 @@ function handleWebSocketRequest(request, response, client) {
packet.push(low & 0xff);
}
var array = new Uint8Array(packet.length + message.length);
let array = new Uint8Array(packet.length + message.length);
array.set(packet, 0);
array.set(message, packet.length);
try {
@ -252,50 +252,50 @@ function handleWebSocketRequest(request, response, client) {
client.read(function(data) {
if (data) {
var newBuffer = new Uint8Array(buffer.length + data.length);
let newBuffer = new Uint8Array(buffer.length + data.length);
newBuffer.set(buffer, 0);
newBuffer.set(data, buffer.length);
buffer = newBuffer;
while (buffer.length >= 2) {
var bits0 = buffer[0];
var bits1 = buffer[1];
let bits0 = buffer[0];
let bits1 = buffer[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;
let opCode = bits0 & 0xf;
let fin = bits0 & (1 << 7);
let payloadLength = bits1 & 0x7f;
let maskStart = 2;
if (payloadLength == 126) {
payloadLength = 0;
for (var i = 0; i < 2; i++) {
for (let i = 0; i < 2; i++) {
payloadLength <<= 8;
payloadLength |= buffer[2 + i];
}
maskStart = 4;
} else if (payloadLength == 127) {
payloadLength = 0;
for (var i = 0; i < 8; i++) {
for (let i = 0; i < 8; i++) {
payloadLength <<= 8;
payloadLength |= buffer[2 + i];
}
maskStart = 10;
}
var havePayload = buffer.length >= payloadLength + 2 + 4;
let havePayload = buffer.length >= payloadLength + 2 + 4;
if (havePayload) {
var mask = buffer.slice(maskStart, maskStart + 4);
var dataStart = maskStart + 4;
var decoded = new Array(payloadLength);
var payload = buffer.slice(dataStart, dataStart + payloadLength);
let mask = buffer.slice(maskStart, maskStart + 4);
let dataStart = maskStart + 4;
let decoded = new Array(payloadLength);
let payload = buffer.slice(dataStart, dataStart + payloadLength);
buffer = buffer.slice(dataStart + payloadLength);
for (var i = 0; i < payloadLength; i++) {
for (let i = 0; i < payloadLength; i++) {
decoded[i] = payload[i] ^ mask[i % 4];
}
var newBuffer = new Uint8Array(frame.length + decoded.length);
let newBuffer = new Uint8Array(frame.length + decoded.length);
newBuffer.set(frame, 0);
newBuffer.set(decoded, frame.length);
frame = newBuffer;
@ -339,17 +339,17 @@ function handleWebSocketRequest(request, response, client) {
}
function webSocketAcceptResponse(key) {
var kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
var kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var hex = sha1.hash(key + kMagic)
var binary = "";
for (var i = 0; i < hex.length; i += 6) {
var characters = hex.substring(i, i + 6);
let kMagic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
let kAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let hex = sha1.hash(key + kMagic)
let binary = "";
for (let i = 0; i < hex.length; i += 6) {
let 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) {
let value = parseInt(characters, 16);
for (let bit = 0; bit < 8 * 3; bit += 6) {
if (i * 8 / 2 + bit >= 8 * hex.length / 2) {
binary += kAlphabet.charAt(64);
} else {
@ -361,9 +361,9 @@ function webSocketAcceptResponse(key) {
}
function badRequest(client, reason) {
var now = new Date();
var count = 0;
var old = gBadRequests[client.peerName];
let now = new Date();
let count = 0;
let old = gBadRequests[client.peerName];
if (!old) {
gBadRequests[client.peerName] = {
expire: new Date(now.getTime() + 1 * 60 * 1000),
@ -381,9 +381,9 @@ function badRequest(client, reason) {
}
function allowRequest(client) {
var old = gBadRequests[client.peerName];
let old = gBadRequests[client.peerName];
if (old) {
var now = new Date();
let now = new Date();
if (old.expire < now) {
delete gBadRequests[client.peerName];
return true;
@ -404,15 +404,15 @@ function handleConnection(client) {
}
client.info = 'accepted';
var inputBuffer = new Uint8Array(0);
var request;
var headers = {};
var lineByLine = true;
var bodyToRead = -1;
var body;
var requestCount = -1;
var readCount = 0;
var isWebsocket = false;
let inputBuffer = new Uint8Array(0);
let request;
let headers = {};
let parsing_header = true;
let bodyToRead = -1;
let body;
let requestCount = -1;
let readCount = 0;
let isWebsocket = false;
function resetTimeout(requestIndex) {
if (isWebsocket) {
@ -430,7 +430,7 @@ function handleConnection(client) {
}
}, kRequestTimeout);
} else {
var lastReadCount = readCount;
let lastReadCount = readCount;
setTimeout(function() {
if (readCount == lastReadCount) {
client.info = 'stalled';
@ -450,7 +450,7 @@ function handleConnection(client) {
inputBuffer = new Uint8Array(0);
request = undefined;
headers = {};
lineByLine = true;
parsing_header = true;
bodyToRead = -1;
body = undefined;
client.info = 'reset';
@ -459,8 +459,8 @@ function handleConnection(client) {
function finish() {
client.info = 'finishing';
var requestObject = new Request(request[0], request[1], request[2], headers, body, client);
var response = new Response(requestObject, client);
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
let response = new Response(requestObject, client);
try {
handleRequest(requestObject, response)
if (client.isConnected) {
@ -472,69 +472,6 @@ function handleConnection(client) {
}
}
function handleLine(line, length) {
if (bodyToRead == -1) {
line = utf8Decode(line);
if (!request) {
if (!line) {
badRequest(client, 'Empty request.');
return false;
}
request = line.split(' ');
if (request.length != 3 || !request[2].startsWith('HTTP/1.')) {
badRequest(client, 'Bad request.');
request = null;
return false;
}
return true;
} else if (line) {
var colon = line.indexOf(':');
var key = line.slice(0, colon).trim();
var value = line.slice(colon + 1).trim();
headers[key.toLowerCase()] = value;
return true;
} else {
if (headers["content-length"] != undefined) {
bodyToRead = parseInt(headers["content-length"]);
lineByLine = false;
if (bodyToRead > 16 * 1024 * 1024) {
badRequest(client, 'Request too large: ' + bodyToRead + '.');
return false;
}
body = new Uint8Array(bodyToRead);
client.info = 'waiting for body';
resetTimeout(requestCount);
return true;
} else if (headers["connection"]
&& headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
&& headers["upgrade"]
&& headers["upgrade"].toLowerCase() == "websocket") {
isWebsocket = true;
client.info = 'websocket';
var requestObject = new Request(request[0], request[1], request[2], headers, body, client);
var response = new Response(requestObject, client);
handleWebSocketRequest(requestObject, response, client);
/* Prevent the timeout from disconnecting us. */
requestCount++;
return false;
} else {
finish();
return false;
}
}
} else {
var offset = body.length - bodyToRead;
if (line.length > body.length - offset) {
line = line.slice(0, body.length - offset);
}
body.set(line, offset);
bodyToRead -= line.length;
if (bodyToRead <= 0) {
finish();
}
}
}
client.noDelay = true;
client.onError(function(error) {
@ -547,36 +484,72 @@ function handleConnection(client) {
if (bodyToRead != -1 && !isWebsocket) {
resetTimeout(requestCount);
}
const kMaxLineLength = 4096;
var newBuffer = new Uint8Array(inputBuffer.length + data.length);
let newBuffer = new Uint8Array(inputBuffer.length + data.length);
newBuffer.set(inputBuffer, 0);
newBuffer.set(data, inputBuffer.length);
inputBuffer = newBuffer;
var newLine = '\n'.charCodeAt(0);
var carriageReturn = '\r'.charCodeAt(0);
if (parsing_header)
{
let result = parseHttp(inputBuffer, inputBuffer.length - data.length);
if (result) {
if (typeof result === 'number') {
if (result == -2) {
/* More. */
} else {
badRequest(client, 'Bad request.');
return;
}
} else if (typeof result === 'object') {
request = [
result.method,
result.path,
`HTTP/1.${result.minor_version}`,
];
headers = Object.fromEntries(Object.entries(result.headers).map(x => [x[0].toLowerCase(), x[1]]));
parsing_header = false;
inputBuffer = inputBuffer.slice(result.bytes_parsed);
var more = true;
while (more) {
if (lineByLine) {
more = false;
var end = inputBuffer.indexOf(newLine);
var realEnd = end;
if (end > 0 && inputBuffer[end - 1] == carriageReturn) {
--end;
}
if (end > kMaxLineLength || end == -1 && inputBuffer.length > kMaxLineLength) {
badRequest(client, 'Request too long.');
return;
}
if (end != -1) {
var line = inputBuffer.slice(0, end);
inputBuffer = inputBuffer.slice(realEnd + 1);
more = handleLine(line, realEnd + 1);
if (headers["content-length"] != undefined) {
bodyToRead = parseInt(headers["content-length"]);
if (bodyToRead > 16 * 1024 * 1024) {
badRequest(client, 'Request too large: ' + bodyToRead + '.');
return;
}
body = new Uint8Array(bodyToRead);
client.info = 'waiting for body';
resetTimeout(requestCount);
} else if (headers["connection"]
&& headers["connection"].toLowerCase().split(",").map(x => x.trim()).indexOf("upgrade") != -1
&& headers["upgrade"]
&& headers["upgrade"].toLowerCase() == "websocket") {
isWebsocket = true;
client.info = 'websocket';
let requestObject = new Request(request[0], request[1], request[2], headers, body, client);
let response = new Response(requestObject, client);
handleWebSocketRequest(requestObject, response, client);
/* Prevent the timeout from disconnecting us. */
requestCount++;
} else {
finish();
}
}
}
}
if (!parsing_header && inputBuffer.length)
{
let offset = body.length - bodyToRead;
let length = Math.min(inputBuffer.length, body.length - offset);
if (inputBuffer.length > body.length - offset) {
body.set(inputBuffer.slice(0, length), offset);
inputBuffer = inputBuffer.slice(length);
} else {
more = handleLine(inputBuffer, inputBuffer.length);
inputBuffer = new Uint8Array(0);
body.set(inputBuffer, offset);
}
bodyToRead -= length;
if (bodyToRead <= 0) {
finish();
}
}
} else {
@ -586,12 +559,12 @@ function handleConnection(client) {
});
}
var kBacklog = 8;
var kHost = "0.0.0.0"
let kBacklog = 8;
let kHost = "0.0.0.0"
var socket = new Socket();
let socket = new Socket();
socket.bind(kHost, tildefriends.http_port).then(function() {
var listenResult = socket.listen(kBacklog, function() {
let listenResult = socket.listen(kBacklog, function() {
socket.accept().then(handleConnection).catch(function(error) {
logError("[" + new Date() + "] accept error " + error);
});
@ -601,17 +574,17 @@ socket.bind(kHost, tildefriends.http_port).then(function() {
});
if (tildefriends.https_port) {
var tls = {};
var secureSocket = new Socket();
let tls = {};
let secureSocket = new Socket();
secureSocket.bind(kHost, tildefriends.https_port).then(function() {
return secureSocket.listen(kBacklog, async function() {
try {
var client = await secureSocket.accept();
let client = await secureSocket.accept();
client.tls = true;
const kCertificatePath = "data/httpd/certificate.pem";
const kPrivateKeyPath = "data/httpd/privatekey.pem";
var stat = await Promise.all([
let stat = await Promise.all([
await File.stat(kCertificatePath),
await File.stat(kPrivateKeyPath),
]);
@ -621,8 +594,8 @@ if (tildefriends.https_port) {
tls.keyStat.mtime != stat[1].mtime ||
tls.keyStat.size != stat[1].size) {
print("Reloading " + kCertificatePath + " and " + kPrivateKeyPath);
var privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
var certificate = utf8Decode(await File.readFile(kCertificatePath));
let privateKey = utf8Decode(await File.readFile(kPrivateKeyPath));
let certificate = utf8Decode(await File.readFile(kCertificatePath));
tls.context = new TlsContext();
tls.context.setPrivateKey(privateKey);