proxy.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. const url = require('url')
  2. const { Agent: httpAgent } = require('http')
  3. const { Agent: httpsAgent } = require('https')
  4. const httpProxy = require('http-proxy')
  5. const _ = require('lodash')
  6. const { lookup } = require('../utils/dns-utils')
  7. const log = require('../logger').create('proxy')
  8. function parseProxyConfig (proxies, config) {
  9. proxies = proxies || []
  10. return _.sortBy(_.map(proxies, function (proxyConfiguration, proxyPath) {
  11. if (typeof proxyConfiguration === 'string') {
  12. proxyConfiguration = { target: proxyConfiguration }
  13. }
  14. let proxyUrl = proxyConfiguration.target
  15. // eslint-disable-next-line node/no-deprecated-api
  16. const proxyDetails = url.parse(proxyUrl)
  17. let pathname = proxyDetails.pathname
  18. if (proxyPath.endsWith('/') && !proxyUrl.endsWith('/')) {
  19. log.warn(`proxy "${proxyUrl}" normalized to "${proxyUrl}/"`)
  20. proxyUrl += '/'
  21. pathname += '/'
  22. }
  23. if (!proxyPath.endsWith('/') && proxyUrl.endsWith('/')) {
  24. log.warn(`proxy "${proxyPath}" normalized to "${proxyPath}/"`)
  25. proxyPath += '/'
  26. }
  27. if (pathname === '/' && !proxyUrl.endsWith('/')) {
  28. pathname = ''
  29. }
  30. const hostname = proxyDetails.hostname || config.hostname
  31. const protocol = proxyDetails.protocol || config.protocol
  32. const defaultPorts = {
  33. 'http:': '80',
  34. 'https:': '443'
  35. }
  36. const port = proxyDetails.port || defaultPorts[proxyDetails.protocol] || config.port
  37. const changeOrigin = proxyConfiguration.changeOrigin || false
  38. const Agent = protocol === 'https:' ? httpsAgent : httpAgent
  39. const agent = new Agent({
  40. keepAlive: true,
  41. lookup
  42. })
  43. const proxy = httpProxy.createProxyServer({
  44. target: { host: hostname, port, protocol },
  45. xfwd: true,
  46. changeOrigin: changeOrigin,
  47. secure: config.proxyValidateSSL,
  48. agent
  49. })
  50. ;['proxyReq', 'proxyRes'].forEach(function (name) {
  51. const callback = proxyDetails[name] || config[name]
  52. if (callback) {
  53. proxy.on(name, callback)
  54. }
  55. })
  56. proxy.on('error', function proxyError (err, req, res) {
  57. if (err.code === 'ECONNRESET' && req.socket.destroyed) {
  58. log.debug(`failed to proxy ${req.url} (browser hung up the socket)`)
  59. } else {
  60. log.warn(`failed to proxy ${req.url} (${err.message})`)
  61. }
  62. res.destroy()
  63. })
  64. return { path: proxyPath, baseUrl: pathname, host: hostname, port, proxy, agent }
  65. }), 'path').reverse()
  66. }
  67. /**
  68. * Returns a handler which understands the proxies and its redirects, along with the proxy to use
  69. * @param proxies An array of proxy record objects
  70. * @param urlRoot The URL root that karma is mounted on
  71. * @return {Function} handler function
  72. */
  73. function createProxyHandler (proxies, urlRoot) {
  74. if (!proxies.length) {
  75. const nullProxy = (request, response, next) => next()
  76. nullProxy.upgrade = () => {}
  77. return nullProxy
  78. }
  79. function createProxy (request, response, next) {
  80. const proxyRecord = proxies.find((p) => request.url.startsWith(p.path))
  81. if (proxyRecord) {
  82. log.debug(`proxying request - ${request.url} to ${proxyRecord.host}:${proxyRecord.port}`)
  83. request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl)
  84. proxyRecord.proxy.web(request, response)
  85. } else {
  86. return next()
  87. }
  88. }
  89. createProxy.upgrade = function (request, socket, head) {
  90. // special-case karma's route to avoid upgrading it
  91. if (request.url.startsWith(urlRoot)) {
  92. log.debug(`NOT upgrading proxyWebSocketRequest ${request.url}`)
  93. return
  94. }
  95. const proxyRecord = proxies.find((p) => request.url.startsWith(p.path))
  96. if (proxyRecord) {
  97. log.debug(`upgrade proxyWebSocketRequest ${request.url} to ${proxyRecord.host}:${proxyRecord.port}`)
  98. request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl)
  99. proxyRecord.proxy.ws(request, socket, head)
  100. }
  101. }
  102. return createProxy
  103. }
  104. exports.create = function (/* config */config, /* config.proxies */proxies, /* emitter */emitter) {
  105. const proxyRecords = parseProxyConfig(proxies, config)
  106. emitter.on('exit', (done) => {
  107. log.debug('Destroying proxy agents')
  108. proxyRecords.forEach((proxyRecord) => {
  109. proxyRecord.agent.destroy()
  110. })
  111. done()
  112. })
  113. return createProxyHandler(proxyRecords, config.urlRoot)
  114. }