starzky
Version:
A higher level and opinionated library on top of node-amqp.
211 lines (173 loc) • 4.31 kB
JavaScript
/*!
* Module dependencies.
*/
var snake = require('to-snake-case');
var debug = require('debug')('starsky:consumer');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
/**
* Export `Consumer`.
*/
module.exports = Consumer;
/**
* Consumer constructor.
*
* TODO: document me...
*
* @param {Starsky} starsky
* @param {String} queue
* @constructor
*/
function Consumer (starsky, name, brokerArguments) {
this.starsky = starsky;
this.name = snake(name);
this.topics = [];
this.inbox = {};
this.tag = null;
this.queue = null;
this.options = { prefetch: 1 };
this.brokerArguments = brokerArguments || {};
EventEmitter.call(this);
}
/**
* Inherits `EventEmitter`.
*/
util.inherits(Consumer, EventEmitter);
/**
* Sets `options.prefetch`.
*
* TODO: validate number
*
* @param {String} key
* @param {Mixed} value
* @public
*/
Consumer.prototype.prefetch = function (value) {
debug('prefetch %s', value);
this.options.prefetch = value;
return this;
};
/**
* Adds topics to subscribe to. Note that this only works prior
* to the queue being created after calling `process`.
*
* @param {String|Array} topic
*/
Consumer.prototype.subscribe = function (topic) {
debug('subscribe %j', topic);
if (Array.isArray(topic)) {
this.topics = this.topics.concat(topic);
} else {
this.topics.push(topic);
}
return this;
};
/**
* Initializes the queue, topic bindings and subscription with RabbitMQ. Upon
* each message receieved, we execute the `callback` with the message
* payload and pass the "next" callback.
*
*
* @param {Function} callback
* @private
*/
Consumer.prototype.process = function (callback) {
debug('process');
if (!this.starsky.ready) {
throw new Error('not ready');
}
var name = this.name;
var namespace = this.starsky.get('namespace');
var options = { durable: true, autoDelete: false, arguments: this.brokerArguments };
if (namespace) {
name = namespace + ':' + name;
}
this.starsky.connection.queue(name, options, function (queue) {
debug('queue declared');
var exchange = this.starsky.get('mq exchange');
var self = this;
this.queue = queue;
this.topics.forEach(bind);
function bind (topic) {
debug('bind %s', topic);
self.queue.bind(exchange, topic, next);
}
function next () {
debug('next');
subscribe();
}
function subscribe () {
debug('subscribe');
var prefetch = self.starsky.get('prefetch');
var options = { ack: true , prefetchCount: self.options.prefetch };
var consumer = queue.subscribe(options, self.onMessage(callback));
consumer.addCallback(tag);
}
function tag (ok) {
debug('tag');
self.tag = ok.consumerTag;
self.emit('ready');
}
}.bind(this));
};
/**
* Handles incoming messages.
*
* TODO: timeouts?
*
* TODO: the packet thing was a hack for a hackathon actually, so
* we need to actully implement the rest of it.
*
* @param {Object} data
* @param {Object} headers
* @param {Object} info
* @param {Message} msg
* @private
*/
Consumer.prototype.onMessage = function (callback) {
var self = this;
return function (body, headers, info, msg) {
// just the essentials needed for the callback
var packet = {
id: info.messageId,
body: body,
timestamp: info.timestamp,
topic: msg.routingKey
};
// keep track of packets in-process, see quit()
self.inbox[packet.id] = packet;
callback(packet, function (err) {
delete self.inbox[packet.id];
if (err) {
msg.reject();
} else {
msg.acknowledge();
}
});
};
};
/**
* Unsubscribes the `queue` and waits until all in-process
* messages have been completed. Once the `callback` returns
* control to you, you can safely exit the process.
*
* TODO: timeout
*
* @param {Function} callback
* @private
*/
Consumer.prototype.quit = function (callback) {
if (!this.queue) return callback();
var self = this;
this.queue.unsubscribe(self.tag);
function check () {
var n = Object.keys(self.inbox).length;
if (0 === n) return done();
setTimeout(check, 100);
}
function done () {
callback();
}
debug('quit');
return check();
};