conextra
Version:
Conextra for Web Development. Helps create a SPWA (Single-page Progressive Web Application).
163 lines (116 loc) • 5.49 kB
JavaScript
'use script';
var { Worker } = require('node:worker_threads'),
http = require("node:http"),
https = require("node:https"),
fs = require('node:fs'),
path = require('node:path'),
crypto = require('node:crypto'),
configSets = require('config-sets'),
options_server = configSets('tiny-https-server'),
conextraConfig = configSets('conextra', {
isDebug: false,
pathToResourseMap: 'data/resourse_map.json',
pathToDataDir: "data"
}),
websocketWorkerMap = {},
isTLS = options_server.port !== 80 && options_server.pathToPrivkey,
port = (options_server.port || 80) + 1,
host = options_server.host || '0.0.0.0',
server = (isTLS ? https : http)
.createServer(function (req, res) {
res.writeHead(403, { 'Content-Type': 'text/plain' });
res.end('403 Forbidden\n');
})
.on('upgrade', upgradeSocket)
.on('clientError', (err, socket) => {
if (err.code === 'ECONNRESET' || !socket.writable) {
return;
}
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
})
.on('error', function (err) { pError('Server error:', err); })
.listen({ port, host, exclusive: false }, function () {
pDebug('WebSocket-Main server started on port', port);
});
if (isTLS && options_server.pathToCert) {
server.setSecureContext({
key: fs.readFileSync(options_server.pathToPrivkey),
cert: fs.readFileSync(options_server.pathToCert)
});
var certUpdateWaitTimeout;
fs.watch(path.dirname(options_server.pathToCert), function (eventType, filename) {
if (!fs.existsSync(options_server.pathToPrivkey) || !fs.existsSync(options_server.pathToCert)) { return; }
clearTimeout(certUpdateWaitTimeout);
certUpdateWaitTimeout = setTimeout(function () {
server.setSecureContext({
key: fs.readFileSync(options_server.pathToPrivkey),
cert: fs.readFileSync(options_server.pathToCert)
});
}, 1000);
});
}
function upgradeSocket(request, socket, head) {
var protocol = request.headers['sec-websocket-protocol'].split(',')[0].trim(),
url = request.url,
ident = protocol + request.url,
//Default port range for WebSocket is 49152-65535
port = 49152;
if (!websocketWorkerMap[ident]) {
while (Object.keys(websocketWorkerMap).find(function (k) { return websocketWorkerMap[k].port === port; })) { port++; }
websocketWorkerMap[ident] = {
port,
worker: new Worker(path.join(__dirname, './websocket_worker.js'), {
workerData: {
isTLS,
host: options_server.host,
port,
url,
protocol
}
})
};
pDebug('WebSocket-Main server create new WebSocket-Worker:', ident, port);
pDebug('WebSocket-Worker.count:', Object.keys(websocketWorkerMap).length);
websocketWorkerMap[ident].worker.on('error', pError);
websocketWorkerMap[ident].worker.on('exit', () => {
delete websocketWorkerMap[ident];
pDebug('WebSocket-Worker.count:', Object.keys(websocketWorkerMap).length);
});
}
setTimeout(sendRedirect, 200, socket, request.headers['sec-websocket-key'] || '', websocketWorkerMap[ident].port, protocol);
function sendRedirect(socket, wstKey, redirectPort, protocol) {
var headers = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept: ' + crypto.createHash('sha1').update(wstKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'),
'Sec-WebSocket-Protocol: ' + protocol,
'\r\n'
].join('\r\n')
if (!['open', 'writeOnly'].includes(socket.readyState)) { return; }
socket.write(headers, 'utf8', function () {
if (!['open', 'writeOnly'].includes(socket.readyState)) { return; }
writeRedirectFrame(function () { // Send redirect frame
if (!['open', 'writeOnly'].includes(socket.readyState)) { return; }
writeCloseFrame(function () { // Send close frame
//socket.end(); // Close the connection
pDebug('WebSocket-Main server redirect_to_port:', redirectPort);
});
});
});
function writeRedirectFrame(cb) {
if (socket.readyState !== 'open') { return; }
socket.write(Buffer.from([129, 23]), 'utf8', function () {
if (socket.readyState !== 'open') { return; }
socket.write('$redirect_to_port:' + redirectPort, 'utf8', cb);
});
}
function writeCloseFrame(cb) {
if (socket.readyState !== 'open') { return; }
socket.write(Buffer.from([136, 0]), 'utf8', cb);
}
}
}
// Debugging
function pDebug(...args) { if (conextraConfig.isDebug) { console.log(`[ DEBUG ] 'ws-user-link' `, ...args); } }
function pError(...args) { if (conextraConfig.isDebug) { console.error(`[ ERROR ] 'ws-user-link' `, ...args); } }