elasticio-sailor-nodejs 3.0.0-sailor-proxy-dev17 → 3.0.0-sailor-proxy-dev19
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/config/local.json +1 -1
- package/lib/proxy-client.js +111 -75
- package/lib/settings.js +0 -7
- package/package.json +1 -1
package/config/local.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"ELASTICIO_WORKSPACE_ID": "69b26526c4796609ca0da128",
|
|
8
8
|
"ELASTICIO_USER_ID": "69b26526c4796609ca0da127",
|
|
9
9
|
"ELASTICIO_COMP_ID": "69b26526c4796609ca0da126",
|
|
10
|
-
"ELASTICIO_FUNCTION": "
|
|
10
|
+
"ELASTICIO_FUNCTION": "data_trigger",
|
|
11
11
|
"ELASTICIO_API_URI": "http://localhost:9000",
|
|
12
12
|
"ELASTICIO_API_USERNAME": "task-692ee23d4ab5d34bb7321559",
|
|
13
13
|
"ELASTICIO_API_KEY": "976ffec8-455b-494e-9478-2d66761f4040",
|
package/lib/proxy-client.js
CHANGED
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
NGHTTP2_NO_ERROR
|
|
18
18
|
} = http2.constants;
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const MESSAGE_METADATA_HEADER = 'message-metadata';
|
|
21
21
|
const HEADER_ROUTING_KEY = 'x-eio-routing-key';
|
|
22
22
|
const AMQP_HEADER_META_PREFIX = 'x-eio-meta-';
|
|
23
23
|
const OBJECT_ID_HEADER = 'x-ipaas-object-storage-id';
|
|
@@ -155,6 +155,9 @@ class ProxyClient {
|
|
|
155
155
|
log.warn({ reason, details }, 'Connection lost, initiating reconnection');
|
|
156
156
|
this.reconnecting = true;
|
|
157
157
|
|
|
158
|
+
// Clean up existing message streams
|
|
159
|
+
this._cleanupMessageStreams();
|
|
160
|
+
|
|
158
161
|
// Clean up the current session
|
|
159
162
|
if (this.clientSession && !this.clientSession.destroyed) {
|
|
160
163
|
this.clientSession.destroy();
|
|
@@ -228,7 +231,6 @@ class ProxyClient {
|
|
|
228
231
|
return resolve();
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
// TODO: what if incoming message is received during graceful shutdown?
|
|
232
234
|
this.clientSession.close(() => {
|
|
233
235
|
log.debug('Successfully closed HTTP2 connection');
|
|
234
236
|
resolve();
|
|
@@ -237,21 +239,24 @@ class ProxyClient {
|
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
async _ensureConnection() {
|
|
240
|
-
if (
|
|
242
|
+
// Check if we have a valid connection (not just "isConnected" which includes reconnecting state)
|
|
243
|
+
if (!this.closed && this.clientSession && !this.clientSession.destroyed) {
|
|
241
244
|
return;
|
|
242
245
|
}
|
|
243
246
|
|
|
244
247
|
if (this.reconnecting) {
|
|
245
|
-
// Wait for reconnection to complete
|
|
248
|
+
// Wait for reconnection to complete and ensure we have a valid session
|
|
246
249
|
log.info('Waiting for reconnection to complete');
|
|
247
250
|
const maxWait = 30000; // 30 seconds
|
|
248
251
|
const startTime = Date.now();
|
|
249
|
-
while (this.reconnecting
|
|
252
|
+
while ((this.reconnecting || !this.clientSession || this.clientSession.destroyed) &&
|
|
253
|
+
!this.closed && (Date.now() - startTime) < maxWait) {
|
|
250
254
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
251
255
|
}
|
|
252
256
|
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
// Double-check we have a valid session after waiting
|
|
258
|
+
if (this.closed || !this.clientSession || this.clientSession.destroyed) {
|
|
259
|
+
throw new Error('Failed to establish valid connection within timeout period');
|
|
255
260
|
}
|
|
256
261
|
return;
|
|
257
262
|
}
|
|
@@ -269,6 +274,7 @@ class ProxyClient {
|
|
|
269
274
|
const retryDelay = this.settings.PROXY_OBJECT_REQUEST_RETRY_DELAY;
|
|
270
275
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
271
276
|
try {
|
|
277
|
+
await this._ensureConnection();
|
|
272
278
|
const result = await requestFn();
|
|
273
279
|
if (attempt > 0) {
|
|
274
280
|
log.info({ attempt, maxRetries }, `${operationName} succeeded after retry`);
|
|
@@ -286,8 +292,9 @@ class ProxyClient {
|
|
|
286
292
|
attempt,
|
|
287
293
|
maxRetries,
|
|
288
294
|
error: error.message,
|
|
289
|
-
|
|
290
|
-
|
|
295
|
+
errorCode: error.code,
|
|
296
|
+
errorStatusCode: error.statusCode
|
|
297
|
+
}, `${operationName} failed and will not be retried`);
|
|
291
298
|
throw error;
|
|
292
299
|
}
|
|
293
300
|
|
|
@@ -313,8 +320,6 @@ class ProxyClient {
|
|
|
313
320
|
}
|
|
314
321
|
|
|
315
322
|
async fetchMessageBody(message, logger) {
|
|
316
|
-
await this._ensureConnection();
|
|
317
|
-
|
|
318
323
|
const { body, headers } = message;
|
|
319
324
|
|
|
320
325
|
logger.info('Checking if incoming messages is lightweight...');
|
|
@@ -363,8 +368,6 @@ class ProxyClient {
|
|
|
363
368
|
}
|
|
364
369
|
|
|
365
370
|
async uploadMessageBody(bodyBuf) {
|
|
366
|
-
await this._ensureConnection();
|
|
367
|
-
|
|
368
371
|
return this._proxyRequestWithRetries('Upload message body', () => new Promise((resolve, reject) => {
|
|
369
372
|
const postMessageStream = this.clientSession.request({
|
|
370
373
|
[HTTP2_HEADER_PATH]: '/object',
|
|
@@ -427,7 +430,6 @@ class ProxyClient {
|
|
|
427
430
|
prefetch
|
|
428
431
|
}).toString();
|
|
429
432
|
log.info({ prefetch }, 'Requesting message from proxy');
|
|
430
|
-
// TODO: what timeout is here? e.g. when flow is realtime - we might need to wait a long time...
|
|
431
433
|
const getMessageStream = this.clientSession.request({
|
|
432
434
|
[HTTP2_HEADER_PATH]: `/message?${queryParams}`,
|
|
433
435
|
[HTTP2_HEADER_METHOD]: 'GET',
|
|
@@ -435,61 +437,100 @@ class ProxyClient {
|
|
|
435
437
|
});
|
|
436
438
|
this.getMessageStreams.add(getMessageStream);
|
|
437
439
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
440
|
+
try {
|
|
441
|
+
const { headers, body } = await new Promise((resolve, reject) => {
|
|
442
|
+
getMessageStream.on('response', (headers, flags) => {
|
|
443
|
+
log.info({ headers, flags }, 'Connected to message stream');
|
|
444
|
+
if (headers[HTTP2_HEADER_STATUS] === 204) {
|
|
445
|
+
log.info('Received empty message stream, closing stream and going to request again');
|
|
446
|
+
getMessageStream.close(NGHTTP2_NO_ERROR);
|
|
447
|
+
return;
|
|
448
|
+
} else if (headers[HTTP2_HEADER_STATUS] !== 200) {
|
|
449
|
+
return reject(new Error(`Failed to get message, status code: ${headers[HTTP2_HEADER_STATUS]}`));
|
|
450
|
+
}
|
|
451
|
+
const chunks = [];
|
|
452
|
+
getMessageStream.on('data', chunk => {
|
|
453
|
+
chunks.push(chunk);
|
|
454
|
+
});
|
|
455
|
+
getMessageStream.on('end', () => {
|
|
456
|
+
log.info('Message stream ended by server');
|
|
457
|
+
const body = Buffer.concat(chunks);
|
|
458
|
+
log.info({
|
|
459
|
+
messageId: headers.messageId,
|
|
460
|
+
messageSize: body.length
|
|
461
|
+
}, 'Received complete message from server');
|
|
462
|
+
log.trace({ body: body.toString() }, 'Message body as string');
|
|
463
|
+
resolve({ headers, body });
|
|
464
|
+
});
|
|
458
465
|
});
|
|
459
|
-
});
|
|
460
466
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
467
|
+
getMessageStream.on('close', () => {
|
|
468
|
+
log.warn('Message stream closed by server');
|
|
469
|
+
if (getMessageStream.rstCode !== NGHTTP2_NO_ERROR) {
|
|
470
|
+
reject(new Error('Message stream closed by server'));
|
|
471
|
+
} else {
|
|
472
|
+
resolve({ headers: null, body: null });
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
getMessageStream.on('error', (err) => {
|
|
476
|
+
log.error(err, 'Error on message stream');
|
|
477
|
+
reject(err);
|
|
478
|
+
});
|
|
468
479
|
});
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
480
|
+
if (headers === null && body === null) {
|
|
481
|
+
// Stream was closed without data (e.g. 204 No Content), just return to listen for the next messageMetadata
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const messageMetadata = this._extractMessageMetadata(headers);
|
|
486
|
+
const message = this._decodeMessage(body, messageMetadata);
|
|
487
|
+
log.debug({ messageMetadata, message }, 'Processing received message');
|
|
488
|
+
await messageHandler(messageMetadata, message);
|
|
489
|
+
} finally {
|
|
490
|
+
// Remove this specific stream from the tracking set
|
|
491
|
+
this.getMessageStreams.delete(getMessageStream);
|
|
492
|
+
// Ensure the stream is properly closed
|
|
493
|
+
if (!getMessageStream.closed && !getMessageStream.destroyed) {
|
|
494
|
+
try {
|
|
495
|
+
getMessageStream.destroy();
|
|
496
|
+
} catch (err) {
|
|
497
|
+
log.debug({ err }, 'Error closing message stream');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
475
501
|
}));
|
|
476
502
|
} catch (err) {
|
|
503
|
+
log.error(err, 'Error while listening for messages');
|
|
477
504
|
if (this.closed) {
|
|
478
505
|
log.info('Connection closed, stopping message listener');
|
|
479
506
|
break;
|
|
480
507
|
}
|
|
508
|
+
// Clean up any invalid streams from the error
|
|
509
|
+
this._cleanupMessageStreams();
|
|
510
|
+
} finally {
|
|
511
|
+
// Note: _cleanupMessageStreams() is called in the catch block for errors
|
|
512
|
+
// and in _handleDisconnection() for disconnections, so we only clear here
|
|
513
|
+
// for normal completion (which shouldn't happen in the infinite loop)
|
|
514
|
+
if (this.closed || !this.listeningForMessages) {
|
|
515
|
+
log.debug('Cleaning up message streams on exit');
|
|
516
|
+
this._cleanupMessageStreams();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
481
521
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
522
|
+
_cleanupMessageStreams() {
|
|
523
|
+
log.debug({ streamCount: this.getMessageStreams.size }, 'Cleaning up message streams due to disconnection');
|
|
524
|
+
for (const stream of this.getMessageStreams) {
|
|
525
|
+
if (stream && !stream.closed && !stream.destroyed) {
|
|
526
|
+
try {
|
|
527
|
+
stream.destroy();
|
|
528
|
+
} catch (err) {
|
|
529
|
+
log.debug({ err }, 'Error destroying message stream');
|
|
486
530
|
}
|
|
487
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
488
|
-
} finally {
|
|
489
|
-
log.debug('Cleaning up message streams');
|
|
490
|
-
this.getMessageStreams.clear();
|
|
491
531
|
}
|
|
492
532
|
}
|
|
533
|
+
this.getMessageStreams.clear();
|
|
493
534
|
}
|
|
494
535
|
|
|
495
536
|
async stopListeningForMessages() {
|
|
@@ -522,6 +563,7 @@ class ProxyClient {
|
|
|
522
563
|
...metadata,
|
|
523
564
|
messageId: metadata.messageId || uuid.v4()
|
|
524
565
|
};
|
|
566
|
+
delete preparedMetadata.reboundIteration;
|
|
525
567
|
let preparedData = data;
|
|
526
568
|
if (preparedData && preparedData.headers) {
|
|
527
569
|
preparedData.headers = _.omitBy(
|
|
@@ -546,8 +588,6 @@ class ProxyClient {
|
|
|
546
588
|
metadata,
|
|
547
589
|
forceProtocolVersion
|
|
548
590
|
}) {
|
|
549
|
-
await this._ensureConnection();
|
|
550
|
-
|
|
551
591
|
const throttledSend = this.throttles[type];
|
|
552
592
|
if (throttledSend) {
|
|
553
593
|
log.debug({ incomingMessageId, type, metadata }, 'Applying rate limiting for message send');
|
|
@@ -584,7 +624,7 @@ class ProxyClient {
|
|
|
584
624
|
[HTTP2_HEADER_PATH]: `/message?${queryParams}`,
|
|
585
625
|
[HTTP2_HEADER_METHOD]: 'POST',
|
|
586
626
|
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader,
|
|
587
|
-
[
|
|
627
|
+
[MESSAGE_METADATA_HEADER]: JSON.stringify(preparedMetadata)
|
|
588
628
|
});
|
|
589
629
|
if (preparedData) {
|
|
590
630
|
postMessageStream.write(preparedData);
|
|
@@ -652,16 +692,15 @@ class ProxyClient {
|
|
|
652
692
|
}
|
|
653
693
|
|
|
654
694
|
async finishProcessing(metadata, status) {
|
|
655
|
-
await this._ensureConnection();
|
|
656
|
-
|
|
657
695
|
if (Object.values(MESSAGE_PROCESSING_STATUS).indexOf(status) === -1) {
|
|
658
696
|
throw new Error(`Invalid message processing status: ${status}`);
|
|
659
697
|
}
|
|
660
|
-
const incomingMessageId = metadata
|
|
661
|
-
log.debug({ incomingMessageId, status }, 'Finishing processing of message');
|
|
698
|
+
const { messageId: incomingMessageId, reboundIteration } = metadata;
|
|
699
|
+
log.debug({ incomingMessageId, status, reboundIteration }, 'Finishing processing of message');
|
|
662
700
|
const queryParams = new URLSearchParams({
|
|
663
701
|
incomingMessageId,
|
|
664
|
-
status
|
|
702
|
+
status,
|
|
703
|
+
...(typeof reboundIteration !== 'undefined' ? { reboundIteration } : {})
|
|
665
704
|
}).toString();
|
|
666
705
|
|
|
667
706
|
await this._proxyRequestWithRetries('Finish processing', () => new Promise((resolve, reject) => {
|
|
@@ -703,8 +742,6 @@ class ProxyClient {
|
|
|
703
742
|
}
|
|
704
743
|
|
|
705
744
|
async sendError(err, outgoingMetadata, originalMessage, metadata) {
|
|
706
|
-
await this._ensureConnection();
|
|
707
|
-
|
|
708
745
|
const encryptedError = this._encryptor.encryptMessageContent({
|
|
709
746
|
name: err.name,
|
|
710
747
|
message: err.message,
|
|
@@ -742,8 +779,6 @@ class ProxyClient {
|
|
|
742
779
|
}
|
|
743
780
|
|
|
744
781
|
async sendRebound(reboundError, metadata, outgoingMetadata) {
|
|
745
|
-
await this._ensureConnection();
|
|
746
|
-
|
|
747
782
|
outgoingMetadata.end = new Date().getTime();
|
|
748
783
|
outgoingMetadata.reboundReason = reboundError.message;
|
|
749
784
|
return this.sendMessage({
|
|
@@ -754,8 +789,6 @@ class ProxyClient {
|
|
|
754
789
|
}
|
|
755
790
|
|
|
756
791
|
async sendSnapshot(data, metadata) {
|
|
757
|
-
await this._ensureConnection();
|
|
758
|
-
|
|
759
792
|
const payload = JSON.stringify(data);
|
|
760
793
|
return this.sendMessage({
|
|
761
794
|
type: 'snapshot',
|
|
@@ -766,20 +799,20 @@ class ProxyClient {
|
|
|
766
799
|
|
|
767
800
|
_extractMessageMetadata(headers) {
|
|
768
801
|
log.trace({ headers }, 'Extracting message metadata');
|
|
769
|
-
const
|
|
770
|
-
if (!
|
|
802
|
+
const messageMetadata = headers[MESSAGE_METADATA_HEADER];
|
|
803
|
+
if (!messageMetadata) {
|
|
771
804
|
log.error({ headers }, 'Missing metadata in message stream response');
|
|
772
805
|
throw new Error('Missing metadata in message stream response');
|
|
773
806
|
}
|
|
774
807
|
|
|
775
808
|
let parsedMessageMetadata;
|
|
776
809
|
try {
|
|
777
|
-
parsedMessageMetadata = JSON.parse(
|
|
810
|
+
parsedMessageMetadata = JSON.parse(messageMetadata);
|
|
778
811
|
} catch (e) {
|
|
779
|
-
log.error({
|
|
812
|
+
log.error({ messageMetadata: messageMetadata }, 'Failed to parse metadata JSON');
|
|
780
813
|
throw new Error('Failed to parse metadata JSON');
|
|
781
814
|
}
|
|
782
|
-
log.debug({ parsedMessageMetadata }, 'Parsed metadata from
|
|
815
|
+
log.debug({ parsedMessageMetadata }, 'Parsed metadata from header');
|
|
783
816
|
|
|
784
817
|
// Get meta headers
|
|
785
818
|
const metaHeaderNames = Object.keys(parsedMessageMetadata)
|
|
@@ -804,6 +837,9 @@ class ProxyClient {
|
|
|
804
837
|
if (parsedMessageMetadata.reply_to) {
|
|
805
838
|
result.reply_to = parsedMessageMetadata.reply_to;
|
|
806
839
|
}
|
|
840
|
+
if (parsedMessageMetadata.reboundIteration) {
|
|
841
|
+
result.reboundIteration = parsedMessageMetadata.reboundIteration;
|
|
842
|
+
}
|
|
807
843
|
log.debug({ result }, 'Extracted message metadata');
|
|
808
844
|
return result;
|
|
809
845
|
}
|
package/lib/settings.js
CHANGED
|
@@ -19,17 +19,10 @@ function getOptionalEnvVars(envVars) {
|
|
|
19
19
|
PROXY_OBJECT_REQUEST_RETRY_DELAY: 100,
|
|
20
20
|
PROXY_OBJECT_REQUEST_MAX_RETRY_DELAY: 5 * 60 * 1000, // 5 mins
|
|
21
21
|
|
|
22
|
-
// TODO: Move to proxy?
|
|
23
22
|
DATA_RATE_LIMIT: 10, // 10 data events every 100ms
|
|
24
23
|
ERROR_RATE_LIMIT: 2, // 2 errors every 100ms
|
|
25
24
|
SNAPSHOT_RATE_LIMIT: 2, // 2 Snapshots every 100ms
|
|
26
25
|
RATE_INTERVAL: 100, // 100ms
|
|
27
|
-
PROCESS_AMQP_DRAIN: true,
|
|
28
|
-
AMQP_PUBLISH_RETRY_DELAY: 100, // 100ms
|
|
29
|
-
AMQP_PUBLISH_RETRY_ATTEMPTS: Infinity,
|
|
30
|
-
AMQP_PUBLISH_MAX_RETRY_DELAY: 5 * 60 * 1000, // 5 mins
|
|
31
|
-
// Should be defaulted to true and moved to proxy
|
|
32
|
-
AMQP_PERSISTENT_MESSAGES: false,
|
|
33
26
|
|
|
34
27
|
OBJECT_STORAGE_SIZE_THRESHOLD: 1048576,
|
|
35
28
|
OUTGOING_MESSAGE_SIZE_LIMIT: 10485760,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elasticio-sailor-nodejs",
|
|
3
3
|
"description": "The official elastic.io library for bootstrapping and executing for Node.js connectors",
|
|
4
|
-
"version": "3.0.0-sailor-proxy-
|
|
4
|
+
"version": "3.0.0-sailor-proxy-dev19",
|
|
5
5
|
"main": "run.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc",
|