tiny-https-server
Version:
Tiny web server with HTTPS support, static file serving, subdomain support, middleware support, and service worker support.
290 lines (188 loc) • 9.37 kB
JavaScript
/** Copyright (c) Manuel Lõhmus (MIT License). */
;
var configSets = require("config-sets"),
{ createHttpServer, createHttpsServer, availableLinks } = require('./server'),
os = require('node:os'),
cluster = require('node:cluster'),
clusterOptions = configSets('web-cluster', {
isDebug: false,
parallelism: 'auto',
httpToHttpsRedirect: true
});
module.exports = WebCluster;
return;
/**
* @typedef {Object} WebCluster
* @property {Options} clusterOptions
* @property {Options} serverOptions
* @property {http} serverHttpToHttps
* @property {[fork]} workers
*/
/**
* Runs the web server in cluster mode
* @param {Options} options
* @param {(server:http)=>void} _initServer
* @returns {WebCluster}
*/
function WebCluster(
options = {},
_initServer = function (server) { /* Initialize */ }
) {
var serverOptions = configSets('tiny-https-server', {}),
isSSL = false,
webCluster = Object.create(null, {
isSSL: { get: function () { return isSSL; }, configurable: false, enumerable: false },
clusterOptions: { get: function () { return clusterOptions; }, configurable: false, enumerable: false },
serverOptions: { get: function () { return serverOptions; }, configurable: false, enumerable: false },
serverHttpToHttps: { value: null, writable: true, configurable: false, enumerable: false },
web_workers: { get: function () { return web_workers; }, configurable: false, enumerable: false },
availableLinks: { value: availableLinks, writable: false, configurable: false, enumerable: false },
}),
web_workers = [];
if (typeof options.isDebug === 'boolean') {
clusterOptions.isDebug = options.isDebug;
serverOptions.isDebug = options.isDebug;
}
delete options.isDebug;
if (options.parallelism) { clusterOptions.parallelism = options.parallelism; }
delete options.parallelism;
serverOptions = configSets.assign(serverOptions, options, true);
isSSL = (serverOptions.port === 80) ? false
: Boolean(serverOptions.key && serverOptions.cert || serverOptions.port === 443);
if (cluster.isPrimary) {
setImmediate(initWorkers);
cluster.on('fork', (worker) => {
pDebug(`Web-Worker.type '${worker.workerType}' started.`);
pDebug("Web-Workers.count", Object.keys(cluster.workers).length);
});
cluster.on('exit', (worker, code, signal) => {
pDebug(`Web-Worker.type '${worker.workerType}' died (`, signal || code, `).`);
pDebug("Web-Workers.count", Object.keys(cluster.workers).length);
if (!Object.keys(cluster.workers).length) { initWorkers(); }
});
return webCluster;
function initWorkers() {
var parallelism = isNaN(parseInt(clusterOptions.parallelism)) ? clusterOptions.parallelism : parseInt(clusterOptions.parallelism),
max_parallelism = os.availableParallelism?.() || os.cpus().length,
workersCount = 0;
//create http to https
if (isSSL && clusterOptions.httpToHttpsRedirect) { webCluster.serverHttpToHttps = createHttp(true); }
// parallelism is a valid number
if (typeof parallelism === 'number' && (0 < parallelism && parallelism < max_parallelism)) {
if (parallelism > 1) { serverOptions.exclusive = false; }
for (var i = 0; i < parallelism; i++) {
webCluster.web_workers.push(createWorker());
}
}
// parallelism is not a valid number
else if (typeof parallelism === 'number') {
serverOptions.exclusive = false;
for (var i = 0; i < max_parallelism; i++) {
webCluster.web_workers.push(createWorker());
}
}
// parallelism is string 'auto' | 'auto 1'| 'auto 2' ...
else {
serverOptions.exclusive = false;
var [auto, start_parallelism] = (parallelism + '').split(/\s+/);
start_parallelism = parseInt(start_parallelism);
// start_parallelism is number
if (!isNaN(start_parallelism)) {
if (start_parallelism > max_parallelism) { start_parallelism = max_parallelism; }
for (var i = 0; i < start_parallelism; i++) {
webCluster.web_workers.push(createWorker('auto'));
}
}
// start_parallelism is not a number, creates core count workers
else {
for (var i = 0; i < max_parallelism / 2; i++) {
webCluster.web_workers.push(createWorker('auto'));
}
}
}
return;
function createWorker(workerType = '') {
switch (workerType) {
case 'auto': return createWorker(true);
case 'autoscaling': return createAutoScalingWorker();
default: return createWorker();
}
return;
function createWorker(auto = false) {
workersCount++;
var worker = cluster.fork({ workerType, exclusive: serverOptions.exclusive });
worker.workerType = workerType;
//pDebug(`Web Worker ${auto ? '(Auto Scaling up) ' : ''}starts:`, worker.id);
//pDebug("Start: Web Worker Count:", workersCount);
worker.on('exit', (code) => {
workersCount--;
if (code) {
//pDebug(`Web Worker ${auto ? '(Auto Scaling up) ' : ''}exit:`, worker.id);
//pDebug("Error: Web Worker Count:", workersCount);
createWorker(workerType);
}
});
worker.on('message', function (msg) {
if (auto && msg.cmd === 'autoscaling') {
createAutoScalingWorker();
}
});
return worker;
}
function createAutoScalingWorker() {
if (workersCount < max_parallelism) {
workersCount++;
var worker = cluster.fork({ workerType: 'autoscaling', exclusive: serverOptions.exclusive });
worker.workerType = 'autoscaling';
//pDebug("Scaled up: Web Worker starts:", worker.id);
//pDebug("Scaled up: Web Worker Count:", workersCount);
worker.on('exit', () => {
workersCount--;
//pDebug("Scaled down: Web Worker exit:", worker.id);
//pDebug("Scaled down: Web Worker Count:", workersCount);
});
worker.on('message', function (msg) {
if (msg.cmd && msg.cmd === 'autoscaling') {
createAutoScalingWorker();
}
});
return worker;
}
}
}
function createHttp(isHttpToHttps = false) {
if (isHttpToHttps) { pDebug("Http -> Https Server starts."); }
else { pDebug("Http Server starts."); }
var httpServer = createHttpServer(serverOptions, isHttpToHttps);
return httpServer;
}
}
}
else if (cluster.isWorker) {
var workerType = process.env.workerType;
serverOptions.exclusive = process.env.exclusive === 'true';
switch (workerType) {
case 'auto': return createServer();
case 'autoscaling': return createServer(true);
default: return createServer();
}
return process;
function createServer(isAutoExit = false) {
var options = configSets.assign({}, serverOptions, true);
var server = isSSL
? createHttpsServer(options)
: createHttpServer(options);
if (isAutoExit) { server.isAutoExit = true; }
server.on("autoscaling", function () {
process.send({ cmd: 'autoscaling' });
});
_initServer.call(webCluster, server);
return server;
}
}
else { pError('Something went wrong.'); }
return webCluster;
}
// Debugging
function pDebug(msg) { if (clusterOptions.isDebug) { console.log(`[ DEBUG ] 'web-cluster' `, ...arguments); } }
function pError(msg) { if (clusterOptions.isDebug) { console.error(`[ ERROR ] 'web-cluster' `, ...arguments); } }