elasticio-sailor-nodejs 3.0.0-sailor-proxy-dev17 → 3.0.0-sailor-proxy-dev18
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 +103 -72
- package/lib/settings.js +0 -6
- 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() {
|
|
@@ -546,8 +587,6 @@ class ProxyClient {
|
|
|
546
587
|
metadata,
|
|
547
588
|
forceProtocolVersion
|
|
548
589
|
}) {
|
|
549
|
-
await this._ensureConnection();
|
|
550
|
-
|
|
551
590
|
const throttledSend = this.throttles[type];
|
|
552
591
|
if (throttledSend) {
|
|
553
592
|
log.debug({ incomingMessageId, type, metadata }, 'Applying rate limiting for message send');
|
|
@@ -584,7 +623,7 @@ class ProxyClient {
|
|
|
584
623
|
[HTTP2_HEADER_PATH]: `/message?${queryParams}`,
|
|
585
624
|
[HTTP2_HEADER_METHOD]: 'POST',
|
|
586
625
|
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader,
|
|
587
|
-
[
|
|
626
|
+
[MESSAGE_METADATA_HEADER]: JSON.stringify(preparedMetadata)
|
|
588
627
|
});
|
|
589
628
|
if (preparedData) {
|
|
590
629
|
postMessageStream.write(preparedData);
|
|
@@ -652,8 +691,6 @@ class ProxyClient {
|
|
|
652
691
|
}
|
|
653
692
|
|
|
654
693
|
async finishProcessing(metadata, status) {
|
|
655
|
-
await this._ensureConnection();
|
|
656
|
-
|
|
657
694
|
if (Object.values(MESSAGE_PROCESSING_STATUS).indexOf(status) === -1) {
|
|
658
695
|
throw new Error(`Invalid message processing status: ${status}`);
|
|
659
696
|
}
|
|
@@ -703,8 +740,6 @@ class ProxyClient {
|
|
|
703
740
|
}
|
|
704
741
|
|
|
705
742
|
async sendError(err, outgoingMetadata, originalMessage, metadata) {
|
|
706
|
-
await this._ensureConnection();
|
|
707
|
-
|
|
708
743
|
const encryptedError = this._encryptor.encryptMessageContent({
|
|
709
744
|
name: err.name,
|
|
710
745
|
message: err.message,
|
|
@@ -742,8 +777,6 @@ class ProxyClient {
|
|
|
742
777
|
}
|
|
743
778
|
|
|
744
779
|
async sendRebound(reboundError, metadata, outgoingMetadata) {
|
|
745
|
-
await this._ensureConnection();
|
|
746
|
-
|
|
747
780
|
outgoingMetadata.end = new Date().getTime();
|
|
748
781
|
outgoingMetadata.reboundReason = reboundError.message;
|
|
749
782
|
return this.sendMessage({
|
|
@@ -754,8 +787,6 @@ class ProxyClient {
|
|
|
754
787
|
}
|
|
755
788
|
|
|
756
789
|
async sendSnapshot(data, metadata) {
|
|
757
|
-
await this._ensureConnection();
|
|
758
|
-
|
|
759
790
|
const payload = JSON.stringify(data);
|
|
760
791
|
return this.sendMessage({
|
|
761
792
|
type: 'snapshot',
|
|
@@ -766,20 +797,20 @@ class ProxyClient {
|
|
|
766
797
|
|
|
767
798
|
_extractMessageMetadata(headers) {
|
|
768
799
|
log.trace({ headers }, 'Extracting message metadata');
|
|
769
|
-
const
|
|
770
|
-
if (!
|
|
800
|
+
const messageMetadata = headers[MESSAGE_METADATA_HEADER];
|
|
801
|
+
if (!messageMetadata) {
|
|
771
802
|
log.error({ headers }, 'Missing metadata in message stream response');
|
|
772
803
|
throw new Error('Missing metadata in message stream response');
|
|
773
804
|
}
|
|
774
805
|
|
|
775
806
|
let parsedMessageMetadata;
|
|
776
807
|
try {
|
|
777
|
-
parsedMessageMetadata = JSON.parse(
|
|
808
|
+
parsedMessageMetadata = JSON.parse(messageMetadata);
|
|
778
809
|
} catch (e) {
|
|
779
|
-
log.error({
|
|
810
|
+
log.error({ messageMetadata: messageMetadata }, 'Failed to parse metadata JSON');
|
|
780
811
|
throw new Error('Failed to parse metadata JSON');
|
|
781
812
|
}
|
|
782
|
-
log.debug({ parsedMessageMetadata }, 'Parsed metadata from
|
|
813
|
+
log.debug({ parsedMessageMetadata }, 'Parsed metadata from header');
|
|
783
814
|
|
|
784
815
|
// Get meta headers
|
|
785
816
|
const metaHeaderNames = Object.keys(parsedMessageMetadata)
|
package/lib/settings.js
CHANGED
|
@@ -24,12 +24,6 @@ function getOptionalEnvVars(envVars) {
|
|
|
24
24
|
ERROR_RATE_LIMIT: 2, // 2 errors every 100ms
|
|
25
25
|
SNAPSHOT_RATE_LIMIT: 2, // 2 Snapshots every 100ms
|
|
26
26
|
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
27
|
|
|
34
28
|
OBJECT_STORAGE_SIZE_THRESHOLD: 1048576,
|
|
35
29
|
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-dev18",
|
|
5
5
|
"main": "run.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc",
|