source: trip-planner-front/node_modules/agentkeepalive/lib/agent.js@ 6a80231

Last change on this file since 6a80231 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 14.8 KB
Line 
1'use strict';
2
3const OriginalAgent = require('http').Agent;
4const ms = require('humanize-ms');
5const debug = require('debug')('agentkeepalive');
6const deprecate = require('depd')('agentkeepalive');
7const {
8 INIT_SOCKET,
9 CURRENT_ID,
10 CREATE_ID,
11 SOCKET_CREATED_TIME,
12 SOCKET_NAME,
13 SOCKET_REQUEST_COUNT,
14 SOCKET_REQUEST_FINISHED_COUNT,
15} = require('./constants');
16
17// OriginalAgent come from
18// - https://github.com/nodejs/node/blob/v8.12.0/lib/_http_agent.js
19// - https://github.com/nodejs/node/blob/v10.12.0/lib/_http_agent.js
20
21// node <= 10
22let defaultTimeoutListenerCount = 1;
23const majorVersion = parseInt(process.version.split('.', 1)[0].substring(1));
24if (majorVersion >= 11 && majorVersion <= 12) {
25 defaultTimeoutListenerCount = 2;
26} else if (majorVersion >= 13) {
27 defaultTimeoutListenerCount = 3;
28}
29
30class Agent extends OriginalAgent {
31 constructor(options) {
32 options = options || {};
33 options.keepAlive = options.keepAlive !== false;
34 // default is keep-alive and 15s free socket timeout
35 if (options.freeSocketTimeout === undefined) {
36 options.freeSocketTimeout = 15000;
37 }
38 // Legacy API: keepAliveTimeout should be rename to `freeSocketTimeout`
39 if (options.keepAliveTimeout) {
40 deprecate('options.keepAliveTimeout is deprecated, please use options.freeSocketTimeout instead');
41 options.freeSocketTimeout = options.keepAliveTimeout;
42 delete options.keepAliveTimeout;
43 }
44 // Legacy API: freeSocketKeepAliveTimeout should be rename to `freeSocketTimeout`
45 if (options.freeSocketKeepAliveTimeout) {
46 deprecate('options.freeSocketKeepAliveTimeout is deprecated, please use options.freeSocketTimeout instead');
47 options.freeSocketTimeout = options.freeSocketKeepAliveTimeout;
48 delete options.freeSocketKeepAliveTimeout;
49 }
50
51 // Sets the socket to timeout after timeout milliseconds of inactivity on the socket.
52 // By default is double free socket timeout.
53 if (options.timeout === undefined) {
54 // make sure socket default inactivity timeout >= 30s
55 options.timeout = Math.max(options.freeSocketTimeout * 2, 30000);
56 }
57
58 // support humanize format
59 options.timeout = ms(options.timeout);
60 options.freeSocketTimeout = ms(options.freeSocketTimeout);
61 options.socketActiveTTL = options.socketActiveTTL ? ms(options.socketActiveTTL) : 0;
62
63 super(options);
64
65 this[CURRENT_ID] = 0;
66
67 // create socket success counter
68 this.createSocketCount = 0;
69 this.createSocketCountLastCheck = 0;
70
71 this.createSocketErrorCount = 0;
72 this.createSocketErrorCountLastCheck = 0;
73
74 this.closeSocketCount = 0;
75 this.closeSocketCountLastCheck = 0;
76
77 // socket error event count
78 this.errorSocketCount = 0;
79 this.errorSocketCountLastCheck = 0;
80
81 // request finished counter
82 this.requestCount = 0;
83 this.requestCountLastCheck = 0;
84
85 // including free socket timeout counter
86 this.timeoutSocketCount = 0;
87 this.timeoutSocketCountLastCheck = 0;
88
89 this.on('free', socket => {
90 // https://github.com/nodejs/node/pull/32000
91 // Node.js native agent will check socket timeout eqs agent.options.timeout.
92 // Use the ttl or freeSocketTimeout to overwrite.
93 const timeout = this.calcSocketTimeout(socket);
94 if (timeout > 0 && socket.timeout !== timeout) {
95 socket.setTimeout(timeout);
96 }
97 });
98 }
99
100 get freeSocketKeepAliveTimeout() {
101 deprecate('agent.freeSocketKeepAliveTimeout is deprecated, please use agent.options.freeSocketTimeout instead');
102 return this.options.freeSocketTimeout;
103 }
104
105 get timeout() {
106 deprecate('agent.timeout is deprecated, please use agent.options.timeout instead');
107 return this.options.timeout;
108 }
109
110 get socketActiveTTL() {
111 deprecate('agent.socketActiveTTL is deprecated, please use agent.options.socketActiveTTL instead');
112 return this.options.socketActiveTTL;
113 }
114
115 calcSocketTimeout(socket) {
116 /**
117 * return <= 0: should free socket
118 * return > 0: should update socket timeout
119 * return undefined: not find custom timeout
120 */
121 let freeSocketTimeout = this.options.freeSocketTimeout;
122 const socketActiveTTL = this.options.socketActiveTTL;
123 if (socketActiveTTL) {
124 // check socketActiveTTL
125 const aliveTime = Date.now() - socket[SOCKET_CREATED_TIME];
126 const diff = socketActiveTTL - aliveTime;
127 if (diff <= 0) {
128 return diff;
129 }
130 if (freeSocketTimeout && diff < freeSocketTimeout) {
131 freeSocketTimeout = diff;
132 }
133 }
134 // set freeSocketTimeout
135 if (freeSocketTimeout) {
136 // set free keepalive timer
137 // try to use socket custom freeSocketTimeout first, support headers['keep-alive']
138 // https://github.com/node-modules/urllib/blob/b76053020923f4d99a1c93cf2e16e0c5ba10bacf/lib/urllib.js#L498
139 const customFreeSocketTimeout = socket.freeSocketTimeout || socket.freeSocketKeepAliveTimeout;
140 return customFreeSocketTimeout || freeSocketTimeout;
141 }
142 }
143
144 keepSocketAlive(socket) {
145 const result = super.keepSocketAlive(socket);
146 // should not keepAlive, do nothing
147 if (!result) return result;
148
149 const customTimeout = this.calcSocketTimeout(socket);
150 if (typeof customTimeout === 'undefined') {
151 return true;
152 }
153 if (customTimeout <= 0) {
154 debug('%s(requests: %s, finished: %s) free but need to destroy by TTL, request count %s, diff is %s',
155 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], customTimeout);
156 return false;
157 }
158 if (socket.timeout !== customTimeout) {
159 socket.setTimeout(customTimeout);
160 }
161 return true;
162 }
163
164 // only call on addRequest
165 reuseSocket(...args) {
166 // reuseSocket(socket, req)
167 super.reuseSocket(...args);
168 const socket = args[0];
169 const req = args[1];
170 req.reusedSocket = true;
171 const agentTimeout = this.options.timeout;
172 if (getSocketTimeout(socket) !== agentTimeout) {
173 // reset timeout before use
174 socket.setTimeout(agentTimeout);
175 debug('%s reset timeout to %sms', socket[SOCKET_NAME], agentTimeout);
176 }
177 socket[SOCKET_REQUEST_COUNT]++;
178 debug('%s(requests: %s, finished: %s) reuse on addRequest, timeout %sms',
179 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT],
180 getSocketTimeout(socket));
181 }
182
183 [CREATE_ID]() {
184 const id = this[CURRENT_ID]++;
185 if (this[CURRENT_ID] === Number.MAX_SAFE_INTEGER) this[CURRENT_ID] = 0;
186 return id;
187 }
188
189 [INIT_SOCKET](socket, options) {
190 // bugfix here.
191 // https on node 8, 10 won't set agent.options.timeout by default
192 // TODO: need to fix on node itself
193 if (options.timeout) {
194 const timeout = getSocketTimeout(socket);
195 if (!timeout) {
196 socket.setTimeout(options.timeout);
197 }
198 }
199
200 if (this.options.keepAlive) {
201 // Disable Nagle's algorithm: http://blog.caustik.com/2012/04/08/scaling-node-js-to-100k-concurrent-connections/
202 // https://fengmk2.com/benchmark/nagle-algorithm-delayed-ack-mock.html
203 socket.setNoDelay(true);
204 }
205 this.createSocketCount++;
206 if (this.options.socketActiveTTL) {
207 socket[SOCKET_CREATED_TIME] = Date.now();
208 }
209 // don't show the hole '-----BEGIN CERTIFICATE----' key string
210 socket[SOCKET_NAME] = `sock[${this[CREATE_ID]()}#${options._agentKey}]`.split('-----BEGIN', 1)[0];
211 socket[SOCKET_REQUEST_COUNT] = 1;
212 socket[SOCKET_REQUEST_FINISHED_COUNT] = 0;
213 installListeners(this, socket, options);
214 }
215
216 createConnection(options, oncreate) {
217 let called = false;
218 const onNewCreate = (err, socket) => {
219 if (called) return;
220 called = true;
221
222 if (err) {
223 this.createSocketErrorCount++;
224 return oncreate(err);
225 }
226 this[INIT_SOCKET](socket, options);
227 oncreate(err, socket);
228 };
229
230 const newSocket = super.createConnection(options, onNewCreate);
231 if (newSocket) onNewCreate(null, newSocket);
232 }
233
234 get statusChanged() {
235 const changed = this.createSocketCount !== this.createSocketCountLastCheck ||
236 this.createSocketErrorCount !== this.createSocketErrorCountLastCheck ||
237 this.closeSocketCount !== this.closeSocketCountLastCheck ||
238 this.errorSocketCount !== this.errorSocketCountLastCheck ||
239 this.timeoutSocketCount !== this.timeoutSocketCountLastCheck ||
240 this.requestCount !== this.requestCountLastCheck;
241 if (changed) {
242 this.createSocketCountLastCheck = this.createSocketCount;
243 this.createSocketErrorCountLastCheck = this.createSocketErrorCount;
244 this.closeSocketCountLastCheck = this.closeSocketCount;
245 this.errorSocketCountLastCheck = this.errorSocketCount;
246 this.timeoutSocketCountLastCheck = this.timeoutSocketCount;
247 this.requestCountLastCheck = this.requestCount;
248 }
249 return changed;
250 }
251
252 getCurrentStatus() {
253 return {
254 createSocketCount: this.createSocketCount,
255 createSocketErrorCount: this.createSocketErrorCount,
256 closeSocketCount: this.closeSocketCount,
257 errorSocketCount: this.errorSocketCount,
258 timeoutSocketCount: this.timeoutSocketCount,
259 requestCount: this.requestCount,
260 freeSockets: inspect(this.freeSockets),
261 sockets: inspect(this.sockets),
262 requests: inspect(this.requests),
263 };
264 }
265}
266
267// node 8 don't has timeout attribute on socket
268// https://github.com/nodejs/node/pull/21204/files#diff-e6ef024c3775d787c38487a6309e491dR408
269function getSocketTimeout(socket) {
270 return socket.timeout || socket._idleTimeout;
271}
272
273function installListeners(agent, socket, options) {
274 debug('%s create, timeout %sms', socket[SOCKET_NAME], getSocketTimeout(socket));
275
276 // listener socket events: close, timeout, error, free
277 function onFree() {
278 // create and socket.emit('free') logic
279 // https://github.com/nodejs/node/blob/master/lib/_http_agent.js#L311
280 // no req on the socket, it should be the new socket
281 if (!socket._httpMessage && socket[SOCKET_REQUEST_COUNT] === 1) return;
282
283 socket[SOCKET_REQUEST_FINISHED_COUNT]++;
284 agent.requestCount++;
285 debug('%s(requests: %s, finished: %s) free',
286 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT]);
287
288 // should reuse on pedding requests?
289 const name = agent.getName(options);
290 if (socket.writable && agent.requests[name] && agent.requests[name].length) {
291 // will be reuse on agent free listener
292 socket[SOCKET_REQUEST_COUNT]++;
293 debug('%s(requests: %s, finished: %s) will be reuse on agent free event',
294 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT]);
295 }
296 }
297 socket.on('free', onFree);
298
299 function onClose(isError) {
300 debug('%s(requests: %s, finished: %s) close, isError: %s',
301 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT], isError);
302 agent.closeSocketCount++;
303 }
304 socket.on('close', onClose);
305
306 // start socket timeout handler
307 function onTimeout() {
308 // onTimeout and emitRequestTimeout(_http_client.js)
309 // https://github.com/nodejs/node/blob/v12.x/lib/_http_client.js#L711
310 const listenerCount = socket.listeners('timeout').length;
311 // node <= 10, default listenerCount is 1, onTimeout
312 // 11 < node <= 12, default listenerCount is 2, onTimeout and emitRequestTimeout
313 // node >= 13, default listenerCount is 3, onTimeout,
314 // onTimeout(https://github.com/nodejs/node/pull/32000/files#diff-5f7fb0850412c6be189faeddea6c5359R333)
315 // and emitRequestTimeout
316 const timeout = getSocketTimeout(socket);
317 const req = socket._httpMessage;
318 const reqTimeoutListenerCount = req && req.listeners('timeout').length || 0;
319 debug('%s(requests: %s, finished: %s) timeout after %sms, listeners %s, defaultTimeoutListenerCount %s, hasHttpRequest %s, HttpRequest timeoutListenerCount %s',
320 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT],
321 timeout, listenerCount, defaultTimeoutListenerCount, !!req, reqTimeoutListenerCount);
322 if (debug.enabled) {
323 debug('timeout listeners: %s', socket.listeners('timeout').map(f => f.name).join(', '));
324 }
325 agent.timeoutSocketCount++;
326 const name = agent.getName(options);
327 if (agent.freeSockets[name] && agent.freeSockets[name].indexOf(socket) !== -1) {
328 // free socket timeout, destroy quietly
329 socket.destroy();
330 // Remove it from freeSockets list immediately to prevent new requests
331 // from being sent through this socket.
332 agent.removeSocket(socket, options);
333 debug('%s is free, destroy quietly', socket[SOCKET_NAME]);
334 } else {
335 // if there is no any request socket timeout handler,
336 // agent need to handle socket timeout itself.
337 //
338 // custom request socket timeout handle logic must follow these rules:
339 // 1. Destroy socket first
340 // 2. Must emit socket 'agentRemove' event tell agent remove socket
341 // from freeSockets list immediately.
342 // Otherise you may be get 'socket hang up' error when reuse
343 // free socket and timeout happen in the same time.
344 if (reqTimeoutListenerCount === 0) {
345 const error = new Error('Socket timeout');
346 error.code = 'ERR_SOCKET_TIMEOUT';
347 error.timeout = timeout;
348 // must manually call socket.end() or socket.destroy() to end the connection.
349 // https://nodejs.org/dist/latest-v10.x/docs/api/net.html#net_socket_settimeout_timeout_callback
350 socket.destroy(error);
351 agent.removeSocket(socket, options);
352 debug('%s destroy with timeout error', socket[SOCKET_NAME]);
353 }
354 }
355 }
356 socket.on('timeout', onTimeout);
357
358 function onError(err) {
359 const listenerCount = socket.listeners('error').length;
360 debug('%s(requests: %s, finished: %s) error: %s, listenerCount: %s',
361 socket[SOCKET_NAME], socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT],
362 err, listenerCount);
363 agent.errorSocketCount++;
364 if (listenerCount === 1) {
365 // if socket don't contain error event handler, don't catch it, emit it again
366 debug('%s emit uncaught error event', socket[SOCKET_NAME]);
367 socket.removeListener('error', onError);
368 socket.emit('error', err);
369 }
370 }
371 socket.on('error', onError);
372
373 function onRemove() {
374 debug('%s(requests: %s, finished: %s) agentRemove',
375 socket[SOCKET_NAME],
376 socket[SOCKET_REQUEST_COUNT], socket[SOCKET_REQUEST_FINISHED_COUNT]);
377 // We need this function for cases like HTTP 'upgrade'
378 // (defined by WebSockets) where we need to remove a socket from the
379 // pool because it'll be locked up indefinitely
380 socket.removeListener('close', onClose);
381 socket.removeListener('error', onError);
382 socket.removeListener('free', onFree);
383 socket.removeListener('timeout', onTimeout);
384 socket.removeListener('agentRemove', onRemove);
385 }
386 socket.on('agentRemove', onRemove);
387}
388
389module.exports = Agent;
390
391function inspect(obj) {
392 const res = {};
393 for (const key in obj) {
394 res[key] = obj[key].length;
395 }
396 return res;
397}
Note: See TracBrowser for help on using the repository browser.