-
-
Notifications
You must be signed in to change notification settings - Fork 15
/
wait-port.js
228 lines (196 loc) · 7.21 KB
/
wait-port.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
const debug = require('debug')('wait-port');
const net = require('net');
const outputFunctions = require('./output-functions');
const validateParameters = require('./validate-parameters');
const ConnectionError = require('./errors/connection-error');
function createConnectionWithTimeout({ host, port }, timeout, callback) {
// Variable to hold the timer we'll use to kill the socket if we don't
// connect in time.
let timer = null;
// Try and open the socket, with the params and callback.
const socket = net.createConnection({ host, port }, (err) => {
if (!err) clearTimeout(timer);
return callback(err);
});
// TODO: Check for the socket ECONNREFUSED event.
socket.on('error', (error) => {
debug(`Socket error: ${error}`);
clearTimeout(timer);
socket.destroy();
callback(error);
});
// Kill the socket if we don't open in time.
timer = setTimeout(() => {
socket.destroy();
const error = new Error(`Timeout trying to open socket to ${host}:${port}`);
error.code = 'ECONNTIMEOUT';
callback(error);
}, timeout);
// Return the socket.
return socket;
}
function checkHttp(socket, params, timeout, callback) {
// Create the HTTP request.
const request =
`GET ${params.path} HTTP/1.1
Host: ${params.host}
`;
let timer = null;
timer = setTimeout(() => {
socket.destroy();
const error = new Error(`Timeout waiting for data from ${params.host}:${params.port}`);
error.code = 'EREQTIMEOUT';
callback(error);
}, timeout);
// Get ready for a response.
socket.on('data', function(data) {
// Get the response as text.
const response = data.toString();
const statusLine = response.split('\n')[0];
// Stop the timer.
clearTimeout(timer);
// Check the data. Remember an HTTP response is:
// HTTP/1.1 XXX Stuff
const statusLineParts = statusLine.split(' ');
if (statusLineParts < 2 || statusLineParts[1].startsWith('2') === false) {
debug(`Invalid HTTP status line: ${statusLine}`);
const error = new Error('Invalid response from server');
error.code = 'ERESPONSE';
callback(error);
}
// ALL good!
debug(`Successful HTTP status line: ${statusLine}`);
callback();
});
// Send the request.
socket.write(request);
}
// This function attempts to open a connection, given a limited time window.
// This is the function which we will run repeatedly until we connect.
function tryConnect(options, timeout) {
return new Promise((resolve, reject) => {
try {
const socket = createConnectionWithTimeout(options, timeout, (err) => {
if (err) {
if (err.code === 'ECONNREFUSED') {
// We successfully *tried* to connect, so resolve with false so
// that we try again.
debug('Socket not open: ECONNREFUSED');
socket.destroy();
return resolve(false);
} else if (err.code === 'ECONNTIMEOUT') {
// We've successfully *tried* to connect, but we're timing out
// establishing the connection. This is not ideal (either
// the port is open or it ain't).
debug('Socket not open: ECONNTIMEOUT');
socket.destroy();
return resolve(false);
} else if (err.code === 'ECONNRESET') {
// This can happen if the target server kills its connection before
// we can read from it, we can normally just try again.
debug('Socket not open: ECONNRESET');
socket.destroy();
return resolve(false);
} else if (err.code === 'ENOTFOUND') {
// This will occur if the address is not found, i.e. due to a dns
// lookup fail (normally a problem if the domain is wrong).
debug('Socket cannot be opened: ENOTFOUND');
socket.destroy();
// If we are going to wait for DNS records, we can actually just try
// again...
if (options.waitForDns === true) return resolve(false);
// ...otherwise, we will explicitly fail with a meaningful error for
// the user.
return reject(new ConnectionError(`The address '${options.host}' cannot be found`));
}
// Trying to open the socket has resulted in an error we don't
// understand. Better give up.
debug(`Unexpected error trying to open socket: ${err}`);
socket.destroy();
return reject(err);
}
// Boom, we connected!
debug('Socket connected!');
// If we are not dealing with http, we're done.
if (options.protocol !== 'http') {
// Disconnect, stop the timer and resolve.
socket.destroy();
return resolve(true);
}
// TODO: we should only use the portion of the timeout for this interval which is still left to us.
// Now we've got to wait for a HTTP response.
checkHttp(socket, options, timeout, (err) => {
if (err) {
if (err.code === 'EREQTIMEOUT') {
debug('HTTP error: EREQTIMEOUT');
socket.destroy();
return resolve(false);
} else if (err.code === 'ERESPONSE') {
debug('HTTP error: ERESPONSE');
socket.destroy();
return resolve(false);
}
debug(`Unexpected error checking http response: ${err}`);
socket.destroy();
return reject(err);
}
socket.destroy();
return resolve(true);
});
});
} catch (err) {
// Trying to open the socket has resulted in an exception we don't
// understand. Better give up.
debug(`Unexpected exception trying to open socket: ${err}`);
return reject(err);
}
});
}
function waitPort(params) {
return new Promise((resolve, reject) => {
const {
protocol,
host,
port,
path,
interval,
timeout,
output,
waitForDns,
} = validateParameters(params);
// Keep track of the start time (needed for timeout calcs).
const startTime = new Date();
// Don't wait for more than connectTimeout to try and connect.
const connectTimeout = 1000;
// Grab the object for output.
const outputFunction = outputFunctions[output];
outputFunction.starting({ host, port });
// Start trying to connect.
const loop = () => {
outputFunction.tryConnect();
tryConnect({ protocol, host, port, path, waitForDns }, connectTimeout)
.then((open) => {
debug(`Socket status is: ${open}`);
// The socket is open, we're done.
if (open) {
outputFunction.connected();
return resolve(true);
}
// If we have a timeout, and we've passed it, we're done.
if (timeout && (new Date() - startTime) > timeout) {
outputFunction.timeout();
return resolve(false);
}
// Run the loop again.
return setTimeout(loop, interval);
})
.catch((err) => {
debug(`Unhandled error occured trying to connect: ${err}`);
return reject(err);
});
};
// Start the loop.
loop();
});
}
module.exports = waitPort;