4917e2204738449af6889c2f08d376c68c399e35
[platform/upstream/nodejs.git] / lib / _http_agent.js
1 'use strict';
2
3 const net = require('net');
4 const util = require('util');
5 const EventEmitter = require('events').EventEmitter;
6 const debug = util.debuglog('http');
7
8 // New Agent code.
9
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
14 // request object.
15
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.
19
20 function Agent(options) {
21   if (!(this instanceof Agent))
22     return new Agent(options);
23
24   EventEmitter.call(this);
25
26   var self = this;
27
28   self.defaultPort = 80;
29   self.protocol = 'http:';
30
31   self.options = util._extend({}, options);
32
33   // don't confuse net and make it think that we're connecting to a pipe
34   self.options.path = null;
35   self.requests = {};
36   self.sockets = {};
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;
42
43   self.on('free', function(socket, options) {
44     var name = self.getName(options);
45     debug('agent.on(free)', name);
46
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) {
51         // don't leak
52         delete self.requests[name];
53       }
54     } else {
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;
58       if (req &&
59           req.shouldKeepAlive &&
60           !socket.destroyed &&
61           self.options.keepAlive) {
62         var freeSockets = self.freeSockets[name];
63         var freeLen = freeSockets ? freeSockets.length : 0;
64         var count = freeLen;
65         if (self.sockets[name])
66           count += self.sockets[name].length;
67
68         if (count >= self.maxSockets || freeLen >= self.maxFreeSockets) {
69           self.removeSocket(socket, options);
70           socket.destroy();
71         } else {
72           freeSockets = freeSockets || [];
73           self.freeSockets[name] = freeSockets;
74           socket.setKeepAlive(true, self.keepAliveMsecs);
75           socket.unref();
76           socket._httpMessage = null;
77           self.removeSocket(socket, options);
78           freeSockets.push(socket);
79         }
80       } else {
81         self.removeSocket(socket, options);
82         socket.destroy();
83       }
84     }
85   });
86 }
87
88 util.inherits(Agent, EventEmitter);
89 exports.Agent = Agent;
90
91 Agent.defaultMaxSockets = Infinity;
92
93 Agent.prototype.createConnection = net.createConnection;
94
95 // Get the key for a given set of request options
96 Agent.prototype.getName = function(options) {
97   var name = '';
98
99   if (options.host)
100     name += options.host;
101   else
102     name += 'localhost';
103
104   name += ':';
105   if (options.port)
106     name += options.port;
107   name += ':';
108   if (options.localAddress)
109     name += options.localAddress;
110   name += ':';
111   return name;
112 };
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
124   var name = this.getName(options);
125   if (!this.sockets[name]) {
126     this.sockets[name] = [];
127   }
128
129   var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0;
130   var sockLen = freeLen + this.sockets[name].length;
131
132   if (freeLen) {
133     // we have a free socket, so use that.
134     var socket = this.freeSockets[name].shift();
135     debug('have free socket');
136
137     // don't leak
138     if (!this.freeSockets[name].length)
139       delete this.freeSockets[name];
140
141     socket.ref();
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));
148   } else {
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] = [];
153     }
154     this.requests[name].push(req);
155   }
156 };
157
158 Agent.prototype.createSocket = function(req, options) {
159   var self = this;
160   options = util._extend({}, options);
161   options = util._extend(options, self.options);
162
163   options.servername = options.host;
164   if (req) {
165     var hostHeader = req.getHeader('host');
166     if (hostHeader) {
167       options.servername = hostHeader.replace(/:.*$/, '');
168     }
169   }
170
171   var name = self.getName(options);
172
173   debug('createConnection', name, options);
174   options.encoding = null;
175   var s = self.createConnection(options);
176   if (!self.sockets[name]) {
177     self.sockets[name] = [];
178   }
179   this.sockets[name].push(s);
180   debug('sockets', name, this.sockets[name].length);
181
182   function onFree() {
183     self.emit('free', s, options);
184   }
185   s.on('free', onFree);
186
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);
193   }
194   s.on('close', onClose);
195
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);
205   }
206   s.on('agentRemove', onRemove);
207   return s;
208 };
209
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];
214
215   // If the socket was destroyed, remove it from the free buffers too.
216   if (s.destroyed)
217     sets.push(this.freeSockets);
218
219   for (var sk = 0; sk < sets.length; sk++) {
220     var sockets = sets[sk];
221
222     if (sockets[name]) {
223       var index = sockets[name].indexOf(s);
224       if (index !== -1) {
225         sockets[name].splice(index, 1);
226         // Don't leak
227         if (sockets[name].length === 0)
228           delete sockets[name];
229       }
230     }
231   }
232
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');
238   }
239 };
240
241 Agent.prototype.destroy = function() {
242   var sets = [this.freeSockets, this.sockets];
243   for (var s = 0; s < sets.length; s++) {
244     var set = sets[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();
250       }
251     }
252   }
253 };
254
255 exports.globalAgent = new Agent();