Initial commit

pull/2/head
Jim Heising 2014-08-21 15:03:09 -07:00
commit 3b81bd9f7b
4 changed files with 185 additions and 0 deletions

30
.gitignore vendored 100644
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript
.idea

6
config.js 100644
View File

@ -0,0 +1,6 @@
exports.port = process.env.PORT || 3000;
exports.fetch_regex = /^\/fetch\/(.*)$/; // The URL to look for when parsing the request.
exports.proxy_request_timeout_ms = 10000; // The lenght of time we'll wait for a proxy server to respond before timing out.
exports.max_request_length = 100000; // The maximum length of characters allowed for a request or a response.
exports.enable_rate_limiting = true;
exports.max_requests_per_second = 1; // The maximum number of requests per second to allow from a given IP.

12
package.json 100644
View File

@ -0,0 +1,12 @@
{
"name": "thingproxy.freeboard.io",
"version": "0.0.1",
"description": "A simple forward proxy server for processing API calls to servers that don't send CORS headers or support HTTPS.",
"main": "thingproxy.js",
"author": "Jim Heising <jim@buglabs.net>",
"license": "MIT",
"dependencies": {
"request": "^2.40.0",
"tokenthrottle": "^1.1.0"
}
}

137
thingproxy.js 100644
View File

@ -0,0 +1,137 @@
var http = require('http');
var config = require("./config");
var url = require("url");
var request = require("request");
var throttle = require("tokenthrottle")({rate: config.max_requests_per_second});
function addCORSHeaders(res) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Headers", "Origin,Content-Type,Accept");
res.setHeader("Content-Type", "text/plain");
}
function writeResponse(res, httpCode, body) {
res.statusCode = httpCode;
res.end(body);
}
function sendInvalidURLResponse(res) {
return writeResponse(res, 400, "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.");
}
function getClientAddress(req) {
return (req.headers['x-forwarded-for'] || '').split(',')[0]
|| req.connection.remoteAddress;
}
function processRequest(req, res)
{
addCORSHeaders(res);
// Return options pre-flight requests right away
if (req.method.toUpperCase() === "OPTIONS") {
return writeResponse(res, 204);
}
var result = config.fetch_regex.exec(req.url);
if (result && result.length == 2 && result[1]) {
var remoteURL;
try {
remoteURL = url.parse(decodeURI(result[1]));
}
catch (e) {
return sendInvalidURLResponse(res);
}
// We only support http and https
if (remoteURL.protocol != "http:" && remoteURL.protocol !== "https:") {
return writeResponse(res, 400, "only http and https are supported");
}
var proxyRequest = request({
url: remoteURL,
headers: req.headers,
method: req.method,
timeout: config.proxy_request_timeout_ms,
strictSSL : false
});
proxyRequest.on('error', function(err){
console.log(err);
if(err.code === "ENOTFOUND")
{
return writeResponse(res, 502, "host cannot be found.")
}
else
{
return writeResponse(res, 500);
}
});
var requestSize = 0;
var proxyResponseSize = 0;
req.pipe(proxyRequest).on('data', function(data){
requestSize += data.length;
if(requestSize >= config.max_request_length)
{
proxyRequest.end();
return sendTooBigResponse(res);
}
});
proxyRequest.pipe(res).on('data', function (data) {
proxyResponseSize += data.length;
if(proxyResponseSize >= config.max_request_length)
{
proxyRequest.end();
return sendTooBigResponse(res);
}
});
}
else {
return sendInvalidURLResponse(res);
}
}
http.createServer(function (req, res) {
var remoteIP = getClientAddress(req);
// Log our request
console.log("%s %s %s", (new Date()).toJSON(), remoteIP, req.method, req.url);
if(config.enable_rate_limiting)
{
throttle.rateLimit(remoteIP, function(err, limited) {
if (limited)
{
return writeResponse(res, 429, "enhance your calm");
}
processRequest(req, res);
})
}
else
{
processRequest(req, res);
}
}).listen(config.port);
console.log("thingproxy.freeboard.io started");