iamqp
Version:
RabbitMQ wrapper for NodeJS with several produce(reply)/consume scenarios
169 lines (156 loc) • 5.72 kB
JavaScript
const MESSAGES = require('../internal').MESSAGES
const _get = require('lodash/get')
const _isString = require('lodash/isString')
/**
* RPC server. Init example:
* <pre>
* const iamqp = require('iamqp');
* const amqpUri = 'amqp://localhost';
* const rpcChannel = 'rpc-channel-a';
* let rpcServer = new iamqp.RPCServer(amqpUri, rpcChannel);
*
* // This needs to be done or the errors will bubble up.
* rpcServer.getEventer().on('error', (err) => {
* console.log('RPC server error:' + err.message);
* });
*
* // For when the connection is established.
* rpcServer.getEventer().on('connected', () => {
* console.log('RPC server connected');
* });
*
* // An event gets emitted on each call.
* // Handling of the pCorrelationId is optional and should be done only if you expect a single RPC server instance
* // to handle multiple RPC clients.
* // For example if the RPC server interacts with a DB on the behalf of the client and the calls are just
* // coming in.
* // This means that you can parameters pCorrelationId in both receiving the call and responding.
* rpcServer.getEventer().on('call', (callData, pCorrelationId) => {
* console.log('RPC server got a call: ' + callData);
*
* // ...
*
* let isServerResponding = rpcServer.respond({
* 'a': 3
* }, pCorrelationId);
* });
*
* // Open the connection - this takes some time and is not sync
* rpcServer.openConnection();
*
* // ...
*
* // Close the connection
* if (rpcServer.closeConnection()) {
* console.log('RPC server connection closing ...');
* }
* </pre>
* ...
*/
class RpcServer extends require('../common') {
/**
* @param {AmqpURI} amqpUri
* @param {ChannelName} channelName
* @param {Configuration} [configuration]
*/
constructor (amqpUri, channelName, configuration) {
super(amqpUri, channelName, configuration)
this._name = `RPC-SERVER: ${this._configuration.channelName}`
this._messageDispatcherWrapper('info', MESSAGES.INIT)
this._lastMessage = null
this._calls = {}
this._E.on('connected', () => {
this._createChannel()
})
}
/**
* 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} -> ${MESSAGES.QUEUE_ASSERTING} -> ${MESSAGES.BINDING_CONSUMER_FUNCTION}`)
this._lastMessage = null
this._amqp.channel = createdChannel
this._amqp.channel.assertQueue(this._configuration.channelName, {
durable: false,
arguments: _get(this._configuration, 'configuration.queueArguments', {})
})
// this._amqp.channel.prefetch(1);
this._amqp.channel.consume(this._configuration.channelName, (data) => {
if (
data &&
_get(data, 'properties.replyTo', null) &&
_get(data, 'properties.correlationId', null)
) {
this._calls[data.properties.correlationId] = data
this._lastMessage = data
this._E.emit('call', this._parseConsumedMessage(data), data.properties.correlationId)
} else {
this._lastMessage = null
this._messageDispatcherWrapper('error', `${MESSAGES.GOT_MESSAGE} -> ${MESSAGES.INVALID_PROPERTY}`)
}
}, {
noAck: false
})
})
}
/**
* Respond to the last call/message.
* @param {number | string | boolean | Object | Array} contentToPublish
* @param {string} correlationId correlation ID (as gotten from the call) parameter to use when the server
* is to process multiple calls at once
* @returns {boolean}
*/
respond (contentToPublish, correlationId) {
if (this._isReady === true) {
let lastMessage = null
let isFromCallsStorage = false
if (correlationId) {
if (
_isString(correlationId) &&
Object.prototype.hasOwnProperty.call(this._calls, correlationId)
) {
lastMessage = this._calls[correlationId]
isFromCallsStorage = true
} else {
this._messageDispatcherWrapper('error', 'attempting to respond with an invalid correlation ID')
return false
}
} else if (this._lastMessage) {
lastMessage = this._lastMessage
}
if (
!lastMessage ||
!_get(lastMessage, 'properties.replyTo', null) ||
!_get(lastMessage, 'properties.correlationId', null)
) {
this._messageDispatcherWrapper('error', 'attempting to respond to an invalid message')
return false
}
this._amqp.channel.sendToQueue(
lastMessage.properties.replyTo,
this._preparePublishingMessage(contentToPublish),
{
correlationId: lastMessage.properties.correlationId
}
)
this._amqp.channel.ack(lastMessage)
process.nextTick(() => {
delete this._calls[lastMessage.properties.correlationId]
if (!isFromCallsStorage) {
this._lastMessage = null
}
})
return true
} else {
this._messageDispatcherWrapper('error', `${MESSAGES.RESPOND} -> ${MESSAGES.CONNECTION_ERROR}`)
return false
}
}
}
module.exports = RpcServer