You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
5.0 KiB

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