proxy.js 4.1 KB
var url = require('url')
var httpProxy = require('http-proxy')
var _ = require('lodash')

var log = require('../logger').create('proxy')

var parseProxyConfig = function (proxies, config) {
  var endsWithSlash = function (str) {
    return str.substr(-1) === '/'
  }

  if (!proxies) {
    return []
  }

  return _.sortBy(_.map(proxies, function (proxyConfiguration, proxyPath) {
    if (typeof proxyConfiguration === 'string') {
      proxyConfiguration = {target: proxyConfiguration}
    }
    var proxyUrl = proxyConfiguration.target
    var proxyDetails = url.parse(proxyUrl)
    var pathname = proxyDetails.pathname

    // normalize the proxies config
    // should we move this to lib/config.js ?
    if (endsWithSlash(proxyPath) && !endsWithSlash(proxyUrl)) {
      log.warn('proxy "%s" normalized to "%s"', proxyUrl, proxyUrl + '/')
      proxyUrl += '/'
      pathname += '/'
    }

    if (!endsWithSlash(proxyPath) && endsWithSlash(proxyUrl)) {
      log.warn('proxy "%s" normalized to "%s"', proxyPath, proxyPath + '/')
      proxyPath += '/'
    }

    if (pathname === '/' && !endsWithSlash(proxyUrl)) {
      pathname = ''
    }

    var hostname = proxyDetails.hostname || config.hostname
    var protocol = proxyDetails.protocol || config.protocol
    var https = proxyDetails.protocol === 'https:'
    var port
    if (proxyDetails.port) {
      port = proxyDetails.port
    } else if (proxyDetails.protocol) {
      port = proxyDetails.protocol === 'https:' ? '443' : '80'
    } else {
      port = config.port
    }
    var changeOrigin = 'changeOrigin' in proxyConfiguration ? proxyConfiguration.changeOrigin : false
    var proxy = httpProxy.createProxyServer({
      target: {
        host: hostname,
        port: port,
        https: https,
        protocol: protocol
      },
      xfwd: true,
      changeOrigin: changeOrigin,
      secure: config.proxyValidateSSL
    })

    proxy.on('error', function proxyError (err, req, res) {
      if (err.code === 'ECONNRESET' && req.socket.destroyed) {
        log.debug('failed to proxy %s (browser hung up the socket)', req.url)
      } else {
        log.warn('failed to proxy %s (%s)', req.url, err.message)
      }

      res.destroy()
    })

    return {
      path: proxyPath,
      baseUrl: pathname,
      host: hostname,
      port: port,
      https: https,
      proxy: proxy
    }
  }), 'path').reverse()
}

/**
 * Returns a handler which understands the proxies and its redirects, along with the proxy to use
 * @param proxies An array of proxy record objects
 * @param urlRoot The URL root that karma is mounted on
 * @return {Function} handler function
 */
var createProxyHandler = function (proxies, urlRoot) {
  if (!proxies.length) {
    var nullProxy = function createNullProxy (request, response, next) {
      return next()
    }
    nullProxy.upgrade = function upgradeNullProxy () {}
    return nullProxy
  }

  var middleware = function createProxy (request, response, next) {
    var proxyRecord = _.find(proxies, function (p) {
      return request.url.indexOf(p.path) === 0
    })

    if (!proxyRecord) {
      return next()
    }

    log.debug('proxying request - %s to %s:%s', request.url, proxyRecord.host, proxyRecord.port)
    request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl)
    proxyRecord.proxy.web(request, response)
  }

  middleware.upgrade = function upgradeProxy (request, socket, head) {
    // special-case karma's route to avoid upgrading it
    if (request.url.indexOf(urlRoot) === 0) {
      log.debug('NOT upgrading proxyWebSocketRequest %s', request.url)
      return
    }

    var proxyRecord = _.find(proxies, function (p) {
      return request.url.indexOf(p.path) === 0
    })

    if (!proxyRecord) {
      return
    }

    log.debug('upgrade proxyWebSocketRequest %s to %s:%s',
      request.url, proxyRecord.host, proxyRecord.port)
    request.url = request.url.replace(proxyRecord.path, proxyRecord.baseUrl)
    proxyRecord.proxy.ws(request, socket, head)
  }

  return middleware
}

exports.create = function (/* config */config, /* config.proxies */proxies) {
  return createProxyHandler(parseProxyConfig(proxies, config), config.urlRoot)
}