elasticio-sailor-nodejs 2.7.0-dev3 → 2.7.0
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/.nsprc +6 -0
- package/CHANGELOG.md +144 -0
- package/lib/amqp.js +272 -80
- package/lib/cipher.js +0 -82
- package/lib/component_reader.js +14 -7
- package/lib/encryptor.js +107 -14
- package/lib/executor.js +1 -1
- package/lib/ipc.js +13 -0
- package/lib/sailor.js +112 -104
- package/lib/service.js +1 -2
- package/lib/settings.js +72 -53
- package/package.json +13 -10
- package/run.js +60 -21
- package/runService.js +5 -1
- package/.travis.yml +0 -8
- package/Procfile +0 -2
- package/createQueues.js +0 -148
- package/gulpfile.js +0 -31
package/.nsprc
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
## 2.7.0 (September 15, 2022)
|
|
2
|
+
|
|
3
|
+
* Add AMQP_PERSISTENT_MESSAGES configuration env var to enable persistent delivery mode.
|
|
4
|
+
|
|
5
|
+
## 2.6.29 (July 14, 2022)
|
|
6
|
+
|
|
7
|
+
* Enabled keep-alive for global HTTPS agent ([#6359](https://github.com/elasticio/elasticio/issues/6359))
|
|
8
|
+
|
|
9
|
+
## 2.6.28 (June 21, 2022)
|
|
10
|
+
|
|
11
|
+
* Fix: "sailor-nodejs ignores errors from maester during lightweight message upload" [#6233](https://github.com/elasticio/elasticio/issues/6233))
|
|
12
|
+
|
|
13
|
+
## 2.6.27 (March 10, 2022)
|
|
14
|
+
|
|
15
|
+
* Added npm audit to CI and fixed all dependencies
|
|
16
|
+
|
|
17
|
+
## 2.6.26 (June 24, 2021)
|
|
18
|
+
|
|
19
|
+
* Replaced exit on "unhandledRejection" with error logging
|
|
20
|
+
|
|
21
|
+
## 2.6.25 (June 17, 2021)
|
|
22
|
+
|
|
23
|
+
* Improve consume performance by switching back to pushing instead of polling, keeping reconnections mechanism
|
|
24
|
+
([#5432](https://github.com/elasticio/elasticio/issues/5432))
|
|
25
|
+
|
|
26
|
+
## 2.6.24 (January 27, 2021)
|
|
27
|
+
|
|
28
|
+
* Fix "Big messages processing is not stable" errors ([#5051](https://github.com/elasticio/elasticio/issues/5051))
|
|
29
|
+
|
|
30
|
+
## 2.6.23 (January 4, 2021)
|
|
31
|
+
|
|
32
|
+
* Fix "Cannot read property 'publish' of undefined" error ([#5036](https://github.com/elasticio/elasticio/issues/5036))
|
|
33
|
+
|
|
34
|
+
## 2.6.22 (December 23, 2020)
|
|
35
|
+
|
|
36
|
+
* Enabled keep-alive for global HTTP agent ([#5012](#https://github.com/elasticio/elasticio/issues/5012))
|
|
37
|
+
|
|
38
|
+
## 2.6.21 (December 3, 2020)
|
|
39
|
+
|
|
40
|
+
* Fixed rebound message expiry time ([#4950](https://github.com/elasticio/elasticio/issues/4950))
|
|
41
|
+
|
|
42
|
+
## 2.6.20 (December 1, 2020)
|
|
43
|
+
|
|
44
|
+
* Fixed rebound message headers ([#4950](https://github.com/elasticio/elasticio/issues/4950))
|
|
45
|
+
|
|
46
|
+
## 2.6.19 (November 23, 2020)
|
|
47
|
+
|
|
48
|
+
* Separate connections for consuming and publishing
|
|
49
|
+
* Consuming is done with polling instead of pushing
|
|
50
|
+
* Reconnects on connection errors
|
|
51
|
+
* Handling consumer cancel notification
|
|
52
|
+
* Lowered log levels of some developers' log messages
|
|
53
|
+
* Addded env vars:
|
|
54
|
+
* AMQP_RECONNECT_ATTEMPTS - number of retries on connection close
|
|
55
|
+
* AMQP_RECONNECT_TIMEOUT - delay between connection retries
|
|
56
|
+
* WAIT_MESSAGES_TIMEOUT - delay between next poll when queue is empty
|
|
57
|
+
|
|
58
|
+
## 2.6.18 (October 26, 2020)
|
|
59
|
+
|
|
60
|
+
* Remove the logging of triggers and actions processing errors stack
|
|
61
|
+
|
|
62
|
+
## 2.6.17 (October 15, 2020)
|
|
63
|
+
|
|
64
|
+
* Annual audit of the component code to check if it exposes a sensitive data in the logs
|
|
65
|
+
|
|
66
|
+
## 2.6.16 (October 12, 2020)
|
|
67
|
+
|
|
68
|
+
* Fix incoming headers appearance in the logs (part 2)
|
|
69
|
+
|
|
70
|
+
## 2.6.15 (October 12, 2020)
|
|
71
|
+
|
|
72
|
+
* Fix incoming headers appearance in the logs (part 1)
|
|
73
|
+
|
|
74
|
+
## 2.6.14 (July 06, 2020)
|
|
75
|
+
|
|
76
|
+
* Add Lightweight messages support
|
|
77
|
+
* Sync this.emit() calls are not supported anymore. Use async process() interface and await this.emit() calls instead
|
|
78
|
+
|
|
79
|
+
## 2.6.13 (July 01, 2020)
|
|
80
|
+
|
|
81
|
+
* Error as incoming message in custom error handler
|
|
82
|
+
|
|
83
|
+
## 2.6.10 (June 03, 2020)
|
|
84
|
+
|
|
85
|
+
* Fix bug with incorrect publish retry policy.Dynamic flow control
|
|
86
|
+
* Dynamic flow control
|
|
87
|
+
|
|
88
|
+
## 2.6.9 (May 26, 2020)
|
|
89
|
+
|
|
90
|
+
* Fix bugs with 'Retention policy' notification and 'Nodejs sailor return promise interface does not support dynamic flow control'
|
|
91
|
+
|
|
92
|
+
## 2.6.8 (May 18, 2020)
|
|
93
|
+
|
|
94
|
+
* Fix bug when Lookout throws exception if incoming message from error queue doesn't have errorInput property
|
|
95
|
+
* From now on errors (description + stack) happening during component initialization won't be ignored and you will see them on frontend and in logs
|
|
96
|
+
|
|
97
|
+
## 2.6.7 (May 07, 2020)
|
|
98
|
+
|
|
99
|
+
* Add ability to publish to arbitrary exchange
|
|
100
|
+
|
|
101
|
+
## 2.6.6 (May 06, 2020)
|
|
102
|
+
|
|
103
|
+
* From now on Wiper will not suspend overloaded flows if all the components in the flow have the latest sailor (which supports dynamic flow control)
|
|
104
|
+
|
|
105
|
+
## 2.6.5 (February 20, 2020)
|
|
106
|
+
|
|
107
|
+
* Add support of non-base64 message in Admiral. This function is activated if 2 neighbour components' Sailors support a feature of non-base64 message
|
|
108
|
+
|
|
109
|
+
## 2.6.4 (February 14, 2020)
|
|
110
|
+
|
|
111
|
+
* Enable graceful restart for tasks pods
|
|
112
|
+
|
|
113
|
+
## 2.6.2 (January 29, 2020)
|
|
114
|
+
|
|
115
|
+
* Fix bug when Sailor does not reliably publish large messages
|
|
116
|
+
|
|
117
|
+
## 2.6.1 (January 15, 2020)
|
|
118
|
+
|
|
119
|
+
* Fix bug when publishing fail leads to fail in all subsequent pending messages
|
|
120
|
+
|
|
121
|
+
## 2.6.0 (January 06, 2020)
|
|
122
|
+
|
|
123
|
+
* A step must not put its own output message into the passthrough object
|
|
124
|
+
|
|
125
|
+
## 2.5.4 (December 23, 2019)
|
|
126
|
+
|
|
127
|
+
* Introduce new environment variable for Sailor: ELASTICIO_OUTGOING_MESSAGE_SIZE_LIMIT, which controls the outgoing message size limit
|
|
128
|
+
|
|
129
|
+
## 2.5.3 (December 11, 2019)
|
|
130
|
+
|
|
131
|
+
* Introduce new component environment variable ELASTICIO_ADDITIONAL_VARS_FOR_HEADERS. It contains comma-separated environment variables that will be passed to message headers
|
|
132
|
+
|
|
133
|
+
## 2.4.1 (July 18, 2019)
|
|
134
|
+
|
|
135
|
+
* Add additional information to RabbitMQ messages
|
|
136
|
+
|
|
137
|
+
## 2.4.0 (June 11, 2019)
|
|
138
|
+
|
|
139
|
+
* Add elastic's Threads functionality support
|
|
140
|
+
* Add component custom logger. E.g. `this.logger.info('hello')`
|
|
141
|
+
|
|
142
|
+
## 2.3.0 (October 19, 2018)
|
|
143
|
+
|
|
144
|
+
* Sailor now handles RabbitMQ disconnects correctly
|
package/lib/amqp.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const log = require('./logging.js');
|
|
2
2
|
const amqplib = require('amqplib');
|
|
3
3
|
const { IllegalOperationError } = require('amqplib/lib/error');
|
|
4
|
-
const
|
|
4
|
+
const Encryptor = require('./encryptor.js');
|
|
5
5
|
const _ = require('lodash');
|
|
6
6
|
const eventToPromise = require('event-to-promise');
|
|
7
7
|
const uuid = require('uuid');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
|
|
9
10
|
const HEADER_ROUTING_KEY = 'x-eio-routing-key';
|
|
10
11
|
const HEADER_ERROR_RESPONSE = 'x-eio-error-response';
|
|
@@ -12,32 +13,34 @@ const HEADER_ERROR_RESPONSE = 'x-eio-error-response';
|
|
|
12
13
|
class Amqp {
|
|
13
14
|
constructor(settings) {
|
|
14
15
|
this.settings = settings;
|
|
16
|
+
this._encryptor = new Encryptor(this.settings.MESSAGE_CRYPTO_PASSWORD, this.settings.MESSAGE_CRYPTO_IV);
|
|
17
|
+
this.closed = true;
|
|
18
|
+
this.consume = undefined;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
async connect(
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
21
|
-
this.amqp.on('error', log.criticalErrorAndExit.bind(log, 'amqp.error'));
|
|
22
|
-
this.amqp.on('close', log.criticalErrorAndExit.bind(log, 'amqp.close'));
|
|
23
|
-
}
|
|
24
|
-
log.debug('Connected to AMQP');
|
|
25
|
-
|
|
26
|
-
this.subscribeChannel = await this.amqp.createChannel();
|
|
27
|
-
this.subscribeChannel.on('error', log.criticalErrorAndExit.bind(log, 'subscribeChannel.error'));
|
|
28
|
-
log.debug('Opened subscribe channel');
|
|
29
|
-
|
|
30
|
-
this.publishChannel = await this.amqp.createConfirmChannel();
|
|
31
|
-
this.publishChannel.on('error', log.criticalErrorAndExit.bind(log, 'publishChannel.error'));
|
|
32
|
-
log.debug('Opened publish channel');
|
|
21
|
+
async connect() {
|
|
22
|
+
await Promise.all([this._ensurePublishChannel(), this._ensureConsumerChannel()]);
|
|
23
|
+
this.closed = false;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
async disconnect() {
|
|
27
|
+
await this.stopConsume();
|
|
28
|
+
this.closed = true;
|
|
36
29
|
log.trace('Close AMQP connections');
|
|
37
|
-
this.
|
|
30
|
+
if (this._readConnection) {
|
|
31
|
+
this._readConnection.removeAllListeners('close');
|
|
32
|
+
}
|
|
33
|
+
if (this._writeConnection) {
|
|
34
|
+
this._writeConnection.removeAllListeners('close');
|
|
35
|
+
}
|
|
36
|
+
if (this.consumerChannel) {
|
|
37
|
+
this.consumerChannel.removeAllListeners('close');
|
|
38
|
+
}
|
|
39
|
+
if (this.publishChannel) {
|
|
40
|
+
this.publishChannel.removeAllListeners('close');
|
|
41
|
+
}
|
|
38
42
|
try {
|
|
39
|
-
await this.
|
|
40
|
-
await this.subscribeChannel.close();
|
|
43
|
+
await this.consumerChannel.close();
|
|
41
44
|
} catch (alreadyClosed) {
|
|
42
45
|
log.debug('Subscribe channel is closed already');
|
|
43
46
|
}
|
|
@@ -47,41 +50,227 @@ class Amqp {
|
|
|
47
50
|
log.debug('Publish channel is closed already');
|
|
48
51
|
}
|
|
49
52
|
try {
|
|
50
|
-
await this.
|
|
53
|
+
await this._readConnection.close();
|
|
51
54
|
} catch (alreadyClosed) {
|
|
52
|
-
log.debug('AMQP connection is closed already');
|
|
55
|
+
log.debug('AMQP read connection is closed already');
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await this._writeConnection.close();
|
|
59
|
+
} catch (alreadyClosed) {
|
|
60
|
+
log.debug('AMQP write connection is closed already');
|
|
53
61
|
}
|
|
54
62
|
log.debug('Successfully closed AMQP connections');
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
async
|
|
58
|
-
if (this.
|
|
59
|
-
|
|
65
|
+
async _ensureReadConnection() {
|
|
66
|
+
if (this._readConnection) {
|
|
67
|
+
return this._readConnection;
|
|
60
68
|
}
|
|
61
|
-
|
|
69
|
+
if (this._creatingReadConnection) {
|
|
70
|
+
do {
|
|
71
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
72
|
+
} while (!this._readConnection);
|
|
73
|
+
return this._readConnection;
|
|
74
|
+
}
|
|
75
|
+
log.debug('Creating new read connection');
|
|
76
|
+
this._creatingReadConnection = true;
|
|
77
|
+
this._readConnection = await this._createConnection('read');
|
|
78
|
+
this._creatingReadConnection = false;
|
|
79
|
+
log.debug('Read connection created');
|
|
80
|
+
this._readConnection.on('error', err => log.error({ err }, 'AMQP read Connection error'));
|
|
81
|
+
this._readConnection.once('close', err => {
|
|
82
|
+
log.error({ err }, 'Unexpected connection close.');
|
|
83
|
+
delete this._readConnection;
|
|
84
|
+
});
|
|
85
|
+
return this._readConnection;
|
|
86
|
+
}
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
88
|
+
async _ensureWriteConnection() {
|
|
89
|
+
if (this._writeConnection) {
|
|
90
|
+
return this._writeConnection;
|
|
91
|
+
}
|
|
92
|
+
if (this._creatingWriteConnection) {
|
|
93
|
+
do {
|
|
94
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
95
|
+
} while (!this._writeConnection);
|
|
96
|
+
return this._writeConnection;
|
|
97
|
+
}
|
|
98
|
+
log.debug('Creating new write connection');
|
|
99
|
+
this._creatingWriteConnection = true;
|
|
100
|
+
this._writeConnection = await this._createConnection('write');
|
|
101
|
+
this._creatingWriteConnection = false;
|
|
102
|
+
log.debug('Write connection created');
|
|
103
|
+
this._writeConnection.on('error', err => log.error({ err }, 'AMQP write Connection error'));
|
|
104
|
+
this._writeConnection.once('close', err => {
|
|
105
|
+
log.error({ err }, 'Unexpected connection close.');
|
|
106
|
+
delete this._writeConnection;
|
|
107
|
+
});
|
|
108
|
+
return this._writeConnection;
|
|
66
109
|
}
|
|
67
110
|
|
|
68
|
-
async
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
111
|
+
async _ensureConsumerChannel() {
|
|
112
|
+
if (this.consumerChannel) {
|
|
113
|
+
return this.consumerChannel;
|
|
114
|
+
}
|
|
115
|
+
if (this._creatingConsumerChannel) {
|
|
116
|
+
do {
|
|
117
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
118
|
+
} while (!this.consumerChannel);
|
|
119
|
+
return this.consumerChannel;
|
|
120
|
+
}
|
|
121
|
+
log.debug({ prefetch: this.settings.RABBITMQ_PREFETCH_SAILOR }, 'Creating new consume channel');
|
|
122
|
+
this._creatingConsumerChannel = true;
|
|
123
|
+
const amqp = await this._ensureReadConnection();
|
|
124
|
+
this.consumerChannel = await amqp.createChannel();
|
|
125
|
+
log.debug('Consume channel created');
|
|
126
|
+
this._creatingConsumerChannel = false;
|
|
127
|
+
this.consumerChannel.prefetch(this.settings.RABBITMQ_PREFETCH_SAILOR);
|
|
128
|
+
this.consumerChannel.on('error', err => log.error({ err }, 'Consumer channel error'));
|
|
129
|
+
this.consumerChannel.once('close', () => {
|
|
130
|
+
delete this.consumerChannel;
|
|
131
|
+
if (this.consume) {
|
|
132
|
+
log.warn('Channel unexpectedly closed, but we were listening. Reconnecting and re-listening queue');
|
|
133
|
+
this.consume.consumerTag = undefined;
|
|
134
|
+
// when RabbitMQ closes connection, amqplib will first emit 'close' for channel and then for connection
|
|
135
|
+
// we use setImmediate to wait for connection 'close' event which will unset connection property
|
|
136
|
+
// otherwise listenQueue will try to create channel on closing connection and fail hard
|
|
137
|
+
setImmediate(() => this.listenQueue(this.consume.queue, this.consume.messageHandler));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return this.consumerChannel;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async _ensurePublishChannel() {
|
|
144
|
+
if (this.publishChannel) {
|
|
145
|
+
return this.publishChannel;
|
|
73
146
|
}
|
|
74
|
-
if (
|
|
75
|
-
|
|
147
|
+
if (this._creatingPublishChannel) {
|
|
148
|
+
do {
|
|
149
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
150
|
+
} while (!this.publishChannel);
|
|
151
|
+
return this.publishChannel;
|
|
152
|
+
}
|
|
153
|
+
log.debug('Creating new publish connection and channel');
|
|
154
|
+
this._creatingPublishChannel = true;
|
|
155
|
+
const amqp = await this._ensureWriteConnection();
|
|
156
|
+
this.publishChannel = await amqp.createConfirmChannel();
|
|
157
|
+
log.debug('Publish connection and channel created');
|
|
158
|
+
this._creatingPublishChannel = false;
|
|
159
|
+
this.publishChannel.on('error', err => log.error({ err }, 'Publish channel error'));
|
|
160
|
+
this.publishChannel.once('close', () => {
|
|
161
|
+
delete this.publishChannel;
|
|
162
|
+
});
|
|
163
|
+
return this.publishChannel;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async _createConnection(name) {
|
|
167
|
+
const uri = this.settings.AMQP_URI;
|
|
168
|
+
const allowedAttempts = parseInt(this.settings.AMQP_RECONNECT_ATTEMPTS);
|
|
169
|
+
let lastErr;
|
|
170
|
+
let attempts = 0;
|
|
171
|
+
while (attempts <= allowedAttempts) {
|
|
172
|
+
if (attempts > 0) {
|
|
173
|
+
await new Promise(resolve => setTimeout(resolve, parseInt(this.settings.AMQP_RECONNECT_TIMEOUT)));
|
|
174
|
+
log.debug(
|
|
175
|
+
{
|
|
176
|
+
reconnectAttempt: attempts,
|
|
177
|
+
AMQP_RECONNECT_ATTEMPTS: this.settings.AMQP_RECONNECT_ATTEMPTS
|
|
178
|
+
},
|
|
179
|
+
'AMQP Reconnecting'
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
return await amqplib.connect(uri,
|
|
184
|
+
{ clientProperties: { connection_name: `${os.hostname()}-${name}` } });
|
|
185
|
+
} catch (err) {
|
|
186
|
+
lastErr = err;
|
|
187
|
+
log.error(err, 'AMQP Connection error');
|
|
188
|
+
attempts++;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
throw lastErr;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async stopConsume() {
|
|
195
|
+
if (!this.consume) {
|
|
76
196
|
return;
|
|
77
197
|
}
|
|
78
|
-
|
|
79
|
-
this.
|
|
80
|
-
|
|
198
|
+
const consume = this.consume;
|
|
199
|
+
this.consume = undefined;
|
|
200
|
+
await this.consumerChannel.cancel(consume.consumerTag);
|
|
201
|
+
log.debug({ queue: consume.queue }, 'Stopped listening for messages');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async listenQueue(queue, messageHandler) {
|
|
205
|
+
await this._ensureConsumerChannel();
|
|
206
|
+
|
|
207
|
+
const { consumerTag } = await this.consumerChannel.consume(queue, async (amqpMessage) => {
|
|
208
|
+
if (!amqpMessage) {
|
|
209
|
+
log.warn('Consumer cancelled by rabbitmq');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let message;
|
|
213
|
+
try {
|
|
214
|
+
message = this._decodeMessage(amqpMessage);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
log.error({ err, deliveryTag: amqpMessage.fields.deliveryTag },
|
|
217
|
+
'Error occurred while parsing message payload');
|
|
218
|
+
this.reject(amqpMessage);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
await messageHandler(message, amqpMessage);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
log.error({ err, deliveryTag: amqpMessage.fields.deliveryTag }, 'Failed to process message, reject');
|
|
225
|
+
this.reject(amqpMessage);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
log.debug({ queue }, 'Started listening for messages');
|
|
229
|
+
this.consume = { queue, messageHandler, consumerTag };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_decodeMessage(amqpMessage) {
|
|
233
|
+
log.trace('Message received');
|
|
234
|
+
let message;
|
|
235
|
+
if (this.settings.INPUT_FORMAT === 'error') {
|
|
236
|
+
message = this._decodeErrorMessage(amqpMessage);
|
|
237
|
+
} else {
|
|
238
|
+
message = this._decodeDefaultMessage(amqpMessage);
|
|
239
|
+
}
|
|
240
|
+
message.headers = message.headers || {};
|
|
241
|
+
if (amqpMessage.properties.headers.reply_to) {
|
|
242
|
+
message.headers.reply_to = amqpMessage.properties.headers.reply_to;
|
|
243
|
+
}
|
|
244
|
+
return message;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
_decodeDefaultMessage(amqpMessage) {
|
|
248
|
+
const protocolVersion = Number(amqpMessage.properties.headers.protocolVersion || 1);
|
|
249
|
+
return this._encryptor.decryptMessageContent(
|
|
250
|
+
amqpMessage.content,
|
|
251
|
+
protocolVersion < 2 ? 'base64' : undefined
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_decodeErrorMessage(amqpMessage) {
|
|
256
|
+
const errorBody = JSON.parse(amqpMessage.content.toString());
|
|
257
|
+
// NOTICE both error and errorInput are transferred as base64 encoded.
|
|
258
|
+
// this does not depend on protocolVersion header of message (see _decodeDefault message)
|
|
259
|
+
// this should be fixed in future, but it's OK at this moment
|
|
260
|
+
if (errorBody.error) {
|
|
261
|
+
errorBody.error = this._encryptor.decryptMessageContent(Buffer.from(errorBody.error), 'base64');
|
|
262
|
+
}
|
|
263
|
+
if (errorBody.errorInput) {
|
|
264
|
+
errorBody.errorInput = this._encryptor.decryptMessageContent(errorBody.errorInput, 'base64');
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
body: errorBody,
|
|
268
|
+
headers: amqpMessage.properties.headers
|
|
269
|
+
};
|
|
81
270
|
}
|
|
82
271
|
|
|
83
272
|
decryptMessage(callback, message) {
|
|
84
|
-
log.trace('Message received
|
|
273
|
+
log.trace('Message received');
|
|
85
274
|
|
|
86
275
|
if (message === null) {
|
|
87
276
|
log.warn('NULL message received');
|
|
@@ -91,7 +280,7 @@ class Amqp {
|
|
|
91
280
|
const protocolVersion = Number(message.properties.headers.protocolVersion || 1);
|
|
92
281
|
let decryptedContent;
|
|
93
282
|
try {
|
|
94
|
-
decryptedContent =
|
|
283
|
+
decryptedContent = this._encryptor.decryptMessageContent(
|
|
95
284
|
message.content,
|
|
96
285
|
protocolVersion < 2 ? 'base64' : undefined
|
|
97
286
|
);
|
|
@@ -118,16 +307,17 @@ class Amqp {
|
|
|
118
307
|
|
|
119
308
|
ack(message) {
|
|
120
309
|
log.debug('Message #%j ack', message.fields.deliveryTag);
|
|
121
|
-
this.
|
|
310
|
+
this.consumerChannel.ack(message);
|
|
122
311
|
}
|
|
123
312
|
|
|
124
313
|
reject(message) {
|
|
125
314
|
log.debug('Message #%j reject', message.fields.deliveryTag);
|
|
126
|
-
return this.
|
|
315
|
+
return this.consumerChannel.reject(message, false);
|
|
127
316
|
}
|
|
128
317
|
|
|
129
318
|
async sendToExchange(exchangeName, routingKey, payload, options, throttle) {
|
|
130
319
|
if (throttle) {
|
|
320
|
+
log.debug('Throttling outgoing message');
|
|
131
321
|
await throttle();
|
|
132
322
|
}
|
|
133
323
|
const buffer = Buffer.from(payload);
|
|
@@ -140,6 +330,8 @@ class Amqp {
|
|
|
140
330
|
if (iteration) {
|
|
141
331
|
options.headers.retry = iteration;
|
|
142
332
|
}
|
|
333
|
+
// AMQP_PERSISTENT_MESSAGES is false by default if not specified by env var
|
|
334
|
+
options.persistent = this.settings.AMQP_PERSISTENT_MESSAGES;
|
|
143
335
|
|
|
144
336
|
log.debug('Current memory usage: %s Mb', process.memoryUsage().heapUsed / 1048576);
|
|
145
337
|
log.trace('Pushing to exchange=%s, routingKey=%s, messageSize=%d, options=%j, iteration=%d',
|
|
@@ -185,6 +377,7 @@ class Amqp {
|
|
|
185
377
|
}
|
|
186
378
|
|
|
187
379
|
async _promisifiedPublish(exchangeName, routingKey, payloadBuffer, options) {
|
|
380
|
+
await this._ensurePublishChannel();
|
|
188
381
|
try {
|
|
189
382
|
let result;
|
|
190
383
|
const publishChannel = this.publishChannel;
|
|
@@ -211,7 +404,7 @@ class Amqp {
|
|
|
211
404
|
}
|
|
212
405
|
|
|
213
406
|
encryptMessageContent(body, protocolVersion = 1) {
|
|
214
|
-
return
|
|
407
|
+
return this._encryptor.encryptMessageContent(
|
|
215
408
|
body,
|
|
216
409
|
protocolVersion < 2
|
|
217
410
|
? 'base64'
|
|
@@ -241,7 +434,6 @@ class Amqp {
|
|
|
241
434
|
const settings = this.settings;
|
|
242
435
|
const routingKey = getRoutingKeyFromHeaders(data.headers) || settings.DATA_ROUTING_KEY;
|
|
243
436
|
properties.headers.protocolVersion = settings.PROTOCOL_VERSION;
|
|
244
|
-
|
|
245
437
|
return this.prepareMessageAndSendToExchange(data, properties, routingKey, throttle);
|
|
246
438
|
}
|
|
247
439
|
|
|
@@ -257,10 +449,13 @@ class Amqp {
|
|
|
257
449
|
}
|
|
258
450
|
|
|
259
451
|
async sendError(err, headers, originalMessage, throttle) {
|
|
452
|
+
// NOTICE both error and errorInput are transferred as base64 encoded.
|
|
453
|
+
// this does not depend on protocolVersion header of message (see _decodeDefaultMessage or sendData methods)
|
|
454
|
+
// this should be fixed in future, but it's OK at this moment
|
|
260
455
|
const properties = this._createPropsFromHeaders(headers);
|
|
261
456
|
const settings = this.settings;
|
|
262
457
|
|
|
263
|
-
const encryptedError =
|
|
458
|
+
const encryptedError = this._encryptor.encryptMessageContent({
|
|
264
459
|
name: err.name,
|
|
265
460
|
message: err.message,
|
|
266
461
|
stack: err.stack
|
|
@@ -270,12 +465,10 @@ class Amqp {
|
|
|
270
465
|
error: encryptedError
|
|
271
466
|
};
|
|
272
467
|
if (originalMessage && originalMessage.content) {
|
|
273
|
-
// TODO inconsistency with sendData. sendData build protocol version from settings
|
|
274
|
-
// but send Error takes it from headers.
|
|
275
468
|
const protocolVersion = Number(originalMessage.properties.headers.protocolVersion || 1);
|
|
276
469
|
if (protocolVersion >= 2) {
|
|
277
|
-
payload.errorInput =
|
|
278
|
-
|
|
470
|
+
payload.errorInput = this._encryptor.encryptMessageContent(
|
|
471
|
+
this._encryptor.decryptMessageContent(originalMessage.content),
|
|
279
472
|
'base64'
|
|
280
473
|
).toString();
|
|
281
474
|
} else {
|
|
@@ -284,10 +477,14 @@ class Amqp {
|
|
|
284
477
|
}
|
|
285
478
|
const errorPayload = JSON.stringify(payload);
|
|
286
479
|
|
|
287
|
-
let result = this.sendToExchange(
|
|
288
|
-
|
|
480
|
+
let result = this.sendToExchange(
|
|
481
|
+
settings.PUBLISH_MESSAGES_TO,
|
|
482
|
+
settings.ERROR_ROUTING_KEY,
|
|
483
|
+
errorPayload, properties,
|
|
484
|
+
throttle
|
|
485
|
+
);
|
|
289
486
|
|
|
290
|
-
if (headers.reply_to) {
|
|
487
|
+
if (!settings.NO_ERROR_REPLIES && headers.reply_to) {
|
|
291
488
|
log.debug('Sending error to %s', headers.reply_to);
|
|
292
489
|
const replyToOptions = _.cloneDeep(properties);
|
|
293
490
|
replyToOptions.headers[HEADER_ERROR_RESPONSE] = true;
|
|
@@ -298,20 +495,20 @@ class Amqp {
|
|
|
298
495
|
return result;
|
|
299
496
|
}
|
|
300
497
|
|
|
301
|
-
async sendRebound(reboundError, originalMessage
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const settings = this.settings;
|
|
498
|
+
async sendRebound(reboundError, originalMessage) {
|
|
499
|
+
const { settings } = this;
|
|
500
|
+
let { properties: { headers } } = originalMessage;
|
|
501
|
+
headers = {
|
|
502
|
+
...headers,
|
|
503
|
+
end: new Date().getTime(),
|
|
504
|
+
reboundReason: reboundError.message
|
|
505
|
+
};
|
|
506
|
+
log.trace('Rebound message');
|
|
507
|
+
let reboundIteration = 1;
|
|
312
508
|
|
|
313
|
-
|
|
314
|
-
|
|
509
|
+
if (headers.reboundIteration && typeof headers.reboundIteration === 'number') {
|
|
510
|
+
reboundIteration = headers.reboundIteration + 1;
|
|
511
|
+
}
|
|
315
512
|
|
|
316
513
|
if (reboundIteration > settings.REBOUND_LIMIT) {
|
|
317
514
|
return this.sendError(
|
|
@@ -320,8 +517,15 @@ class Amqp {
|
|
|
320
517
|
originalMessage
|
|
321
518
|
);
|
|
322
519
|
} else {
|
|
323
|
-
properties
|
|
324
|
-
|
|
520
|
+
const properties = {
|
|
521
|
+
...originalMessage.properties,
|
|
522
|
+
// retry in 15 sec, 30 sec, 1 min, 2 min, 4 min, 8 min, etc.
|
|
523
|
+
expiration: Math.pow(2, reboundIteration - 1) * settings.REBOUND_INITIAL_EXPIRATION,
|
|
524
|
+
headers: {
|
|
525
|
+
...headers,
|
|
526
|
+
reboundIteration
|
|
527
|
+
}
|
|
528
|
+
};
|
|
325
529
|
|
|
326
530
|
return this.sendToExchange(
|
|
327
531
|
settings.PUBLISH_MESSAGES_TO,
|
|
@@ -330,18 +534,6 @@ class Amqp {
|
|
|
330
534
|
properties
|
|
331
535
|
);
|
|
332
536
|
}
|
|
333
|
-
|
|
334
|
-
function getReboundIteration(previousIteration) {
|
|
335
|
-
if (previousIteration && typeof previousIteration === 'number') {
|
|
336
|
-
return previousIteration + 1;
|
|
337
|
-
}
|
|
338
|
-
return 1;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// retry in 15 sec, 30 sec, 1 min, 2 min, 4 min, 8 min, etc.
|
|
342
|
-
function getExpiration(iteration) {
|
|
343
|
-
return Math.pow(2, iteration - 1) * settings.REBOUND_INITIAL_EXPIRATION;
|
|
344
|
-
}
|
|
345
537
|
}
|
|
346
538
|
|
|
347
539
|
async sendSnapshot(data, headers, throttle) {
|