functionserver/index.js
2020-04-18 13:44:21 +04:30

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);
}
});
}
};