artdaq_node_server  v1_01_01
serverbase_test.js
1 #!/usr/bin/node
2 // serverbase.js : v0.5 : Node HTTPS Server
3 // Author: Eric Flumerfelt, FNAL RSI
4 // Last Modified: June 3, 2015
5 // Modified By: Eric Flumerfelt
6 //
7 // serverbase sets up a basic HTTPS server and directs requests
8 // to one of its submodules.
9 //
10 // Implementation Notes: modules should assign their emitter to the module_holder[<modulename>] object
11 // modules will emit 'data' and 'end' signals and implement the function MasterInitFunction()
12 
13 var fs = require('fs');
14 var path_module = require('path');
15 var module_holder = {};
16 var workerData = {};
17 var child_process = require('child_process');
18 
19 var util = require('util');
20 var log_file = fs.createWriteStream('/tmp/server.' + process.env["USER"] + '.log', { flags : 'a' });
21 var log_stdout = process.stdout;
22 
23 var getversion = function () {
24  console.log("Getting Server Version");
25  if (fs.existsSync("./version.txt")) {
26  console.log("Reading Server Version from File");
27  return "" + fs.readFileSync("./version.txt");
28  }
29  else {
30  child_process.exec("git describe --tags", function (error, stdout, stderr) {
31  version = stdout.trim() + "-Git";
32  child_process.exec("git status --porcelain", function (error, stdout) {
33  if (stdout.length > 0) {
34  version += "*";
35  }
36  });
37  });
38  }
39 }
40 var version = getversion();
41 
42 var config = {
43  ignored_modules: [],
44  baseport: 8080,
45  portOffset: 80,
46  hostname: "localhost",
47  module_config: []
48 };
49 function loadConfig() {
50  if (fs.existsSync("config.json")) {
51  config = JSON.parse(fs.readFileSync("config.json"));
52  } else {
53  fs.writeFileSync("config.json", JSON.stringify(config));
54  }
55 
56  if (process.env.ARTDAQDEMO_BASE_PORT) {
57  config.baseport = parseInt(process.env["ARTDAQDEMO_BASE_PORT"]) + config.portOffset;
58  }
59 
60  if (config.hostname === "localhost" && cluster.isMaster) {
61  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.");
62  }
63 }
64 
65 loadConfig();
66 
67 console.log = function (d) { //
68  log_file.write(util.format(d) + '\n');
69  log_stdout.write(util.format(d) + '\n');
70 };
71 
72 function LoadCerts(path) {
73  var output = [];
74  var files = fs.readdirSync(path);
75  for (var i = 0; i < files.length; i++) {
76  if (files[i].search(".pem") > 0 || files[i].search(".crt") > 0) {
77  output.push(fs.readFileSync(path + "/" + files[i]));
78  }
79  }
80  return output;
81 }
82 
83 // Sub-Module files
84 // From: http://stackoverflow.com/questions/10914751/loading-node-js-modules-dynamically-based-on-route
85 function LoadModules(path) {
86  var stat = fs.lstatSync(path);
87  if (stat.isDirectory()) {
88  // we have a directory: do a tree walk
89  var files = fs.readdirSync(path);
90  var f, l = files.length;
91  for (var i = 0; i < l; i++) {
92  f = path_module.join(path, files[i]);
93  LoadModules(f);
94  }
95  } else if (path.search("_module.js") > 0 && path.search("js~") < 0) {
96  console.log("Loading Submodule " + path);
97  // we have a file: load it
98  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
99  require(path)(module_holder);
100  console.log("Initialized Submodule " + path);
101  }
102 }
103 var DIR = path_module.join(__dirname, "modules");
104 LoadModules(DIR);
105 
106 // Node.js framework "includes"
107 var https = require('https');
108 var http = require('http');
109 var url = require('url');
110 var qs = require('querystring');
111 
112 for (var name in module_holder) {
113  if (module_holder.hasOwnProperty(name)) {
114  module_holder[name].on("message", function (data) {
115  //console.log("Received message from module " + data.name);
116  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
117  //process.send(data);
118  });
119  try {
120  module_holder[name].MasterInitFunction(workerData, config.module_config[name]);
121  module_holder[name].WorkerInitFunction(workerData);
122  } catch (err) {
123  ;
124  }
125  }
126 }
127 
128 function serve(req, res, readOnly, username) {
129  // req is the HTTP request, res is the response the server will send
130  // pathname is the URL after the http://host:port/ clause
131  var pathname = url.parse(req.url, true).pathname;
132  if (pathname[0] === '/') {
133  pathname = pathname.substr(1);
134  }
135 
136  var moduleName = pathname.substr(0, pathname.indexOf('/'));
137  var functionName = pathname.substr(pathname.indexOf('/') + 1);
138 
139  var dnsDone = false;
140  // ReSharper disable once UseOfImplicitGlobalInFunctionScope
141  require('dns').reverse(req.connection.remoteAddress, function (err, domains) {
142  dnsDone = true;
143  if (!err) {
144  if (functionName.search(".min.map") < 0) {
145  // ReSharper disable UseOfImplicitGlobalInFunctionScope
146  console.log("Received " + req.method + ", Client: " + domains[0] + " [" + req.connection.remoteAddress + "], PID: " + process.pid + " Module: " + moduleName + ", function: " + functionName);
147 // ReSharper restore UseOfImplicitGlobalInFunctionScope
148  }
149  return domains[0];
150  } else {
151  if (functionName.search(".min.map") < 0) {
152  // ReSharper disable UseOfImplicitGlobalInFunctionScope
153  console.log("Received " + req.method + ", Client: " + req.connection.remoteAddress + ", PID: " + process.pid + " Module: " + moduleName + ", function: " + functionName);
154 // ReSharper restore UseOfImplicitGlobalInFunctionScope
155  }
156  return "";
157  }
158  });
159  if (functionName.search("GET_ServerVersion") >= 0) {
160  res.setHeader("Content-Type", "text/plain");
161  res.statusCode = 200;
162  res.end(version);
163  return;
164  }
165  if (moduleName === ".." || functionName.search("\\.\\.") >= 0) {
166  console.log("Possible break-in attempt!: " + pathname);
167  res.writeHeader(404, { 'Content-Type': 'text/html' });
168  res.end("Error");
169  return;
170  }
171  res.setHeader("Content-Type", "application/json");
172  res.statusCode = 200;
173  // Log to console...
174  //console.log("Received " + req.method + " for " + pathname);
175  //console.log("Proceeding...");
176 
177  // If we're recieving a POST to /runcommand (As defined in the module),
178  // handle that here
179  if (req.method === "POST") {
180  var body = "";
181 
182  // Callback for request data (may come in async)
183  req.on('data', function (data) {
184  body += data;
185  });
186 
187  req.on('end', function () {
188  // Get the content of the POST request
189  var post;
190  try {
191  post = JSON.parse(body);
192  } catch (e) {
193  post = qs.parse(body);
194  }
195  post.who = username;
196 
197  if (module_holder[moduleName] != null) {
198  console.log("Module " + moduleName + ", function " + functionName + " accessType " + (readOnly ? "RO" : "RW"));
199  var dataTemp = "";
200  module_holder[moduleName].removeAllListeners('data').on('data', function (data) {
201  dataTemp += data;
202  });
203  module_holder[moduleName].removeAllListeners('end').on('end', function (data) {
204  //console.log("POST Operation Complete, sending data to client: " + JSON.stringify(dataTemp + data));
205  res.end(JSON.stringify(dataTemp + data));
206  });
207  module_holder[moduleName].removeAllListeners('stream').on('stream', function (str, hdrs, code) {
208  console.log("Stream message received: " + hdrs + " CODE: " + code);
209  res.writeHead(code, hdrs);
210  str.pipe(res);
211  });
212  var data;
213  if (readOnly) {
214  try {
215  data = module_holder[moduleName]["RO_" + functionName](post, workerData[moduleName]);
216  if (data != null) {
217  //console.log("RO POST Returning: " + JSON.stringify(data));
218  res.end(JSON.stringify(data));
219  }
220  } catch (err) {
221  console.log("Error caught: " + err.stack);
222  if (err instanceof TypeError) {
223  //console.log( "Unauthorized access attempt: " + username + ": " + moduleName + "/" + functionName );
224  res.end(JSON.stringify(null));
225  }
226  }
227  } else {
228  try {
229  data = module_holder[moduleName]["RW_" + functionName](post, workerData[moduleName]);
230  if (data != null) {
231  //console.log("RW POST returned data: " + JSON.stringify(data));
232  res.end(JSON.stringify(data));
233  }
234  } catch (err2) {
235  console.log("Error caught; text: " + JSON.stringify(err2));
236  console.log("Error caught: " + err2.stack);
237  if (err2 instanceof TypeError) {
238  //RW_ version not available, try read-only version:
239  data = module_holder[moduleName]["RO_" + functionName](post, workerData[moduleName]);
240  if (data != null) {
241  //console.log("RO Fallback POST returned data: " + JSON.stringify(data));
242  res.end(JSON.stringify(data));
243  }
244  }
245  }
246  }
247  } else {
248  console.log("Unknown POST URL: " + pathname);
249  res.writeHeader(404, { 'Content-Type': 'text/html' });
250  res.end("Error");
251  }
252  });
253  }
254  //We got a GET request!
255  if (req.method === "GET" || req.method === "HEAD") {
256  //console.log(req.headers);
257  if (functionName.indexOf(".") > 0) {
258  //console.log("Client File Access Requested");
259  var ext = functionName.substr(functionName.lastIndexOf(".") + 1);
260  res.setHeader("Content-Type", "text/plain");
261  //console.log("Extension: " + ext);
262  switch (ext) {
263  case "css":
264  res.setHeader("Content-Type", "text/css");
265  break;
266  case "js":
267  res.setHeader("Content-Type", "text/javascript");
268  break;
269  case "html":
270  res.setHeader("Content-Type", "text/html");
271  break;
272  case "htm":
273  res.setHeader("Content-Type", "text/html");
274  break;
275  case "root":
276  res.setHeader("Content-Type", "application/root+root.exe");
277  break;
278  case "gif":
279  res.setHeader("Content-Type", "image/gif");
280  break;
281  }
282 
283  var filename = "./modules/" + moduleName + "/client/" + functionName;
284  if (functionName.search("favicon.ico") >= 0) {
285  filename = "./modules/base/client/images/favicon.ico";
286  }
287  if (fs.existsSync(filename)) {
288  res.setHeader("Content-Length", fs.statSync(filename)["size"]);
289  if (req.headers.range != null) {
290  var range = req.headers.range;
291  var offset = parseInt(range.substr(range.indexOf('=') + 1, range.indexOf('-') - (range.indexOf('=') + 1)));
292  var endOffset = parseInt(range.substr(range.indexOf('-') + 1));
293  console.log("Reading (" + offset + ", " + endOffset + ")");
294 
295  res.setHeader("Content-Length", (endOffset - offset + 1).toString());
296  var readStream = fs.createReadStream(filename, { start: parseInt(offset), end: parseInt(endOffset) });
297  readStream.pipe(res);
298  } else {
299  res.end(fs.readFileSync(filename));
300  }
301  //console.log("Done sending file");
302  } else {
303  console.log("File not found: " + filename);
304  res.setHeader("Content-Type", "text/plain");
305  res.end("File Not Found.");
306  }
307  } else if (module_holder[moduleName] != null) {
308  console.log("Module " + moduleName + ", function GET_" + functionName);
309 
310  var dataTemp = "";
311  module_holder[moduleName].removeAllListeners('data').on('data', function (data) {
312  //res.write(JSON.stringify(data));
313  dataTemp += data;
314  });
315  module_holder[moduleName].removeAllListeners('end').on('end', function (data) {
316  //console.log("GET Operation complete, sending response to client: " + JSON.stringify(dataTemp + data));
317  res.end(JSON.stringify(dataTemp + data));
318  });
319  module_holder[moduleName].removeAllListeners('stream').on('stream', function (str, hdrs, code) {
320  res.writeHead(code, hdrs);
321  str.pipe(res);
322  });
323  try {
324  var data = module_holder[moduleName]["GET_" + functionName](workerData[moduleName]);
325  if (data != null) {
326  //console.log("GET Returned a value, sending response to client: " + JSON.stringify(data));
327  res.end(JSON.stringify(data));
328  }
329  } catch (err) {
330  console.log("Error caught: " + err.stack);
331  }
332  } else {
333  console.log("Sending client.html");
334  // Write out the frame code
335  res.setHeader("Content-Type", "text/html");
336  res.end(fs.readFileSync("./client.html"), 'utf-8');
337  console.log("Done sending client.html");
338  }
339  }
340 };
341 
342 console.log("Setting up options");
343 var options = {
344  key: fs.readFileSync('./certs/server.key'),
345  cert: fs.readFileSync('./certs/server.crt'),
346  ca: LoadCerts("./certs/certificates"),
347  requestCert: true,
348  rejectUnauthorized: false
349 };
350 var authlist = " " + fs.readFileSync("./certs/authorized_users");
351 console.log("Done setting up options");
352 
353 // Make an http server
354 var server = https.createServer(options, function (req, res) {
355  var readOnly = true;
356  var clientCertificate = req.connection.getPeerCertificate();
357  var username = "HTTPS User";
358  if (req.client.authorized) {
359  username = clientCertificate.subject.CN[0];
360  var useremail = clientCertificate.subject.CN[1].substr(4);
361  if (authlist.search(username) > 0 || authlist.search(useremail) > 0) {
362  readOnly = false;
363  }
364  }
365 
366  try {
367  serve(req, res, readOnly, username);
368  } catch (e) {
369  console.trace("Unhandled error in serve: " + JSON.stringify(e));
370  }
371 });
372 var insecureServer = http.createServer(function (req, res) {
373  // serve( req,res,true,"HTTP User" );
374  try {
375  serve(req, res, false, "HTTP User");
376  } catch (e) {
377  console.trace("Unhandled error in serve: " + JSON.stringify(e));
378  }
379 });
380 
381 var baseport = 8080;
382 if (__dirname.search("dev") >= 0) {
383  baseport = 9090;
384 }
385 console.log("Listening on ports " + baseport + " and " + (baseport + 1));
386 server.listen(baseport + 1);
387 insecureServer.listen(baseport);