150 lines
6.0 KiB
JavaScript
150 lines
6.0 KiB
JavaScript
const Path = require('path');
|
|
module.exports.Server = class fawServer extends require('events').EventEmitter {
|
|
constructor({ insecure = false, keepConnectionTimeout = 30, enableHttp2Push = false, rejectUnauthorized = true, peerMaxConcurrentStreams = 6, maxHeaderListPairs = 20, requestCert = false, debug = true, share = {}, cert, key, host = 'localhost', memory = 10, http1 = false } = { http1: false, host: 'localhost', memory: 10 }) {
|
|
if (!new.target) {
|
|
return new fawServer(...arguments);
|
|
}
|
|
super();
|
|
if (!insecure && !cert && !key) {
|
|
({ 'private': key, cert } = require('self-signed').generate(null, {
|
|
keySize: 2048,
|
|
pkcs7: true,
|
|
algorithm: 'sha256',
|
|
expire: 1 * 24 * 60 * 60 * 1000 * 30 // one day
|
|
}));
|
|
}
|
|
this.functions = new Map();
|
|
this.share = share;
|
|
this.debug = debug;
|
|
if (!insecure) {
|
|
this._server = require('http2').createSecureServer({
|
|
settings: { enablePush: enableHttp2Push },
|
|
handshakeTimeout: 10 * 1000,
|
|
sessionTimeout: keepConnectionTimeout,
|
|
rejectUnauthorized, requestCert, pauseOnConnect: true, ipv6Only: false, cert, key, allowHTTP1: http1, maxSessionMemory: memory,
|
|
maxHeaderListPairs, peerMaxConcurrentStreams
|
|
});
|
|
} else {
|
|
this._server = require('http2').createServer({
|
|
settings: { enablePush: enableHttp2Push },
|
|
handshakeTimeout: 10 * 1000,
|
|
sessionTimeout: keepConnectionTimeout,
|
|
rejectUnauthorized, requestCert, pauseOnConnect: true, ipv6Only: false, allowHTTP1: http1, maxSessionMemory: memory,
|
|
maxHeaderListPairs, peerMaxConcurrentStreams
|
|
});
|
|
}
|
|
this._server.on('error', this.emit.bind(this, 'error'));
|
|
this._server.on('close', this.emit.bind(this, 'close'));
|
|
this._server.on('listening', this.emit.bind(this, 'listening'));
|
|
this._server.on('stream', this._onStream.bind(undefined, this));
|
|
this._server.on('session', this.emit.bind(this, 'session'));
|
|
if (!http1)
|
|
this._server.on('unknownProtocol', this.emit.bind(this, 'unknownProtocol'));
|
|
this.address = this._server.address.bind(this._server);
|
|
this.setTimeout = this._server.setTimeout.bind(this._server);
|
|
return this;
|
|
}
|
|
scanDirectory({ replace = false, recursiceLevel = -1, path = process.cwd(), __tmpMap = new Map() } = {}) {
|
|
let files = require('fs').readdirSync(path, { withFileTypes: true, encoding: 'ascii' });
|
|
for (let file of files) {
|
|
if (file.isFile()) {
|
|
if (Path.extname(file.name) != '.js' && file.name !== '.js') {
|
|
continue;
|
|
}
|
|
let absPath;
|
|
|
|
absPath = require.resolve(Path.join(path, file.name));
|
|
delete require.cache[absPath];
|
|
let mod = require(absPath);
|
|
let modName = ((!mod.name || mod.name.length === 0) ? undefined : mod.name) || Path.basename(file.name, '.js');
|
|
if (modName === '') {
|
|
modName = undefined;
|
|
}
|
|
if ((this.functions.has(modName) || __tmpMap.has(modName)) && !replace) {
|
|
throw new Error(`"${absPath}": ${modName} name exists`);
|
|
}
|
|
__tmpMap.set(modName, mod);
|
|
} else if (file.isDirectory()) {
|
|
if (recursiceLevel === 0) {
|
|
continue;
|
|
}
|
|
if (recursiceLevel > 0) {
|
|
--recursiceLevel;
|
|
}
|
|
this.scanDirectory({ replace, recursiceLevel, path: Path.join(path, file.name), __tmpMap });
|
|
}
|
|
}
|
|
|
|
for (const [modName, mod] of __tmpMap.entries()) {
|
|
this.functions.set(modName, mod);
|
|
}
|
|
return Array.from(__tmpMap.keys());
|
|
}
|
|
close() {
|
|
let that = this;
|
|
return new Promise((R, T) => {
|
|
that.once('close', R);
|
|
try {
|
|
that._server.close();
|
|
} catch (e) {
|
|
T(e);
|
|
} finally {
|
|
that.off('close', R);
|
|
}
|
|
});
|
|
}
|
|
async _onStream(that, stream, headers) {
|
|
stream.on('error', (e) => {
|
|
if (that.debug)
|
|
console.error(e.message);
|
|
});
|
|
if (headers[':method'] === 'OPTIONS') {
|
|
stream.respond({
|
|
':status': 204,
|
|
'Allow-Control-Allow-Origin': that.address().address
|
|
});
|
|
return stream.end();
|
|
}
|
|
let modName = (headers[':path'].slice(1).length === 0) ? undefined : headers[':path'].slice(1);
|
|
let mod = that.functions.get(modName);
|
|
if (!mod) {
|
|
mod = that.functions.get(undefined);
|
|
}
|
|
try {
|
|
if (!mod) {
|
|
throw new Error('undifined function not defined!');
|
|
}
|
|
console.time(modName);
|
|
let result = mod.call(that, { share: that.share, stream, headers });
|
|
if (result instanceof Promise) await result;
|
|
console.timeEnd(modName);
|
|
} catch (err) {
|
|
console.timeEnd(modName);
|
|
try {
|
|
stream.respond({
|
|
':status': (err.code >= 100 && err.code < 600) ? Math.floor(err.code) : 500
|
|
});
|
|
} catch (_) { }
|
|
try {
|
|
if (!stream.closed)
|
|
stream.end();
|
|
} catch (_) { }
|
|
if (that.debug) { console.warn(err); }
|
|
}
|
|
}
|
|
listen({ port = 0, host, reuse = false, ipv6Only } = {}) {
|
|
let that = this;
|
|
return new Promise((R, T) => {
|
|
try {
|
|
that._server.listen({ port, hostname: host, exclusive: !reuse, ipv6Only }, (e) => {
|
|
if (e) T(e); else R();
|
|
});
|
|
} catch (e) {
|
|
T(e);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
|