node-red-contrib-web-worldmap
Version:
A Node-RED node to provide a web page of a world map for plotting things on.
178 lines (150 loc) • 5.98 kB
JavaScript
/**
* Module dependencies.
*/
var url = require('url');
var extend = require('extend');
var debug = require('debug')('cgi');
var spawn = require('child_process').spawn;
var CGIParser = require('./parser');
/**
* Module exports.
*/
module.exports = cgi;
/**
* Constants.
*/
var SERVER_SOFTWARE = 'Node/' + process.version;
var SERVER_PROTOCOL = 'HTTP/1.1';
var GATEWAY_INTERFACE = 'CGI/1.1';
function cgi(cgiBin, options) {
options = extend({}, cgi.DEFAULTS, options);
return function layer(req, res, next) {
if (!next) {
// define a default "next" handler if none was passed
next = function(err) {
debug('"next" called: %o', err);
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found\n");
};
}
if (!req.hasOwnProperty("uri")) { req.uri = url.parse(req.url); }
if (req.uri.pathname.substring(0, options.mountPoint.length) !== options.mountPoint) return next();
debug('handling HTTP request: %o', req.url);
var host = (req.headers.host || '').split(':');
var address = host[0];
var port = host[1];
if ((!address || !port) && typeof this.address == 'function') {
var serverAddress = this.address();
debug('server address and port: %o', serverAddress);
if (!address) address = serverAddress.address;
if (!port) port = serverAddress.port;
}
// Take environment variables from the current server process
var env = extend({}, process.env);
// Determine the correct PATH_INFO variable.
// It must be prepended with a "/" char as per:
// https://tools.ietf.org/html/rfc3875#section-4.1.5
var pathInfo = req.uri.pathname.substring(options.mountPoint.length);
if ('/' !== pathInfo[0]) pathInfo = '/' + pathInfo;
debug('calculated PATH_INFO variable: %o', pathInfo);
// These meta-variables below can be overwritten by a
// user's 'env' object in options
extend(env, {
GATEWAY_INTERFACE: GATEWAY_INTERFACE,
SCRIPT_NAME: options.mountPoint,
PATH_INFO: pathInfo,
SERVER_NAME: address || 'unknown',
SERVER_PORT: port || 80,
SERVER_PROTOCOL: SERVER_PROTOCOL,
SERVER_SOFTWARE: SERVER_SOFTWARE
});
// The client HTTP request headers are attached to the env as well,
// in the format: "User-Agent" -> "HTTP_USER_AGENT"
for (var header in req.headers) {
var name = 'HTTP_' + header.toUpperCase().replace(/-/g, '_');
env[name] = req.headers[header];
}
// Now add the user-specified env variables
if (options.env) extend(env, options.env);
// These final environment variables take precedence over user-specified ones.
env.REQUEST_METHOD = req.method;
env.QUERY_STRING = req.uri.query || '';
env.REMOTE_ADDR = req.connection.remoteAddress;
env.REMOTE_PORT = req.connection.remotePort;
if ('content-length' in req.headers) {
env.CONTENT_LENGTH = req.headers['content-length'];
}
if ('content-type' in req.headers) {
env.CONTENT_TYPE = req.headers['content-type'];
}
if ('authorization' in req.headers) {
var auth = req.headers.authorization.split(' ');
env.AUTH_TYPE = auth[0];
//var unbase = new Buffer(auth[1], 'base64').toString().split(':');
}
var opts = extend({}, options);
// Now we can spawn the CGI executable
debug('env: %o', env);
opts.env = env;
var cgiSpawn = spawn(cgiBin, opts.args, opts);
debug('cgi spawn (pid: %o)', cgiSpawn.pid);
// The request body is piped to 'stdin' of the CGI spawn
req.pipe(cgiSpawn.stdin);
// If `options.stderr` is set to a Stream instance, then re-emit the
// 'data' events onto the stream.
if (options.stderr) {
cgiSpawn.stderr.pipe(options.stderr);
}
// A proper CGI script is supposed to print headers to 'stdout'
// followed by a blank line, then a response body.
var cgiResult;
if (!options.nph) {
cgiResult = new CGIParser(cgiSpawn.stdout);
// When the blank line after the headers has been parsed, then
// the 'headers' event is emitted with a Headers instance.
cgiResult.on('headers', function(headers) {
headers.forEach(function(header) {
// Don't set the 'Status' header. It's special, and should be
// used to set the HTTP response code below.
if (header.key === 'Status') return;
res.setHeader(header.key, header.value);
});
// set the response status code
res.statusCode = parseInt(headers.status, 10) || 200;
// The response body is piped to the response body of the HTTP request
cgiResult.pipe(res);
});
} else {
// If it's an NPH script, then responsibility of the HTTP response is
// completely passed off to the child process.
cgiSpawn.stdout.pipe(res.connection);
}
cgiSpawn.on('exit', function(code, signal) {
debug('cgi spawn %o "exit" event (code %o) (signal %o)', cgiSpawn.pid, code, signal);
// TODO: react on a failure status code (dump stderr to the response?)
});
cgiSpawn.stdout.on('end', function () {
// clean up event listeners upon the "end" event
debug('cgi spawn %o stdout "end" event', cgiSpawn.pid);
if (cgiResult) {
cgiResult.cleanup();
}
//if (options.stderr) {
// cgiSpawn.stderr.unpipe(options.stderr);
//}
});
};
}
// The default config options to use for each `cgi()` call.
cgi.DEFAULTS = {
// The 'cgi' handler will take effect when the req.url begins with "mountPoint"
mountPoint: '/',
// Any additional variables to insert into the CGI script's Environment
env: {},
// Set to 'true' if the CGI script is an NPH script
nph: false,
// Set to a `Stream` instance if you want to log stderr of the CGI script somewhere
stderr: null,
// A list of arguments for the cgi bin to be used by spawn
args: []
};