diff --git a/lib/caronte/common.js b/lib/caronte/common.js new file mode 100644 index 000000000..7028bb171 --- /dev/null +++ b/lib/caronte/common.js @@ -0,0 +1,32 @@ +var common = exports; + +/** + * Copies the right headers from `options` and `req` to + * `outgoing` which is then used to fire the proxied + * request. + * + * Examples: + * + * common.setupOutgoing(outgoing, options, req) + * // => { host: ..., hostname: ...} + * + * @param {Object} Outgoing Base object to be filled with required properties + * @param {Object} Options Config object passed to the proxy + * @param {ClientRequest} Req Request Object + *  + * @return {Object} Outgoing Object with all required properties set + * + * @api private + */ + +common.setupOutgoing = function(outgoing, options, req) { + ['host', 'hostname', 'port', 'socketPath', 'agent'].forEach( + function(e) { outgoing[e] = options[e]; } + ); + + ['method', 'path', 'headers'].forEach( + function(e) { outgoing[e] = req[e]; } + ); + + return outgoing; +}; \ No newline at end of file diff --git a/lib/caronte/index.js b/lib/caronte/index.js index 7304850d1..0b9f544c0 100644 --- a/lib/caronte/index.js +++ b/lib/caronte/index.js @@ -5,6 +5,22 @@ var caronte = exports, caronte.createWebProxy = createRightProxy('web'); caronte.createWsProxy = createRightProxy('ws'); +/** + * Returns a function that creates the loader for + * either `ws` or `web`'s passes. + * + * Examples: + * + * caronte.createRightProxy('ws') + * // => [Function] + * + * @param {String} Type Either 'ws' or 'web' + *  + * @return {Function} Loader Function that when called returns an iterator for the right passes + * + * @api private + */ + function createRightProxy(type) { passes = type === 'ws' ? ws : web; return function(options) { diff --git a/lib/caronte/passes/web.js b/lib/caronte/passes/web.js index be6753ccc..c15f4e192 100644 --- a/lib/caronte/passes/web.js +++ b/lib/caronte/passes/web.js @@ -12,19 +12,51 @@ var ForwardStream = require('../streams/forward'), [ // <-- +/** + * Sets `content-length` to '0' if request is of DELETE type. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + function deleteLength(req, res, options) { if(req.method === 'DELETE' && !req.headers['content-length']) { req.headers['content-length'] = '0'; } } +/** + * Sets timeout in request socket if it was specified in options. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + function timeout(req, res, options) { if(options.timeout) { req.socket.setTimeout(options.timeout); } } +/** + * Sets `x-forwarded-*` headers if specified in config. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + function XHeaders(req, res, options) { + if(!options.xfwd) return; + var values = { for : req.connection.remoteAddress || req.socket.remoteAddress, port : req.connection.remotePort || req.socket.remotePort, @@ -39,6 +71,18 @@ function XHeaders(req, res, options) { }); } +/** + * Does the actual proxying. If `forward` is enabled fires up + * a ForwardStream, same happens for ProxyStream. The request + * just dies otherwise. + * + * @param {ClientRequest} Req Request object + * @param {IncomingMessage} Res Response object + * @param {Object} Options Config object passed to the proxy + * + * @api private + */ + function stream(req, res, options) { if(options.forward) { req.pipe(new ForwardStream(options.forward)); diff --git a/lib/caronte/streams/forward.js b/lib/caronte/streams/forward.js index d813e1cc6..640c75d01 100644 --- a/lib/caronte/streams/forward.js +++ b/lib/caronte/streams/forward.js @@ -1,3 +1,79 @@ +var Writable = require('stream').Writable, + common = require('../common'), + http = require('http'), + https = require('https'); + +module.exports = ForwardStream; + +/** + * Forwards the request to the external target specified in options + * + * Examples: + * + * new ForwardStream(options) + * // => { ... } + * + * @param {Object} Options Config object passed to the proxy + *  + * @return {ForwardStream} Stream A clone of ForwardStream + * + * @api private + */ + function ForwardStream() { - -} \ No newline at end of file + Writable.call(this); + + this.once('pipe', this.onPipe); + this.once('finish', this.onFinish); +} + +/** + * Fires up the request to the external target + * + * Examples: + * + * (new ForwardStream(options)).onPipe(req) + * // => undefined + * + * @param {HttpRequest} Req Request object + * + * @api private + */ + +ForwardStream.prototype.onPipe = function(request) { + this.forwardReq = (options.ssl ? https : http).request( + common.setupOutgoing(options.ssl || {}, options, request); + ); +}; + +/** + * Closes forwarded request when `pipe` is finished + * + * Examples: + * + * (new ForwardStream(options)).onFinish() + * // => undefined + * + * @api private + */ + +ForwardStream.prototype.onFinish = function() { + this.forwardReq.end(); +}; + +/** + * Implements `stream.Writable`, writes to the forwarded request + * + * Examples: + * + * (new ForwardStream(options))._write(chunk, encoding, clb) + * // => undefined + * + * @api private + */ + +ForwardStream.prototype._write = function(chunk, encoding, clb) { + this.forwardReq.write(chunk, encoding, clb); +}; + +require('util').inherits(ForwardStream, Writable); \ No newline at end of file