index.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. var _ = require('lodash')
  2. var httpProxy = require('http-proxy')
  3. var configFactory = require('./config-factory')
  4. var handlers = require('./handlers')
  5. var contextMatcher = require('./context-matcher')
  6. var PathRewriter = require('./path-rewriter')
  7. var Router = require('./router')
  8. var logger = require('./logger').getInstance()
  9. var getArrow = require('./logger').getArrow
  10. module.exports = HttpProxyMiddleware
  11. function HttpProxyMiddleware (context, opts) {
  12. // https://github.com/chimurai/http-proxy-middleware/issues/57
  13. var wsUpgradeDebounced = _.debounce(handleUpgrade)
  14. var wsInitialized = false
  15. var config = configFactory.createConfig(context, opts)
  16. var proxyOptions = config.options
  17. // create proxy
  18. var proxy = httpProxy.createProxyServer({})
  19. logger.info('[HPM] Proxy created:', config.context, ' -> ', proxyOptions.target)
  20. var pathRewriter = PathRewriter.create(proxyOptions.pathRewrite) // returns undefined when "pathRewrite" is not provided
  21. // attach handler to http-proxy events
  22. handlers.init(proxy, proxyOptions)
  23. // log errors for debug purpose
  24. proxy.on('error', logError)
  25. // https://github.com/chimurai/http-proxy-middleware/issues/19
  26. // expose function to upgrade externally
  27. middleware.upgrade = wsUpgradeDebounced
  28. return middleware
  29. function middleware (req, res, next) {
  30. if (shouldProxy(config.context, req)) {
  31. var activeProxyOptions = prepareProxyRequest(req)
  32. proxy.web(req, res, activeProxyOptions)
  33. } else {
  34. next()
  35. }
  36. if (proxyOptions.ws === true) {
  37. // use initial request to access the server object to subscribe to http upgrade event
  38. catchUpgradeRequest(req.connection.server)
  39. }
  40. }
  41. function catchUpgradeRequest (server) {
  42. // subscribe once; don't subscribe on every request...
  43. // https://github.com/chimurai/http-proxy-middleware/issues/113
  44. if (!wsInitialized) {
  45. server.on('upgrade', wsUpgradeDebounced)
  46. wsInitialized = true
  47. }
  48. }
  49. function handleUpgrade (req, socket, head) {
  50. // set to initialized when used externally
  51. wsInitialized = true
  52. if (shouldProxy(config.context, req)) {
  53. var activeProxyOptions = prepareProxyRequest(req)
  54. proxy.ws(req, socket, head, activeProxyOptions)
  55. logger.info('[HPM] Upgrading to WebSocket')
  56. }
  57. }
  58. /**
  59. * Determine whether request should be proxied.
  60. *
  61. * @private
  62. * @param {String} context [description]
  63. * @param {Object} req [description]
  64. * @return {Boolean}
  65. */
  66. function shouldProxy (context, req) {
  67. var path = (req.originalUrl || req.url)
  68. return contextMatcher.match(context, path, req)
  69. }
  70. /**
  71. * Apply option.router and option.pathRewrite
  72. * Order matters:
  73. * Router uses original path for routing;
  74. * NOT the modified path, after it has been rewritten by pathRewrite
  75. * @param {Object} req
  76. * @return {Object} proxy options
  77. */
  78. function prepareProxyRequest (req) {
  79. // https://github.com/chimurai/http-proxy-middleware/issues/17
  80. // https://github.com/chimurai/http-proxy-middleware/issues/94
  81. req.url = (req.originalUrl || req.url)
  82. // store uri before it gets rewritten for logging
  83. var originalPath = req.url
  84. var newProxyOptions = _.assign({}, proxyOptions)
  85. // Apply in order:
  86. // 1. option.router
  87. // 2. option.pathRewrite
  88. __applyRouter(req, newProxyOptions)
  89. __applyPathRewrite(req, pathRewriter)
  90. // debug logging for both http(s) and websockets
  91. if (proxyOptions.logLevel === 'debug') {
  92. var arrow = getArrow(originalPath, req.url, proxyOptions.target, newProxyOptions.target)
  93. logger.debug('[HPM] %s %s %s %s', req.method, originalPath, arrow, newProxyOptions.target)
  94. }
  95. return newProxyOptions
  96. }
  97. // Modify option.target when router present.
  98. function __applyRouter (req, options) {
  99. var newTarget
  100. if (options.router) {
  101. newTarget = Router.getTarget(req, options)
  102. if (newTarget) {
  103. logger.debug('[HPM] Router new target: %s -> "%s"', options.target, newTarget)
  104. options.target = newTarget
  105. }
  106. }
  107. }
  108. // rewrite path
  109. function __applyPathRewrite (req, pathRewriter) {
  110. if (pathRewriter) {
  111. var path = pathRewriter(req.url, req)
  112. if (typeof path === 'string') {
  113. req.url = path
  114. } else {
  115. logger.info('[HPM] pathRewrite: No rewritten path found. (%s)', req.url)
  116. }
  117. }
  118. }
  119. function logError (err, req, res) {
  120. var hostname = (req.headers && req.headers.host) || (req.hostname || req.host) // (websocket) || (node0.10 || node 4/5)
  121. var target = proxyOptions.target.host || proxyOptions.target
  122. var errorMessage = '[HPM] Error occurred while trying to proxy request %s from %s to %s (%s) (%s)'
  123. var errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors' // link to Node Common Systems Errors page
  124. logger.error(errorMessage, req.url, hostname, target, err.code, errReference)
  125. }
  126. }