Merge pull request #5 from Freeboard/cluster_support

Added cluster support
master
Jim Heising 2016-12-26 15:01:43 -08:00 committed by GitHub
commit cef0ce54a6
2 changed files with 133 additions and 146 deletions

View File

@ -6,3 +6,4 @@ exports.max_request_length = 100000; // The maximum length of characters allowed
exports.enable_rate_limiting = true;
exports.max_requests_per_second = 10; // The maximum number of requests per second to allow from a given IP.
exports.blacklist_hostname_regex = /^(10\.|192\.|127\.|localhost$)/i; // Good for limiting access to internal IP addresses and hosts.
exports.cluster_process_count = Number(process.env.CLUSTER_PROCESS_COUNT) || require("os").cpus().length;

276
server.js
View File

@ -2,205 +2,191 @@ var http = require('http');
var config = require("./config");
var url = require("url");
var request = require("request");
var cluster = require('cluster');
var throttle = require("tokenthrottle")({rate: config.max_requests_per_second});
var publicAddressFinder = require("public-address");
var publicIP;
// Get our public IP address
publicAddressFinder(function(err, data){
if(!err && data)
{
publicIP = data.address;
}
publicAddressFinder(function (err, data) {
if (!err && data) {
publicIP = data.address;
}
});
function addCORSHeaders(req, res)
{
if (req.method.toUpperCase() === "OPTIONS")
{
if(req.headers["access-control-request-headers"])
{
res.setHeader("Access-Control-Allow-Headers", req.headers["access-control-request-headers"]);
}
function addCORSHeaders(req, res) {
if (req.method.toUpperCase() === "OPTIONS") {
if (req.headers["access-control-request-headers"]) {
res.setHeader("Access-Control-Allow-Headers", req.headers["access-control-request-headers"]);
}
if(req.headers["access-control-request-method"])
{
res.setHeader("Access-Control-Allow-Methods", req.headers["access-control-request-method"]);
}
}
if (req.headers["access-control-request-method"]) {
res.setHeader("Access-Control-Allow-Methods", req.headers["access-control-request-method"]);
}
}
if(req.headers["origin"])
{
res.setHeader("Access-Control-Allow-Origin", req.headers["origin"]);
}
else
{
res.setHeader("Access-Control-Allow-Origin", "*");
}
if (req.headers["origin"]) {
res.setHeader("Access-Control-Allow-Origin", req.headers["origin"]);
}
else {
res.setHeader("Access-Control-Allow-Origin", "*");
}
}
function writeResponse(res, httpCode, body) {
res.statusCode = httpCode;
res.end(body);
res.statusCode = httpCode;
res.end(body);
}
function sendInvalidURLResponse(res) {
return writeResponse(res, 404, "url must be in the form of /fetch/{some_url_here}");
return writeResponse(res, 404, "url must be in the form of /fetch/{some_url_here}");
}
function sendTooBigResponse(res) {
return writeResponse(res, 413, "the content in the request or response cannot exceed " + config.max_request_length + " characters.");
return writeResponse(res, 413, "the content in the request or response cannot exceed " + config.max_request_length + " characters.");
}
function getClientAddress(req) {
return (req.headers['x-forwarded-for'] || '').split(',')[0]
|| req.connection.remoteAddress;
return (req.headers['x-forwarded-for'] || '').split(',')[0]
|| req.connection.remoteAddress;
}
function processRequest(req, res)
{
addCORSHeaders(req, res);
function processRequest(req, res) {
addCORSHeaders(req, res);
// Return options pre-flight requests right away
if (req.method.toUpperCase() === "OPTIONS")
{
return writeResponse(res, 204);
}
// Return options pre-flight requests right away
if (req.method.toUpperCase() === "OPTIONS") {
return writeResponse(res, 204);
}
var result = config.fetch_regex.exec(req.url);
var result = config.fetch_regex.exec(req.url);
if (result && result.length == 2 && result[1]) {
var remoteURL;
if (result && result.length == 2 && result[1]) {
var remoteURL;
try {
remoteURL = url.parse(decodeURI(result[1]));
}
catch (e) {
return sendInvalidURLResponse(res);
}
try {
remoteURL = url.parse(decodeURI(result[1]));
}
catch (e) {
return sendInvalidURLResponse(res);
}
// We don't support relative links
if(!remoteURL.host)
{
return writeResponse(res, 404, "relative URLS are not supported");
}
// We don't support relative links
if (!remoteURL.host) {
return writeResponse(res, 404, "relative URLS are not supported");
}
// Naughty, naughty— deny requests to blacklisted hosts
if(config.blacklist_hostname_regex.test(remoteURL.hostname))
{
return writeResponse(res, 400, "naughty, naughty...");
}
// Naughty, naughty— deny requests to blacklisted hosts
if (config.blacklist_hostname_regex.test(remoteURL.hostname)) {
return writeResponse(res, 400, "naughty, naughty...");
}
// We only support http and https
if (remoteURL.protocol != "http:" && remoteURL.protocol !== "https:") {
return writeResponse(res, 400, "only http and https are supported");
}
// We only support http and https
if (remoteURL.protocol != "http:" && remoteURL.protocol !== "https:") {
return writeResponse(res, 400, "only http and https are supported");
}
if(publicIP)
{
// Add an X-Forwarded-For header
if(req.headers["x-forwarded-for"])
{
req.headers["x-forwarded-for"] += ", " + publicIP;
}
else
{
req.headers["x-forwarded-for"] = req.clientIP + ", " + publicIP;
}
}
if (publicIP) {
// Add an X-Forwarded-For header
if (req.headers["x-forwarded-for"]) {
req.headers["x-forwarded-for"] += ", " + publicIP;
}
else {
req.headers["x-forwarded-for"] = req.clientIP + ", " + publicIP;
}
}
// Make sure the host header is to the URL we're requesting, not thingproxy
if(req.headers["host"]) {
if (req.headers["host"]) {
req.headers["host"] = remoteURL.host;
}
var proxyRequest = request({
url: remoteURL,
headers: req.headers,
method: req.method,
timeout: config.proxy_request_timeout_ms,
strictSSL : false
});
var proxyRequest = request({
url: remoteURL,
headers: req.headers,
method: req.method,
timeout: config.proxy_request_timeout_ms,
strictSSL: false
});
proxyRequest.on('error', function(err){
proxyRequest.on('error', function (err) {
if(err.code === "ENOTFOUND")
{
return writeResponse(res, 502, "host cannot be found.")
}
else
{
console.log("Proxy Request Error: " + err.toString());
return writeResponse(res, 500);
}
if (err.code === "ENOTFOUND") {
return writeResponse(res, 502, "host cannot be found.")
}
else {
console.log("Proxy Request Error: " + err.toString());
return writeResponse(res, 500);
}
});
});
var requestSize = 0;
var proxyResponseSize = 0;
var requestSize = 0;
var proxyResponseSize = 0;
req.pipe(proxyRequest).on('data', function(data){
req.pipe(proxyRequest).on('data', function (data) {
requestSize += data.length;
requestSize += data.length;
if(requestSize >= config.max_request_length)
{
proxyRequest.end();
return sendTooBigResponse(res);
}
});
if (requestSize >= config.max_request_length) {
proxyRequest.end();
return sendTooBigResponse(res);
}
});
proxyRequest.pipe(res).on('data', function (data) {
proxyRequest.pipe(res).on('data', function (data) {
proxyResponseSize += data.length;
proxyResponseSize += data.length;
if(proxyResponseSize >= config.max_request_length)
{
proxyRequest.end();
return sendTooBigResponse(res);
}
});
}
else {
return sendInvalidURLResponse(res);
}
if (proxyResponseSize >= config.max_request_length) {
proxyRequest.end();
return sendTooBigResponse(res);
}
});
}
else {
return sendInvalidURLResponse(res);
}
}
http.createServer(function (req, res) {
if (cluster.isMaster) {
for (var i = 0; i < config.cluster_process_count; i++) {
cluster.fork();
}
}
else
{
http.createServer(function (req, res) {
// Process AWS health checks
if(req.url === "/health")
{
return writeResponse(res, 200);
}
// Process AWS health checks
if (req.url === "/health") {
return writeResponse(res, 200);
}
var clientIP = getClientAddress(req);
var clientIP = getClientAddress(req);
req.clientIP = clientIP;
req.clientIP = clientIP;
// Log our request
if(config.enable_logging)
{
console.log("%s %s %s", (new Date()).toJSON(), clientIP, req.method, req.url);
}
// Log our request
if (config.enable_logging) {
console.log("%s %s %s", (new Date()).toJSON(), clientIP, req.method, req.url);
}
if(config.enable_rate_limiting)
{
throttle.rateLimit(clientIP, function(err, limited) {
if (limited)
{
return writeResponse(res, 429, "enhance your calm");
}
if (config.enable_rate_limiting) {
throttle.rateLimit(clientIP, function (err, limited) {
if (limited) {
return writeResponse(res, 429, "enhance your calm");
}
processRequest(req, res);
})
}
else
{
processRequest(req, res);
}
processRequest(req, res);
})
}
else {
processRequest(req, res);
}
}).listen(config.port);
}).listen(config.port);
console.log("thingproxy.freeboard.io started");
console.log("thingproxy.freeboard.io process started (PID " + process.pid + ")");
}