| var httpNative = require('http'), |
| httpsNative = require('https'), |
| web_o = require('./web-outgoing'), |
| common = require('../common'), |
| followRedirects = require('follow-redirects'); |
| |
| web_o = Object.keys(web_o).map(function(pass) { |
| return web_o[pass]; |
| }); |
| |
| var nativeAgents = { http: httpNative, https: httpsNative }; |
| |
| /*! |
| * Array 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. |
| */ |
| |
| |
| module.exports = { |
| |
| /** |
| * 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 |
| */ |
| |
| deleteLength: function deleteLength(req, res, options) { |
| if((req.method === 'DELETE' || req.method === 'OPTIONS') |
| && !req.headers['content-length']) { |
| req.headers['content-length'] = '0'; |
| delete req.headers['transfer-encoding']; |
| } |
| }, |
| |
| /** |
| * 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 |
| */ |
| |
| timeout: 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 |
| */ |
| |
| XHeaders: function XHeaders(req, res, options) { |
| if(!options.xfwd) return; |
| |
| var encrypted = req.isSpdy || common.hasEncryptedConnection(req); |
| var values = { |
| for : req.connection.remoteAddress || req.socket.remoteAddress, |
| port : common.getPort(req), |
| proto: encrypted ? '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]; |
| }); |
| |
| req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || ''; |
| }, |
| |
| /** |
| * 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 |
| */ |
| |
| stream: function stream(req, res, options, _, server, clb) { |
| |
| // And we begin! |
| server.emit('start', req, res, options.target || options.forward); |
| |
| var agents = options.followRedirects ? followRedirects : nativeAgents; |
| var http = agents.http; |
| var https = agents.https; |
| |
| if(options.forward) { |
| // If forward enable, so just pipe the request |
| var forwardReq = (options.forward.protocol === 'https:' ? https : http).request( |
| common.setupOutgoing(options.ssl || {}, options, req, 'forward') |
| ); |
| |
| // error handler (e.g. ECONNRESET, ECONNREFUSED) |
| // Handle errors on incoming request as well as it makes sense to |
| var forwardError = createErrorHandler(forwardReq, options.forward); |
| req.on('error', forwardError); |
| forwardReq.on('error', forwardError); |
| |
| (options.buffer || req).pipe(forwardReq); |
| if(!options.target) { return res.end(); } |
| } |
| |
| // Request initalization |
| var proxyReq = (options.target.protocol === 'https:' ? https : http).request( |
| common.setupOutgoing(options.ssl || {}, options, req) |
| ); |
| |
| // Enable developers to modify the proxyReq before headers are sent |
| proxyReq.on('socket', function(socket) { |
| if(server) { server.emit('proxyReq', proxyReq, req, res, options); } |
| }); |
| |
| // allow outgoing socket to timeout so that we could |
| // show an error page at the initial request |
| if(options.proxyTimeout) { |
| proxyReq.setTimeout(options.proxyTimeout, function() { |
| proxyReq.abort(); |
| }); |
| } |
| |
| // Ensure we abort proxy if request is aborted |
| req.on('aborted', function () { |
| proxyReq.abort(); |
| }); |
| |
| // handle errors in proxy and incoming request, just like for forward proxy |
| var proxyError = createErrorHandler(proxyReq, options.target); |
| req.on('error', proxyError); |
| proxyReq.on('error', proxyError); |
| |
| function createErrorHandler(proxyReq, url) { |
| return function proxyError(err) { |
| if (req.socket.destroyed && err.code === 'ECONNRESET') { |
| server.emit('econnreset', err, req, res, url); |
| return proxyReq.abort(); |
| } |
| |
| if (clb) { |
| clb(err, req, res, url); |
| } else { |
| server.emit('error', err, req, res, url); |
| } |
| } |
| } |
| |
| (options.buffer || req).pipe(proxyReq); |
| |
| proxyReq.on('response', function(proxyRes) { |
| if(server) { server.emit('proxyRes', proxyRes, req, res); } |
| |
| if(!res.headersSent && !options.selfHandleResponse) { |
| for(var i=0; i < web_o.length; i++) { |
| if(web_o[i](req, res, proxyRes, options)) { break; } |
| } |
| } |
| |
| if (!res.finished) { |
| // Allow us to listen when the proxy has completed |
| proxyRes.on('end', function () { |
| if (server) server.emit('end', req, res, proxyRes); |
| }); |
| // We pipe to the response unless its expected to be handled by the user |
| if (!options.selfHandleResponse) proxyRes.pipe(res); |
| } else { |
| if (server) server.emit('end', req, res, proxyRes); |
| } |
| }); |
| } |
| |
| }; |