diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1bd722694 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +*.swp diff --git a/index.js b/index.js new file mode 100644 index 000000000..7007beda1 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/caronte'); \ No newline at end of file diff --git a/lib/caronte.js b/lib/caronte.js new file mode 100644 index 000000000..97e2411b6 --- /dev/null +++ b/lib/caronte.js @@ -0,0 +1,61 @@ +var http = require('http'), + https = require('https'), + url = require('url'), + caronte = require('./caronte'), + events = require('eventemitter2'), + proxy = exports; + +/** + * Creates the proxy server. + * + * Examples: + * + * caronte.createProxyServer({ .. }, 8000) + * // => '{ web: [Function], ws: [Function] ... }' + * + * @param {Object} Options Config object passed to the proxy + * + * @return {Object} Proxy Proxy object with handlers for `ws` and `web` requests + * + * @api public + */ + +proxy.createProxyServer = function createProxyServer(options) { + if(!options) { + throw new Error([ + "`options` is needed and it must have the following layout:", + " ", + " { ", + " target : ", + " forward: ", + " ssl : ", + " ws : ", + " xfwd : ", + " } ", + " ", + "NOTE: `options.ws` and `options.ssl` are optional ", + " either one or both `options.target` and ", + " `options.forward` must exist " + ].join("\n")); + } + + ['target', 'forward'].forEach(function(key) { + options[key] = url.parse(options[key]); + }); + + return { + __proto__: new events.EventEmitter2({ wildcard: true, delimiter: ':' }), + web : caronte.createWebProxy(options), + ws : caronte.createWsProxy(options), + listen : function listen(port) { + var server = options.ssl ? http.createServer(this.web) : https.createServer(options.ssl, this.web); + + if(options.ws) { + server.on('upgrade', this.ws); + } + + return server; + } + }; +}; + diff --git a/lib/caronte/index.js b/lib/caronte/index.js new file mode 100644 index 000000000..25b4984c8 --- /dev/null +++ b/lib/caronte/index.js @@ -0,0 +1,4 @@ +var caronte = exports; + +caronte.createWebProxy = require('./web'); +caronte.createWsProxy = require('./ws'); \ No newline at end of file diff --git a/lib/caronte/streams/forward.js b/lib/caronte/streams/forward.js new file mode 100644 index 000000000..d813e1cc6 --- /dev/null +++ b/lib/caronte/streams/forward.js @@ -0,0 +1,3 @@ +function ForwardStream() { + +} \ No newline at end of file diff --git a/lib/caronte/streams/proxy.js b/lib/caronte/streams/proxy.js new file mode 100644 index 000000000..8d1f0672f --- /dev/null +++ b/lib/caronte/streams/proxy.js @@ -0,0 +1,3 @@ +function ProxyStream() { + +} \ No newline at end of file diff --git a/lib/caronte/web.js b/lib/caronte/web.js new file mode 100644 index 000000000..3c76b3ab1 --- /dev/null +++ b/lib/caronte/web.js @@ -0,0 +1,25 @@ +var passes = require('./web/'); + +module.exports = createWebProxy; + +function createWebProxy(options) { + passes = Object.keys(passes).map(function(pass) { + return passes[pass]; + }); + + return function(req, res) { + var self = this; + + self.emit('caronte:web:begin', req, res); + + passes.forEach(function(pass) { + var event = 'caronte:web:' + pass.name.toLowerCase(); + + self.emit(event + ':begin', req, res); + pass(req, res, options); + self.emit(event + ':end'); + }); + + self.emit('caronte:web:end'); + }; +}; \ No newline at end of file diff --git a/lib/caronte/web/index.js b/lib/caronte/web/index.js new file mode 100644 index 000000000..f0d9426ea --- /dev/null +++ b/lib/caronte/web/index.js @@ -0,0 +1,56 @@ +var ForwardStream = require('../streams/forward'), + ProxyStream = require('../streams/proxy'), + passes = exports; + +/*! + * List of passes. + * + * A `pass` is just a function that is executed on `req, res, options` + * so that you can easily add new checks while still keeping the base + * flexible. + * + */ + +passes.XHeaders = XHeaders; +passes.deleteLength = deleteLength; +passes.timeout = timeout; +passes.stream = stream; + +function deleteLength(req, res, options) { + if(req.method === 'DELETE' && !req.headers['content-length']) { + req.headers['content-length'] = '0'; + } +} + +function timeout(req, res, options) { + if(options.timeout) { + req.socket.setTimeout(options.timeout); + } +} + +function XHeaders(req, res, options) { + var values = { + for : req.connection.remoteAddress || req.socket.remoteAddress, + port : req.connection.remotePort || req.socket.remotePort, + proto: req.isSpdy ? 'https' : (req.connection.pair ? 'https' : 'http') + }; + + ['for', 'port', 'proto'].forEach(function(header) { + req.headers['x-forwarded-' + header] = + (req.headers['x-forwarded-' + header] || '') + + (req.headers['x-forwarded-' + header] ? ',' : '') + + values[header] + }); +} + +function stream(req, res, options) { + if(options.forward) { + req.pipe(new ForwardStream(options.forward)); + } + + if(options.target) { + return req.pipe(new ProxyStream(res, options)).pipe(res); + } + + res.end(); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 000000000..67ab1fd14 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name" : "caronte", + "version" : "0.0.0", + "description" : "HTTP proxying for the masses", + "author" : "yawnt ", + + "main" : "index.js", + + "dependencies" : { + "eventemitter2": "*" + }, + "devDependencies": { + "mocha" : "*", + "expect.js": "*", + "dox" : "*" + }, + "scripts" : { + "test" : "mocha -t 20000 -R spec -r expect test/*-test.js" + }, + + "license" : "MIT" +}