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