Skip to content
This repository
Browse code

Moved redis to lib/redis

  • Loading branch information...
commit 1b4d5da617f7baf94c68367e0917040963fc63a3 1 parent 317ae54
TJ Holowaychuk authored
1  index.js
2  index.js
... ... @@ -0,0 +1,2 @@
  1 +
  2 +module.exports = require('./lib/connect-redis');
2  lib/connect-redis.js
@@ -11,7 +11,7 @@
11 11
12 12 var sys = require('sys'),
13 13 Store = require('connect/middleware/session/store'),
14   - redis = require('./support/redis/lib/redis-client');
  14 + redis = require('./redis/lib/redis-client');
15 15
16 16 /**
17 17 * Initialize RedisStore with the given `options`.
1  lib/redis
... ... @@ -0,0 +1 @@
  1 +Subproject commit 30e9f41e7f62137ba47a0f5929ddd8a1e40a6553
1  support/redis/.gitignore
... ... @@ -1 +0,0 @@
1   -*.swp
19 support/redis/LICENSE
... ... @@ -1,19 +0,0 @@
1   -© 2010 by Fictorial LLC
2   -
3   -Permission is hereby granted, free of charge, to any person obtaining a copy
4   -of this software and associated documentation files (the "Software"), to deal
5   -in the Software without restriction, including without limitation the rights
6   -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7   -copies of the Software, and to permit persons to whom the Software is
8   -furnished to do so, subject to the following conditions:
9   -
10   -The above copyright notice and this permission notice shall be included in
11   -all copies or substantial portions of the Software.
12   -
13   -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14   -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15   -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16   -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17   -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18   -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19   -THE SOFTWARE.
103 support/redis/README.md
Source Rendered
... ... @@ -1,103 +0,0 @@
1   -# Redis client for Node.js
2   -
3   -## In a nutshell
4   -
5   -- Talk to Redis from Node.js
6   -- Fully asynchronous; your code is called back when an operation completes
7   -- [Binary-safe](http://github.com/fictorial/redis-node-client/blob/master/test/test.js#L353-363); uses Node.js Buffer objects for request serialization and reply parsing
8   - - e.g. store a PNG in Redis if you'd like
9   -- Client API directly follows Redis' [command specification](http://code.google.com/p/redis/wiki/CommandReference)
10   -- *You have to understand how Redis works and the semantics of its command set to most effectively use this client*
11   -- Supports Redis' new exciting [PUBSUB](http://code.google.com/p/redis/wiki/PublishSubscribe) commands
12   -- Automatically reconnects to Redis (doesn't drop commands sent while waiting to reconnect either) using [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff)
13   - - Be sure to see [this script](http://github.com/fictorial/redis-node-client/blob/master/test/test_shutdown_reconnect.js) for a deeper discussion
14   -
15   -## Synopsis
16   -
17   -When working from a git clone:
18   -
19   - var sys = require("sys");
20   - var client = require("../lib/redis-client").createClient();
21   - client.info(function (err, info) {
22   - if (err) throw new Error(err);
23   - sys.puts("Redis Version is: " + info.redis_version);
24   - client.close();
25   - });
26   -
27   -When working with a Kiwi-based installation:
28   -
29   - // $ kiwi install redis-client
30   -
31   - var sys = require("sys"),
32   - kiwi = require("kiwi"),
33   - client = kiwi.require("redis-client").createClient();
34   -
35   - client.info(function (err, info) {
36   - if (err) throw new Error(err);
37   - sys.puts("Redis Version is: " + info.redis_version);
38   - client.close();
39   - });
40   -
41   -- Refer to the many tests in `test/test.js` for many usage examples.
42   -- Refer to the `examples/` directory for focused examples.
43   -
44   -## Installation
45   -
46   -This version requires at least `Node.js v0.1.90` and Redis `1.3.8`.
47   -
48   -Tested with Node.js `v0.1.95` and `v0.1.96` and Redis `2.1.1` (the current unstable).
49   -
50   -You have a number of choices:
51   -
52   -- git clone this repo or download a tarball and simply copy `lib/redis-client.js` into your project
53   -- use git submodule
54   -- use the [Kiwi](http://github.com/visionmedia/kiwi) package manager for Node.js
55   -
56   -Please let me know if the package manager "seeds" and/or metadata have issues.
57   -Installation via Kiwi or NPM at this point isn't really possible since this repo
58   -depends on a unreleased version of Node.js.
59   -
60   -## Running the tests
61   -
62   -A good way to learn about this client is to read the test code.
63   -
64   -To run the tests, install and run redis on the localhost on port 6379 (defaults).
65   -Then run `node test/test.js [-v|-q]` where `-v` is for "verbose" and `-q` is for "quiet".
66   -
67   - $ node test/test.js
68   - ..................................................................
69   - ...........................++++++++++++++++++++++++++++++++++++
70   -
71   - [INFO] All tests have passed.
72   -
73   -If you see something like "PSUBSCRIBE: unknown command" then it is time to upgrade
74   -your Redis installation.
75   -
76   -## Documentation
77   -
78   -There is a method per Redis command. E.g. `SETNX` becomes `client.setnx`.
79   -
80   -For example, the Redis command [INCRBY](http://code.google.com/p/redis/wiki/IncrCommand)
81   -is specified as `INCRBY key integer`. Also, the INCRBY spec says that the reply will
82   -be "... the new value of key after the increment or decrement."
83   -
84   -This translates to the following client code which increments key 'foo' by 42. If
85   -the value at key 'foo' was 0 or non-existent, 'newValue' will take value 42 when
86   -the callback function is called.
87   -
88   - client.incrby('foo', 42, function (err, newValue) {
89   - // ...
90   - });
91   -
92   -This can get [a little wacky](http://github.com/fictorial/redis-node-client/blob/master/test/test.js#L1093-1097).
93   -I'm open to suggestions for improvement here.
94   -
95   -Note: for PUBSUB, you should use `subscribeTo` and `unsubscribeFrom` instead of the generated
96   -methods for Redis' `SUBSCRIBE` and `UNSUBSCRIBE` commands. See [this](http://github.com/fictorial/redis-node-client/blob/master/lib/redis-client.js#L682-694)
97   -and [this](http://github.com/fictorial/redis-node-client/blob/master/examples/subscriber.js#L14).
98   -
99   -## Notes
100   -
101   -All commands/requests use the Redis *multi-bulk request* format which will be
102   -the only accepted request protocol come Redis 2.0.
103   -
23 support/redis/TODO.md
Source Rendered
... ... @@ -1,23 +0,0 @@
1   -## Soon
2   -
3   -- Support MULTI/EXEC/DISCARD. This is a little tricky given that its
4   - referred to as a "transaction" (but it's really a macro). The API
5   - must clearly define what the hell is going on in error cases, etc.
6   - Also, we'll need to add support for nested multi-bulk replies. The
7   - reply parser, while it handles non-bulk replies inside a multi-bulk
8   - reply, does not handle multi-bulk replies inside multi-bulk replies.
9   - This is required for MULTI/EXEC.
10   -
11   -- WATCH support
12   -
13   -- Now that Node.js has UDP support and Redis 2.0 will have UDP support,
14   - I suppose we should add, you know, UDP support here.
15   -
16   -## Later
17   -
18   -- Provide wrapper around the pretty-raw sort method?
19   -
20   -## Maybe
21   -
22   -- Add support for consistent hashing ala redis-rb and txRedisAPI
23   -- Add a higher-level interface similar to Ohm (Ruby)
32 support/redis/examples/README.md
Source Rendered
... ... @@ -1,32 +0,0 @@
1   -## Some examples.
2   -
3   -Note: a large number of usage examples can be found in `../test/test.js`.
4   -
5   -## Publisher-Subscriber (PUBSUB)
6   -
7   -In one terminal:
8   -
9   - $ ./publisher.js
10   - Published message to no one.
11   - Published message to no one.
12   - Published message to no one.
13   - Published message to 1 subscriber(s). <-- Started the subscriber.
14   - Published message to 1 subscriber(s).
15   - Published message to 1 subscriber(s).
16   - Published message to 1 subscriber(s).
17   - Published message to no one. <-- Killed (^C) the subscriber.
18   - ^C
19   -
20   -In another terminal:
21   -
22   - $ ./subscriber.js
23   - waiting for messages...
24   - [channel-6702921148389578]: The time is Fri Apr 02 2010 16:52:19 GMT-0400 (EDT)
25   - [channel-9212789069861174]: The time is Fri Apr 02 2010 16:52:20 GMT-0400 (EDT)
26   - [channel-30327219143509865]: The time is Fri Apr 02 2010 16:52:21 GMT-0400 (EDT)
27   - [channel-35810230672359467]: The time is Fri Apr 02 2010 16:52:22 GMT-0400 (EDT)
28   - [channel-5208229701966047]: The time is Fri Apr 02 2010 16:52:23 GMT-0400 (EDT)
29   - [channel-26559297926723957]: The time is Fri Apr 02 2010 16:52:24 GMT-0400 (EDT)
30   - [channel-9280104916542768]: The time is Fri Apr 02 2010 16:52:25 GMT-0400 (EDT)
31   - ^C
32   -
21 support/redis/examples/publisher.js
... ... @@ -1,21 +0,0 @@
1   -#!/usr/bin/env node
2   -
3   -// This script plays the role of publisher.
4   -
5   -var
6   - sys = require("sys"),
7   - client = require("../lib/redis-client").createClient();
8   -
9   -// Publish a message once a second to a random channel.
10   -
11   -setInterval(function () {
12   - var
13   - channelName = "channel-" + Math.random().toString().substr(2),
14   - payload = "The time is " + (new Date());
15   -
16   - client.publish(channelName, payload,
17   - function (err, reply) {
18   - sys.puts("Published message to " +
19   - (reply === 0 ? "no one" : (reply + " subscriber(s).")));
20   - });
21   -}, 1000);
7 support/redis/examples/redis-version.js
... ... @@ -1,7 +0,0 @@
1   -var sys = require("sys");
2   -var client = require("../lib/redis-client").createClient();
3   -client.info(function (err, info) {
4   - if (err) throw new Error(err);
5   - sys.puts("Redis Version is: " + info.redis_version);
6   - client.close();
7   -});
19 support/redis/examples/subscriber.js
... ... @@ -1,19 +0,0 @@
1   -#!/usr/bin/env node
2   -
3   -// This script plays the role of listener/subscriber/consumer
4   -// to **all** channels/classes.
5   -
6   -var
7   - sys = require("sys"),
8   - client = require("../lib/redis-client").createClient();
9   -
10   -sys.puts("waiting for messages...");
11   -
12   -client.subscribeTo("*",
13   - function (channel, message, subscriptionPattern) {
14   - var output = "[" + channel;
15   - if (subscriptionPattern)
16   - output += " (from pattern '" + subscriptionPattern + "')";
17   - output += "]: " + message;
18   - sys.puts(output);
19   - });
16 support/redis/examples/using-kiwi.js
... ... @@ -1,16 +0,0 @@
1   -// Kiwi is a package manager for Node.js
2   -// http://wiki.github.com/visionmedia/kiwi/getting-started
3   -//
4   -// $ kiwi install redis-client
5   -
6   -var sys = require("sys"),
7   - kiwi = require("kiwi"),
8   - client = kiwi.require("redis-client").createClient();
9   -
10   -client.stream.addListener("connect", function () {
11   - client.info(function (err, info) {
12   - if (err) throw new Error(err);
13   - sys.puts("Redis Version is: " + info.redis_version);
14   - client.close();
15   - });
16   -});
917 support/redis/lib/redis-client.js
... ... @@ -1,917 +0,0 @@
1   -/*
2   -
3   -© 2010 by Fictorial LLC
4   -
5   -Permission is hereby granted, free of charge, to any person obtaining a copy
6   -of this software and associated documentation files (the "Software"), to deal
7   -in the Software without restriction, including without limitation the rights
8   -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9   -copies of the Software, and to permit persons to whom the Software is
10   -furnished to do so, subject to the following conditions:
11   -
12   -The above copyright notice and this permission notice shall be included in
13   -all copies or substantial portions of the Software.
14   -
15   -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16   -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17   -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18   -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19   -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20   -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21   -THE SOFTWARE.
22   -
23   -*/
24   -
25   -// To add support for new commands, edit the array called "commands" at the
26   -// bottom of this file.
27   -
28   -// Set this to true to aid in debugging wire protocol input/output,
29   -// parsing methods, etc.
30   -
31   -exports.debugMode = false;
32   -
33   -var net = require("net"),
34   - sys = require("sys"),
35   - Buffer = require('buffer').Buffer,
36   - events = require('events'),
37   -
38   - CRLF = "\r\n",
39   - CRLF_LEN = 2,
40   -
41   - PLUS = exports.PLUS = 0x2B, // +
42   - MINUS = exports.MINUS = 0x2D, // -
43   - DOLLAR = exports.DOLLAR = 0x24, // $
44   - STAR = exports.STAR = 0x2A, // *
45   - COLON = exports.COLON = 0x3A, // :
46   - CR = exports.CR = 0x0D, // \r
47   - LF = exports.LF = 0x0A, // \n
48   -
49   - NONE = exports.NONE = "NONE",
50   - BULK = exports.BULK = "BULK",
51   - MULTIBULK = exports.MULTIBULK = "MULTIBULK",
52   - INLINE = exports.INLINE = "INLINE",
53   - INTEGER = exports.INTEGER = "INTEGER",
54   - ERROR = exports.ERROR = "ERROR";
55   -
56   -exports.DEFAULT_HOST = '127.0.0.1';
57   -exports.DEFAULT_PORT = 6379;
58   -
59   -exports.COMMAND_ORPHANED_ERROR = "connection lost before reply received";
60   -exports.NO_CONNECTION_ERROR = "failed to establish a connection to Redis";
61   -
62   -function debugFilter(buffer, len) {
63   - // Redis is binary-safe but assume for debug display that
64   - // the encoding of textual data is UTF-8.
65   -
66   - var filtered = buffer.utf8Slice(0, len || buffer.length);
67   -
68   - filtered = filtered.replace(/\r\n/g, '<CRLF>');
69   - filtered = filtered.replace(/\r/g, '<CR>');
70   - filtered = filtered.replace(/\n/g, '<LF>');
71   -
72   - return filtered;
73   -}
74   -
75   -// A fully interruptable, binary-safe Redis reply parser.
76   -// 'callback' is called with each reply parsed in 'feed'.
77   -// 'thisArg' is the "thisArg" for the callback "call".
78   -
79   -function ReplyParser(callback, thisArg) {
80   - this.onReply = callback;
81   - this.thisArg = thisArg;
82   - this.clearState();
83   - this.clearMultiBulkState();
84   -}
85   -
86   -exports.ReplyParser = ReplyParser;
87   -
88   -ReplyParser.prototype.clearState = function () {
89   - this.type = NONE;
90   - this.bulkLengthExpected = null;
91   - this.valueBufferLen = 0;
92   - this.skip = 0;
93   - this.valueBuffer = new Buffer(4096);
94   -};
95   -
96   -ReplyParser.prototype.clearMultiBulkState = function () {
97   - this.multibulkReplies = null;
98   - this.multibulkRepliesExpected = null;
99   -};
100   -
101   -ReplyParser.prototype.feed = function (inbound) {
102   - for (var i=0; i < inbound.length; ++i) {
103   - if (this.skip > 0) {
104   - this.skip--;
105   - continue;
106   - }
107   -
108   - var typeBefore = this.type;
109   -
110   - if (this.type === NONE) {
111   - switch (inbound[i]) {
112   - case DOLLAR: this.type = BULK; break;
113   - case STAR: this.type = MULTIBULK; break;
114   - case COLON: this.type = INTEGER; break;
115   - case PLUS: this.type = INLINE; break;
116   - case MINUS: this.type = ERROR; break;
117   - }
118   - }
119   -
120   - // Just a state transition on '*', '+', etc.?
121   -
122   - if (typeBefore != this.type)
123   - continue;
124   -
125   - // If the reply is a part of a multi-bulk reply. Save it. If we have
126   - // received all the expected replies of a multi-bulk reply, then
127   - // callback. If the reply is not part of a multi-bulk. Call back
128   - // immediately.
129   -
130   - var self = this;
131   -
132   - var maybeCallbackWithReply = function (reply) {
133   - if (self.multibulkReplies != null) {
134   - self.multibulkReplies.push(reply);
135   - if (--self.multibulkRepliesExpected == 0) {
136   - self.onReply.call(self.thisArg, {
137   - type: MULTIBULK,
138   - value: self.multibulkReplies
139   - });
140   - self.clearMultiBulkState();
141   - }
142   - } else {
143   - self.onReply.call(self.thisArg, reply);
144   - }
145   - self.clearState();
146   - self.skip = 1; // Skip LF
147   - };
148   -
149   - switch (inbound[i]) {
150   - case CR:
151   - switch (this.type) {
152   - case INLINE:
153   - case ERROR:
154   - // CR denotes end of the inline/error value.
155   - // +OK\r\n
156   - // ^
157   -
158   - var inlineBuf = new Buffer(this.valueBufferLen);
159   - this.valueBuffer.copy(inlineBuf, 0, 0, this.valueBufferLen);
160   - maybeCallbackWithReply({ type:this.type, value:inlineBuf });
161   - break;
162   -
163   - case INTEGER:
164   - // CR denotes the end of the integer value.
165   - // :42\r\n
166   - // ^
167   -
168   - var n = parseInt(this.valueBuffer.asciiSlice(0, this.valueBufferLen), 10);
169   - maybeCallbackWithReply({ type:INTEGER, value:n });
170   - break;
171   -
172   - case BULK:
173   - if (this.bulkLengthExpected == null) {
174   - // CR denotes end of first line of a bulk reply,
175   - // which is the length of the bulk reply value.
176   - // $5\r\nhello\r\n
177   - // ^
178   -
179   - var bulkLengthExpected =
180   - parseInt(this.valueBuffer.asciiSlice(0, this.valueBufferLen), 10);
181   -
182   - if (bulkLengthExpected <= 0) {
183   - maybeCallbackWithReply({ type:BULK, value:null });
184   - } else {
185   - this.clearState();
186   -
187   - this.bulkLengthExpected = bulkLengthExpected;
188   - this.type = BULK;
189   - this.skip = 1; // skip LF
190   - }
191   - } else if (this.valueBufferLen == this.bulkLengthExpected) {
192   - // CR denotes end of the bulk reply value.
193   - // $5\r\nhello\r\n
194   - // ^
195   -
196   - var bulkBuf = new Buffer(this.valueBufferLen);
197   - this.valueBuffer.copy(bulkBuf, 0, 0, this.valueBufferLen);
198   - maybeCallbackWithReply({ type:BULK, value:bulkBuf });
199   - } else {
200   - // CR is just an embedded CR and has nothing to do
201   - // with the reply specification.
202   - // $11\r\nhello\rworld\r\n
203   - // ^
204   -
205   - this.valueBuffer[this.valueBufferLen++] = inbound[i];
206   - }
207   - break;
208   -
209   - case MULTIBULK:
210   - // Parse the count which is the number of expected replies
211   - // in the multi-bulk reply.
212   - // *2\r\n$5\r\nhello\r\n$5\r\nworld\r\n
213   - // ^
214   -
215   - var multibulkRepliesExpected =
216   - parseInt(this.valueBuffer.asciiSlice(0, this.valueBufferLen), 10);
217   -
218   - if (multibulkRepliesExpected <= 0) {
219   - maybeCallbackWithReply({ type:MULTIBULK, value:null });
220   - } else {
221   - this.clearState();
222   - this.skip = 1; // skip LF
223   - this.multibulkReplies = [];
224   - this.multibulkRepliesExpected = multibulkRepliesExpected;
225   - }
226   - break;
227   - }
228   - break;
229   -
230   - default:
231   - this.valueBuffer[this.valueBufferLen++] = inbound[i];
232   - break;
233   - }
234   -
235   - // If the current value buffer is too big, create a new buffer, copy in
236   - // the old buffer, and replace the old buffer with the new buffer.
237   -
238   - if (this.valueBufferLen === this.valueBuffer.length) {
239   - var newBuffer = new Buffer(this.valueBuffer.length * 2);
240   - this.valueBuffer.copy(newBuffer, 0, 0);
241   - this.valueBuffer = newBuffer;
242   - }
243   - }
244   -};
245   -
246   -/**
247   - * Emits:
248   - *
249   - * - 'connected' when connected (or on a reconnection, reconnected).
250   - * - 'reconnecting' when about to retry to connect to Redis.
251   - * - 'reconnected' when connected after a reconnection was established.
252   - * - 'noconnection' when a connection (or reconnection) cannot be established.
253   - * - 'drained' when no submitted commands are expecting a reply from Redis.
254   - *
255   - * Options:
256   - *
257   - * - maxReconnectionAttempts (default: 10)
258   - */
259   -
260   -function Client(stream, options) {
261   - events.EventEmitter.call(this);
262   -
263   - this.stream = stream;
264   - this.originalCommands = [];
265   - this.queuedOriginalCommands = [];
266   - this.queuedRequestBuffers = [];
267   - this.channelCallbacks = {};
268   - this.requestBuffer = new Buffer(512);
269   - this.replyParser = new ReplyParser(this.onReply_, this);
270   - this.reconnectionTimer = null;
271   - this.maxReconnectionAttempts = 10;
272   - this.reconnectionAttempts = 0;
273   - this.reconnectionDelay = 500; // doubles, so starts at 1s delay
274   - this.connectionsMade = 0;
275   -
276   - if (options !== undefined)
277   - this.maxReconnectionAttempts = Math.abs(options.maxReconnectionAttempts || 10);
278   -
279   - var client = this;
280   -
281   - stream.addListener("connect", function () {
282   - if (exports.debugMode)
283   - sys.debug("[CONNECT]");
284   -
285   - stream.setNoDelay();
286   - stream.setTimeout(0);
287   -
288   - client.reconnectionAttempts = 0;
289   - client.reconnectionDelay = 500;
290   - if (client.reconnectionTimer) {
291   - clearTimeout(client.reconnectionTimer);
292   - client.reconnectionTimer = null;
293   - }
294   -
295   - var eventName = client.connectionsMade == 0
296   - ? 'connected'
297   - : 'reconnected';
298   -
299   - client.connectionsMade++;
300   - client.expectingClose = false;
301   -
302   - // If this a reconnection and there were commands submitted, then they
303   - // are gone! We cannot say with any confidence which were processed by
304   - // Redis; perhaps some were processed but we never got the reply, or
305   - // perhaps all were processed but Redis is configured with less than
306   - // 100% durable writes, etc.
307   - //
308   - // We punt to the user by calling their callback with an I/O error.
309   - // However, we provide enough information to allow the user to retry
310   - // the interrupted operation. We are certainly not retrying anything
311   - // for them as it is too dangerous and application-specific.
312   -
313   - if (client.connectionsMade > 1 && client.originalCommands.length > 0) {
314   - if (exports.debug) {
315   - sys.debug("[RECONNECTION] some commands orphaned (" +
316   - client.originalCommands.length + "). notifying...");
317   - }
318   -
319   - client.callbackOrphanedCommandsWithError();
320   - }
321   -
322   - client.originalCommands = [];
323   - client.flushQueuedCommands();
324   -
325   - client.emit(eventName, client);
326   - });
327   -
328   - stream.addListener('error', function (e) {
329   - if (exports.debugMode)
330   - sys.debug("[ERROR] Connection to redis encountered an error: " + e);
331   - });
332   -
333   - stream.addListener("data", function (buffer) {
334   - if (exports.debugMode)
335   - sys.debug("[RECV] " + debugFilter(buffer));
336   -
337   - client.replyParser.feed(buffer);
338   - });
339   -
340   - stream.addListener("error", function (e) {
341   - if (exports.debugMode)
342   - sys.debug('[ERROR] ' + e);
343   - client.replyParser.clearState();
344   - client.maybeReconnect();
345   - throw e;
346   - });
347   -
348   - stream.addListener("end", function () {
349   - if (exports.debugMode && client.originalCommands.length > 0) {
350   - sys.debug("Connection to redis closed with " +
351   - client.originalCommands.length +
352   - " commands pending replies that will never arrive!");
353   - }
354   -
355   - stream.end();
356   - });
357   -
358   - stream.addListener("close", function (hadError) {
359   - if (exports.debugMode)
360   - sys.debug("[NO CONNECTION]");
361   -
362   - client.maybeReconnect();
363   - });
364   -}
365   -
366   -sys.inherits(Client, events.EventEmitter);
367   -
368   -exports.Client = Client;
369   -
370   -exports.createClient = function (port, host, options) {
371   - var port = port || exports.DEFAULT_PORT;
372   - var host = host || exports.DEFAULT_HOST;
373   -
374   - var client = new Client(net.createConnection(port, host), options);
375   -
376   - client.port = port;
377   - client.host = host;
378   -
379   - return client;
380   -};
381   -
382   -Client.prototype.close = function () {
383   - this.expectingClose = true;
384   - this.stream.end();
385   -};
386   -
387   -Client.prototype.onReply_ = function (reply) {
388   - this.flushQueuedCommands();
389   -
390   - if (this.handlePublishedMessage_(reply))
391   - return;
392   -
393   - var originalCommand = this.originalCommands.shift();
394   - var callback = originalCommand[originalCommand.length - 1];
395   -
396   - // Callbacks expect (err, reply) as args.
397   -
398   - if (typeof callback == "function") {
399   - if (reply.type == ERROR) {
400   - callback(reply.value.utf8Slice(0, reply.value.length), null);
401   - } else {
402   - callback(null, maybeConvertReplyValue(originalCommand[0], reply));
403   - }
404   - }
405   -
406   - if (this.originalCommands.length == 0)
407   - this.emit('drained', this);
408   -};
409   -
410   -Client.prototype.handlePublishedMessage_ = function (reply) {
411   - // We're looking for a multibulk resembling
412   - // ["message", "channelName", messageBuffer]; or
413   - // ["pmessage", "matchingPattern", "channelName", messageBuffer]
414   - // The latter is sent when the client subscribed to a channel by a pattern;
415   - // the former when subscribed to a channel by name.
416   - // If the client subscribes by name -and- by pattern and there's some
417   - // overlap, the client -will- receive multiple p/message notifications.
418   -
419   - if (reply.type != MULTIBULK || !(reply.value instanceof Array))
420   - return false;
421   -
422   - var isMessage = (reply.value.length == 3 &&
423   - reply.value[0].value.length == 7 &&
424   - reply.value[0].value.asciiSlice(0, 7) == 'message');
425   -
426   - var isPMessage = (reply.value.length == 4 &&
427   - reply.value[0].value.length == 8 &&
428   - reply.value[0].value.asciiSlice(0, 8) == 'pmessage');
429   -
430   - if (!isMessage && !isPMessage)
431   - return false;
432   -
433   - // This is tricky. We are returning true even though there
434   - // might not be any callback called! This may happen when a
435   - // caller subscribes then unsubscribes while a published
436   - // message is in transit to us. When the message arrives, no
437   - // one is there to consume it. In essence, as long as the
438   - // reply type is a published message (see above), then we've
439   - // "handled" the reply.
440   -
441   - if (Object.getOwnPropertyNames(this.channelCallbacks).length == 0)
442   - return true;
443   -
444   - var channelName, channelPattern, channelCallback, payload;
445   -
446   - if (isMessage) {
447   - channelName = reply.value[1].value;
448   - channelCallback = this.channelCallbacks[channelName];
449   - payload = reply.value[2].value;
450   - } else if (isPMessage) {
451   - channelPattern = reply.value[1].value;
452   - channelName = reply.value[2].value;
453   - channelCallback = this.channelCallbacks[channelPattern];
454   - payload = reply.value[3].value;
455   - } else {
456   - return false;
457   - }
458   -
459   - if (typeof channelCallback == "function") {
460   - channelCallback(channelName, payload, channelPattern);
461   - return true;
462   - }
463   -
464   - return false;
465   -}
466   -
467   -function maybeAsNumber(str) {
468   - var value = parseInt(str, 10);
469   -
470   - if (isNaN(value))
471   - value = parseFloat(str);
472   -
473   - if (isNaN(value))
474   - return str;
475   -
476   - return value;
477   -}
478   -
479   -function maybeConvertReplyValue(commandName, reply) {
480   - if (reply.value === null)
481   - return null;
482   -
483   - // Redis' INFO command returns a BULK reply of the form:
484   - // "redis_version:1.3.8
485   - // arch_bits:64
486   - // multiplexing_api:kqueue
487   - // process_id:11604
488   - // ..."
489   - //
490   - // We convert that to a JS object like:
491   - // { redis_version: '1.3.8'
492   - // , arch_bits: '64'
493   - // , multiplexing_api: 'kqueue'
494   - // , process_id: '11604'
495   - // , ... }
496   -
497   - if (commandName === 'info' && reply.type === BULK) {
498   - var info = {};
499   - reply.value.asciiSlice(0, reply.value.length).split(/\r\n/g)
500   - .forEach(function (line) {
501   - var parts = line.split(':');
502   - if (parts.length === 2)
503   - info[parts[0]] = parts[1];
504   - });
505   - return info;
506   - }
507   -
508   - // HGETALL returns a MULTIBULK where each consecutive reply-pair
509   - // is a key and value for the Redis HASH. We convert this into
510   - // a JS object.
511   -
512   - if (commandName === 'hgetall' &&
513   - reply.type === MULTIBULK &&
514   - reply.value.length % 2 === 0) {
515   -
516   - var hash = {};
517   - for (var i=0; i<reply.value.length; i += 2)
518   - hash[reply.value[i].value] = reply.value[i + 1].value;
519   - return hash;
520   - }
521   -
522   - // Redis returns "+OK\r\n" to signify success.
523   - // We convert this into a JS boolean with value true.
524   -
525   - if (reply.type === INLINE && reply.value.asciiSlice(0,2) === 'OK')
526   - return true;
527   -
528   - // ZSCORE returns a string representation of a floating point number.
529   - // We convert this into a JS number.
530   -
531   - if (commandName === "zscore")
532   - return maybeAsNumber(reply.value);
533   -
534   - // Multibulk replies are returned from our reply parser as an
535   - // array like: [ {type:BULK, value:"foo"}, {type:BULK, value:"bar"} ]
536   - // But, end-users want the value and don't care about the
537   - // Redis protocol reply types. We here extract the value from each
538   - // object in the multi-bulk array.
539   -
540   - if (reply.type === MULTIBULK)
541   - return reply.value.map(function (element) { return element.value; });
542   -
543   - // Otherwise, we have no conversions to offer.
544   -
545   - return reply.value;
546   -}
547   -
548   -exports.maybeConvertReplyValue_ = maybeConvertReplyValue;
549   -
550   -var commands = [
551   - "append",
552   - "auth",
553   - "bgsave",
554   - "blpop",
555   - "brpop",
556   - "dbsize",
557   - "decr",
558   - "decrby",
559   - "del",
560   - "exists",
561   - "expire",
562   - "expireat",
563   - "flushall",
564   - "flushdb",
565   - "get",
566   - "getset",
567   - "hdel",
568   - "hexists",
569   - "hget",
570   - "hgetall",
571   - "hincrby",
572   - "hkeys",
573   - "hlen",
574   - "hmget",
575   - "hmset",
576   - "hset",
577   - "hvals",
578   - "incr",
579   - "incrby",
580   - "info",
581   - "keys",
582   - "lastsave",
583   - "len",
584   - "lindex",
585   - "llen",
586   - "lpop",
587   - "lpush",
588   - "lrange",
589   - "lrem",
590   - "lset",
591   - "ltrim",
592   - "mget",
593   - "move",
594   - "mset",
595   - "msetnx",
596   - "psubscribe",
597   - "publish",
598   - "punsubscribe",
599   - "randomkey",
600   - "rename",
601   - "renamenx",
602   - "rpop",
603   - "rpoplpush",
604   - "rpush",
605   - "sadd",
606   - "save",
607   - "scard",
608   - "sdiff",
609   - "sdiffstore",
610   - "select",
611   - "set",
612   - "setex",
613   - "setnx",
614   - "shutdown",
615   - "sinter",
616   - "sinterstore",
617   - "sismember",
618   - "smembers",
619   - "smove",
620   - "sort",
621   - "spop",
622   - "srandmember",
623   - "srem",
624   - "subscribe",
625   - "sunion",
626   - "sunionstore",
627   - "ttl",
628   - "type",
629   - "unsubscribe",
630   - "zadd",
631   - "zcard",
632   - "zcount",
633   - "zincrby",
634   - "zinter",
635   - "zrange",
636   - "zrangebyscore",
637   - "zrank",
638   - "zrem",
639   - "zrembyrank",
640   - "zremrangebyrank",
641   - "zremrangebyscore",
642   - "zrevrange",
643   - "zrevrank",
644   - "zscore",
645   - "zunion",
646   -];
647   -
648   -// For internal use but maybe useful in rare cases or when the client command
649   -// set is not 100% up to date with Redis' latest commands.
650   -// client.sendCommand('GET', 'foo', function (err, value) {...});
651   -//
652   -// arguments[0] = commandName
653   -// arguments[1..N-2] = Redis command arguments
654   -// arguments[N-1] = callback function
655   -
656   -Client.prototype.sendCommand = function () {
657   - var originalCommand = Array.prototype.slice.call(arguments);
658   -
659   - // If this client has given up trying to connect/reconnect to Redis,
660   - // just call the errback (if any). Regardless, don't enqueue the command.
661   -
662   - if (this.noConnection) {
663   - if (arguments.length > 0 && typeof arguments[arguments.length - 1] == 'function')
664   - arguments[arguments.length - 1](this.makeErrorForCommand(originalCommand, exports.NO_CONNECTION_ERROR));
665   - return;
666   - }
667   -
668   - this.flushQueuedCommands();
669   -
670   - var commandName = arguments[0].toLowerCase();
671   -
672   - // Invariant: number of queued callbacks == number of commands sent to
673   - // Redis whose replies have not yet been received and processed. Thus,
674   - // if no callback was given, we create a dummy callback.
675   -
676   - var argCount = arguments.length;
677   - if (typeof arguments[argCount - 1] == 'function')
678   - --argCount;
679   -
680   - // All requests are formatted as multi-bulk.
681   - // The first line of a multi-bulk request is "*<number of parts to follow>\r\n".
682   - // Next is: "$<length of the command name>\r\n<command name>\r\n".
683   -
684   - // Write the request as we go into a request Buffer. Recall that buffers
685   - // are fixed length. We thus guess at how much space is needed. If we
686   - // need to grow beyond this, we create a new buffer, copy the old one, and
687   - // continue. Once we're ready to write the buffer, we use a 0-copy slice
688   - // to send just that which we've written to the buffer.
689   - //
690   - // We reuse the buffer after each request. When the buffer "grows" to
691   - // accomodate a request, it stays that size until it needs to grown again,
692   - // which may of course be never.
693   -
694   - var offset = this.requestBuffer.utf8Write('*' + argCount.toString() + CRLF +
695   - '$' + commandName.length + CRLF +
696   - commandName + CRLF, 0);
697   -
698   - var self = this;
699   -
700   - function ensureSpaceFor(atLeast) {
701   - var currentLength = self.requestBuffer.length;
702   -
703   - if (offset + atLeast > currentLength) {
704   - // If we know how much space we need, use that + 10%.
705   - // Else double the size of the buffer.
706   -
707   - var bufferLength = Math.max(currentLength * 2, atLeast * 1.1);
708   - var newBuffer = new Buffer(Math.round(bufferLength));
709   - self.requestBuffer.copy(newBuffer, 0, 0, offset); // target, targetStart, srcStart, srcEnd
710   - self.requestBuffer = newBuffer;
711   - }
712   - }
713   -
714   - // Serialize the arguments into the request buffer
715   - // If the request is a Buffer, just copy. Else if
716   - // the arg has a .toString() method, call it and write
717   - // it to the request buffer as UTF8.
718   -
719   - var extrasLength = 5; // '$', '\r\n', '\r\n'
720   -
721   - for (var i=1; i < argCount; ++i) {
722   - var arg = arguments[i];
723   - if (arg instanceof Buffer) {
724   - ensureSpaceFor(arg.length + arg.length.toString().length + extrasLength);
725   - offset += this.requestBuffer.asciiWrite('$' + arg.length + CRLF, offset);
726   - offset += arg.copy(this.requestBuffer, offset, 0); // target, targetStart, srcStart
727   - offset += this.requestBuffer.asciiWrite(CRLF, offset);
728   - } else if (arg.toString) {
729   - var asString = arg.toString();
730   - var serialized = '$' + Buffer.byteLength(asString, "binary") + CRLF + asString + CRLF;
731   - ensureSpaceFor(Buffer.byteLength(serialized, "binary"));
732   - offset += this.requestBuffer.binaryWrite(serialized, offset);
733   - }
734   - }
735   -
736   - // If the stream is writable, write the command. Else enqueue the command
737   - // for when we first establish a connection or reconnect.
738   -
739   - if (this.stream.writable) {
740   - this.originalCommands.push(originalCommand);
741   - var outBuffer = new Buffer(offset);
742   - this.requestBuffer.copy(outBuffer, 0, 0, offset);
743   - this.stream.write(outBuffer, 'binary');
744   -
745   - if (exports.debugMode)
746   - sys.debug("[SEND] " + debugFilter(this.requestBuffer, offset) +
747   - " originalCommands = " + this.originalCommands.length);
748   - } else {
749   - var toEnqueue = new Buffer(offset);
750   - this.requestBuffer.copy(toEnqueue, 0, 0, offset); // dst, dstStart, srcStart, srcEnd
751   - this.queuedRequestBuffers.push(toEnqueue);
752   - this.queuedOriginalCommands.push(originalCommand);
753   -
754   - if (exports.debugMode) {
755   - sys.debug("[ENQUEUE] Not connected. Request queued. There are " +
756   - this.queuedRequestBuffers.length + " requests queued.");
757   - }
758   - }
759   -};
760   -
761   -commands.forEach(function (commandName) {
762   - Client.prototype[commandName] = function () {
763   - var args = Array.prototype.slice.call(arguments);
764   - // [[1,2,3],function(){}] => [1,2,3,function(){}]
765   - if (args.length > 0 && Array.isArray(args[0]))
766   - args = args.shift().concat(args);
767   - args.unshift(commandName);
768   - this.sendCommand.apply(this, args);
769   - };
770   -});
771   -
772   -// Send any commands that were queued while we were not connected.
773   -
774   -Client.prototype.flushQueuedCommands = function () {
775   - if (exports.debugMode && this.queuedRequestBuffers.length > 0)
776   - sys.debug("[FLUSH QUEUE] " + this.queuedRequestBuffers.length +
777   - " queued request buffers.");
778   -
779   - for (var i=0; i<this.queuedRequestBuffers.length && this.stream.writable; ++i) {
780   - var buffer = this.queuedRequestBuffers.shift();
781   - this.stream.write(buffer, 'binary');
782   - this.originalCommands.push(this.queuedOriginalCommands.shift());
783   -
784   - if (exports.debugMode)
785   - sys.debug("[DEQUEUE/SEND] " + debugFilter(buffer) +
786   - ". queued buffers remaining = " +
787   - this.queuedRequestBuffers.length);
788   - }
789   -};
790   -
791   -Client.prototype.makeErrorForCommand = function (command, errorMessage) {
792   - var err = new Error(errorMessage);
793   - err.originalCommand = command;
794   - return err;
795   -};
796   -
797   -Client.prototype.callbackCommandWithError = function (command, errorMessage) {
798   - var callback = command[command.length - 1];
799   - if (typeof callback == "function")
800   - callback(this.makeErrorForCommand(command, errorMessage));
801   -};
802   -
803   -Client.prototype.callbackOrphanedCommandsWithError = function () {
804   - for (var i=0, n=this.originalCommands.length; i<n; ++i)
805   - this.callbackCommandWithError(this.originalCommands[i], exports.COMMAND_ORPHANED_ERROR);
806   - this.originalCommands = [];
807   -};
808   -
809   -Client.prototype.callbackQueuedCommandsWithError = function () {
810   - for (var i=0, n=this.queuedOriginalCommands.length; i<n; ++i)
811   - this.callbackCommandWithError(this.queuedOriginalCommands[i], exports.NO_CONNECTION_ERROR);
812   - this.queuedOriginalCommands = [];
813   - this.queuedRequestBuffers = [];
814   -};
815   -
816   -Client.prototype.giveupConnectionAttempts = function () {
817   - this.callbackOrphanedCommandsWithError();
818   - this.callbackQueuedCommandsWithError();
819   - this.noConnection = true;
820   - this.emit('noconnection', this);
821   -};
822   -
823   -Client.prototype.maybeReconnect = function () {
824   - if (this.stream.writable && this.stream.readable)
825   - return;
826   -
827   - if (this.expectingClose)
828   - return;
829   -
830   - // Do not reconnect on first connection failure.
831   - // Else try to reconnect if we're asked to.
832   -
833   - if (this.connectionsMade == 0) {
834   - this.giveupConnectionAttempts();
835   - } else if (this.maxReconnectionAttempts > 0) {
836   - if (this.reconnectionAttempts++ >= this.maxReconnectionAttempts) {
837   - this.giveupConnectionAttempts();
838   - } else {
839   - this.reconnectionDelay *= 2;
840   -
841   - if (exports.debugMode) {
842   - sys.debug("[RECONNECTING " + this.reconnectionAttempts + "/" +
843   - this.maxReconnectionAttempts + "]");
844   -
845   - sys.debug("[WAIT " + this.reconnectionDelay + " ms]");
846   - }
847   -
848   - var self = this;
849   -
850   - this.reconnectionTimer = setTimeout(function () {
851   - self.emit('reconnecting', self);
852   - self.stream.connect(self.port, self.host);
853   - }, this.reconnectionDelay);
854   - }
855   - }
856   -};
857   -
858   -// Wraps 'subscribe' and 'psubscribe' methods to manage a single
859   -// callback function per subscribed channel name/pattern.
860   -//
861   -// 'nameOrPattern' is a channel name like "hello" or a pattern like
862   -// "h*llo", "h?llo", or "h[ae]llo".
863   -//
864   -// 'callback' is a function that is called back with 2 args:
865   -// channel name/pattern and message payload.
866   -//