elasticio-sailor-nodejs 3.0.0-sailor-proxy-dev16 → 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 +109 -77
- 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';
|
|
@@ -58,6 +58,7 @@ class ProxyClient {
|
|
|
58
58
|
compId: settings.COMP_ID,
|
|
59
59
|
function: settings.FUNCTION
|
|
60
60
|
}, proxySecret);
|
|
61
|
+
this.authHeader = `Bearer ${this.proxyJWT}`;
|
|
61
62
|
|
|
62
63
|
this.throttles = {
|
|
63
64
|
// 100 Messages per Second
|
|
@@ -154,6 +155,9 @@ class ProxyClient {
|
|
|
154
155
|
log.warn({ reason, details }, 'Connection lost, initiating reconnection');
|
|
155
156
|
this.reconnecting = true;
|
|
156
157
|
|
|
158
|
+
// Clean up existing message streams
|
|
159
|
+
this._cleanupMessageStreams();
|
|
160
|
+
|
|
157
161
|
// Clean up the current session
|
|
158
162
|
if (this.clientSession && !this.clientSession.destroyed) {
|
|
159
163
|
this.clientSession.destroy();
|
|
@@ -227,7 +231,6 @@ class ProxyClient {
|
|
|
227
231
|
return resolve();
|
|
228
232
|
}
|
|
229
233
|
|
|
230
|
-
// TODO: what if incoming message is received during graceful shutdown?
|
|
231
234
|
this.clientSession.close(() => {
|
|
232
235
|
log.debug('Successfully closed HTTP2 connection');
|
|
233
236
|
resolve();
|
|
@@ -236,21 +239,24 @@ class ProxyClient {
|
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
async _ensureConnection() {
|
|
239
|
-
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) {
|
|
240
244
|
return;
|
|
241
245
|
}
|
|
242
246
|
|
|
243
247
|
if (this.reconnecting) {
|
|
244
|
-
// Wait for reconnection to complete
|
|
248
|
+
// Wait for reconnection to complete and ensure we have a valid session
|
|
245
249
|
log.info('Waiting for reconnection to complete');
|
|
246
250
|
const maxWait = 30000; // 30 seconds
|
|
247
251
|
const startTime = Date.now();
|
|
248
|
-
while (this.reconnecting
|
|
252
|
+
while ((this.reconnecting || !this.clientSession || this.clientSession.destroyed) &&
|
|
253
|
+
!this.closed && (Date.now() - startTime) < maxWait) {
|
|
249
254
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
250
255
|
}
|
|
251
256
|
|
|
252
|
-
|
|
253
|
-
|
|
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');
|
|
254
260
|
}
|
|
255
261
|
return;
|
|
256
262
|
}
|
|
@@ -268,6 +274,7 @@ class ProxyClient {
|
|
|
268
274
|
const retryDelay = this.settings.PROXY_OBJECT_REQUEST_RETRY_DELAY;
|
|
269
275
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
270
276
|
try {
|
|
277
|
+
await this._ensureConnection();
|
|
271
278
|
const result = await requestFn();
|
|
272
279
|
if (attempt > 0) {
|
|
273
280
|
log.info({ attempt, maxRetries }, `${operationName} succeeded after retry`);
|
|
@@ -285,8 +292,9 @@ class ProxyClient {
|
|
|
285
292
|
attempt,
|
|
286
293
|
maxRetries,
|
|
287
294
|
error: error.message,
|
|
288
|
-
|
|
289
|
-
|
|
295
|
+
errorCode: error.code,
|
|
296
|
+
errorStatusCode: error.statusCode
|
|
297
|
+
}, `${operationName} failed and will not be retried`);
|
|
290
298
|
throw error;
|
|
291
299
|
}
|
|
292
300
|
|
|
@@ -312,8 +320,6 @@ class ProxyClient {
|
|
|
312
320
|
}
|
|
313
321
|
|
|
314
322
|
async fetchMessageBody(message, logger) {
|
|
315
|
-
await this._ensureConnection();
|
|
316
|
-
|
|
317
323
|
const { body, headers } = message;
|
|
318
324
|
|
|
319
325
|
logger.info('Checking if incoming messages is lightweight...');
|
|
@@ -336,7 +342,7 @@ class ProxyClient {
|
|
|
336
342
|
const getObjectStream = this.clientSession.request({
|
|
337
343
|
[HTTP2_HEADER_PATH]: `/object/${objectId}`,
|
|
338
344
|
[HTTP2_HEADER_METHOD]: 'GET',
|
|
339
|
-
[HTTP2_HEADER_AUTHORIZATION]: this.
|
|
345
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader
|
|
340
346
|
}).pipe(this._encryptor.createDecipher());
|
|
341
347
|
|
|
342
348
|
const chunks = [];
|
|
@@ -362,13 +368,11 @@ class ProxyClient {
|
|
|
362
368
|
}
|
|
363
369
|
|
|
364
370
|
async uploadMessageBody(bodyBuf) {
|
|
365
|
-
await this._ensureConnection();
|
|
366
|
-
|
|
367
371
|
return this._proxyRequestWithRetries('Upload message body', () => new Promise((resolve, reject) => {
|
|
368
372
|
const postMessageStream = this.clientSession.request({
|
|
369
373
|
[HTTP2_HEADER_PATH]: '/object',
|
|
370
374
|
[HTTP2_HEADER_METHOD]: 'POST',
|
|
371
|
-
[HTTP2_HEADER_AUTHORIZATION]: this.
|
|
375
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader
|
|
372
376
|
});
|
|
373
377
|
|
|
374
378
|
let responseData = '';
|
|
@@ -426,69 +430,107 @@ class ProxyClient {
|
|
|
426
430
|
prefetch
|
|
427
431
|
}).toString();
|
|
428
432
|
log.info({ prefetch }, 'Requesting message from proxy');
|
|
429
|
-
// TODO: what timeout is here? e.g. when flow is realtime - we might need to wait a long time...
|
|
430
433
|
const getMessageStream = this.clientSession.request({
|
|
431
434
|
[HTTP2_HEADER_PATH]: `/message?${queryParams}`,
|
|
432
435
|
[HTTP2_HEADER_METHOD]: 'GET',
|
|
433
|
-
[HTTP2_HEADER_AUTHORIZATION]: this.
|
|
436
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader
|
|
434
437
|
});
|
|
435
438
|
this.getMessageStreams.add(getMessageStream);
|
|
436
439
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
+
});
|
|
457
465
|
});
|
|
458
|
-
});
|
|
459
466
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
+
});
|
|
467
479
|
});
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
}
|
|
474
501
|
}));
|
|
475
502
|
} catch (err) {
|
|
503
|
+
log.error(err, 'Error while listening for messages');
|
|
476
504
|
if (this.closed) {
|
|
477
505
|
log.info('Connection closed, stopping message listener');
|
|
478
506
|
break;
|
|
479
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
|
+
}
|
|
480
521
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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');
|
|
485
530
|
}
|
|
486
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
487
|
-
} finally {
|
|
488
|
-
log.debug('Cleaning up message streams');
|
|
489
|
-
this.getMessageStreams.clear();
|
|
490
531
|
}
|
|
491
532
|
}
|
|
533
|
+
this.getMessageStreams.clear();
|
|
492
534
|
}
|
|
493
535
|
|
|
494
536
|
async stopListeningForMessages() {
|
|
@@ -545,8 +587,6 @@ class ProxyClient {
|
|
|
545
587
|
metadata,
|
|
546
588
|
forceProtocolVersion
|
|
547
589
|
}) {
|
|
548
|
-
await this._ensureConnection();
|
|
549
|
-
|
|
550
590
|
const throttledSend = this.throttles[type];
|
|
551
591
|
if (throttledSend) {
|
|
552
592
|
log.debug({ incomingMessageId, type, metadata }, 'Applying rate limiting for message send');
|
|
@@ -582,8 +622,8 @@ class ProxyClient {
|
|
|
582
622
|
const postMessageStream = this.clientSession.request({
|
|
583
623
|
[HTTP2_HEADER_PATH]: `/message?${queryParams}`,
|
|
584
624
|
[HTTP2_HEADER_METHOD]: 'POST',
|
|
585
|
-
[HTTP2_HEADER_AUTHORIZATION]: this.
|
|
586
|
-
[
|
|
625
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader,
|
|
626
|
+
[MESSAGE_METADATA_HEADER]: JSON.stringify(preparedMetadata)
|
|
587
627
|
});
|
|
588
628
|
if (preparedData) {
|
|
589
629
|
postMessageStream.write(preparedData);
|
|
@@ -651,8 +691,6 @@ class ProxyClient {
|
|
|
651
691
|
}
|
|
652
692
|
|
|
653
693
|
async finishProcessing(metadata, status) {
|
|
654
|
-
await this._ensureConnection();
|
|
655
|
-
|
|
656
694
|
if (Object.values(MESSAGE_PROCESSING_STATUS).indexOf(status) === -1) {
|
|
657
695
|
throw new Error(`Invalid message processing status: ${status}`);
|
|
658
696
|
}
|
|
@@ -667,7 +705,7 @@ class ProxyClient {
|
|
|
667
705
|
const postMessageStream = this.clientSession.request({
|
|
668
706
|
[HTTP2_HEADER_PATH]: `/finish-processing?${queryParams}`,
|
|
669
707
|
[HTTP2_HEADER_METHOD]: 'POST',
|
|
670
|
-
[HTTP2_HEADER_AUTHORIZATION]: this.
|
|
708
|
+
[HTTP2_HEADER_AUTHORIZATION]: this.authHeader
|
|
671
709
|
});
|
|
672
710
|
postMessageStream.end();
|
|
673
711
|
|
|
@@ -702,8 +740,6 @@ class ProxyClient {
|
|
|
702
740
|
}
|
|
703
741
|
|
|
704
742
|
async sendError(err, outgoingMetadata, originalMessage, metadata) {
|
|
705
|
-
await this._ensureConnection();
|
|
706
|
-
|
|
707
743
|
const encryptedError = this._encryptor.encryptMessageContent({
|
|
708
744
|
name: err.name,
|
|
709
745
|
message: err.message,
|
|
@@ -741,8 +777,6 @@ class ProxyClient {
|
|
|
741
777
|
}
|
|
742
778
|
|
|
743
779
|
async sendRebound(reboundError, metadata, outgoingMetadata) {
|
|
744
|
-
await this._ensureConnection();
|
|
745
|
-
|
|
746
780
|
outgoingMetadata.end = new Date().getTime();
|
|
747
781
|
outgoingMetadata.reboundReason = reboundError.message;
|
|
748
782
|
return this.sendMessage({
|
|
@@ -753,8 +787,6 @@ class ProxyClient {
|
|
|
753
787
|
}
|
|
754
788
|
|
|
755
789
|
async sendSnapshot(data, metadata) {
|
|
756
|
-
await this._ensureConnection();
|
|
757
|
-
|
|
758
790
|
const payload = JSON.stringify(data);
|
|
759
791
|
return this.sendMessage({
|
|
760
792
|
type: 'snapshot',
|
|
@@ -765,20 +797,20 @@ class ProxyClient {
|
|
|
765
797
|
|
|
766
798
|
_extractMessageMetadata(headers) {
|
|
767
799
|
log.trace({ headers }, 'Extracting message metadata');
|
|
768
|
-
const
|
|
769
|
-
if (!
|
|
800
|
+
const messageMetadata = headers[MESSAGE_METADATA_HEADER];
|
|
801
|
+
if (!messageMetadata) {
|
|
770
802
|
log.error({ headers }, 'Missing metadata in message stream response');
|
|
771
803
|
throw new Error('Missing metadata in message stream response');
|
|
772
804
|
}
|
|
773
805
|
|
|
774
806
|
let parsedMessageMetadata;
|
|
775
807
|
try {
|
|
776
|
-
parsedMessageMetadata = JSON.parse(
|
|
808
|
+
parsedMessageMetadata = JSON.parse(messageMetadata);
|
|
777
809
|
} catch (e) {
|
|
778
|
-
log.error({
|
|
810
|
+
log.error({ messageMetadata: messageMetadata }, 'Failed to parse metadata JSON');
|
|
779
811
|
throw new Error('Failed to parse metadata JSON');
|
|
780
812
|
}
|
|
781
|
-
log.debug({ parsedMessageMetadata }, 'Parsed metadata from
|
|
813
|
+
log.debug({ parsedMessageMetadata }, 'Parsed metadata from header');
|
|
782
814
|
|
|
783
815
|
// Get meta headers
|
|
784
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",
|