3 const net = require('net');
4 const util = require('util');
5 const EventEmitter = require('events').EventEmitter;
6 const debug = util.debuglog('http');
10 // The largest departure from the previous implementation is that
11 // an Agent instance holds connections for a variable number of host:ports.
12 // Surprisingly, this is still API compatible as far as third parties are
13 // concerned. The only code that really notices the difference is the
16 // Another departure is that all code related to HTTP parsing is in
17 // ClientRequest.onSocket(). The Agent is now *strictly*
18 // concerned with managing a connection pool.
20 function Agent(options) {
21 if (!(this instanceof Agent))
22 return new Agent(options);
24 EventEmitter.call(this);
28 self.defaultPort = 80;
29 self.protocol = 'http:';
31 self.options = util._extend({}, options);
33 // don't confuse net and make it think that we're connecting to a pipe
34 self.options.path = null;
37 self.freeSockets = {};
38 self.keepAliveMsecs = self.options.keepAliveMsecs || 1000;
39 self.keepAlive = self.options.keepAlive || false;
40 self.maxSockets = self.options.maxSockets || Agent.defaultMaxSockets;
41 self.maxFreeSockets = self.options.maxFreeSockets || 256;
43 self.on('free', function(socket, options) {
44 var name = self.getName(options);
45 debug('agent.on(free)', name);
47 if (!socket.destroyed &&
48 self.requests[name] && self.requests[name].length) {
49 self.requests[name].shift().onSocket(socket);
50 if (self.requests[name].length === 0) {
52 delete self.requests[name];
55 // If there are no pending requests, then put it in
56 // the freeSockets pool, but only if we're allowed to do so.
57 var req = socket._httpMessage;
59 req.shouldKeepAlive &&
61 self.options.keepAlive) {
62 var freeSockets = self.freeSockets[name];
63 var freeLen = freeSockets ? freeSockets.length : 0;
65 if (self.sockets[name])
66 count += self.sockets[name].length;
68 if (count >= self.maxSockets || freeLen >= self.maxFreeSockets) {
69 self.removeSocket(socket, options);
72 freeSockets = freeSockets || [];
73 self.freeSockets[name] = freeSockets;
74 socket.setKeepAlive(true, self.keepAliveMsecs);
76 socket._httpMessage = null;
77 self.removeSocket(socket, options);
78 freeSockets.push(socket);
81 self.removeSocket(socket, options);
88 util.inherits(Agent, EventEmitter);
89 exports.Agent = Agent;
91 Agent.defaultMaxSockets = Infinity;
93 Agent.prototype.createConnection = net.createConnection;
95 // Get the key for a given set of request options
96 Agent.prototype.getName = function(options) {
100 name += options.host;
106 name += options.port;
108 if (options.localAddress)
109 name += options.localAddress;
114 Agent.prototype.addRequest = function(req, options) {
115 // Legacy API: addRequest(req, host, port, path)
116 if (typeof options === 'string') {
124 var name = this.getName(options);
125 if (!this.sockets[name]) {
126 this.sockets[name] = [];
129 var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0;
130 var sockLen = freeLen + this.sockets[name].length;
133 // we have a free socket, so use that.
134 var socket = this.freeSockets[name].shift();
135 debug('have free socket');
138 if (!this.freeSockets[name].length)
139 delete this.freeSockets[name];
142 req.onSocket(socket);
143 this.sockets[name].push(socket);
144 } else if (sockLen < this.maxSockets) {
145 debug('call onSocket', sockLen, freeLen);
146 // If we are under maxSockets create a new one.
147 req.onSocket(this.createSocket(req, options));
149 debug('wait for socket');
150 // We are over limit so we'll add it to the queue.
151 if (!this.requests[name]) {
152 this.requests[name] = [];
154 this.requests[name].push(req);
158 Agent.prototype.createSocket = function(req, options) {
160 options = util._extend({}, options);
161 options = util._extend(options, self.options);
163 options.servername = options.host;
165 var hostHeader = req.getHeader('host');
167 options.servername = hostHeader.replace(/:.*$/, '');
171 var name = self.getName(options);
173 debug('createConnection', name, options);
174 options.encoding = null;
175 var s = self.createConnection(options);
176 if (!self.sockets[name]) {
177 self.sockets[name] = [];
179 this.sockets[name].push(s);
180 debug('sockets', name, this.sockets[name].length);
183 self.emit('free', s, options);
185 s.on('free', onFree);
187 function onClose(err) {
188 debug('CLIENT socket onClose');
189 // This is the only place where sockets get removed from the Agent.
190 // If you want to remove a socket from the pool, just close it.
191 // All socket errors end in a close event anyway.
192 self.removeSocket(s, options);
194 s.on('close', onClose);
196 function onRemove() {
197 // We need this function for cases like HTTP 'upgrade'
198 // (defined by WebSockets) where we need to remove a socket from the
199 // pool because it'll be locked up indefinitely
200 debug('CLIENT socket onRemove');
201 self.removeSocket(s, options);
202 s.removeListener('close', onClose);
203 s.removeListener('free', onFree);
204 s.removeListener('agentRemove', onRemove);
206 s.on('agentRemove', onRemove);
210 Agent.prototype.removeSocket = function(s, options) {
211 var name = this.getName(options);
212 debug('removeSocket', name, 'destroyed:', s.destroyed);
213 var sets = [this.sockets];
215 // If the socket was destroyed, remove it from the free buffers too.
217 sets.push(this.freeSockets);
219 for (var sk = 0; sk < sets.length; sk++) {
220 var sockets = sets[sk];
223 var index = sockets[name].indexOf(s);
225 sockets[name].splice(index, 1);
227 if (sockets[name].length === 0)
228 delete sockets[name];
233 if (this.requests[name] && this.requests[name].length) {
234 debug('removeSocket, have a request, make a socket');
235 var req = this.requests[name][0];
236 // If we have pending requests and a socket gets closed make a new one
237 this.createSocket(req, options).emit('free');
241 Agent.prototype.destroy = function() {
242 var sets = [this.freeSockets, this.sockets];
243 for (var s = 0; s < sets.length; s++) {
245 var keys = Object.keys(set);
246 for (var v = 0; v < keys.length; v++) {
247 var setName = set[keys[v]];
248 for (var n = 0; n < setName.length; n++) {
249 setName[n].destroy();
255 exports.globalAgent = new Agent();