devebot
Version:
Nodejs Microservice Framework
201 lines (193 loc) • 7.98 kB
JavaScript
;
const Promise = require("bluebird");
const lodash = require("lodash");
const util = require("util");
const http = require("http");
const https = require("https");
const fs = require("fs");
const WebSocketServer = require("ws").Server;
const Kernel = require("./kernel");
const LoggingWrapper = require("./backbone/logging-wrapper");
const RepeatedTimer = require("./backbone/repeated-timer");
const constx = require("./utils/constx");
const chores = require("./utils/chores");
const blockRef = chores.getBlockRef(__filename);
const CLOSING_SERVER_TIMEOUT = 60000;
const FRAMEWORK_NAMESPACE = constx.FRAMEWORK.NAMESPACE;
function Server(params = {}) {
Kernel.call(this, params);
// init the default parameters
const loggingWrapper = new LoggingWrapper(blockRef);
const L = loggingWrapper.getLogger();
const T = loggingWrapper.getTracer();
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "constructor-begin"],
text: " + constructor start ..."
}));
// lookup service instances
const injektor = this._injektor;
delete this._injektor;
const profileConfig = injektor.lookup("profileConfig", chores.injektorContext);
const loggingFactory = injektor.lookup("loggingFactory", chores.injektorContext);
const sandboxManager = injektor.lookup("sandboxManager", chores.injektorContext);
const scriptExecutor = injektor.lookup("scriptExecutor", chores.injektorContext);
const scriptRenderer = injektor.lookup("scriptRenderer", chores.injektorContext);
const securityManager = injektor.lookup("securityManager", chores.injektorContext);
// framework configures
const frameworkCfg = chores.getFrameworkProfileConfig(profileConfig);
// application root url
const basepath = lodash.get(frameworkCfg, ["basepath"]);
const appName = injektor.lookup("appName", chores.injektorContext);
const appRootUrl = basepath || "/-";
const tunnelCfg = lodash.get(frameworkCfg, ["tunnel"], {});
const sslEnabled = tunnelCfg.enabled && tunnelCfg.key_file && tunnelCfg.crt_file;
function processRequest(req, res) {
if (chores.isDevelopmentMode() || frameworkCfg.appInfoLevel === "all") {
const appInfo = injektor.lookup("appInfo", chores.injektorContext);
const appInfoBody = JSON.stringify(appInfo, null, 2);
res.writeHead(200, "OK", {
"Content-Length": Buffer.byteLength(appInfoBody, "utf8"),
"Content-Type": "application/json"
});
res.end(appInfoBody);
} else {
res.writeHead(200, "OK");
res.end();
}
}
// creates a HttpServer instance
const server = sslEnabled ? https.createServer({
key: _readFileSync(tunnelCfg.key_file),
cert: _readFileSync(tunnelCfg.crt_file)
}, processRequest) : http.createServer(processRequest);
const tictac = new RepeatedTimer({
loggingFactory: loggingFactory,
period: 60 * 1000,
target: function () {
L.has("dunce") && L.log("dunce", " - Since: %s, Uptime: %s", this.startTime.toISOString(), this.uptime);
}
});
const mode = ["silent", "tictac", "server"].indexOf(_getServiceMode(frameworkCfg.mode));
this.start = function () {
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "start()"],
text: "start() is invoked"
}));
return Promise.resolve().then(function () {
if (mode === 0) return Promise.resolve();
if (mode === 1) return tictac.start();
return new Promise(function (resolve, reject) {
const serverHost = lodash.get(frameworkCfg, ["host"], "0.0.0.0");
const serverPort = lodash.get(frameworkCfg, ["port"], 17779);
const serverInstance = server.listen(serverPort, serverHost, function () {
const proto = sslEnabled ? "wss" : "ws";
const host = serverInstance.address().address;
const port = serverInstance.address().port;
chores.isVerboseForced(["framework", FRAMEWORK_NAMESPACE], frameworkCfg) && chores.logConsole("%s is listening on %s://%s:%s%s", appName, proto, host, port, appRootUrl);
resolve(serverInstance);
});
});
}).then(function () {
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "start()", "webserver-started"],
text: "webserver has started"
}));
return sandboxManager.startTriggers();
}).then(function (info) {
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "start()", "triggers-started"],
text: "triggers have started"
}));
return info;
});
};
this.open = this.start; // alias
this.stop = function () {
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "close()"],
text: "close() is invoked"
}));
return Promise.resolve().then(function () {
return sandboxManager.stopTriggers();
}).then(function () {
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "close()", "triggers-stopped"],
text: "triggers have stopped"
}));
if (mode === 0) return Promise.resolve();
if (mode === 1) return tictac.stop();
return new Promise(function (resolve, reject) {
const timeoutHandler = setTimeout(function () {
L.has("dunce") && L.log("dunce", "Timeout closing Server");
reject(new Error("CLOSING_SERVER_TIMEOUT"));
}, CLOSING_SERVER_TIMEOUT);
const serverCloseEvent = function () {
L.has("dunce") && L.log("dunce", "HTTP Server is invoked");
if (server && lodash.isFunction(serverCloseEvent)) {
server.removeListener("close", serverCloseEvent);
}
};
server.on("close", serverCloseEvent);
server.close(function () {
L.has("dunce") && L.log("dunce", "HTTP Server has been closed");
clearTimeout(timeoutHandler);
resolve();
});
});
}).then(function () {
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "close()", "webserver-stopped"],
text: "webserver has stopped"
}));
chores.isVerboseForced(["framework", FRAMEWORK_NAMESPACE], frameworkCfg) && chores.logConsole("%s has been closed", appName);
return Promise.resolve();
});
};
this.close = this.stop; // alias
const wss = new WebSocketServer({
server: server,
path: appRootUrl + "/execute",
verifyClient: function (info, callback) {
securityManager.authenticate(lodash.pick(info.req.headers, ["x-token-jwt", "x-token-key", "x-token-secret"])).then(function (output) {
callback(output.result, output.code, output.name);
});
}
});
wss.on("connection", function connection(ws) {
const outlet = scriptRenderer.createOutlet({
ws: ws
});
ws.on("open", function handler() {
L.has("dunce") && L.log("dunce", " - Websocket@server is opened");
});
ws.on("message", function incoming(command) {
L.has("dunce") && L.log("dunce", " - Websocket@server is received a command: <%s>", command);
scriptExecutor.executeCommand(command, outlet);
});
ws.on("close", function handler(code, message) {
L.has("dunce") && L.log("dunce", " - Websocket@server is closed, code: <%s>, message: <%s>", code, message);
});
ws.on("error", function handler(error) {
L.has("dunce") && L.log("dunce", " - Websocket@server encounter an error: <%s>", error);
});
});
wss.on("error", function connection(error) {
L.has("dunce") && L.log("dunce", " - Websocket@server has an error: <%s>", JSON.stringify(error));
});
L.has("silly") && L.log("silly", T.toMessage({
tags: [blockRef, "constructor-end"],
text: " - constructor has finished"
}));
}
util.inherits(Server, Kernel);
module.exports = Server;
const MODE_MAP = {
"command": "server",
"heartbeat": "tictac"
};
function _getServiceMode(mode) {
return lodash.get(MODE_MAP, mode, mode);
}
function _readFileSync(filename) {
return fs.readFileSync(filename);
}