UNPKG

3.8 kBJavaScriptView Raw
1'use strict';
2const net = require('net');
3const {TimeoutError} = require('./errors');
4
5const reentry = Symbol('reentry');
6
7function addTimeout(delay, callback, ...args) {
8 // Event loop order is timers, poll, immediates.
9 // The timed event may emit during the current tick poll phase, so
10 // defer calling the handler until the poll phase completes.
11 let immediate;
12 const timeout = setTimeout(
13 () => {
14 immediate = setImmediate(callback, delay, ...args);
15 /* istanbul ignore next: added in node v9.7.0 */
16 if (immediate.unref) {
17 immediate.unref();
18 }
19 },
20 delay
21 );
22 timeout.unref();
23 return () => {
24 clearTimeout(timeout);
25 clearImmediate(immediate);
26 };
27}
28
29module.exports = (request, options) => {
30 /* istanbul ignore next: this makes sure timed-out isn't called twice */
31 if (request[reentry]) {
32 return;
33 }
34
35 request[reentry] = true;
36 const {gotTimeout: delays, host, hostname} = options;
37 const timeoutHandler = (delay, event) => {
38 request.abort();
39 request.emit('error', new TimeoutError(delay, event, options));
40 };
41
42 const cancelers = [];
43 const cancelTimeouts = () => {
44 cancelers.forEach(cancelTimeout => cancelTimeout());
45 };
46
47 request.on('error', cancelTimeouts);
48 request.once('response', response => {
49 response.once('end', cancelTimeouts);
50 });
51
52 if (delays.request !== undefined) {
53 const cancelTimeout = addTimeout(
54 delays.request,
55 timeoutHandler,
56 'request'
57 );
58 cancelers.push(cancelTimeout);
59 }
60
61 if (delays.socket !== undefined) {
62 request.setTimeout(
63 delays.socket,
64 () => {
65 timeoutHandler(delays.socket, 'socket');
66 }
67 );
68 }
69
70 if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
71 request.once('socket', socket => {
72 /* istanbul ignore next: hard to test */
73 if (socket.connecting) {
74 const cancelTimeout = addTimeout(
75 delays.lookup,
76 timeoutHandler,
77 'lookup'
78 );
79 cancelers.push(cancelTimeout);
80 socket.once('lookup', cancelTimeout);
81 }
82 });
83 }
84
85 if (delays.connect !== undefined) {
86 request.once('socket', socket => {
87 /* istanbul ignore next: hard to test */
88 if (socket.connecting) {
89 const timeConnect = () => {
90 const cancelTimeout = addTimeout(
91 delays.connect,
92 timeoutHandler,
93 'connect'
94 );
95 cancelers.push(cancelTimeout);
96 return cancelTimeout;
97 };
98
99 if (request.socketPath || net.isIP(hostname || host)) {
100 socket.once('connect', timeConnect());
101 } else {
102 socket.once('lookup', () => {
103 socket.once('connect', timeConnect());
104 });
105 }
106 }
107 });
108 }
109
110 if (delays.secureConnect !== undefined && options.protocol === 'https:') {
111 request.once('socket', socket => {
112 /* istanbul ignore next: hard to test */
113 if (socket.connecting) {
114 socket.once('connect', () => {
115 const cancelTimeout = addTimeout(
116 delays.secureConnect,
117 timeoutHandler,
118 'secureConnect'
119 );
120 cancelers.push(cancelTimeout);
121 socket.once('secureConnect', cancelTimeout);
122 });
123 }
124 });
125 }
126
127 if (delays.send !== undefined) {
128 request.once('socket', socket => {
129 const timeRequest = () => {
130 const cancelTimeout = addTimeout(
131 delays.send,
132 timeoutHandler,
133 'send'
134 );
135 cancelers.push(cancelTimeout);
136 return cancelTimeout;
137 };
138 /* istanbul ignore next: hard to test */
139 if (socket.connecting) {
140 socket.once('connect', () => {
141 request.once('upload-complete', timeRequest());
142 });
143 } else {
144 request.once('upload-complete', timeRequest());
145 }
146 });
147 }
148
149 if (delays.response !== undefined) {
150 request.once('upload-complete', () => {
151 const cancelTimeout = addTimeout(
152 delays.response,
153 timeoutHandler,
154 'response'
155 );
156 cancelers.push(cancelTimeout);
157 request.once('response', cancelTimeout);
158 });
159 }
160};