elasticio-sailor-nodejs 3.0.0-dev3 → 3.0.0-dev5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/proxy-client.js +158 -59
- package/lib/sailor.js +2 -0
- package/lib/settings.js +3 -0
- package/mise.toml +2 -0
- package/package.json +1 -1
- package/run.js +9 -19
package/lib/proxy-client.js
CHANGED
|
@@ -202,6 +202,7 @@ class ProxyClient {
|
|
|
202
202
|
return resolve();
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// TODO: what if incoming message is received during graceful shutdown?
|
|
205
206
|
this.clientSession.close(() => {
|
|
206
207
|
log.debug('Successfully closed HTTP2 connection');
|
|
207
208
|
resolve();
|
|
@@ -262,34 +263,74 @@ class ProxyClient {
|
|
|
262
263
|
|
|
263
264
|
logger.info('Going to fetch message body.', { objectId });
|
|
264
265
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
266
|
+
const maxRetries = this.settings.PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS;
|
|
267
|
+
const retryDelay = this.settings.PROXY_OBJECT_REQUEST_RETRY_DELAY;
|
|
268
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
269
|
+
try {
|
|
270
|
+
object = await new Promise((resolve, reject) => {
|
|
271
|
+
const getObjectStream = this.clientSession.request({
|
|
272
|
+
[HTTP2_HEADER_PATH]: `/object/${objectId}`,
|
|
273
|
+
[HTTP2_HEADER_METHOD]: 'GET',
|
|
274
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader
|
|
275
|
+
}).pipe(this._encryptor.createDecipher());
|
|
276
|
+
|
|
277
|
+
const chunks = [];
|
|
278
|
+
getObjectStream.on('data', chunk => {
|
|
279
|
+
chunks.push(chunk);
|
|
280
|
+
});
|
|
281
|
+
getObjectStream.on('error', (err) => {
|
|
282
|
+
logger.error(err, 'Error during fetching message body');
|
|
283
|
+
reject(err);
|
|
284
|
+
});
|
|
285
|
+
getObjectStream.on('end', () => {
|
|
286
|
+
logger.info('Message stream ended by server');
|
|
287
|
+
const buffer = Buffer.concat(chunks);
|
|
288
|
+
logger.info({ messageSize: buffer.length }, 'Received complete message from server');
|
|
289
|
+
resolve({ data: JSON.parse(buffer.toString()) });
|
|
290
|
+
});
|
|
286
291
|
});
|
|
287
|
-
});
|
|
288
|
-
} catch (e) {
|
|
289
|
-
log.error(e);
|
|
290
|
-
throw new Error(`Failed to get message body with id=${objectId}`);
|
|
291
|
-
}
|
|
292
292
|
|
|
293
|
+
if (attempt > 0) {
|
|
294
|
+
log.info({ attempt, maxRetries }, 'Fetch message body succeeded after retry');
|
|
295
|
+
}
|
|
296
|
+
return objectId;
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const isLastAttempt = attempt === maxRetries;
|
|
299
|
+
const isRetryable = error.isNetworkError ||
|
|
300
|
+
(error.statusCode && error.statusCode >= 500) ||
|
|
301
|
+
error.code === 'ECONNRESET' ||
|
|
302
|
+
error.code === 'ETIMEDOUT';
|
|
303
|
+
|
|
304
|
+
if (!isRetryable || isLastAttempt) {
|
|
305
|
+
log.error({
|
|
306
|
+
attempt,
|
|
307
|
+
maxRetries,
|
|
308
|
+
error: error.message,
|
|
309
|
+
isRetryable
|
|
310
|
+
}, 'Fetch message body failed, no more retries');
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const delay = Math.min(
|
|
315
|
+
retryDelay * Math.pow(2, attempt),
|
|
316
|
+
this.settings.PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY
|
|
317
|
+
);
|
|
318
|
+
log.warn({
|
|
319
|
+
attempt,
|
|
320
|
+
maxRetries,
|
|
321
|
+
error: error.message,
|
|
322
|
+
nextRetryIn: delay
|
|
323
|
+
}, 'Fetch message body failed, retrying...');
|
|
324
|
+
|
|
325
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
326
|
+
|
|
327
|
+
// Ensure connection is still valid before retry
|
|
328
|
+
if (!this.isConnected()) {
|
|
329
|
+
log.info('Reconnecting before retry...');
|
|
330
|
+
await this.connect();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
293
334
|
logger.info('Successfully obtained message body.', { objectId });
|
|
294
335
|
logger.trace('Message body object received');
|
|
295
336
|
|
|
@@ -299,43 +340,101 @@ class ProxyClient {
|
|
|
299
340
|
async uploadMessageBody(bodyBuf) {
|
|
300
341
|
await this._ensureConnection();
|
|
301
342
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
343
|
+
const maxRetries = this.settings.PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS;
|
|
344
|
+
const retryDelay = this.settings.PROXY_OBJECT_REQUEST_RETRY_DELAY;
|
|
345
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
346
|
+
try {
|
|
347
|
+
const objectId = await new Promise((resolve, reject) => {
|
|
348
|
+
const postMessageStream = this.clientSession.request({
|
|
349
|
+
[HTTP2_HEADER_PATH]: '/object',
|
|
350
|
+
[HTTP2_HEADER_METHOD]: 'POST',
|
|
351
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader
|
|
352
|
+
});
|
|
309
353
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
354
|
+
let responseData = '';
|
|
355
|
+
let statusCode = null;
|
|
356
|
+
|
|
357
|
+
postMessageStream.on('response', (headers, flags) => {
|
|
358
|
+
statusCode = headers[http2.constants.HTTP2_HEADER_STATUS];
|
|
359
|
+
if (statusCode !== 200) {
|
|
360
|
+
const error = new Error(`Failed to upload message body, status code: ${statusCode}`);
|
|
361
|
+
error.statusCode = statusCode;
|
|
362
|
+
return reject(error);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
postMessageStream.on('data', chunk => {
|
|
367
|
+
responseData += chunk;
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
postMessageStream.on('error', (err) => {
|
|
371
|
+
log.error(err, 'Error during upload message body');
|
|
372
|
+
err.isNetworkError = true;
|
|
373
|
+
reject(err);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
postMessageStream.on('end', () => {
|
|
377
|
+
if (!responseData) {
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const responseJson = JSON.parse(responseData);
|
|
382
|
+
resolve(responseJson.objectId);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
log.error(e, 'Failed to parse upload message body response');
|
|
385
|
+
reject(e);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const cipher = this._encryptor.createCipher();
|
|
390
|
+
cipher.pipe(postMessageStream);
|
|
391
|
+
cipher.write(bodyBuf);
|
|
392
|
+
cipher.end();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Success - return the objectId
|
|
396
|
+
if (attempt > 0) {
|
|
397
|
+
log.info({ attempt, maxRetries }, 'Upload message body succeeded after retry');
|
|
314
398
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
399
|
+
return objectId;
|
|
400
|
+
|
|
401
|
+
} catch (error) {
|
|
402
|
+
const isLastAttempt = attempt === maxRetries;
|
|
403
|
+
const isRetryable = error.isNetworkError ||
|
|
404
|
+
(error.statusCode && error.statusCode >= 500) ||
|
|
405
|
+
error.code === 'ECONNRESET' ||
|
|
406
|
+
error.code === 'ETIMEDOUT';
|
|
407
|
+
|
|
408
|
+
if (!isRetryable || isLastAttempt) {
|
|
409
|
+
log.error({
|
|
410
|
+
attempt,
|
|
411
|
+
maxRetries,
|
|
412
|
+
error: error.message,
|
|
413
|
+
isRetryable
|
|
414
|
+
}, 'Upload message body failed, no more retries');
|
|
415
|
+
throw error;
|
|
331
416
|
}
|
|
332
|
-
});
|
|
333
417
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
418
|
+
const delay = Math.min(
|
|
419
|
+
retryDelay * Math.pow(2, attempt),
|
|
420
|
+
this.settings.PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY
|
|
421
|
+
);
|
|
422
|
+
log.warn({
|
|
423
|
+
attempt,
|
|
424
|
+
maxRetries,
|
|
425
|
+
error: error.message,
|
|
426
|
+
nextRetryIn: delay
|
|
427
|
+
}, 'Upload message body failed, retrying...');
|
|
428
|
+
|
|
429
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
430
|
+
|
|
431
|
+
// Ensure connection is still valid before retry
|
|
432
|
+
if (!this.isConnected()) {
|
|
433
|
+
log.info('Reconnecting before retry...');
|
|
434
|
+
await this.connect();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
339
438
|
}
|
|
340
439
|
|
|
341
440
|
async listenForMessages(messageHandler) {
|
package/lib/sailor.js
CHANGED
|
@@ -35,6 +35,8 @@ class Sailor {
|
|
|
35
35
|
//eslint-disable-next-line new-cap
|
|
36
36
|
this.apiClient = RestApiClient(
|
|
37
37
|
settings.API_USERNAME,
|
|
38
|
+
// TODO find a way to make username and key consistent (without running api tests and looking up
|
|
39
|
+
// correct values in MongoDB)
|
|
38
40
|
settings.API_KEY,
|
|
39
41
|
{
|
|
40
42
|
retryCount: settings.API_REQUEST_RETRY_ATTEMPTS,
|
package/lib/settings.js
CHANGED
|
@@ -15,6 +15,9 @@ function getOptionalEnvVars(envVars) {
|
|
|
15
15
|
PROXY_RECONNECT_MAX_DELAY: 30 * 1000, // 30 seconds
|
|
16
16
|
PROXY_RECONNECT_BACKOFF_MULTIPLIER: 2,
|
|
17
17
|
PROXY_RECONNECT_JITTER_FACTOR: 0.3,
|
|
18
|
+
PROXY_OBJECT_REQUEST_RETRY_ATTEMPTS: Infinity,
|
|
19
|
+
PROXY_OBJECT_REQUEST_RETRY_DELAY: 100,
|
|
20
|
+
PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY: 5 * 60 * 1000, // 5 mins
|
|
18
21
|
|
|
19
22
|
// TODO: Move to proxy?
|
|
20
23
|
DATA_RATE_LIMIT: 10, // 10 data events every 100ms
|
package/mise.toml
ADDED
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -57,23 +57,6 @@ async function putOutToSea(settings, ipc) {
|
|
|
57
57
|
ipc.send('init:ended');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
async function disconnectAndExit() {
|
|
61
|
-
if (!disconnectRequired) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
disconnectRequired = false;
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
logger.info('Disconnecting...');
|
|
68
|
-
await sailor.disconnect();
|
|
69
|
-
logger.info('Successfully disconnected');
|
|
70
|
-
process.exit();
|
|
71
|
-
} catch (err) {
|
|
72
|
-
logger.error(err, 'Unable to disconnect');
|
|
73
|
-
process.exit(-1);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
60
|
async function gracefulShutdown() {
|
|
78
61
|
if (!disconnectRequired) {
|
|
79
62
|
return;
|
|
@@ -90,8 +73,15 @@ async function gracefulShutdown() {
|
|
|
90
73
|
await sailorInit;
|
|
91
74
|
logger.trace('Waited an init before graceful shutdown');
|
|
92
75
|
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
try {
|
|
77
|
+
logger.info('Disconnecting...');
|
|
78
|
+
await sailor.scheduleShutdown();
|
|
79
|
+
logger.info('Successfully disconnected');
|
|
80
|
+
process.exit();
|
|
81
|
+
} catch (err) {
|
|
82
|
+
logger.error(err, 'Unable to disconnect');
|
|
83
|
+
process.exit(-1);
|
|
84
|
+
}
|
|
95
85
|
}
|
|
96
86
|
|
|
97
87
|
async function run(settings, ipc) {
|