index.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /**
  2. * Module dependencies.
  3. */
  4. var url = require('url');
  5. var LRU = require('lru-cache');
  6. var extend = require('extend');
  7. var Agent = require('agent-base');
  8. var inherits = require('util').inherits;
  9. var debug = require('debug')('proxy-agent');
  10. var PacProxyAgent = require('pac-proxy-agent');
  11. var HttpProxyAgent = require('http-proxy-agent');
  12. var HttpsProxyAgent = require('https-proxy-agent');
  13. var SocksProxyAgent = require('socks-proxy-agent');
  14. /**
  15. * Module exports.
  16. */
  17. exports = module.exports = ProxyAgent;
  18. /**
  19. * Number of `http.Agent` instances to cache.
  20. *
  21. * This value was arbitrarily chosen... a better
  22. * value could be conceived with some benchmarks.
  23. */
  24. var cacheSize = 20;
  25. /**
  26. * Cache for `http.Agent` instances.
  27. */
  28. exports.cache = new LRU(cacheSize);
  29. /**
  30. * Built-in proxy types.
  31. */
  32. exports.proxies = Object.create(null);
  33. exports.proxies.http = httpOrHttpsProxy;
  34. exports.proxies.https = httpOrHttpsProxy;
  35. exports.proxies.socks = SocksProxyAgent;
  36. exports.proxies.socks4 = SocksProxyAgent;
  37. exports.proxies.socks4a = SocksProxyAgent;
  38. exports.proxies.socks5 = SocksProxyAgent;
  39. exports.proxies.socks5h = SocksProxyAgent;
  40. PacProxyAgent.protocols.forEach(function (protocol) {
  41. exports.proxies['pac+' + protocol] = PacProxyAgent;
  42. });
  43. function httpOrHttpsProxy (opts, secureEndpoint) {
  44. if (secureEndpoint) {
  45. // HTTPS
  46. return new HttpsProxyAgent(opts);
  47. } else {
  48. // HTTP
  49. return new HttpProxyAgent(opts);
  50. }
  51. }
  52. /**
  53. * Attempts to get an `http.Agent` instance based off of the given proxy URI
  54. * information, and the `secure` flag.
  55. *
  56. * An LRU cache is used, to prevent unnecessary creation of proxy
  57. * `http.Agent` instances.
  58. *
  59. * @param {String} uri proxy url
  60. * @param {Boolean} secure true if this is for an HTTPS request, false for HTTP
  61. * @return {http.Agent}
  62. * @api public
  63. */
  64. function ProxyAgent (opts) {
  65. if (!(this instanceof ProxyAgent)) return new ProxyAgent(opts);
  66. if ('string' == typeof opts) opts = url.parse(opts);
  67. if (!opts) throw new TypeError('an HTTP(S) proxy server `host` and `protocol` must be specified!');
  68. debug('creating new ProxyAgent instance: %o', opts);
  69. Agent.call(this, connect);
  70. var proxies;
  71. if (opts.proxies) {
  72. proxies = extend(Object.create(exports.proxies), opts.proxies);
  73. } else {
  74. proxies = exports.proxies;
  75. }
  76. // get the requested proxy "protocol"
  77. var protocol = opts.protocol;
  78. if (!protocol) {
  79. throw new TypeError('You must specify a string "protocol" for the ' +
  80. 'proxy type (' + types().join(', ') + ')');
  81. }
  82. // strip the trailing ":" if present
  83. if (':' == protocol[protocol.length - 1]) {
  84. protocol = protocol.substring(0, protocol.length - 1);
  85. }
  86. // get the proxy `http.Agent` creation function
  87. var proxyFn = proxies[protocol];
  88. if ('function' != typeof proxyFn) {
  89. throw new TypeError('unsupported proxy protocol: "' + protocol + '"');
  90. }
  91. this.proxy = opts;
  92. // format the proxy info back into a URI, since an opts object
  93. // could have been passed in originally. This generated URI is used
  94. // as part of the "key" for the LRU cache
  95. this.proxyUri = url.format({
  96. protocol: protocol + ':',
  97. slashes: true,
  98. hostname: opts.hostname || opts.host,
  99. port: opts.port
  100. });
  101. this.proxyFn = proxyFn;
  102. }
  103. inherits(ProxyAgent, Agent);
  104. /**
  105. *
  106. */
  107. function connect (req, opts, fn) {
  108. // create the "key" for the LRU cache
  109. var key = this.proxyUri;
  110. if (opts.secureEndpoint) key += ' secure';
  111. // attempt to get a cached `http.Agent` instance first
  112. var agent = exports.cache.get(key);
  113. if (!agent) {
  114. // get an `http.Agent` instance from protocol-specific agent function
  115. agent = this.proxyFn(this.proxy, opts.secureEndpoint);
  116. if (agent) exports.cache.set(key, agent);
  117. } else {
  118. debug('cache hit with key: %o', key);
  119. }
  120. // XXX: agent.callback() is an agent-base-ism
  121. // TODO: add support for generic `http.Agent` instances by calling
  122. // agent.addRequest(), but with support for <= 0.10.x and >= 0.12.x
  123. agent.callback(req, opts, fn);
  124. }
  125. /**
  126. * Returns an Array of supported protocol string names.
  127. *
  128. * @return {Array}
  129. * @api private
  130. */
  131. function types () {
  132. var rtn = [];
  133. // not using Object.keys() so that we get any
  134. // potential prototype values as well
  135. for (var type in exports.proxies) rtn.push(type);
  136. return rtn;
  137. }