iamqp
Version:
RabbitMQ wrapper for NodeJS with several produce(reply)/consume scenarios
154 lines (143 loc) • 5.1 kB
JavaScript
const UUIDv4 = require('../uuid_v4')
const MESSAGES = require('../internal').MESSAGES
const _isNull = require('lodash/isNull')
const _get = require('lodash/get')
/**
* RPC client. Init example:
* <pre>
* const iamqp = require('iamqp');
* const amqpUri = 'amqp://localhost';
* const rpcChannel = 'rpc-channel-a';
* let rpcClient = new iamqp.RPCClient(amqpUri, rpcChannel);
*
* // This needs to be done or the errors will bubble up.
* rpcClient.getEventer().on('error', (err) => {
* console.log('RPC client error:' + err.message);
* });
*
* // For when the connection is established.
* rpcClient.getEventer().on('connected', () => {
* console.log('RPC client connected');
* });
*
* rpcClient.getEventer().on('reply', (reply, correlationId) => {
* console.log('RPC client got a reply (' + correlationId + '): ' + reply);
* });
*
* // Open the connection - this takes some time and is not sync
* rpcClient.openConnection();
*
* // ...
*
* // Call the RPC server with some data.
* // The correlation ID strings are to be used to distinguish replies to answers.
* let correlationIdA = rpcClient.callRemote({'a': 5});
* let correlationIdB = rpcClient.callRemote({'b': 5});
* if (correlationIdA === false) {
* console.log('call not performed on A');
* } else {
* console.log('correlationID on A: ' + correlationIdA);
* }
*
* if (correlationIdB === false) {
* console.log('call not performed on B');
* } else {
* console.log('correlationID on B: ' + correlationIdB);
* }
*
* // ...
*
* // Close the connection
* if (rpcClient.closeConnection()) {
* console.log('RPC client connection closing ...');
* }
* </pre>
* ...
*/
class RpcClient extends require('../common') {
/**
* @param {AmqpURI} amqpUri
* @param {ChannelName} channelName
* @param {Configuration} [configuration]
*/
constructor (amqpUri, channelName, configuration) {
super(amqpUri, channelName, configuration)
this._uidGenerator = new UUIDv4()
this._correlationIDStorage = {}
this._name = `RPC-CLIENT: ${this._configuration.channelName}`
this._messageDispatcherWrapper('info', MESSAGES.INIT)
this._E.on('connected', () => {
this._createChannel()
})
}
/**
* Perform the "server" call with some data.
* @param {number | string | boolean | Object | Array} contentToPublish
* @returns {boolean | string} the correlation ID of the call.
* The user can use this as to distinguish between multiple answers.
* If something went wrong, false will be returned.
*/
callRemote (contentToPublish) {
const data = this._commonPublishPreparations(contentToPublish)
if (!_isNull(data)) {
const thisCorrelationId = this._uidGenerator.generate()
this._correlationIDStorage[thisCorrelationId] = true
this._amqp.channel.sendToQueue(this._configuration.channelName, data, {
replyTo: this._amqp.queue,
correlationId: thisCorrelationId
})
return thisCorrelationId
}
return false
}
/**
* Create the channel.
* @private
*/
_createChannel () {
this._amqp.connection.createChannel((channelCreatingErr, createdChannel) => {
if (channelCreatingErr) {
this._emitAndDispatchError(`${MESSAGES.CHANNEL_ERROR} -> ${channelCreatingErr.message}`)
return
}
this._messageDispatcherWrapper('info', MESSAGES.CHANNEL_READY)
this._amqp.channel = createdChannel
this._assertQueue()
})
}
/**
* Assert the queue and bind the consumer function.
* @private
*/
_assertQueue () {
this._amqp.channel.assertQueue('', {
exclusive: true,
arguments: _get(this._configuration, 'configuration.queueArguments', {})
}, (channelAssertErr, asserted) => {
if (channelAssertErr) {
this._emitAndDispatchError(`${MESSAGES.CHANNEL_ERROR} -> ${channelAssertErr.message}`)
} else {
this._messageDispatcherWrapper('info', MESSAGES.CONSUMING)
this._amqp.queue = asserted.queue
this._amqp.channel.consume(this._amqp.queue, (data) => {
if (
!data ||
!data.properties ||
!data.properties.correlationId ||
!Object.prototype.hasOwnProperty.call(this._correlationIDStorage, data.properties.correlationId)
) {
this._emitAndDispatchError(MESSAGES.INVALID_DATA)
return
}
this._logData(data.content, 'consume')
const replied = this._parseConsumedMessage(data)
delete this._correlationIDStorage[data.properties.correlationId]
this._E.emit('reply', replied, data.properties.correlationId)
}, {
noAck: true
})
}
})
}
}
module.exports = RpcClient