You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
7.0 KiB
297 lines
7.0 KiB
"use strict"; |
|
|
|
var Redis = require('ioredis'); |
|
var msgpack = require('msgpack-lite'); |
|
var util = require("./util"); |
|
var wrap = util.wrap; |
|
var defer = util.defer; |
|
var TrieAscoltatore = require("./trie_ascoltatore"); |
|
var AbstractAscoltatore = require('./abstract_ascoltatore'); |
|
var SubsCounter = require("./subs_counter"); |
|
var debug = require("debug")("ascoltatori:redis"); |
|
|
|
/** |
|
* RedisAscoltatore is a class that inherits from AbstractAscoltatore. |
|
* It is implemented through the `node_redis` package and it could be |
|
* backed up by any redis version greater than 2.2. |
|
* |
|
* The RedisAscoltatore defines two |
|
* properties _sub and _pub for handling |
|
* the two connections needed by redis. |
|
* |
|
* The options are: |
|
* - `port`, the optional port to connect to; |
|
* - `host`, the optional host to connect to; |
|
* - `db`, the database to connect to (defaults to 0); |
|
* - `password`, the optional password to use; |
|
* - `sub_conn`, the optional redis connection to use for the sub and psub commands; |
|
* - `pub_conn`, the optional redis connection to use for the pub command; |
|
* - `redis`, the redis module (it will automatically be required if not present). |
|
* |
|
* @api public |
|
* @param {Object} opts The options object |
|
*/ |
|
function RedisAscoltatore(opts) { |
|
AbstractAscoltatore.call(this, opts, { |
|
separator: '/', |
|
wildcardOne: '*', |
|
wildcardSome: '*' |
|
}); |
|
|
|
this._ready_sub = false; |
|
this._ready_pub = false; |
|
this._opts = opts || {}; |
|
|
|
this._ascoltatores = {}; |
|
|
|
this._startSub(); |
|
this._startPub(); |
|
|
|
this._subs_counter = new SubsCounter(); |
|
} |
|
|
|
/** |
|
* Create a connection to redis using a default |
|
* from the opts if there is. |
|
* |
|
* @param {Object} opts The options object |
|
* @param {String} connName The name of the connection |
|
* @api private |
|
*/ |
|
function createConn(opts, connName) { |
|
var conn = opts[connName]; |
|
if (conn === undefined) { |
|
debug("connecting with ", opts); |
|
conn = new Redis(opts); |
|
} |
|
return conn; |
|
} |
|
|
|
/** |
|
* Inheriting |
|
* |
|
* @api private |
|
*/ |
|
RedisAscoltatore.prototype = Object.create(AbstractAscoltatore.prototype); |
|
|
|
/** |
|
* Start the pub connection |
|
* |
|
* @api private |
|
*/ |
|
RedisAscoltatore.prototype._startPub = function() { |
|
var that = this; |
|
if (this._client === undefined) { |
|
this._client = createConn(this._opts, 'client_conn'); |
|
this._client.on("ready", function() { |
|
debug("created pub connection"); |
|
that._updateReady("_ready_pub"); |
|
}); |
|
} |
|
return this._client; |
|
}; |
|
|
|
/** |
|
* Start the sub connection |
|
* |
|
* @api private |
|
*/ |
|
RedisAscoltatore.prototype._startSub = function() { |
|
var that = this, |
|
handler = null; |
|
|
|
if (this._sub === undefined) { |
|
this._sub = createConn(this._opts, 'sub_conn'); |
|
this._sub.on("ready", function() { |
|
debug("created sub connection"); |
|
that._updateReady("_ready_sub"); |
|
}); |
|
|
|
handler = function(sub, topic, payload) { |
|
topic = topic.toString(); // cast to string in case of Buffer instance (nodejs-redis >=2.0) |
|
debug("new message received for topic " + topic); |
|
defer(function() { |
|
// we need to skip out this callback, so we do not |
|
// break the client when an exception occurs |
|
var ascoltatore = that._ascoltatores[sub]; |
|
topic = topic.replace(new RegExp('/$'), ''); |
|
topic = that._recvTopic(topic); |
|
|
|
payload = msgpack.decode(payload); |
|
|
|
if (ascoltatore) { |
|
ascoltatore.publish(topic, payload.message, payload.options); |
|
} else { |
|
debug("no ascoltatore for subscription: " + sub); |
|
} |
|
}); |
|
}; |
|
|
|
this._sub.on("messageBuffer", function (topic, message) { |
|
handler(topic, topic, message); |
|
}); |
|
this._sub.on("pmessageBuffer", function(sub, topic, message) { |
|
handler(sub, topic, message); |
|
}); |
|
} |
|
|
|
return this._sub; |
|
}; |
|
|
|
RedisAscoltatore.prototype._updateReady = function updateReady(key) { |
|
this[key] = true; |
|
if (this._ready_pub && this._ready_sub) { |
|
this.emit("ready"); |
|
} |
|
}; |
|
|
|
RedisAscoltatore.prototype._containsWildcard = function(topic) { |
|
return (topic.indexOf(this._wildcardOne) >= 0) || |
|
(topic.indexOf(this._wildcardSome) >= 0); |
|
}; |
|
|
|
RedisAscoltatore.prototype.subscribe = function subscribe(topic, callback, done) { |
|
this._raiseIfClosed(); |
|
|
|
var newDone = function() { |
|
debug("registered new subscriber for topic " + topic); |
|
defer(done); |
|
}; |
|
|
|
var subTopic = this._subTopic(topic); |
|
|
|
if (!subTopic.match(new RegExp('[/*]$'))) { |
|
subTopic += '/'; |
|
} |
|
|
|
if (this._containsWildcard(topic)) { |
|
this._sub.psubscribe(subTopic, newDone); |
|
} else { |
|
this._sub.subscribe(subTopic, newDone); |
|
} |
|
|
|
debug("redis subscription topic is " + subTopic); |
|
this._subs_counter.add(subTopic); |
|
|
|
var ascoltatore = this._ascoltatores[subTopic]; |
|
|
|
if (!ascoltatore) { |
|
ascoltatore = this._ascoltatores[subTopic] = new TrieAscoltatore(this._opts); |
|
} |
|
|
|
ascoltatore.subscribe(topic, callback); |
|
}; |
|
|
|
RedisAscoltatore.prototype.publish = function publish(topic, message, options, done) { |
|
this._raiseIfClosed(); |
|
|
|
if (message === undefined || message === null) { |
|
message = false; // so we can convert it to JSON |
|
} |
|
|
|
var payload = { |
|
message: message, |
|
options: options |
|
}; |
|
|
|
topic = this._pubTopic(topic); |
|
|
|
if (!topic.match(new RegExp('/$'))) { |
|
topic += '/'; |
|
} |
|
|
|
this._client.publish(topic, msgpack.encode(payload), function() { |
|
debug("new message published to " + topic); |
|
defer(done); |
|
}); |
|
}; |
|
|
|
RedisAscoltatore.prototype.unsubscribe = function unsubscribe(topic, callback, done) { |
|
this._raiseIfClosed(); |
|
|
|
var isWildcard = this._containsWildcard(topic), |
|
subTopic = this._subTopic(topic); |
|
|
|
if (!subTopic.match(new RegExp('[/*]$'))) { |
|
subTopic += '/'; |
|
} |
|
|
|
this._subs_counter.remove(subTopic); |
|
|
|
var ascoltatore = this._ascoltatores[subTopic]; |
|
|
|
if (ascoltatore) { |
|
ascoltatore.unsubscribe(topic, callback); |
|
} |
|
|
|
var newDone = function() { |
|
debug("deregistered subscriber for topic " + topic); |
|
defer(done); |
|
}; |
|
|
|
if (this._subs_counter.include(subTopic)) { |
|
newDone(); |
|
return this; |
|
} |
|
|
|
if (ascoltatore) { |
|
ascoltatore.close(); |
|
delete this._ascoltatores[subTopic]; |
|
} |
|
|
|
if (isWildcard) { |
|
this._sub.punsubscribe(subTopic, newDone); |
|
} else { |
|
this._sub.unsubscribe(subTopic, newDone); |
|
} |
|
|
|
return this; |
|
}; |
|
|
|
RedisAscoltatore.prototype.close = function close(done) { |
|
var that = this, |
|
newDone = null, |
|
closes = 2; |
|
|
|
newDone = function() { |
|
debug("closed"); |
|
defer(done); |
|
}; |
|
|
|
if (this._closed) { |
|
newDone(); |
|
return; |
|
} |
|
|
|
this._subs_counter.clear(); |
|
["_sub", "_client"].forEach(function(c) { |
|
if (that[c] !== undefined) { |
|
that[c].on("end", function() { |
|
closes = closes - 1; |
|
if (closes === 0) { |
|
newDone(); |
|
} |
|
}); |
|
that[c].quit(); |
|
delete that[c]; |
|
} else { |
|
closes = closes - 1; |
|
} |
|
}); |
|
|
|
for (var subTopic in this._ascoltatores) { |
|
this._ascoltatores[subTopic].close(); |
|
} |
|
this._ascoltatores = {}; |
|
|
|
this.emit("closed"); |
|
}; |
|
|
|
util.aliasAscoltatore(RedisAscoltatore.prototype); |
|
|
|
/** |
|
* Exports the RedisAscoltatore |
|
* |
|
* @api public |
|
*/ |
|
module.exports = RedisAscoltatore;
|
|
|