Code coverage report for lib/proxy.js

Statements: 98.04% (50 / 51)      Branches: 96.55% (28 / 29)      Functions: 100% (9 / 9)      Lines: 98.04% (50 / 51)      Ignored: none     

All files » lib/ » proxy.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144    1         1         1 2 2 2         2 30 188 24 24         30 6           1 24       24     24   24     12 12     12 12   12 12 12     24 1 1       1 1       24     24       1 24                               24 8             8   8     24               1 24 12           1   24 48 24     24       24     24   24    
'use strict';
 
var httpProxy    = require('http-proxy'),
    util         = require('util'),
    url          = require('url'),
    configParser = require('./config');
 
var routers = {},
    logger,
    config,
    useGateway;
 
exports.initialize = function initialize(options){
  logger = options.logger || require('./logger');
  config = configParser(options).proxy;
  useGateway = (config.gateway !== null && typeof(config.gateway) === 'object' && typeof(config.gateway.host) === 'string');
 
  // called on every request
  // decides if the request should be handled locally or forwarded to a remote server
  // based on the forwarding ruleset
  return function jsonProxy(req, res, next) {
    var match = config.forward.some(function(rule) {
      if (rule.regexp.test(req.url)) {
        proxyRequest(req, res, rule);
        return true;
      }
    });
 
    // fall through to the next middleware if there wasn't a proxy match rule
    if (match === false) {
      next();
    }
  };
};
 
// injects any LAN proxy servers into the request
function proxyRequest(req, res, rule) {
  var router,
      target,
      path;
 
  injectProxyHeaders(req, rule);
 
  // rewrite the base path of the requested URL
  path = req.url.replace(rule.regexp, rule.target.path);
 
  if (useGateway) {
    // for HTTP LAN proxies, rewrite the request URL from a relative URL to an absolute URL
    // also add a header that can be inspected by the unit tests
    req.url = url.parse(util.format('%s//%s:%s%s', rule.target.protocol, rule.target.host, rule.target.port, path)).href;
    req.headers['X-Forwarded-Url'] = req.url;
 
    // the proxy target is really the HTTP LAN proxy
    target = config.gateway;
    logger.info('proxy: %s %s --> %s:%s --> %s//%s:%s%s', req.method, req.url, config.gateway.host, config.gateway.port, rule.target.protocol, rule.target.host, rule.target.port, path);
  } else {
    target = rule.target;
    logger.info('proxy: %s %s --> %s//%s:%s%s', req.method, req.url, rule.target.protocol, rule.target.host, rule.target.port, path);
    req.url = path;
  }
 
  var errorCallback = function errorCallback(err, proxyRequest, proxyResponse) {
    var status = 500;
    Iif (proxyResponse !== undefined && proxyResponse !== null && proxyResponse.statusCode >= 400) {
      status = proxyResponse.statusCode;
    }
 
    logger.error('proxy: error - %s %s - %s', proxyRequest.method, proxyRequest.url, err.message);
    res.json(status, { error: status, message: err.message });
  };
 
  // get a ProxyServer from the cache
  router = createRouter(target);
 
  // proxy the request
  router.web(req, res, errorCallback);
}
 
// factory method to cache/fetch HttpProxy instances
function createRouter(target) {
  var key = util.format('%s//%s:%s', target.protocol, target.host, target.port),
      router = routers[key],
      options;
 
  // httpProxy.createProxyServer options
  // {
  //   target : <url string to be parsed with the url module>
  //   forward: <url string to be parsed with the url module>
  //   agent  : <object to be passed to http(s).request>
  //   ssl    : <object to be passed to https.createServer()>
  //   ws     : <true/false, if you want to proxy websockets>
  //   xfwd   : <true/false, adds x-forward headers>
  //   secure : <true/false, verify SSL certificate>
  //   toProxy: passes the absolute URL as the path (useful for proxying to proxies)
  // }
 
  if (router === undefined || router === null) {
    options = {
      xfwd: true,
      secure: (target.protocol && target.protocol === 'https://'),
      target: key,
      toProxy: (useGateway === true)
    };
 
    router = httpProxy.createProxyServer(options);
 
    routers[key] = router;
  }
 
  return router;
}
 
// support basic auth for LAN HTTP proxied connections, if needed
// HACK: http-proxy uses node's http.request().pipe(), which doesn't properly
//       support the options.auth setting like http.request.write() as of Node v0.10.24,
//       so this copies the implementation of request.write() from http.request()
// SOURCE: https://github.com/joyent/node/blob/828f14556e0daeae7fdac08fceaa90952de63f73/lib/_http_client.js#L84-L88
function injectAuthHeader(req) {
  if (useGateway === true && typeof(config.gateway.auth) === 'string' && req.headers['authorization'] === undefined) {
    req.headers['authorization'] = 'Basic ' + new Buffer(config.gateway.auth).toString('base64');
  }
}
 
// inject any custom header values into a proxy request
// along with the x-forwarded-for, x-forwarded-port, and via headers
function injectProxyHeaders(req, rule){
  // inject any custom headers as configured
  config.headers.forEach(function(header) {
    if(typeof(header.value) == 'function') {
      req.headers[header.name] = header.value.call(undefined, req);
    }
    else {
      req.headers[header.name] = header.value;
    }
  });
 
  injectAuthHeader(req);
 
  // the HTTP host header is often needed by the target webserver config
  req.headers['host'] = rule.target.host;
  // document that this request was proxied
  req.headers['via'] =  util.format('http://%s:%s', req.connection.address().address, req.connection.address().port);
}