web-incoming.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. var httpNative = require('http'),
  2. httpsNative = require('https'),
  3. web_o = require('./web-outgoing'),
  4. common = require('../common'),
  5. followRedirects = require('follow-redirects');
  6. web_o = Object.keys(web_o).map(function(pass) {
  7. return web_o[pass];
  8. });
  9. var nativeAgents = { http: httpNative, https: httpsNative };
  10. /*!
  11. * Array of passes.
  12. *
  13. * A `pass` is just a function that is executed on `req, res, options`
  14. * so that you can easily add new checks while still keeping the base
  15. * flexible.
  16. */
  17. module.exports = {
  18. /**
  19. * Sets `content-length` to '0' if request is of DELETE type.
  20. *
  21. * @param {ClientRequest} Req Request object
  22. * @param {IncomingMessage} Res Response object
  23. * @param {Object} Options Config object passed to the proxy
  24. *
  25. * @api private
  26. */
  27. deleteLength: function deleteLength(req, res, options) {
  28. if((req.method === 'DELETE' || req.method === 'OPTIONS')
  29. && !req.headers['content-length']) {
  30. req.headers['content-length'] = '0';
  31. delete req.headers['transfer-encoding'];
  32. }
  33. },
  34. /**
  35. * Sets timeout in request socket if it was specified in options.
  36. *
  37. * @param {ClientRequest} Req Request object
  38. * @param {IncomingMessage} Res Response object
  39. * @param {Object} Options Config object passed to the proxy
  40. *
  41. * @api private
  42. */
  43. timeout: function timeout(req, res, options) {
  44. if(options.timeout) {
  45. req.socket.setTimeout(options.timeout);
  46. }
  47. },
  48. /**
  49. * Sets `x-forwarded-*` headers if specified in config.
  50. *
  51. * @param {ClientRequest} Req Request object
  52. * @param {IncomingMessage} Res Response object
  53. * @param {Object} Options Config object passed to the proxy
  54. *
  55. * @api private
  56. */
  57. XHeaders: function XHeaders(req, res, options) {
  58. if(!options.xfwd) return;
  59. var encrypted = req.isSpdy || common.hasEncryptedConnection(req);
  60. var values = {
  61. for : req.connection.remoteAddress || req.socket.remoteAddress,
  62. port : common.getPort(req),
  63. proto: encrypted ? 'https' : 'http'
  64. };
  65. ['for', 'port', 'proto'].forEach(function(header) {
  66. req.headers['x-forwarded-' + header] =
  67. (req.headers['x-forwarded-' + header] || '') +
  68. (req.headers['x-forwarded-' + header] ? ',' : '') +
  69. values[header];
  70. });
  71. req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || '';
  72. },
  73. /**
  74. * Does the actual proxying. If `forward` is enabled fires up
  75. * a ForwardStream, same happens for ProxyStream. The request
  76. * just dies otherwise.
  77. *
  78. * @param {ClientRequest} Req Request object
  79. * @param {IncomingMessage} Res Response object
  80. * @param {Object} Options Config object passed to the proxy
  81. *
  82. * @api private
  83. */
  84. stream: function stream(req, res, options, _, server, clb) {
  85. // And we begin!
  86. server.emit('start', req, res, options.target || options.forward);
  87. var agents = options.followRedirects ? followRedirects : nativeAgents;
  88. var http = agents.http;
  89. var https = agents.https;
  90. if(options.forward) {
  91. // If forward enable, so just pipe the request
  92. var forwardReq = (options.forward.protocol === 'https:' ? https : http).request(
  93. common.setupOutgoing(options.ssl || {}, options, req, 'forward')
  94. );
  95. // error handler (e.g. ECONNRESET, ECONNREFUSED)
  96. // Handle errors on incoming request as well as it makes sense to
  97. var forwardError = createErrorHandler(forwardReq, options.forward);
  98. req.on('error', forwardError);
  99. forwardReq.on('error', forwardError);
  100. (options.buffer || req).pipe(forwardReq);
  101. if(!options.target) { return res.end(); }
  102. }
  103. // Request initalization
  104. var proxyReq = (options.target.protocol === 'https:' ? https : http).request(
  105. common.setupOutgoing(options.ssl || {}, options, req)
  106. );
  107. // Enable developers to modify the proxyReq before headers are sent
  108. proxyReq.on('socket', function(socket) {
  109. if(server && !proxyReq.getHeader('expect')) {
  110. server.emit('proxyReq', proxyReq, req, res, options);
  111. }
  112. });
  113. // allow outgoing socket to timeout so that we could
  114. // show an error page at the initial request
  115. if(options.proxyTimeout) {
  116. proxyReq.setTimeout(options.proxyTimeout, function() {
  117. proxyReq.abort();
  118. });
  119. }
  120. // Ensure we abort proxy if request is aborted
  121. req.on('aborted', function () {
  122. proxyReq.abort();
  123. });
  124. // handle errors in proxy and incoming request, just like for forward proxy
  125. var proxyError = createErrorHandler(proxyReq, options.target);
  126. req.on('error', proxyError);
  127. proxyReq.on('error', proxyError);
  128. function createErrorHandler(proxyReq, url) {
  129. return function proxyError(err) {
  130. if (req.socket.destroyed && err.code === 'ECONNRESET') {
  131. server.emit('econnreset', err, req, res, url);
  132. return proxyReq.abort();
  133. }
  134. if (clb) {
  135. clb(err, req, res, url);
  136. } else {
  137. server.emit('error', err, req, res, url);
  138. }
  139. }
  140. }
  141. (options.buffer || req).pipe(proxyReq);
  142. proxyReq.on('response', function(proxyRes) {
  143. if(server) { server.emit('proxyRes', proxyRes, req, res); }
  144. if(!res.headersSent && !options.selfHandleResponse) {
  145. for(var i=0; i < web_o.length; i++) {
  146. if(web_o[i](req, res, proxyRes, options)) { break; }
  147. }
  148. }
  149. if (!res.finished) {
  150. // Allow us to listen when the proxy has completed
  151. proxyRes.on('end', function () {
  152. if (server) server.emit('end', req, res, proxyRes);
  153. });
  154. // We pipe to the response unless its expected to be handled by the user
  155. if (!options.selfHandleResponse) proxyRes.pipe(res);
  156. } else {
  157. if (server) server.emit('end', req, res, proxyRes);
  158. }
  159. });
  160. }
  161. };