rascal 19.0.0 → 20.1.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/CHANGELOG.md +6 -0
- package/README.md +5 -5
- package/lib/amqp/SubscriberError.js +7 -2
- package/lib/amqp/Subscription.js +24 -3
- package/lib/amqp/XDeath.js +5 -0
- package/lib/config/configure.js +2 -1
- package/lib/management/Client.js +21 -20
- package/package.json +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 20.1.0
|
|
4
|
+
- Ignore and remove immediateNack header based on the xDeath header. See https://github.com/onebeyond/rascal/issues/237
|
|
5
|
+
|
|
6
|
+
## 20.0.0
|
|
7
|
+
- Replaced superagent with native node http client as per https://github.com/onebeyond/rascal/issues/234
|
|
8
|
+
|
|
3
9
|
## 19.0.0
|
|
4
10
|
- I am not aware of any breaking changes in this release, but emitting error events asynchronously could have subtle side effects, hence the major release
|
|
5
11
|
- Deprecate session 'cancelled' event in favour of 'cancel' (both will work)
|
package/README.md
CHANGED
|
@@ -464,7 +464,7 @@ The AMQP protocol doesn't support assertion or checking of vhosts, so Rascal use
|
|
|
464
464
|
}
|
|
465
465
|
```
|
|
466
466
|
|
|
467
|
-
Rascal uses [
|
|
467
|
+
Rascal uses [http.request](https://nodejs.org/api/http.html#httprequesturl-options-callback) under the hood. URL configuration is also supported.
|
|
468
468
|
|
|
469
469
|
```json
|
|
470
470
|
{
|
|
@@ -489,11 +489,11 @@ Rascal uses [superagent](https://github.com/visionmedia/superagent) under the ho
|
|
|
489
489
|
}
|
|
490
490
|
```
|
|
491
491
|
|
|
492
|
-
You can also supply your own agent via the broker components. Use this when you need to set [TLS options](https://
|
|
492
|
+
You can also supply your own agent via the broker components. Use this when you need to set [TLS options](https://nodejs.org/api/https.html#httpsrequesturl-options-callback).
|
|
493
493
|
|
|
494
494
|
```js
|
|
495
|
-
const
|
|
496
|
-
const agent =
|
|
495
|
+
const https = require('https');
|
|
496
|
+
const agent = new https.Agent(options);
|
|
497
497
|
const components = { agent };
|
|
498
498
|
const broker = await Broker.create(config, components);
|
|
499
499
|
```
|
|
@@ -1534,7 +1534,7 @@ As mentioned previously, dead lettering invalid messages is a good strategy with
|
|
|
1534
1534
|
ackOrNack(err, { strategy: 'republish', immediateNack: true });
|
|
1535
1535
|
```
|
|
1536
1536
|
|
|
1537
|
-
|
|
1537
|
+
Prior to Rascal v20.1.0, if you wanted to resend the message to the original queue you had to remove the `properties.headers.rascal.<queue>.immediateNack` header first. From v20.1.0, Rascal will ignore and remove the immediateNack header if the message's xDeath header indicates that the message was dead lettered after it was republished with immediateNack.
|
|
1538
1538
|
|
|
1539
1539
|
##### Forward
|
|
1540
1540
|
|
|
@@ -3,10 +3,11 @@ const format = require('util').format;
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const async = require('async');
|
|
5
5
|
const setTimeoutUnref = require('../utils/setTimeoutUnref');
|
|
6
|
+
const { EMPTY_X_DEATH } = require('./XDeath');
|
|
6
7
|
|
|
7
8
|
module.exports = function SubscriptionRecovery(broker, vhost) {
|
|
8
9
|
this.handle = function (session, message, err, recoveryOptions, next) {
|
|
9
|
-
debug('Handling subscriber error for message: %s', message.properties.messageId);
|
|
10
|
+
debug('Handling subscriber error for message: %s with error: %s', message.properties.messageId, err.message);
|
|
10
11
|
|
|
11
12
|
async.eachSeries(
|
|
12
13
|
[].concat(recoveryOptions || []).concat({ strategy: 'fallback-nack' }),
|
|
@@ -84,7 +85,11 @@ module.exports = function SubscriptionRecovery(broker, vhost) {
|
|
|
84
85
|
_.set(publishOptions, 'headers.rascal.error.code', err.code);
|
|
85
86
|
_.set(publishOptions, 'headers.rascal.restoreRoutingHeaders', _.has(strategyConfig, 'restoreRoutingHeaders') ? strategyConfig.restoreRoutingHeaders : true);
|
|
86
87
|
|
|
87
|
-
if (strategyConfig.immediateNack)
|
|
88
|
+
if (strategyConfig.immediateNack) {
|
|
89
|
+
const xDeathRecords = message.properties.headers['x-death'] || [];
|
|
90
|
+
const xDeath = xDeathRecords.find(({ queue, reason }) => queue === originalQueue && reason === 'rejected') || EMPTY_X_DEATH;
|
|
91
|
+
_.set(publishOptions, ['headers', 'rascal', 'recovery', originalQueue], { immediateNack: true, xDeath });
|
|
92
|
+
}
|
|
88
93
|
|
|
89
94
|
const ackMessage = () => {
|
|
90
95
|
session._ack(message, (err) => {
|
package/lib/amqp/Subscription.js
CHANGED
|
@@ -8,6 +8,7 @@ const SubscriberSession = require('./SubscriberSession');
|
|
|
8
8
|
const SubscriberError = require('./SubscriberError');
|
|
9
9
|
const backoff = require('../backoff');
|
|
10
10
|
const setTimeoutUnref = require('../utils/setTimeoutUnref');
|
|
11
|
+
const { EMPTY_X_DEATH } = require('./XDeath');
|
|
11
12
|
|
|
12
13
|
module.exports = {
|
|
13
14
|
create(broker, vhost, counter, config, next) {
|
|
@@ -95,7 +96,11 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
|
|
|
95
96
|
session._incrementUnacknowledgeMessageCount(message.fields.consumerTag);
|
|
96
97
|
|
|
97
98
|
decorateWithRoutingHeaders(message);
|
|
98
|
-
if (immediateNack(message))
|
|
99
|
+
if (immediateNack(message)) {
|
|
100
|
+
debug('Immediately nacking message: %s from queue: %s', message.properties.messageId, config.queue);
|
|
101
|
+
ackOrNack(session, message, new Error('Immediate nack'));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
99
104
|
|
|
100
105
|
decorateWithRedeliveries(message, (err) => {
|
|
101
106
|
if (err) return handleRedeliveriesError(err, session, message);
|
|
@@ -208,8 +213,24 @@ function Subscription(broker, vhost, subscriptionConfig, counter) {
|
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
function immediateNack(message) {
|
|
211
|
-
|
|
212
|
-
|
|
216
|
+
const originalQueue = message.properties.headers.rascal.originalQueue;
|
|
217
|
+
const xDeathRecords = message.properties.headers['x-death'] || [];
|
|
218
|
+
const currentXDeath = xDeathRecords.find(({ queue, reason }) => queue === originalQueue && reason === 'rejected') || EMPTY_X_DEATH;
|
|
219
|
+
const previousXDeath = _.get(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'xDeath'], EMPTY_X_DEATH);
|
|
220
|
+
const hasImmediateNackHeader = _.has(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'immediateNack']);
|
|
221
|
+
if (!hasImmediateNackHeader) return false;
|
|
222
|
+
debug('Message %s has been marked for immediate nack. Previous xDeath is %o. Current xDeath is %o.', message.properties.messageId, previousXDeath, currentXDeath);
|
|
223
|
+
// See https://github.com/rabbitmq/rabbitmq-server/issues/11331
|
|
224
|
+
// RabbitMQ v3.13 stopped updating the xDeath record's count property.
|
|
225
|
+
// RabbitMQ v3.12 does not update the xDeath record's time property.
|
|
226
|
+
// Therefore having test them both
|
|
227
|
+
if (currentXDeath.count > previousXDeath.count || currentXDeath.time.value > previousXDeath.time.value) {
|
|
228
|
+
debug('Message %s has been replayed after being dead lettered. Removing immediate nack.', message.properties.messageId);
|
|
229
|
+
_.unset(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'immediateNack']);
|
|
230
|
+
_.unset(message, ['properties', 'headers', 'rascal', 'recovery', originalQueue, 'xDeath']);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
return true;
|
|
213
234
|
}
|
|
214
235
|
|
|
215
236
|
function getAckOrNack(session, message) {
|
package/lib/config/configure.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
const debug = require('debug')('rascal:config:configure');
|
|
2
2
|
const format = require('util').format;
|
|
3
3
|
const url = require('url');
|
|
4
|
-
const { URL } = require('node:url');
|
|
5
4
|
const _ = require('lodash');
|
|
6
5
|
const uuid = require('uuid').v4;
|
|
7
6
|
const XRegExp = require('xregexp');
|
|
8
7
|
const baseline = require('./baseline');
|
|
9
8
|
const fqn = require('./fqn');
|
|
10
9
|
|
|
10
|
+
const { URL } = url;
|
|
11
|
+
|
|
11
12
|
module.exports = _.curry((rascalConfig, next) => {
|
|
12
13
|
rascalConfig = _.defaultsDeep(rascalConfig, baseline);
|
|
13
14
|
|
package/lib/management/Client.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
const http = require('http');
|
|
1
2
|
const debug = require('debug')('rascal:management:client');
|
|
2
3
|
const format = require('util').format;
|
|
3
|
-
const _ = require('lodash');
|
|
4
|
-
const defaultAgent = require('superagent');
|
|
5
4
|
|
|
6
|
-
function Client(
|
|
7
|
-
const agent = suppliedAgent || defaultAgent;
|
|
5
|
+
function Client(agent) {
|
|
8
6
|
const self = this;
|
|
9
7
|
|
|
10
8
|
this.assertVhost = function (name, config, next) {
|
|
11
9
|
debug('Asserting vhost: %s', name);
|
|
12
|
-
const
|
|
13
|
-
self._request('
|
|
10
|
+
const url = getUrl(name, config);
|
|
11
|
+
self._request('PUT', url, config.options, (err) => {
|
|
14
12
|
if (!err) return next();
|
|
15
13
|
const _err = err.status ? new Error(format('Failed to assert vhost: %s. %s returned status %d', name, config.loggableUrl, err.status)) : err;
|
|
16
14
|
return next(_err);
|
|
@@ -19,8 +17,8 @@ function Client(suppliedAgent) {
|
|
|
19
17
|
|
|
20
18
|
this.checkVhost = function (name, config, next) {
|
|
21
19
|
debug('Checking vhost: %s', name);
|
|
22
|
-
const
|
|
23
|
-
self._request('
|
|
20
|
+
const url = getUrl(name, config);
|
|
21
|
+
self._request('GET', url, config.options, (err) => {
|
|
24
22
|
if (!err) return next();
|
|
25
23
|
const _err = err.status ? new Error(format('Failed to check vhost: %s. %s returned status %d', name, config.loggableUrl, err.status)) : err;
|
|
26
24
|
return next(_err);
|
|
@@ -29,26 +27,29 @@ function Client(suppliedAgent) {
|
|
|
29
27
|
|
|
30
28
|
this.deleteVhost = function (name, config, next) {
|
|
31
29
|
debug('Deleting vhost: %s', name);
|
|
32
|
-
const
|
|
33
|
-
self._request('
|
|
30
|
+
const url = getUrl(name, config);
|
|
31
|
+
self._request('DELETE', url, config.options, (err) => {
|
|
34
32
|
if (!err) return next();
|
|
35
33
|
const _err = err.status ? new Error(format('Failed to delete vhost: %s. %s returned status %d', name, config.loggableUrl, err.status)) : err;
|
|
36
34
|
return next(_err);
|
|
37
35
|
});
|
|
38
36
|
};
|
|
39
37
|
|
|
40
|
-
this._request = function (method, url,
|
|
41
|
-
agent
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
next();
|
|
45
|
-
}
|
|
46
|
-
.
|
|
38
|
+
this._request = function (method, url, options, next) {
|
|
39
|
+
const req = http.request(url, { ...options, method, agent }, (res) => {
|
|
40
|
+
if (res.statusCode >= 300) {
|
|
41
|
+
const err = Object.assign(new Error('HTTP Error'), { status: res.statusCode });
|
|
42
|
+
return next(err);
|
|
43
|
+
}
|
|
44
|
+
res.on('data', () => {});
|
|
45
|
+
res.on('end', () => next());
|
|
46
|
+
});
|
|
47
|
+
req.on('error', next);
|
|
48
|
+
req.end();
|
|
47
49
|
};
|
|
48
50
|
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
return _.defaultsDeep({ url }, config.options);
|
|
51
|
+
function getUrl(name, config) {
|
|
52
|
+
return format('%s/%s/%s', config.url, 'api/vhosts', name);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rascal",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "20.1.0",
|
|
4
4
|
"description": "A config driven wrapper for amqplib supporting multi-host connections, automatic error recovery, redelivery flood protection, transparent encryption / decryption, channel pooling and publication timeouts",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"lru-cache": "^7.10.1",
|
|
13
13
|
"safe-json-parse": "^4.0.0",
|
|
14
14
|
"stashback": "^2.0.1",
|
|
15
|
-
"superagent": "^8.0.9",
|
|
16
15
|
"uuid": "^8.3.2",
|
|
17
16
|
"xregexp": "^5.1.0"
|
|
18
17
|
},
|
|
@@ -27,7 +26,6 @@
|
|
|
27
26
|
"lint-staged": "^11.2.4",
|
|
28
27
|
"nyc": "^15.1.0",
|
|
29
28
|
"random-readable": "^1.0.1",
|
|
30
|
-
"superagent-defaults": "^0.1.14",
|
|
31
29
|
"zunit": "^4.0.0"
|
|
32
30
|
},
|
|
33
31
|
"peerDependencies": {
|
|
@@ -42,7 +40,7 @@
|
|
|
42
40
|
"lint:fix": "eslint --fix .",
|
|
43
41
|
"lint-staged": "lint-staged",
|
|
44
42
|
"coverage": "nyc --report html --reporter lcov --reporter text-summary zUnit",
|
|
45
|
-
"docker": "docker run -d --name rascal-rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management",
|
|
43
|
+
"docker": "docker run -d --name rascal-rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12.9-management-alpine",
|
|
46
44
|
"prepare": "husky install"
|
|
47
45
|
},
|
|
48
46
|
"lint-staged": {
|