doc: improvements to debugger.markdown copy
[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');
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 = options.host || 'localhost';
98
99   name += ':';
100   if (options.port)
101     name += options.port;
102
103   name += ':';
104   if (options.localAddress)
105     name += options.localAddress;
106
107   return name;
108 };
109
110 Agent.prototype.addRequest = function(req, options) {
111   // Legacy API: addRequest(req, host, port, path)
112   if (typeof options === 'string') {
113     options = {
114       host: options,
115       port: arguments[2],
116       path: arguments[3]
117     };
118   }
119
120   var name = this.getName(options);
121   if (!this.sockets[name]) {
122     this.sockets[name] = [];
123   }
124
125   var freeLen = this.freeSockets[name] ? this.freeSockets[name].length : 0;
126   var sockLen = freeLen + this.sockets[name].length;
127
128   if (freeLen) {
129     // we have a free socket, so use that.
130     var socket = this.freeSockets[name].shift();
131     debug('have free socket');
132
133     // don't leak
134     if (!this.freeSockets[name].length)
135       delete this.freeSockets[name];
136
137     socket.ref();
138     req.onSocket(socket);
139     this.sockets[name].push(socket);
140   } else if (sockLen < this.maxSockets) {
141     debug('call onSocket', sockLen, freeLen);
142     // If we are under maxSockets create a new one.
143     req.onSocket(this.createSocket(req, options));
144   } else {
145     debug('wait for socket');
146     // We are over limit so we'll add it to the queue.
147     if (!this.requests[name]) {
148       this.requests[name] = [];
149     }
150     this.requests[name].push(req);
151   }
152 };
153
154 Agent.prototype.createSocket = function(req, options) {
155   var self = this;
156   options = util._extend({}, options);
157   options = util._extend(options, self.options);
158
159   if (!options.servername) {
160     options.servername = options.host;
161     if (req) {
162       var hostHeader = req.getHeader('host');
163       if (hostHeader) {
164         options.servername = hostHeader.replace(/:.*$/, '');
165       }
166     }
167   }
168
169   var name = self.getName(options);
170   options._agentKey = name;
171
172   debug('createConnection', name, options);
173   options.encoding = null;
174   var s = self.createConnection(options);
175   if (!self.sockets[name]) {
176     self.sockets[name] = [];
177   }
178   this.sockets[name].push(s);
179   debug('sockets', name, this.sockets[name].length);
180
181   function onFree() {
182     self.emit('free', s, options);
183   }
184   s.on('free', onFree);
185
186   function onClose(err) {
187     debug('CLIENT socket onClose');
188     // This is the only place where sockets get removed from the Agent.
189     // If you want to remove a socket from the pool, just close it.
190     // All socket errors end in a close event anyway.
191     self.removeSocket(s, options);
192   }
193   s.on('close', onClose);
194
195   function onRemove() {
196     // We need this function for cases like HTTP 'upgrade'
197     // (defined by WebSockets) where we need to remove a socket from the
198     // pool because it'll be locked up indefinitely
199     debug('CLIENT socket onRemove');
200     self.removeSocket(s, options);
201     s.removeListener('close', onClose);
202     s.removeListener('free', onFree);
203     s.removeListener('agentRemove', onRemove);
204   }
205   s.on('agentRemove', onRemove);
206   return s;
207 };
208
209 Agent.prototype.removeSocket = function(s, options) {
210   var name = this.getName(options);
211   debug('removeSocket', name, 'destroyed:', s.destroyed);
212   var sets = [this.sockets];
213
214   // If the socket was destroyed, remove it from the free buffers too.
215   if (s.destroyed)
216     sets.push(this.freeSockets);
217
218   for (var sk = 0; sk < sets.length; sk++) {
219     var sockets = sets[sk];
220
221     if (sockets[name]) {
222       var index = sockets[name].indexOf(s);
223       if (index !== -1) {
224         sockets[name].splice(index, 1);
225         // Don't leak
226         if (sockets[name].length === 0)
227           delete sockets[name];
228       }
229     }
230   }
231
232   if (this.requests[name] && this.requests[name].length) {
233     debug('removeSocket, have a request, make a socket');
234     var req = this.requests[name][0];
235     // If we have pending requests and a socket gets closed make a new one
236     this.createSocket(req, options).emit('free');
237   }
238 };
239
240 Agent.prototype.destroy = function() {
241   var sets = [this.freeSockets, this.sockets];
242   for (var s = 0; s < sets.length; s++) {
243     var set = sets[s];
244     var keys = Object.keys(set);
245     for (var v = 0; v < keys.length; v++) {
246       var setName = set[keys[v]];
247       for (var n = 0; n < setName.length; n++) {
248         setName[n].destroy();
249       }
250     }
251   }
252 };
253
254 exports.globalAgent = new Agent();