rc-web@69
|
1
|
rc-web@69
|
2 /*!
|
rc-web@69
|
3 * socket.io-node
|
rc-web@69
|
4 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
|
rc-web@69
|
5 * MIT Licensed
|
rc-web@69
|
6 */
|
rc-web@69
|
7
|
rc-web@69
|
8 /**
|
rc-web@69
|
9 * Module dependencies.
|
rc-web@69
|
10 */
|
rc-web@69
|
11
|
rc-web@69
|
12 var crypto = require('crypto')
|
rc-web@69
|
13 , Store = require('../store')
|
rc-web@69
|
14 , assert = require('assert');
|
rc-web@69
|
15
|
rc-web@69
|
16 /**
|
rc-web@69
|
17 * Exports the constructor.
|
rc-web@69
|
18 */
|
rc-web@69
|
19
|
rc-web@69
|
20 exports = module.exports = Redis;
|
rc-web@69
|
21 Redis.Client = Client;
|
rc-web@69
|
22
|
rc-web@69
|
23 /**
|
rc-web@69
|
24 * Redis store.
|
rc-web@69
|
25 * Options:
|
rc-web@69
|
26 * - nodeId (fn) gets an id that uniquely identifies this node
|
rc-web@69
|
27 * - redis (fn) redis constructor, defaults to redis
|
rc-web@69
|
28 * - redisPub (object) options to pass to the pub redis client
|
rc-web@69
|
29 * - redisSub (object) options to pass to the sub redis client
|
rc-web@69
|
30 * - redisClient (object) options to pass to the general redis client
|
rc-web@69
|
31 * - pack (fn) custom packing, defaults to JSON or msgpack if installed
|
rc-web@69
|
32 * - unpack (fn) custom packing, defaults to JSON or msgpack if installed
|
rc-web@69
|
33 *
|
rc-web@69
|
34 * @api public
|
rc-web@69
|
35 */
|
rc-web@69
|
36
|
rc-web@69
|
37 function Redis (opts) {
|
rc-web@69
|
38 opts = opts || {};
|
rc-web@69
|
39
|
rc-web@69
|
40 // node id to uniquely identify this node
|
rc-web@69
|
41 var nodeId = opts.nodeId || function () {
|
rc-web@69
|
42 // by default, we generate a random id
|
rc-web@69
|
43 return Math.abs(Math.random() * Math.random() * Date.now() | 0);
|
rc-web@69
|
44 };
|
rc-web@69
|
45
|
rc-web@69
|
46 this.nodeId = nodeId();
|
rc-web@69
|
47
|
rc-web@69
|
48 // packing / unpacking mechanism
|
rc-web@69
|
49 if (opts.pack) {
|
rc-web@69
|
50 this.pack = opts.pack;
|
rc-web@69
|
51 this.unpack = opts.unpack;
|
rc-web@69
|
52 } else {
|
rc-web@69
|
53 try {
|
rc-web@69
|
54 var msgpack = require('msgpack');
|
rc-web@69
|
55 this.pack = msgpack.pack;
|
rc-web@69
|
56 this.unpack = msgpack.unpack;
|
rc-web@69
|
57 } catch (e) {
|
rc-web@69
|
58 this.pack = JSON.stringify;
|
rc-web@69
|
59 this.unpack = JSON.parse;
|
rc-web@69
|
60 }
|
rc-web@69
|
61 }
|
rc-web@69
|
62
|
rc-web@69
|
63 var redis = opts.redis || require('redis')
|
rc-web@69
|
64 , RedisClient = redis.RedisClient;
|
rc-web@69
|
65
|
rc-web@69
|
66 // initialize a pubsub client and a regular client
|
rc-web@69
|
67 if (opts.redisPub instanceof RedisClient) {
|
rc-web@69
|
68 this.pub = opts.redisPub;
|
rc-web@69
|
69 } else {
|
rc-web@69
|
70 opts.redisPub || (opts.redisPub = {});
|
rc-web@69
|
71 this.pub = redis.createClient(opts.redisPub.port, opts.redisPub.host, opts.redisPub);
|
rc-web@69
|
72 }
|
rc-web@69
|
73 if (opts.redisSub instanceof RedisClient) {
|
rc-web@69
|
74 this.sub = opts.redisSub;
|
rc-web@69
|
75 } else {
|
rc-web@69
|
76 opts.redisSub || (opts.redisSub = {});
|
rc-web@69
|
77 this.sub = redis.createClient(opts.redisSub.port, opts.redisSub.host, opts.redisSub);
|
rc-web@69
|
78 }
|
rc-web@69
|
79 if (opts.redisClient instanceof RedisClient) {
|
rc-web@69
|
80 this.cmd = opts.redisClient;
|
rc-web@69
|
81 } else {
|
rc-web@69
|
82 opts.redisClient || (opts.redisClient = {});
|
rc-web@69
|
83 this.cmd = redis.createClient(opts.redisClient.port, opts.redisClient.host, opts.redisClient);
|
rc-web@69
|
84 }
|
rc-web@69
|
85
|
rc-web@69
|
86 Store.call(this, opts);
|
rc-web@69
|
87
|
rc-web@69
|
88 this.sub.setMaxListeners(0);
|
rc-web@69
|
89 this.setMaxListeners(0);
|
rc-web@69
|
90 };
|
rc-web@69
|
91
|
rc-web@69
|
92 /**
|
rc-web@69
|
93 * Inherits from Store.
|
rc-web@69
|
94 */
|
rc-web@69
|
95
|
rc-web@69
|
96 Redis.prototype.__proto__ = Store.prototype;
|
rc-web@69
|
97
|
rc-web@69
|
98 /**
|
rc-web@69
|
99 * Publishes a message.
|
rc-web@69
|
100 *
|
rc-web@69
|
101 * @api private
|
rc-web@69
|
102 */
|
rc-web@69
|
103
|
rc-web@69
|
104 Redis.prototype.publish = function (name) {
|
rc-web@69
|
105 var args = Array.prototype.slice.call(arguments, 1);
|
rc-web@69
|
106 this.pub.publish(name, this.pack({ nodeId: this.nodeId, args: args }));
|
rc-web@69
|
107 this.emit.apply(this, ['publish', name].concat(args));
|
rc-web@69
|
108 };
|
rc-web@69
|
109
|
rc-web@69
|
110 /**
|
rc-web@69
|
111 * Subscribes to a channel
|
rc-web@69
|
112 *
|
rc-web@69
|
113 * @api private
|
rc-web@69
|
114 */
|
rc-web@69
|
115
|
rc-web@69
|
116 Redis.prototype.subscribe = function (name, consumer, fn) {
|
rc-web@69
|
117 this.sub.subscribe(name);
|
rc-web@69
|
118
|
rc-web@69
|
119 if (consumer || fn) {
|
rc-web@69
|
120 var self = this;
|
rc-web@69
|
121
|
rc-web@69
|
122 self.sub.on('subscribe', function subscribe (ch) {
|
rc-web@69
|
123 if (name == ch) {
|
rc-web@69
|
124 function message (ch, msg) {
|
rc-web@69
|
125 if (name == ch) {
|
rc-web@69
|
126 msg = self.unpack(msg);
|
rc-web@69
|
127
|
rc-web@69
|
128 // we check that the message consumed wasnt emitted by this node
|
rc-web@69
|
129 if (self.nodeId != msg.nodeId) {
|
rc-web@69
|
130 consumer.apply(null, msg.args);
|
rc-web@69
|
131 }
|
rc-web@69
|
132 }
|
rc-web@69
|
133 };
|
rc-web@69
|
134
|
rc-web@69
|
135 self.sub.on('message', message);
|
rc-web@69
|
136
|
rc-web@69
|
137 self.on('unsubscribe', function unsubscribe (ch) {
|
rc-web@69
|
138 if (name == ch) {
|
rc-web@69
|
139 self.sub.removeListener('message', message);
|
rc-web@69
|
140 self.removeListener('unsubscribe', unsubscribe);
|
rc-web@69
|
141 }
|
rc-web@69
|
142 });
|
rc-web@69
|
143
|
rc-web@69
|
144 self.sub.removeListener('subscribe', subscribe);
|
rc-web@69
|
145
|
rc-web@69
|
146 fn && fn();
|
rc-web@69
|
147 }
|
rc-web@69
|
148 });
|
rc-web@69
|
149 }
|
rc-web@69
|
150
|
rc-web@69
|
151 this.emit('subscribe', name, consumer, fn);
|
rc-web@69
|
152 };
|
rc-web@69
|
153
|
rc-web@69
|
154 /**
|
rc-web@69
|
155 * Unsubscribes
|
rc-web@69
|
156 *
|
rc-web@69
|
157 * @api private
|
rc-web@69
|
158 */
|
rc-web@69
|
159
|
rc-web@69
|
160 Redis.prototype.unsubscribe = function (name, fn) {
|
rc-web@69
|
161 this.sub.unsubscribe(name);
|
rc-web@69
|
162
|
rc-web@69
|
163 if (fn) {
|
rc-web@69
|
164 var client = this.sub;
|
rc-web@69
|
165
|
rc-web@69
|
166 client.on('unsubscribe', function unsubscribe (ch) {
|
rc-web@69
|
167 if (name == ch) {
|
rc-web@69
|
168 fn();
|
rc-web@69
|
169 client.removeListener('unsubscribe', unsubscribe);
|
rc-web@69
|
170 }
|
rc-web@69
|
171 });
|
rc-web@69
|
172 }
|
rc-web@69
|
173
|
rc-web@69
|
174 this.emit('unsubscribe', name, fn);
|
rc-web@69
|
175 };
|
rc-web@69
|
176
|
rc-web@69
|
177 /**
|
rc-web@69
|
178 * Destroys the store
|
rc-web@69
|
179 *
|
rc-web@69
|
180 * @api public
|
rc-web@69
|
181 */
|
rc-web@69
|
182
|
rc-web@69
|
183 Redis.prototype.destroy = function () {
|
rc-web@69
|
184 Store.prototype.destroy.call(this);
|
rc-web@69
|
185
|
rc-web@69
|
186 this.pub.end();
|
rc-web@69
|
187 this.sub.end();
|
rc-web@69
|
188 this.cmd.end();
|
rc-web@69
|
189 };
|
rc-web@69
|
190
|
rc-web@69
|
191 /**
|
rc-web@69
|
192 * Client constructor
|
rc-web@69
|
193 *
|
rc-web@69
|
194 * @api private
|
rc-web@69
|
195 */
|
rc-web@69
|
196
|
rc-web@69
|
197 function Client (store, id) {
|
rc-web@69
|
198 Store.Client.call(this, store, id);
|
rc-web@69
|
199 };
|
rc-web@69
|
200
|
rc-web@69
|
201 /**
|
rc-web@69
|
202 * Inherits from Store.Client
|
rc-web@69
|
203 */
|
rc-web@69
|
204
|
rc-web@69
|
205 Client.prototype.__proto__ = Store.Client;
|
rc-web@69
|
206
|
rc-web@69
|
207 /**
|
rc-web@69
|
208 * Redis hash get
|
rc-web@69
|
209 *
|
rc-web@69
|
210 * @api private
|
rc-web@69
|
211 */
|
rc-web@69
|
212
|
rc-web@69
|
213 Client.prototype.get = function (key, fn) {
|
rc-web@69
|
214 this.store.cmd.hget(this.id, key, fn);
|
rc-web@69
|
215 return this;
|
rc-web@69
|
216 };
|
rc-web@69
|
217
|
rc-web@69
|
218 /**
|
rc-web@69
|
219 * Redis hash set
|
rc-web@69
|
220 *
|
rc-web@69
|
221 * @api private
|
rc-web@69
|
222 */
|
rc-web@69
|
223
|
rc-web@69
|
224 Client.prototype.set = function (key, value, fn) {
|
rc-web@69
|
225 this.store.cmd.hset(this.id, key, value, fn);
|
rc-web@69
|
226 return this;
|
rc-web@69
|
227 };
|
rc-web@69
|
228
|
rc-web@69
|
229 /**
|
rc-web@69
|
230 * Redis hash del
|
rc-web@69
|
231 *
|
rc-web@69
|
232 * @api private
|
rc-web@69
|
233 */
|
rc-web@69
|
234
|
rc-web@69
|
235 Client.prototype.del = function (key, fn) {
|
rc-web@69
|
236 this.store.cmd.hdel(this.id, key, fn);
|
rc-web@69
|
237 return this;
|
rc-web@69
|
238 };
|
rc-web@69
|
239
|
rc-web@69
|
240 /**
|
rc-web@69
|
241 * Redis hash has
|
rc-web@69
|
242 *
|
rc-web@69
|
243 * @api private
|
rc-web@69
|
244 */
|
rc-web@69
|
245
|
rc-web@69
|
246 Client.prototype.has = function (key, fn) {
|
rc-web@69
|
247 this.store.cmd.hexists(this.id, key, function (err, has) {
|
rc-web@69
|
248 if (err) return fn(err);
|
rc-web@69
|
249 fn(null, !!has);
|
rc-web@69
|
250 });
|
rc-web@69
|
251 return this;
|
rc-web@69
|
252 };
|
rc-web@69
|
253
|
rc-web@69
|
254 /**
|
rc-web@69
|
255 * Destroys client
|
rc-web@69
|
256 *
|
rc-web@69
|
257 * @param {Number} number of seconds to expire data
|
rc-web@69
|
258 * @api private
|
rc-web@69
|
259 */
|
rc-web@69
|
260
|
rc-web@69
|
261 Client.prototype.destroy = function (expiration) {
|
rc-web@69
|
262 if ('number' != typeof expiration) {
|
rc-web@69
|
263 this.store.cmd.del(this.id);
|
rc-web@69
|
264 } else {
|
rc-web@69
|
265 this.store.cmd.expire(this.id, expiration);
|
rc-web@69
|
266 }
|
rc-web@69
|
267
|
rc-web@69
|
268 return this;
|
rc-web@69
|
269 };
|