13 var cluster = require(
'cluster');
14 var numCPUs = require(
"os").cpus().length;
15 var fs = require(
'fs');
16 var path_module = require(
'path');
17 var module_holder = {};
21 var child_process = require(
'child_process');
23 var util = require(
'util');
25 var data_dir =
"/tmp/artdaq_node_server";
26 if (process.env.ARTDAQ_NODE_SERVER_DATADIR) {
27 data_dir = process.env[
"ARTDAQ_NODE_SERVER_DATADIR"];
28 }
else if (!fs.existsSync(data_dir)) {
29 fs.mkdirSync(data_dir);
33 var log_file = fs.createWriteStream(path_module.join(data_dir,
'server.' + process.env[
"USER"] +
'.log'), { flags:
'a' });
34 var log_stdout = process.stdout;
36 var getversion =
function () {
37 console.log(
"Getting Server Version");
38 if (fs.existsSync(
"./version.txt")) {
39 console.log(
"Reading Server Version from File");
40 return "" + fs.readFileSync(
"./version.txt");
42 else if (fs.existsSync(path_module.join(data_dir,
"version.txt"))) {
43 console.log(
"Reading Server Version from File");
44 return "" + fs.readFileSync(path_module.join(data_dir,
"version.txt"));
47 child_process.exec(
"git describe --tags",
function (error, stdout, stderr) {
48 version = stdout.trim() +
"-Git";
49 child_process.exec(
"git status --porcelain",
function (error, stdout) {
50 if (stdout.length > 0) {
53 fs.writeFileSync(path_module.join(data_dir,
"version.txt"), version);
58 var version = getversion();
64 hostname:
"localhost",
67 function loadConfig() {
68 if (fs.existsSync(
"config.json")) {
69 config = JSON.parse(fs.readFileSync(
"config.json"));
71 fs.writeFileSync(
"config.json", JSON.stringify(config));
74 if (process.env.ARTDAQDEMO_BASE_PORT) {
75 config.baseport = parseInt(process.env[
"ARTDAQDEMO_BASE_PORT"]) + config.portOffset;
78 if (config.hostname ===
"localhost" && cluster.isMaster) {
79 console.log(
"Listening only on localhost. To listen on a different address, set \"hostname\" in config.json.\nUse \"0.0.0.0\" to listen on all interfaces.");
85 console.log =
function (d) {
86 log_file.write(util.format(d) +
'\n');
87 log_stdout.write(util.format(d) +
'\n');
90 function MakePathToFile(filename) {
91 var arr = filename.split(path_module.sep).slice(0, -1);
92 if (fs.existsSync(path_module.join(arr))) {
return; }
93 var outputPath = data_dir;
94 while (arr.length > 0) {
95 outputPath = path_module.join(outputPath, arr.shift());
96 if (!fs.existsSync(outputPath)) {
97 fs.mkdirSync(outputPath);
102 function LoadCerts(path) {
103 if (!fs.existsSync(path)) {
104 console.log(
"Creating certificates directory");
106 var cert = fs.createWriteStream(path_module.join(path,
"cilogon-basic.pem"));
107 https.get(
"https://cilogon.org/cilogon-basic.pem",
function (res) { res.pipe(cert); });
108 var cert2 = fs.createWriteStream(path_module.join(path,
"cilogon-basic.crt"));
109 https.get(
"https://cilogon.org/cilogon-basic.crt",
function (res) { res.pipe(cert2); });
112 var files = fs.readdirSync(path);
113 for (var i = 0; i < files.length; i++) {
114 if (files[i].search(
".pem") > 0 || files[i].search(
".crt") > 0) {
115 output.push(fs.readFileSync(path +
"/" + files[i]));
121 function GetCILogonCRL(path) {
123 var file = fs.createWriteStream(path_module.join(path,
"cilogon-basic.r0"));
124 http.get(
"http://crl-cilogon.ncsa-security.net/cilogon-basic.r0",
function (res) { res.pipe(file); });
125 var file2 = fs.createWriteStream(path_module.join(path,
"cilogon-basic.crl"));
126 http.get(
"http://crl-cilogon.ncsa-security.net/cilogon-basic.crl",
function (res) { res.pipe(file2); });
129 function LoadCRLs(path) {
130 if (!fs.existsSync(path)) {
131 console.log(
"Creating directory " + path);
136 var files = fs.readdirSync(path);
137 for (var i = 0; i < files.length; i++) {
138 if (files[i].search(
".r0") > 0 || files[i].search(
".crl") > 0) {
139 output.push(fs.readFileSync(path +
"/" + files[i]));
147 function LoadModules(path) {
148 var stat = fs.lstatSync(path);
149 if (stat.isDirectory()) {
151 var files = fs.readdirSync(path);
152 var f, l = files.length;
153 for (var i = 0; i < l; i++) {
154 f = path_module.join(path, files[i]);
157 }
else if (path.search(
"_module.js") > 0 && path.search(
"js~") < 0) {
158 for (var im in config.ignored_modules) {
159 if (config.ignored_modules.hasOwnProperty(im) && path.search(config.ignored_modules[im]) >= 0) {
163 console.log(
"Loading Submodule " + path);
166 require(path)(module_holder);
167 console.log(
"Initialized Submodule " + path);
170 var DIR = path_module.join(__dirname,
"modules");
175 if (cluster.isMaster) {
177 function messageHandler(msg) {
181 console.log(
"Depreciated message recieved!");
184 if (msg[
"name"] ===
"request") {
185 console.log(
"Request for Worker Data received");
186 Object.keys(cluster.workers).forEach(
function (
id) {
187 cluster.workers[id].send(workerData);
190 else if (!msg[
"target"]) {
191 console.log(
"Depreciated message recieved!");
198 if (!msg[
"method"]) {
200 workerData[msg.name][msg.target] = msg.data;
202 else if (msg[
"method"] ===
"push") {
204 workerData[msg.name][msg.target].push(msg.data);
206 Object.keys(cluster.workers).forEach(
function (
id) {
207 cluster.workers[id].send({ name: msg.name, target: msg.target, data: workerData[msg.name][msg.target] });
214 for (var name in module_holder) {
215 if (module_holder.hasOwnProperty(name)) {
217 module_holder[name].MasterInitFunction(workerData, config.module_config[name]);
221 module_holder[name].on(
"message", messageHandler);
226 cluster.on(
'online',
function (worker) {
227 worker.send(workerData);
231 for (var i = 0; i < numCPUs; i++) {
233 var worker = cluster.fork();
234 worker.on(
'message', messageHandler);
238 cluster.on(
"exit",
function () {
239 var newWorker = cluster.fork();
240 newWorker.on(
'message', messageHandler);
244 var https = require(
'https');
245 var http = require(
'http');
246 var zlib = require(
'zlib');
247 var url = require(
'url');
248 var qs = require(
'querystring');
250 function workerMessageHandler(msg) {
255 for (var name in module_holder) {
256 if (module_holder.hasOwnProperty(name)) {
258 module_holder[name].Update(workerData[name]);
265 if (!msg[
"target"]) {
267 workerData[msg.name] = msg.data;
270 workerData[msg.name][msg.target] = msg.data;
273 module_holder[msg.name].Update(workerData[msg.name]);
278 process.send({ name:
'request' });
279 process.on(
'message', workerMessageHandler);
281 for (var name in module_holder) {
282 if (module_holder.hasOwnProperty(name)) {
283 module_holder[name].on(
"message",
function (data) {
289 module_holder[name].WorkerInitFunction(workerData);
296 function serve(req, res, readOnly, username) {
301 var pathname = url.parse(req.url,
true).pathname;
302 if (pathname[0] ===
'/') {
303 pathname = pathname.substr(1);
306 var moduleName = pathname.substr(0, pathname.indexOf(
'/'));
307 var functionName = pathname.substr(pathname.indexOf(
'/') + 1);
308 if (workerData[
"serverbase"][req.connection.remoteAddress]) {
309 var clientInfo = workerData[
"serverbase"][req.connection.remoteAddress];
310 if (clientInfo.lastModuleName === moduleName && clientInfo.lastFunctionName === functionName) {
311 if (clientInfo.lastReqTime >= Date.now() - 500) {
312 console.log(
"Flood control active, denying request!");
313 res.writeHeader(200, {
'Content-Type':
'text/html' });
314 res.end(
"{\"Success\": true}");
320 name:
"serverbase", target: req.connection.remoteAddress, data: {
321 lastModuleName: moduleName,
322 lastFunctionName: functionName,
323 lastReqTime: Date.now()
329 require(
'dns').reverse(req.connection.remoteAddress, function (err, domains) {
332 if (functionName.search(
".min.map") < 0) {
334 console.log(
"PID: " + process.pid +
": " +
"Received " + req.method +
", Client: " + domains[0] +
" [" + req.connection.remoteAddress +
"], Module: " + moduleName +
", function: " + functionName);
339 if (functionName.search(
".min.map") < 0) {
341 console.log(
"Received " + req.method +
", Client: " + req.connection.remoteAddress +
", PID: " + process.pid +
" Module: " + moduleName +
", function: " + functionName);
347 if (functionName.search(
"GET_ServerVersion") >= 0) {
348 res.setHeader(
"Content-Type",
"text/plain");
349 res.statusCode = 200;
353 if (moduleName ===
".." || functionName.search(
"\\.\\.") >= 0) {
354 console.log(
"Possible break-in attempt!: " + pathname);
355 res.writeHeader(404, {
'Content-Type':
'text/html' });
359 res.setHeader(
"Content-Type",
"application/json");
360 res.statusCode = 200;
367 if (req.method ===
"POST") {
371 req.on(
'data',
function (data) {
375 req.on(
'end',
function () {
379 post = JSON.parse(body);
381 post = qs.parse(body);
385 if (module_holder[moduleName] != null) {
386 console.log(
"Module " + moduleName +
", function " + functionName +
" accessType " + (readOnly ?
"RO" :
"RW"));
388 module_holder[moduleName].removeAllListeners(
'data').on(
'data',
function (data) {
391 module_holder[moduleName].removeAllListeners(
'end').on(
'end',
function (data) {
393 res.end(JSON.stringify(dataTemp + data));
395 module_holder[moduleName].removeAllListeners(
'stream').on(
'stream',
function (str, hdrs, code) {
396 console.log(
"Stream message received: " + hdrs +
" CODE: " + code);
397 res.writeHead(code, hdrs);
403 data = module_holder[moduleName][
"RO_" + functionName](post, workerData[moduleName]);
406 res.end(JSON.stringify(data));
409 if (err instanceof TypeError) {
411 res.end(JSON.stringify(null));
416 data = module_holder[moduleName][
"RW_" + functionName](post, workerData[moduleName]);
419 res.end(JSON.stringify(data));
422 console.log(
"Error caught; text: " + JSON.stringify(err2));
423 if (err2 instanceof TypeError) {
425 data = module_holder[moduleName][
"RO_" + functionName](post, workerData[moduleName]);
428 res.end(JSON.stringify(data));
434 console.log(
"Unknown POST URL: " + pathname);
435 res.writeHeader(404, {
'Content-Type':
'text/html' });
441 if (req.method ===
"GET" || req.method ===
"HEAD") {
443 if (functionName.indexOf(
".") > 0) {
445 var ext = functionName.substr(functionName.lastIndexOf(
".") + 1);
446 res.setHeader(
"Content-Type",
"text/plain");
450 res.setHeader(
"Content-Type",
"text/css");
453 res.setHeader(
"Content-Type",
"text/javascript");
456 res.setHeader(
"Content-Type",
"text/html");
459 res.setHeader(
"Content-Type",
"text/html");
462 res.setHeader(
"Content-Type",
"application/root+root.exe");
465 res.setHeader(
"Content-Type",
"image/gif");
469 var filename =
"./modules/" + moduleName +
"/client/" + functionName;
470 if (functionName.search(
"favicon.ico") >= 0) {
471 filename =
"./modules/base/client/images/favicon.ico";
473 if (fs.existsSync(filename)) {
474 res.setHeader(
"Content-Length", fs.statSync(filename)[
"size"]);
475 if (req.headers.range != null) {
476 var range = req.headers.range;
477 var offset = parseInt(range.substr(range.indexOf(
'=') + 1, range.indexOf(
'-') - (range.indexOf(
'=') + 1)));
478 var endOffset = parseInt(range.substr(range.indexOf(
'-') + 1));
479 console.log(
"Reading (" + offset +
", " + endOffset +
")");
481 res.setHeader(
"Content-Length", (endOffset - offset + 1).toString());
482 var readStream = fs.createReadStream(filename, { start: parseInt(offset), end: parseInt(endOffset) });
483 readStream.pipe(res);
485 console.log(
"PID: " + process.pid +
": " +
"Sending file");
486 res.end(fs.readFileSync(filename));
488 console.log(
"PID: " + process.pid +
": " +
"Done sending file " + filename);
490 console.log(
"File not found: " + filename);
491 res.setHeader(
"Content-Type",
"text/plain");
492 res.end(
"File Not Found.");
494 }
else if (module_holder[moduleName] != null) {
498 module_holder[moduleName].removeAllListeners(
'data').on(
'data',
function (data) {
502 module_holder[moduleName].removeAllListeners(
'end').on(
'end',
function (data) {
504 res.end(JSON.stringify(dataTemp + data));
506 module_holder[moduleName].removeAllListeners(
'stream').on(
'stream',
function (str, hdrs, code) {
507 res.writeHead(code, hdrs);
510 var data = module_holder[moduleName][
"GET_" + functionName](workerData[moduleName]);
513 res.end(JSON.stringify(data));
516 console.log(
"Sending client.html");
518 res.setHeader(
"Content-Type",
"text/html");
519 res.end(fs.readFileSync(
"./client.html"),
'utf-8');
520 console.log(
"Done sending client.html");
525 console.log(
"Setting up options");
527 key: fs.readFileSync(
'./certs/server.key'),
528 cert: fs.readFileSync(
'./certs/server.crt'),
529 ca: LoadCerts(path_module.join(data_dir,
"certificates")),
530 crl: LoadCRLs(path_module.join(data_dir,
"certificates")),
532 rejectUnauthorized:
false
534 var authlist =
" " + fs.readFileSync(
"./certs/authorized_users");
535 console.log(
"Done setting up options");
538 var server = https.createServer(options,
function (req, res) {
540 var clientCertificate = req.connection.getPeerCertificate();
541 var username =
"HTTPS User";
542 if (req.client.authorized) {
548 username = clientCertificate.subject.CN[0];
549 var useremail = clientCertificate.subjectaltname.substr(6);
550 if (authlist.search(username) > 0 || authlist.search(useremail) > 0) {
555 console.log(
"User: " + username +
", readOnly: " + readOnly);
558 serve(req, res, readOnly, username);
560 console.trace(
"Unhandled error in serve: " + JSON.stringify(e));
563 var insecureServer = http.createServer(
function (req, res) {
566 serve(req, res,
false,
"HTTP User");
568 console.trace(
"Unhandled error in serve: " + JSON.stringify(e));
572 console.log(
"Listening on " + config.hostname +
" ports " + config.baseport +
" and " + (config.baseport + 1));
573 server.listen(config.baseport + 1, config.hostname);
574 insecureServer.listen(config.baseport, config.hostname);