diff --git a/README.md b/README.md index 09b6f75..92e15d7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,46 @@ -http_helper -=========== +# http_helper -A lightweight NodeJS app to provide redirect and proxy service, based on an included config file +# OVERVIEW +A simple node.js redirector and proxy application. Given a config file full of +redirect and proxy mappings, this application will match incoming HOST headers +and take the appropriate action by 301/302 redirecting or proxying. + +This is useful for redirecting www to root domains, redirecting old domains +to new domains, proxying for SSL from non-SSL sources on AWS. + +# PREREQUISITES +* node.js (v0.10.28 preferred) +* npm (comes with node.js) + +# PREFLIGHT +* mv ./config-json.dist to ./config.json +* Update mappings for redirects/proxies based on your needs + +As an example, if we wanted to setup a redirect to yourdomain.com +from www.yourdomain.com, we would add the following block in config.json: + "www.yourdomain.com": { + "host": "http://yourdomain.com", + "code": 301, + "function": "redirect" + }, +(*Note*: The function setting can be either redirect or proxy) + + +# RUNNING THIS APPLICATION +Simply run node ./bin/server.js + + +# FURTHER INFORMATION +Author: JP Schneider (Github: jdotpz, jp@mozillafoundation.org) +Bugs/Requests: https://www.github.com/mozilla/http_helper/issues + +* New Relic monitoring is written into this application. Simply provide your own +newrelic.js or config settings and it will just work. + +* Messina is an amazing GELF log utility written by Brian Brennan (brianloveswords). To output +to your own GELF facility, set the following env variables + +export GRAYLOG_HOST="address.to_your_logging_server.com" #defaults to localhost +export GRAYLOG_PORT=12201 #defaults to 12201 +export GRAYLOG_FACILITY="httphelper-production" #defaults to openbadger +export ENABLE_GELF_LOGS=true diff --git a/bin/server.js b/bin/server.js new file mode 100644 index 0000000..cb746ba --- /dev/null +++ b/bin/server.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node +if ( process.env.NEW_RELIC_ENABLED ) { + require( "newrelic" ); +} + +var config = require("nconf") + .argv() + .file({file:__dirname+"/../config.json"}); + +var helperApp = require('../lib/brain')(config.get('appOptions'), config.get('port')); +console.log('Running redirector / proxy application'); diff --git a/config-json.dist b/config-json.dist new file mode 100644 index 0000000..d372acb --- /dev/null +++ b/config-json.dist @@ -0,0 +1,20 @@ +{ + "port": 80, + "appOptions": { + "host.answering_the_request.com": { + "host": "http://the.target_host.com/target/path", + "function": "proxy" + }, + "a-host.youwant": { + "host": "http://the_url.to_redirect_to.com/path/to/redirect/to", + "code": 301, + "function": "redirect" + }, + "a-host.youwant": { + "host": "http://the_url.to_redirect_to.com/path/to/redirect/to", + "code": 301, + "function": "redirect" + } + } +} + diff --git a/lib/brain.js b/lib/brain.js new file mode 100644 index 0000000..96916f7 --- /dev/null +++ b/lib/brain.js @@ -0,0 +1,84 @@ +var http = require('http'); +var request = require('request'); + +var express = require('express'); +var app = express(); + +// Messina allows for GELF logging +var messina = require('messina'); +var log = messina('myapp'); + +// app.use(log.middleware({ combinedOutput: true })); + +module.exports = function (appOptions, port) { + app.use(log.middleware({ combinedOutput: true })); + + // A simple healthcheck endpoint for load balancers to watch + app.get('/healthcheck', function(req, res){ + res.send('healthcheck'); + console.log('Request to ' + req.protocol + ':://' + req.host + '/' + req.path + ' returned a ' + res.statusCode ); + }); + + // Any other path or route takes us to this block + app.get('*', function(req, res){ + var requestHost = req.headers.host.split(':')[0]; + var hostOptions = appOptions[requestHost]; + + // We only want to allow http protocol + if(req.protocol != 'http'){ + console.log(req.protocol + 'is not a valid protocol'); + res.send(req.protocol + ' is not a valid protocol for this app'); + res.end(); + return; + } + + // We are only proxying or redirecting with GETs + if(req.method != 'GET'){ + console.log(req.method + ' is not an acceptable method'); + res.end(); + return; + } + + // If the host header does not have a corresponding entry in + // the config.json file, bust out. + if(!hostOptions){ + console.log(req.host + ' is not a valid host'); + res.statusCode = 404; + res.setHeader('Content-Type', 'text/plain'); + res.end(req.host + ' is invalid'); + return; + } + + // actionOptions dictates whether this is a proxy or redirect + var actionOptions = hostOptions.function; + + // For requests that match a redirect host + if(actionOptions === "redirect"){ + var url = hostOptions.host + req.url; + + // Set the status and redirect + console.log('Redirecting ' + req.protocol + '://' + req.host + ' to ' + hostOptions.host + ' with a http status code of ' + hostOptions.code); + res.statusCode = hostOptions.code || 302; + res.setHeader('Content-Type', 'text/plain'); + res.setHeader('Location', url); + res.end('Redirecting to '+url); + } + + // For requests that match a proxy host + if(actionOptions === "proxy") { + var url = hostOptions.host + req.url; + request.get(url, function(error, result) { + if (error) { + res.write("there was an error requesting " + req.protocol + '://' + hostOptions.host); + res.write( JSON.stringify(error) ); + return console.error(error); + } + console.log('Pulling ' + url + ' to ' + requestHost); + res.write( result.body ); + res.end(); + return; + }); + } + }); + app.listen(80) ; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..58c4da9 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "mofo_http_manipulator", + "version": "0.0.3", + "author": "JP Schneider jdotpz", + "description": "A http manipulator cobbled together for use in MoFo infra, with code from git://github.com/pksunkara/node-redirect.git", + "main": "", + "repository": { + "type": "git", + "url": "git://github.com/jdotpz/mofo_http_manipulator.git" + }, + "keywords": [ + "redirect", + "302", + "server", + "proxy", + "http" + ], + "homepage": "http://github.com/jdotpz/mofo_http_manipulator", + "scripts": { + "start": "./server" + }, + "contributors": [ + { + "name": "JP Schneider, Pavan Kumar Sunkara, Pomax", + "email": "jp@mozillafoundation.org" + } + ], + "dependencies": { + "nconf": "0.6.x", + "newrelic": "*", + "messina": "0.1.1", + "habitat": "*", + "request": "*" + }, + "devDependencies": {}, + "engines": { + "node": ">=0.6" + }, + "bugs": { + "url": "https://github.com/jdotpz/mofo_http_manipulator/issues" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://github.com/pksunkara/node-redirect/raw/master/LICENSE" + } + ], + "analyze": false +}