rascal 9.3.0 → 9.4.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 +9 -0
- package/README.md +58 -3
- package/lib/amqp/Publication.js +18 -11
- package/lib/amqp/PublicationSession.js +36 -0
- package/lib/amqp/Vhost.js +32 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 9.4.0
|
|
4
|
+
### Added
|
|
5
|
+
- vhost_initialised event
|
|
6
|
+
- publication paused notifications
|
|
7
|
+
- publication.abort
|
|
8
|
+
|
|
9
|
+
### Updated
|
|
10
|
+
- broker error events now include vhost connection details
|
|
11
|
+
|
|
3
12
|
## 9.3.0
|
|
4
13
|
### Updated
|
|
5
14
|
- [Fixed #78](https://github.com/guidesmiths/rascal/issues/78) by using a baseline config, and only laying connection options via withDefaultConfig
|
package/README.md
CHANGED
|
@@ -114,8 +114,8 @@ The reason Rascal nacks the message is because the alternative is to rollback an
|
|
|
114
114
|
1. Immediately after obtaining a broker instance
|
|
115
115
|
|
|
116
116
|
```js
|
|
117
|
-
broker.on('error', (err) => {
|
|
118
|
-
console.error('Broker error', err)
|
|
117
|
+
broker.on('error', (err, { vhost, connectionUrl }) => {
|
|
118
|
+
console.error('Broker error', err, vhost, connectionUrl)
|
|
119
119
|
})
|
|
120
120
|
```
|
|
121
121
|
|
|
@@ -195,6 +195,28 @@ The reason Rascal nacks the message is because the alternative is to rollback an
|
|
|
195
195
|
})
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
+
### Other Broker Events
|
|
199
|
+
|
|
200
|
+
#### vhost_initialised
|
|
201
|
+
The broker emits the `vhost_initialised` event after recovering from a connection error. An object containing the vhost name and connection url (with obfuscated password) are passed to he event handler. e.g.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
broker.on('vhost_initialised', ({ vhost, connectionUrl }) => {
|
|
205
|
+
console.log(`Vhost: ${vhost} was initialised using connection: ${connectionUrl}`);
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### blocked / unblocked
|
|
210
|
+
RabbitMQ notifies clients of [blocked and unblocked](https://www.rabbitmq.com/connection-blocked.html) connections, which rascal forwards from the connection to the broker. e.g.
|
|
211
|
+
```js
|
|
212
|
+
broker.on('blocked', (reason, { vhost, connectionUrl }) => {
|
|
213
|
+
console.log(`Vhost: ${vhost} was blocked using connection: ${connectionUrl}. Reason: ${reason}`);
|
|
214
|
+
})
|
|
215
|
+
broker.on('unblocked', ({ vhost, connectionUrl }) => {
|
|
216
|
+
console.log(`Vhost: ${vhost} was unblocked using connection: ${connectionUrl}.`);
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
198
220
|
## Configuration
|
|
199
221
|
Rascal is highly configurable, but ships with what we consider to be sensible defaults (optimised for reliability rather than speed) for production and test environments.
|
|
200
222
|
|
|
@@ -766,7 +788,7 @@ try {
|
|
|
766
788
|
console.log("Message id was: ", messageId)
|
|
767
789
|
}).on("error", (err, messageId) => {
|
|
768
790
|
console.error("Error was: ", err.message)
|
|
769
|
-
}).on("return", (
|
|
791
|
+
}).on("return", (message) => {
|
|
770
792
|
console.warn("Message was returned: ", message.properties.messageId)
|
|
771
793
|
})
|
|
772
794
|
} catch (err) {
|
|
@@ -817,6 +839,39 @@ When you publish a message using a confirm channel, amqplib will wait for an ack
|
|
|
817
839
|
}
|
|
818
840
|
```
|
|
819
841
|
|
|
842
|
+
#### Aborting
|
|
843
|
+
Rascal uses a channel pool to publish messages. Access to the channel pool is synchronised via an in memory queue, which will be paused if the connection to the broker is temporarily lost. Consequently instead of erroring, publishes will be held until the connection is re-established. If you would rather abort under these circumstances, you can listen for the publication 'paused' event, and call `publication.abort()`. When the connection is re-established any aborted messages will be dropped instead of published.
|
|
844
|
+
|
|
845
|
+
```js
|
|
846
|
+
broker.publish("p1", "some message", (err, publication) => {
|
|
847
|
+
if (err) throw err; // publication didn't exist
|
|
848
|
+
publication.on("success", (messageId) => {
|
|
849
|
+
console.log("Message id was: ", messageId)
|
|
850
|
+
}).on("error", (err, messageId) => {
|
|
851
|
+
console.error("Error was: ", err.message)
|
|
852
|
+
}).on("paused", (messageId) => {
|
|
853
|
+
console.warn("Publication was paused. Aborting message: ", messageId)
|
|
854
|
+
publication.abort();
|
|
855
|
+
})
|
|
856
|
+
})
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
```js
|
|
860
|
+
try {
|
|
861
|
+
const publication = await broker.publish("p1", "some message")
|
|
862
|
+
publication.on("success", (messageId) => {
|
|
863
|
+
console.log("Message id was: ", messageId)
|
|
864
|
+
}).on("error", (err, messageId) => {
|
|
865
|
+
console.error("Error was: ", err.message)
|
|
866
|
+
}).on("paused", (messageId) => {
|
|
867
|
+
console.warn("Publication was paused. Aborting message: ", messageId)
|
|
868
|
+
publication.abort();
|
|
869
|
+
})
|
|
870
|
+
} catch (err) {
|
|
871
|
+
// publication didn't exist
|
|
872
|
+
}
|
|
873
|
+
```
|
|
874
|
+
|
|
820
875
|
#### Encrypting messages
|
|
821
876
|
Rascal can be configured to automatically encrypt outbound messages.
|
|
822
877
|
```json
|
package/lib/amqp/Publication.js
CHANGED
|
@@ -2,8 +2,8 @@ var debug = require('debug')('rascal:Publication');
|
|
|
2
2
|
var format = require('util').format;
|
|
3
3
|
var _ = require('lodash');
|
|
4
4
|
var uuid = require('uuid').v4;
|
|
5
|
-
var EventEmitter = require('events').EventEmitter;
|
|
6
5
|
var crypto = require('crypto');
|
|
6
|
+
var PublicationSession = require('./PublicationSession');
|
|
7
7
|
var setTimeoutUnref = require('../utils/setTimeoutUnref');
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
@@ -30,7 +30,7 @@ function Publication(vhost, borrowChannelFn, returnChannelFn, destroyChannelFn,
|
|
|
30
30
|
|
|
31
31
|
this.init = function(next) {
|
|
32
32
|
debug('Initialising publication: %s', config.name);
|
|
33
|
-
|
|
33
|
+
next(null, self);
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
this.publish = function(payload, overrides, next) {
|
|
@@ -39,7 +39,7 @@ function Publication(vhost, borrowChannelFn, returnChannelFn, destroyChannelFn,
|
|
|
39
39
|
publishConfig.options.contentType = publishConfig.options.contentType || content.type;
|
|
40
40
|
publishConfig.options.messageId = publishConfig.options.messageId || uuid();
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
publishConfig.encryption
|
|
43
43
|
? _publishEncrypted(content.buffer, publishConfig, next)
|
|
44
44
|
: _publish(content.buffer, publishConfig, next);
|
|
45
45
|
};
|
|
@@ -87,32 +87,39 @@ function Publication(vhost, borrowChannelFn, returnChannelFn, destroyChannelFn,
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function _publish(buffer, publishConfig, next) {
|
|
90
|
-
var emitter = new EventEmitter();
|
|
91
90
|
var messageId = publishConfig.options.messageId;
|
|
91
|
+
var session = new PublicationSession(vhost, messageId);
|
|
92
92
|
borrowChannelFn(function(err, channel) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
session._removePausedListener();
|
|
94
|
+
if (err) return session.emit('error', err, messageId);
|
|
95
|
+
if (session.isAborted()) return abortPublish(channel, messageId);
|
|
96
|
+
var errorHandler = _.once(handleChannelError.bind(null, channel, messageId, session, config));
|
|
97
|
+
var returnHandler = session.emit.bind(session, 'return');
|
|
96
98
|
addListeners(channel, errorHandler, returnHandler);
|
|
97
99
|
try {
|
|
98
100
|
publishFn(channel, buffer, publishConfig, function(err, ok) {
|
|
99
101
|
if (err) {
|
|
100
102
|
destroyChannel(channel, errorHandler, returnHandler);
|
|
101
|
-
return
|
|
103
|
+
return session.emit('error', err, messageId);
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
ok ? returnChannel(channel, errorHandler, returnHandler)
|
|
105
107
|
: deferReturnChannel(channel, errorHandler, returnHandler);
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
session.emit('success', messageId);
|
|
108
110
|
});
|
|
109
111
|
} catch(err) {
|
|
110
112
|
returnChannel(channel, errorHandler, returnHandler);
|
|
111
|
-
return
|
|
113
|
+
return session.emit('error', err, messageId);
|
|
112
114
|
};
|
|
113
115
|
});
|
|
114
116
|
|
|
115
|
-
next(null,
|
|
117
|
+
next(null, session);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function abortPublish(channel, messageId) {
|
|
121
|
+
debug('Publication of message: %s was aborted', messageId);
|
|
122
|
+
returnChannelFn(channel);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
function returnChannel(channel, errorHandler, returnHandler) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var debug = require('debug')('rascal:SubscriberSession');
|
|
2
|
+
var EventEmitter = require('events').EventEmitter;
|
|
3
|
+
var inherits = require('util').inherits;
|
|
4
|
+
|
|
5
|
+
module.exports = PublicationSession;
|
|
6
|
+
|
|
7
|
+
inherits(PublicationSession, EventEmitter);
|
|
8
|
+
|
|
9
|
+
function PublicationSession(vhost, messageId) {
|
|
10
|
+
|
|
11
|
+
var self = this;
|
|
12
|
+
var aborted = false;
|
|
13
|
+
|
|
14
|
+
this.abort = function() {
|
|
15
|
+
aborted = true;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
this.isAborted = function() {
|
|
19
|
+
return aborted;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
this._removePausedListener = function() {
|
|
23
|
+
vhost.removeListener('paused',emitPaused);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function emitPaused() {
|
|
27
|
+
self.emit('paused', messageId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
vhost.on('paused', emitPaused);
|
|
31
|
+
|
|
32
|
+
self.on('newListener', function(event) {
|
|
33
|
+
if (event !== 'paused') return;
|
|
34
|
+
if (vhost.isPaused()) emitPaused();
|
|
35
|
+
});
|
|
36
|
+
}
|
package/lib/amqp/Vhost.js
CHANGED
|
@@ -3,7 +3,6 @@ var format = require('util').format;
|
|
|
3
3
|
var inherits = require('util').inherits;
|
|
4
4
|
var EventEmitter = require('events').EventEmitter;
|
|
5
5
|
var async = require('async');
|
|
6
|
-
var forwardEvents = require('forward-emitter');
|
|
7
6
|
var genericPool = require('generic-pool');
|
|
8
7
|
var tasks = require('./tasks');
|
|
9
8
|
var uuid = require('uuid').v4;
|
|
@@ -33,6 +32,7 @@ function Vhost(config) {
|
|
|
33
32
|
var purge = async.compose(tasks.closeConnection, tasks.closeChannel, tasks.purgeQueues, tasks.createChannel, tasks.createConnection);
|
|
34
33
|
var nuke = async.compose(tasks.closeConnection, tasks.closeChannel, tasks.deleteQueues, tasks.deleteExchanges, tasks.createChannel, tasks.createConnection);
|
|
35
34
|
var timer = backoff({});
|
|
35
|
+
var paused = true;
|
|
36
36
|
|
|
37
37
|
this.name = config.name;
|
|
38
38
|
this.connectionIndex = 0;
|
|
@@ -45,22 +45,22 @@ function Vhost(config) {
|
|
|
45
45
|
|
|
46
46
|
init(config, { connectionIndex: self.connectionIndex }, function(err, config, ctx) {
|
|
47
47
|
if (err) return next(err);
|
|
48
|
-
self.emit('connect');
|
|
49
|
-
|
|
50
|
-
attachErrorHandlers(ctx.connection, config);
|
|
51
48
|
|
|
52
|
-
forwardEvents(ctx.connection, self, function(eventName) {
|
|
53
|
-
return eventName === 'blocked' || eventName === 'unblocked';
|
|
54
|
-
});
|
|
55
|
-
debug('vhost: %s was initialised with connection: %s', self.name, ctx.connection._rascal_id);
|
|
56
49
|
connection = ctx.connection;
|
|
57
50
|
self.connectionIndex = ctx.connectionIndex;
|
|
58
51
|
connectionConfig = ctx.connectionConfig;
|
|
59
52
|
timer = backoff(ctx.connectionConfig.retry);
|
|
60
53
|
|
|
54
|
+
attachErrorHandlers(config);
|
|
55
|
+
forwardRabbitMQConnectionEvents();
|
|
61
56
|
ensureChannelPools();
|
|
62
57
|
resumeChannelAllocation();
|
|
63
58
|
|
|
59
|
+
debug('vhost: %s was initialised with connection: %s', self.name, connection._rascal_id);
|
|
60
|
+
|
|
61
|
+
self.emit('connect');
|
|
62
|
+
self.emit('vhost_initialised', getConnectionDetails());
|
|
63
|
+
|
|
64
64
|
return next(null, self);
|
|
65
65
|
});
|
|
66
66
|
return self;
|
|
@@ -162,6 +162,10 @@ function Vhost(config) {
|
|
|
162
162
|
confirmChannelPool.destroy(channel);
|
|
163
163
|
};
|
|
164
164
|
|
|
165
|
+
this.isPaused = function() {
|
|
166
|
+
return paused;
|
|
167
|
+
};
|
|
168
|
+
|
|
165
169
|
function createChannelPool(options) {
|
|
166
170
|
var pool, poolQueue;
|
|
167
171
|
var mode = getChannelMode(options.confirm);
|
|
@@ -326,12 +330,25 @@ function Vhost(config) {
|
|
|
326
330
|
channelCreator.pause();
|
|
327
331
|
regularChannelPool && regularChannelPool.pause();
|
|
328
332
|
confirmChannelPool && confirmChannelPool.pause();
|
|
333
|
+
paused = true;
|
|
334
|
+
self.emit('paused', { vhost: self.name });
|
|
329
335
|
}
|
|
330
336
|
|
|
331
337
|
function resumeChannelAllocation() {
|
|
332
338
|
channelCreator.resume();
|
|
333
339
|
regularChannelPool && regularChannelPool.resume();
|
|
334
340
|
confirmChannelPool && confirmChannelPool.resume();
|
|
341
|
+
paused = false;
|
|
342
|
+
self.emit('resumed', { vhost: self.name });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function forwardRabbitMQConnectionEvents() {
|
|
346
|
+
connection.on('blocked', function(reason) {
|
|
347
|
+
self.emit('blocked', reason, getConnectionDetails());
|
|
348
|
+
});
|
|
349
|
+
connection.on('unblocked', function() {
|
|
350
|
+
self.emit('unblocked', getConnectionDetails());
|
|
351
|
+
});
|
|
335
352
|
}
|
|
336
353
|
|
|
337
354
|
function ensureChannelPools() {
|
|
@@ -350,7 +367,7 @@ function Vhost(config) {
|
|
|
350
367
|
], next);
|
|
351
368
|
}
|
|
352
369
|
|
|
353
|
-
function attachErrorHandlers(
|
|
370
|
+
function attachErrorHandlers(config) {
|
|
354
371
|
connection.removeAllListeners('error');
|
|
355
372
|
var errorHandler = _.once(handleConnectionError.bind(null, connection, config));
|
|
356
373
|
connection.once('error', errorHandler);
|
|
@@ -359,10 +376,10 @@ function Vhost(config) {
|
|
|
359
376
|
|
|
360
377
|
function handleConnectionError(borked, config, err) {
|
|
361
378
|
debug('Handling connection error: %s initially from connection: %s, %s', err.message, borked._rascal_id, connectionConfig.loggableUrl);
|
|
362
|
-
self.emit('disconnect');
|
|
363
379
|
pauseChannelAllocation();
|
|
364
380
|
connection = undefined;
|
|
365
|
-
self.emit('
|
|
381
|
+
self.emit('disconnect');
|
|
382
|
+
self.emit('error', err, getConnectionDetails());
|
|
366
383
|
connectionConfig.retry && self.init(function(err) {
|
|
367
384
|
if (!err) return;
|
|
368
385
|
var delay = timer.next();
|
|
@@ -370,4 +387,8 @@ function Vhost(config) {
|
|
|
370
387
|
setTimeoutUnref(handleConnectionError.bind(null, borked, config, err), delay);
|
|
371
388
|
});
|
|
372
389
|
}
|
|
390
|
+
|
|
391
|
+
function getConnectionDetails() {
|
|
392
|
+
return { vhost: self.name, connectionUrl: connectionConfig.loggableUrl };
|
|
393
|
+
}
|
|
373
394
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rascal",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.4.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": {
|