PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/_http_agent.js

https://gitlab.com/GeekSir/node
JavaScript | 276 lines | 188 code | 40 blank | 48 comment | 41 complexity | 9606226f3daa01536383ef16ee0c8e3d MD5 | raw file
Possible License(s): 0BSD, Apache-2.0, MPL-2.0-no-copyleft-exception, JSON, WTFPL, CC-BY-SA-3.0, Unlicense, ISC, BSD-3-Clause, MIT, AGPL-3.0
  1. // Copyright Joyent, Inc. and other Node contributors.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a
  4. // copy of this software and associated documentation files (the
  5. // "Software"), to deal in the Software without restriction, including
  6. // without limitation the rights to use, copy, modify, merge, publish,
  7. // distribute, sublicense, and/or sell copies of the Software, and to permit
  8. // persons to whom the Software is furnished to do so, subject to the
  9. // following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included
  12. // in all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
  17. // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  18. // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  19. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
  20. // USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. 'use strict';
  22. var net = require('net');
  23. var util = require('util');
  24. var EventEmitter = require('events').EventEmitter;
  25. var debug = util.debuglog('http');
  26. // New Agent code.
  27. // The largest departure from the previous implementation is that
  28. // an Agent instance holds connections for a variable number of host:ports.
  29. // Surprisingly, this is still API compatible as far as third parties are
  30. // concerned. The only code that really notices the difference is the
  31. // request object.
  32. // Another departure is that all code related to HTTP parsing is in
  33. // ClientRequest.onSocket(). The Agent is now *strictly*
  34. // concerned with managing a connection pool.
  35. function Agent(options) {
  36. if (!(this instanceof Agent))
  37. return new Agent(options);
  38. EventEmitter.call(this);
  39. var self = this;
  40. self.defaultPort = 80;
  41. self.protocol = 'http:';
  42. self.options = util._extend({}, options);
  43. // don't confuse net and make it think that we're connecting to a pipe
  44. self.options.path = null;
  45. self.requests = {};
  46. self.sockets = {};
  47. self.freeSockets = {};
  48. self.keepAliveMsecs = self.options.keepAliveMsecs || 1000;
  49. self.keepAlive = self.options.keepAlive || false;
  50. self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets;
  51. self.maxFreeSockets = self.options.maxFreeSockets || 256;
  52. self.on('free', function(socket, options) {
  53. var name = self.getName(options);
  54. debug('agent.on(free)', name);
  55. if (!socket.destroyed &&
  56. self.requests[name] && self.requests[name].length) {
  57. self.requests[name].shift().onSocket(socket);
  58. if (self.requests[name].length === 0) {
  59. // don't leak
  60. delete self.requests[name];
  61. }
  62. } else {
  63. // If there are no pending requests, then put it in
  64. // the freeSockets pool, but only if we're allowed to do so.
  65. var req = socket._httpMessage;
  66. if (req &&
  67. req.shouldKeepAlive &&
  68. !socket.destroyed &&
  69. self.options.keepAlive) {
  70. var freeSockets = self.freeSockets[name];
  71. var freeLen = freeSockets ? freeSockets.length : 0;
  72. var count = freeLen;
  73. if (self.sockets[name])
  74. count += self.sockets[name].length;
  75. if (count >= self.maxSockets || freeLen >= self.maxFreeSockets) {
  76. self.removeSocket(socket, options);
  77. socket.destroy();
  78. } else {
  79. freeSockets = freeSockets || [];
  80. self.freeSockets[name] = freeSockets;
  81. socket.setKeepAlive(true, self.keepAliveMsecs);
  82. socket.unref();
  83. socket._httpMessage = null;
  84. self.removeSocket(socket, options);
  85. freeSockets.push(socket);
  86. }
  87. } else {
  88. self.removeSocket(socket, options);
  89. socket.destroy();
  90. }
  91. }
  92. });
  93. }
  94. util.inherits(Agent, EventEmitter);
  95. exports.Agent = Agent;
  96. Agent.defaultMaxSockets = Infinity;
  97. Agent.prototype.createConnection = net.createConnection;
  98. // Get the key for a given set of request options
  99. Agent.prototype.getName = function(options) {
  100. var name = '';
  101. if (options.host)
  102. name += options.host;
  103. else
  104. name += 'localhost';
  105. name += ':';
  106. if (options.port)
  107. name += options.port;
  108. name += ':';
  109. if (options.localAddress)
  110. name += options.localAddress;
  111. name += ':';
  112. return name;
  113. };
  114. Agent.prototype.addRequest = function(req, options) {
  115. // Legacy API: addRequest(req, host, port, path)
  116. if (typeof options === 'string') {
  117. options = {
  118. host: options,
  119. port: arguments[2],
  120. path: arguments[3]
  121. };
  122. }
  123. var name = this.getName(options);
  124. if (!this.sockets[name]) {
  125. this.sockets[name] = [];
  126. }
  127. var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0;
  128. var sockLen = freeLen + this.sockets[name].length;
  129. if (freeLen) {
  130. // we have a free socket, so use that.
  131. var socket = this.freeSockets[name].shift();
  132. debug('have free socket');
  133. // don't leak
  134. if (!this.freeSockets[name].length)
  135. delete this.freeSockets[name];
  136. socket.ref();
  137. req.onSocket(socket);
  138. this.sockets[name].push(socket);
  139. } else if (sockLen < this.maxSockets) {
  140. debug('call onSocket', sockLen, freeLen);
  141. // If we are under maxSockets create a new one.
  142. req.onSocket(this.createSocket(req, options));
  143. } else {
  144. debug('wait for socket');
  145. // We are over limit so we'll add it to the queue.
  146. if (!this.requests[name]) {
  147. this.requests[name] = [];
  148. }
  149. this.requests[name].push(req);
  150. }
  151. };
  152. Agent.prototype.createSocket = function(req, options) {
  153. var self = this;
  154. options = util._extend({}, options);
  155. options = util._extend(options, self.options);
  156. options.servername = options.host;
  157. if (req) {
  158. var hostHeader = req.getHeader('host');
  159. if (hostHeader) {
  160. options.servername = hostHeader.replace(/:.*$/, '');
  161. }
  162. }
  163. var name = self.getName(options);
  164. debug('createConnection', name, options);
  165. options.encoding = null;
  166. var s = self.createConnection(options);
  167. if (!self.sockets[name]) {
  168. self.sockets[name] = [];
  169. }
  170. this.sockets[name].push(s);
  171. debug('sockets', name, this.sockets[name].length);
  172. function onFree() {
  173. self.emit('free', s, options);
  174. }
  175. s.on('free', onFree);
  176. function onClose(err) {
  177. debug('CLIENT socket onClose');
  178. // This is the only place where sockets get removed from the Agent.
  179. // If you want to remove a socket from the pool, just close it.
  180. // All socket errors end in a close event anyway.
  181. self.removeSocket(s, options);
  182. }
  183. s.on('close', onClose);
  184. function onRemove() {
  185. // We need this function for cases like HTTP 'upgrade'
  186. // (defined by WebSockets) where we need to remove a socket from the
  187. // pool because it'll be locked up indefinitely
  188. debug('CLIENT socket onRemove');
  189. self.removeSocket(s, options);
  190. s.removeListener('close', onClose);
  191. s.removeListener('free', onFree);
  192. s.removeListener('agentRemove', onRemove);
  193. }
  194. s.on('agentRemove', onRemove);
  195. return s;
  196. };
  197. Agent.prototype.removeSocket = function(s, options) {
  198. var name = this.getName(options);
  199. debug('removeSocket', name, 'destroyed:', s.destroyed);
  200. var sets = [this.sockets];
  201. // If the socket was destroyed, remove it from the free buffers too.
  202. if (s.destroyed)
  203. sets.push(this.freeSockets);
  204. for (var sk = 0; sk < sets.length; sk++) {
  205. var sockets = sets[sk];
  206. if (sockets[name]) {
  207. var index = sockets[name].indexOf(s);
  208. if (index !== -1) {
  209. sockets[name].splice(index, 1);
  210. // Don't leak
  211. if (sockets[name].length === 0)
  212. delete sockets[name];
  213. }
  214. }
  215. }
  216. if (this.requests[name] && this.requests[name].length) {
  217. debug('removeSocket, have a request, make a socket');
  218. var req = this.requests[name][0];
  219. // If we have pending requests and a socket gets closed make a new one
  220. this.createSocket(req, options).emit('free');
  221. }
  222. };
  223. Agent.prototype.destroy = function() {
  224. var sets = [this.freeSockets, this.sockets];
  225. for (var s = 0; s < sets.length; s++) {
  226. var set = sets[s];
  227. var keys = Object.keys(set);
  228. for (var v = 0; v < keys.length; v++) {
  229. var setName = set[keys[v]];
  230. for (var n = 0; n < setName.length; n++) {
  231. setName[n].destroy();
  232. }
  233. }
  234. }
  235. };
  236. exports.globalAgent = new Agent();