Nikhil Marathe avatar Nikhil Marathe committed 40c6631

Updated to work with node v0.2.0 and latest dependencies

Comments (0)

Files changed (5)

 
 # Snip #
 
-Snip is a simple pastebin. It was written as the example for an article on [How To Node](http://howtonode.org).
+Snip is a simple pastebin. It was written as the example for an [article](http://howtonode.org/node-redis-fun) on [How To Node](http://howtonode.org).
 
 ## Prerequisites ##
 

deps/redis-node-client/LICENSE

-Copyright (c) 2009, 2010 Fictorial LLC
+© 2010 by Fictorial LLC
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

deps/redis-node-client/README.md

 - Talk to Redis from Node.js 
 - Fully asynchronous; your code is called back when an operation completes
 - [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
+    - e.g. store a PNG in Redis if you'd like
 - Client API directly follows Redis' [command specification](http://code.google.com/p/redis/wiki/CommandReference) 
 - *You have to understand how Redis works and the semantics of its command set to most effectively use this client*
-- Supports Redis' new exciting PUBSUB commands
-
-Recent changes completely break backwards compatibility.  Sorry, it was time.
+- Supports Redis' new exciting [PUBSUB](http://code.google.com/p/redis/wiki/PublishSubscribe) commands
+- Automatically reconnects to Redis (doesn't drop commands sent while waiting to reconnect either) using [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff)
+    - Be sure to see [this script](http://github.com/fictorial/redis-node-client/blob/master/test/test_shutdown_reconnect.js) for a deeper discussion
 
 ## Synopsis
 
 When working from a git clone:
 
-    var client = require("./lib/redis-client").createClient(); 
     var sys = require("sys");
-    client.stream.addListener("connect", function () {
-        client.info(function (err, info) {
-            if (err) throw new Error(err);
-            sys.puts("Redis Version is: " + info.redis_version);
-            client.close();
-        });
+    var client = require("../lib/redis-client").createClient();
+    client.info(function (err, info) {
+        if (err) throw new Error(err);
+        sys.puts("Redis Version is: " + info.redis_version);
+        client.close();
     });
 
 When working with a Kiwi-based installation:
         kiwi = require("kiwi"),
         client = kiwi.require("redis-client").createClient();
 
-    client.stream.addListener("connect", function () {
-        client.info(function (err, info) {
-            if (err) throw new Error(err);
-            sys.puts("Redis Version is: " + info.redis_version);
-            client.close();
-        });
+    client.info(function (err, info) {
+        if (err) throw new Error(err);
+        sys.puts("Redis Version is: " + info.redis_version);
+        client.close();
     });
 
 - Refer to the many tests in `test/test.js` for many usage examples.
 
 This version requires at least `Node.js v0.1.90` and Redis `1.3.8`.
 
-Tested with node `v0.1.91-20-g6e715b8`.
+Tested with Node.js `v0.1.95` and `v0.1.96` and Redis `2.1.1` (the current unstable).
 
 You have a number of choices:
 
 - git clone this repo or download a tarball and simply copy `lib/redis-client.js` into your project
 - use git submodule
 - use the [Kiwi](http://github.com/visionmedia/kiwi) package manager for Node.js
-- use the [NPM](http://github.com/isaacs/npm) package manager for Node.js
 
 Please let me know if the package manager "seeds" and/or metadata have issues.
 Installation via Kiwi or NPM at this point isn't really possible since this repo
 All commands/requests use the Redis *multi-bulk request* format which will be
 the only accepted request protocol come Redis 2.0.
 
-## Metadata
-
-- *Author*: Brian Hammond (brian at fictorial dot com) with various patches 
-  from nice people everywhere.
-- *Copyright*: Copyright (C) 2010 Fictorial LLC.
-- *License*: MIT
-

deps/redis-node-client/lib/redis-client.js

 /*
-    Redis client module for Node.js
 
-    Copyright (C) 2010 Fictorial LLC.
+© 2010 by Fictorial LLC
 
-    Permission is hereby granted, free of charge, to any person obtaining
-    a copy of this software and associated documentation files (the
-    "Software"), to deal in the Software without restriction, including without
-    limitation the rights to use, copy, modify, merge, publish, distribute,
-    sublicense, and/or sell copies of the Software, and to permit persons to
-    whom the Software is furnished to do so, subject to the following
-    conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
-    The above copyright notice and this permission notice shall be included in
-    all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
 
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-    DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
 */
 
 // To add support for new commands, edit the array called "commands" at the
 var net = require("net"),
     sys = require("sys"),
     Buffer = require('buffer').Buffer,
+    events = require('events'),
 
     CRLF = "\r\n",
     CRLF_LEN = 2,
-    MAX_RECONNECTION_ATTEMPTS = 10,
 
     PLUS      = exports.PLUS      = 0x2B, // +
     MINUS     = exports.MINUS     = 0x2D, // -
     INTEGER   = exports.INTEGER   = "INTEGER",  
     ERROR     = exports.ERROR     = "ERROR";    
 
+exports.DEFAULT_HOST = '127.0.0.1';
+exports.DEFAULT_PORT = 6379;
+
+exports.COMMAND_ORPHANED_ERROR = "connection lost before reply received";
+exports.NO_CONNECTION_ERROR = "failed to establish a connection to Redis";
+
 function debugFilter(buffer, len) {
     // Redis is binary-safe but assume for debug display that 
     // the encoding of textual data is UTF-8.
 
-    var filtered = buffer.utf8Slice(0, len);
+    var filtered = buffer.utf8Slice(0, len || buffer.length);
 
     filtered = filtered.replace(/\r\n/g, '<CRLF>');
     filtered = filtered.replace(/\r/g, '<CR>');
     return filtered;
 }
 
-// fnmatch mirrors (mostly) the functionality of fnmatch(3) at least
-// in the same way as Redis.  
-
-var qmarkRE = /\?/g;
-var starRE  = /\*/g;
-var dotRE   = /\./g;
-
-function fnmatch (pattern, test) {
-    var newPattern = pattern.replace(dotRE, '(\\.)')
-                            .replace(qmarkRE, '(.)')
-                            .replace(starRE, '(.*?)');
-    return (new RegExp(newPattern)).test(test);
-}
-
 // A fully interruptable, binary-safe Redis reply parser.
 // 'callback' is called with each reply parsed in 'feed'.
 // 'thisArg' is the "thisArg" for the callback "call".
     }
 };
 
-function Client(stream) {
+/**
+ * Emits:
+ *
+ * - 'connected' when connected (or on a reconnection, reconnected).
+ * - 'reconnecting' when about to retry to connect to Redis.
+ * - 'reconnected' when connected after a reconnection was established.
+ * - 'noconnection' when a connection (or reconnection) cannot be established.
+ * - 'drained' when no submitted commands are expecting a reply from Redis.
+ *
+ * Options: 
+ *
+ * - maxReconnectionAttempts (default: 10)
+ */
+
+function Client(stream, options) {
+    events.EventEmitter.call(this);
+
+    this.stream = stream;
     this.originalCommands = [];
+    this.queuedOriginalCommands = [];
+    this.queuedRequestBuffers = [];
     this.channelCallbacks = {};
     this.requestBuffer = new Buffer(512);
     this.replyParser = new ReplyParser(this.onReply_, this);
+    this.reconnectionTimer = null;
+    this.maxReconnectionAttempts = 10;
+    this.reconnectionAttempts = 0;
+    this.reconnectionDelay = 500;    // doubles, so starts at 1s delay
+    this.connectionsMade = 0;
+
+    if (options !== undefined) 
+        this.maxReconnectionAttempts = Math.abs(options.maxReconnectionAttempts || 10);
+
     var client = this;
-    this.stream = stream;
 
-    this.stream.addListener("connect", function () {
+    stream.addListener("connect", function () {
         if (exports.debugMode)
-            sys.debug("[CONNECTED]");
+            sys.debug("[CONNECT]");
 
-        this.setNoDelay();
-        this.setTimeout(0);
+        stream.setNoDelay();
+        stream.setTimeout(0);
 
-        client.noReconnect = false;
         client.reconnectionAttempts = 0;
+        client.reconnectionDelay = 500;
+        if (client.reconnectionTimer) {
+            clearTimeout(client.reconnectionTimer);
+            client.reconnectionTimer = null;
+        }
+
+        var eventName = client.connectionsMade == 0 
+                      ? 'connected' 
+                      : 'reconnected';
+
+        client.connectionsMade++;
+        client.expectingClose = false;
+
+        // If this a reconnection and there were commands submitted, then they
+        // are gone!  We cannot say with any confidence which were processed by
+        // Redis; perhaps some were processed but we never got the reply, or
+        // perhaps all were processed but Redis is configured with less than
+        // 100% durable writes, etc.  
+        //
+        // We punt to the user by calling their callback with an I/O error.
+        // However, we provide enough information to allow the user to retry
+        // the interrupted operation.  We are certainly not retrying anything
+        // for them as it is too dangerous and application-specific.
+
+        if (client.connectionsMade > 1 && client.originalCommands.length > 0) {
+            if (exports.debug) {
+                sys.debug("[RECONNECTION] some commands orphaned (" + 
+                    client.originalCommands.length + "). notifying...");
+            }
+
+            client.callbackOrphanedCommandsWithError();
+        }
+        
+        client.originalCommands = [];
+        client.flushQueuedCommands();
+
+        client.emit(eventName, client);
     });
 
-    this.stream.addListener("data", function (buffer) {
+    stream.addListener('error', function (e) {
         if (exports.debugMode)
-            sys.debug("[RECV] " + debugFilter(buffer, buffer.length));
+            sys.debug("[ERROR] Connection to redis encountered an error: " + e);
+    });
+
+    stream.addListener("data", function (buffer) {
+        if (exports.debugMode)
+            sys.debug("[RECV] " + debugFilter(buffer));
 
         client.replyParser.feed(buffer);
     });
 
-    this.stream.addListener("end", function () {
-        this.end();
+    stream.addListener("error", function (e) {
+	if (exports.debugMode)
+	  sys.debug('[ERROR] ' + e);
+	client.replyParser.clearState();
+	client.maybeReconnect();
+	throw e;
     });
 
-    stream.addListener("close", function (inError) {
+    stream.addListener("end", function () {
+        if (exports.debugMode && client.originalCommands.length > 0) {
+            sys.debug("Connection to redis closed with " + 
+                      client.originalCommands.length + 
+                      " commands pending replies that will never arrive!");
+        }
+
+        stream.end();
+    });
+
+    stream.addListener("close", function (hadError) {
         if (exports.debugMode)
-            sys.debug("[DISCONNECTED]");
+            sys.debug("[NO CONNECTION]");
 
-        if (!client.noReconnect &&
-            client.reconnectionAttempts++ < MAX_RECONNECTION_ATTEMPTS) {
-            this.setTimeout(30);
-            this.connect(this.port, this.host);
-        }
+        client.maybeReconnect();
     });
 }
 
+sys.inherits(Client, events.EventEmitter);
+
 exports.Client = Client;
 
-exports.createClient = function (port, host) {
-    var port = port || 6379;
-    var host = host || '127.0.0.1';
+exports.createClient = function (port, host, options) {
+    var port = port || exports.DEFAULT_PORT;
+    var host = host || exports.DEFAULT_HOST;
 
-    var client = new Client(new net.createConnection(port, host));
+    var client = new Client(net.createConnection(port, host), options);
 
     client.port = port;
     client.host = host;
 };
 
 Client.prototype.close = function () {
-    this.noReconnect = true;
+    this.expectingClose = true;
     this.stream.end();
 };
 
 Client.prototype.onReply_ = function (reply) {
+    this.flushQueuedCommands();
+
     if (this.handlePublishedMessage_(reply)) 
         return;
 
             callback(null, maybeConvertReplyValue(originalCommand[0], reply));
         }
     }
+
+    if (this.originalCommands.length == 0)
+      this.emit('drained', this);
 };
 
 Client.prototype.handlePublishedMessage_ = function (reply) {
-    // We're looking for a multibulk like: 
-    // ["message", "channelName", messageBuffer] 
+    // We're looking for a multibulk resembling 
+    // ["message", "channelName", messageBuffer]; or
+    // ["pmessage", "matchingPattern", "channelName", messageBuffer]
+    // The latter is sent when the client subscribed to a channel by a pattern;
+    // the former when subscribed to a channel by name.
+    // If the client subscribes by name -and- by pattern and there's some
+    // overlap, the client -will- receive multiple p/message notifications.
 
-    if (reply.type != MULTIBULK ||
-        !(reply.value instanceof Array) ||
-        reply.value.length != 3 ||
-        reply.value[0].value.length != 7 ||
-        reply.value[0].value.asciiSlice(0, 7) != 'message') 
+    if (reply.type != MULTIBULK || !(reply.value instanceof Array))
+        return false;
+
+    var isMessage = (reply.value.length == 3 &&
+                     reply.value[0].value.length == 7 &&
+                     reply.value[0].value.asciiSlice(0, 7) == 'message');
+
+    var isPMessage = (reply.value.length == 4 &&
+                      reply.value[0].value.length == 8 &&
+                      reply.value[0].value.asciiSlice(0, 8) == 'pmessage');
+
+    if (!isMessage && !isPMessage)
         return false;
 
     // This is tricky. We are returning true even though there 
     if (Object.getOwnPropertyNames(this.channelCallbacks).length == 0) 
         return true;
 
-    var channelNameOrPattern = reply.value[1].value;
-    var channelCallback = this.channelCallbacks[channelNameOrPattern];
-    if (typeof channelCallback == 'undefined') {
-        // No 1:1 channel name match. 
-        //
-        // Perhaps the subscription was for a pattern (PSUBSCRIBE)?
-        // Redis does not send the pattern that matched from an
-        // original PSUBSCRIBE request.  It sends the (fn)matching
-        // channel name instead.  Thus, let's try to fnmatch the
-        // channel the message was published to/on to a subscribed
-        // pattern, and callback the associated function.
-        // 
-        // A -> Redis     PSUBSCRIBE foo.*
-        // B -> Redis     PUBLISH foo.bar hello
-        // Redis -> A     MESSAGE foo.bar hello   (no pattern specified)
+    var channelName, channelPattern, channelCallback, payload;
 
-        var channelNamesOrPatterns = 
-            Object.getOwnPropertyNames(this.channelCallbacks);
-
-        for (var i=0; i < channelNamesOrPatterns.length; ++i) {
-            var thisNameOrPattern = channelNamesOrPatterns[i];
-            if (fnmatch(thisNameOrPattern, channelNameOrPattern)) {
-                channelCallback = this.channelCallbacks[thisNameOrPattern];
-                break;
-            }
-        }
+    if (isMessage) {
+        channelName = reply.value[1].value;
+        channelCallback = this.channelCallbacks[channelName];
+        payload = reply.value[2].value;
+    } else if (isPMessage) {
+        channelPattern = reply.value[1].value;
+        channelName = reply.value[2].value;
+        channelCallback = this.channelCallbacks[channelPattern];
+        payload = reply.value[3].value;
+    } else {
+        return false;
     }
 
-    if (typeof(channelCallback) === 'function') {
-        // Good, we found a function to callback.
-
-        var payload = reply.value[2].value;
-        channelCallback(channelNameOrPattern, payload);
+    if (typeof channelCallback == "function") {
+        channelCallback(channelName, payload, channelPattern);
         return true;
     }
 
     "sdiffstore",
     "select",
     "set",
+    "setex",
     "setnx",
     "shutdown",
     "sinter",
 // arguments[N-1]    = callback function
 
 Client.prototype.sendCommand = function () {
+    var originalCommand = Array.prototype.slice.call(arguments);
+
+    // If this client has given up trying to connect/reconnect to Redis,
+    // just call the errback (if any). Regardless, don't enqueue the command.
+
+    if (this.noConnection) {
+        if (arguments.length > 0 && typeof arguments[arguments.length - 1] == 'function')
+            arguments[arguments.length - 1](this.makeErrorForCommand(originalCommand, exports.NO_CONNECTION_ERROR));
+        return;
+    }
+
+    this.flushQueuedCommands();
+
     var commandName = arguments[0].toLowerCase();
-    var originalCommand = Array.prototype.slice.call(arguments);
 
     // Invariant: number of queued callbacks == number of commands sent to
     // Redis whose replies have not yet been received and processed.  Thus,
             offset += this.requestBuffer.asciiWrite(CRLF, offset);
         } else if (arg.toString) {
             var asString = arg.toString();
-            var serialized = '$' + process._byteLength(asString) + CRLF + asString + CRLF;
-            ensureSpaceFor(process._byteLength(serialized));
-            offset += this.requestBuffer.utf8Write(serialized, offset);
+            var serialized = '$' + Buffer.byteLength(asString, "binary") + CRLF + asString + CRLF;
+            ensureSpaceFor(Buffer.byteLength(serialized, "binary"));
+            offset += this.requestBuffer.binaryWrite(serialized, offset);
         }
     }
 
-    this.originalCommands.push(originalCommand);
-    this.stream.write(this.requestBuffer.slice(0, offset));
+    // If the stream is writable, write the command.  Else enqueue the command
+    // for when we first establish a connection or reconnect.
 
-    if (exports.debugMode) 
-        sys.debug("[SEND] " + debugFilter(this.requestBuffer, offset));
+    if (this.stream.writable) {
+        this.originalCommands.push(originalCommand);
+        var outBuffer = new Buffer(offset);
+        this.requestBuffer.copy(outBuffer, 0, 0, offset);
+        this.stream.write(outBuffer, 'binary');
+
+        if (exports.debugMode) 
+            sys.debug("[SEND] " + debugFilter(this.requestBuffer, offset) + 
+                " originalCommands = " + this.originalCommands.length);
+    } else {
+        var toEnqueue = new Buffer(offset);
+        this.requestBuffer.copy(toEnqueue, 0, 0, offset);  // dst, dstStart, srcStart, srcEnd
+        this.queuedRequestBuffers.push(toEnqueue);
+        this.queuedOriginalCommands.push(originalCommand);
+
+        if (exports.debugMode) {
+            sys.debug("[ENQUEUE] Not connected. Request queued. There are " + 
+                this.queuedRequestBuffers.length + " requests queued.");
+        }
+    }
 };
 
 commands.forEach(function (commandName) {
     Client.prototype[commandName] = function () {
-        if (this.stream.readyState != "open") 
-            throw new Error("Sorry, the command cannot be sent to Redis. " +
-                            "The connection state is '" + 
-                            this.stream.readyState + "'.");
-
         var args = Array.prototype.slice.call(arguments);
+        // [[1,2,3],function(){}] => [1,2,3,function(){}]
+        if (args.length > 0 && Array.isArray(args[0])) 
+          args = args.shift().concat(args);
         args.unshift(commandName);
         this.sendCommand.apply(this, args);
     };
 });
 
+// Send any commands that were queued while we were not connected.
+
+Client.prototype.flushQueuedCommands = function () {
+    if (exports.debugMode && this.queuedRequestBuffers.length > 0) 
+        sys.debug("[FLUSH QUEUE] " + this.queuedRequestBuffers.length + 
+                  " queued request buffers.");
+
+    for (var i=0; i<this.queuedRequestBuffers.length && this.stream.writable; ++i) {
+        var buffer = this.queuedRequestBuffers.shift();
+        this.stream.write(buffer, 'binary');
+        this.originalCommands.push(this.queuedOriginalCommands.shift());
+
+        if (exports.debugMode) 
+            sys.debug("[DEQUEUE/SEND] " + debugFilter(buffer) + 
+                      ". queued buffers remaining = " + 
+                      this.queuedRequestBuffers.length);
+    }
+};
+
+Client.prototype.makeErrorForCommand = function (command, errorMessage) {
+    var err = new Error(errorMessage);
+    err.originalCommand = command;
+    return err;
+};
+
+Client.prototype.callbackCommandWithError = function (command, errorMessage) {
+    var callback = command[command.length - 1];
+    if (typeof callback == "function") 
+        callback(this.makeErrorForCommand(command, errorMessage));
+};
+
+Client.prototype.callbackOrphanedCommandsWithError = function () {
+    for (var i=0, n=this.originalCommands.length; i<n; ++i) 
+        this.callbackCommandWithError(this.originalCommands[i], exports.COMMAND_ORPHANED_ERROR);
+    this.originalCommands = [];
+};
+
+Client.prototype.callbackQueuedCommandsWithError = function () {
+    for (var i=0, n=this.queuedOriginalCommands.length; i<n; ++i) 
+        this.callbackCommandWithError(this.queuedOriginalCommands[i], exports.NO_CONNECTION_ERROR);
+    this.queuedOriginalCommands = [];
+    this.queuedRequestBuffers = [];
+};
+
+Client.prototype.giveupConnectionAttempts = function () {
+    this.callbackOrphanedCommandsWithError();
+    this.callbackQueuedCommandsWithError();
+    this.noConnection = true;
+    this.emit('noconnection', this);
+};
+
+Client.prototype.maybeReconnect = function () {
+    if (this.stream.writable && this.stream.readable)
+        return;
+
+    if (this.expectingClose)
+        return;
+
+    // Do not reconnect on first connection failure.
+    // Else try to reconnect if we're asked to. 
+
+    if (this.connectionsMade == 0) {
+        this.giveupConnectionAttempts();
+    } else if (this.maxReconnectionAttempts > 0) {
+        if (this.reconnectionAttempts++ >= this.maxReconnectionAttempts) {
+            this.giveupConnectionAttempts();
+        } else {
+            this.reconnectionDelay *= 2;
+
+            if (exports.debugMode) {
+                sys.debug("[RECONNECTING " + this.reconnectionAttempts + "/" + 
+                    this.maxReconnectionAttempts + "]");
+
+                sys.debug("[WAIT " + this.reconnectionDelay + " ms]");
+            }
+
+            var self = this;
+
+            this.reconnectionTimer = setTimeout(function () {
+                self.emit('reconnecting', self);
+                self.stream.connect(self.port, self.host);
+            }, this.reconnectionDelay);
+        }
+    }
+};
+
 // Wraps 'subscribe' and 'psubscribe' methods to manage a single
 // callback function per subscribed channel name/pattern.
 //
 // issue other commands, use a second client instance.
 
 Client.prototype.subscribeTo = function (nameOrPattern, callback) {
-    if (typeof this.channelCallbacks[nameOrPattern]  === 'function')
+    if (typeof this.channelCallbacks[nameOrPattern] === 'function')
         return;
 
     if (typeof(callback) !== 'function')
 
   var r = redis.createClient();
 
-  r.stream.addListener( 'connect', _.bind( cb, { redis : r } ) );
+  r.stream.on( 'connect', _.bind( cb, { redis : r } ) );
 
-  r.stream.addListener( "end", function(error) {
+  r.stream.on( "end", function(error) {
     if( error ) {
       process.stdio.writeError( "Error connecting to Redis database\n" );
       if( typeof(errback) === "function" )
 
 var getPostParams = function(req, callback){ 
   var body = ''; 
-  req.addListener('data', function(chunk){
+  req.on('data', function(chunk){
      body += chunk;
    }) 
-   .addListener('end', function() { 
+   .on('end', function() { 
      var obj = qs.parse(  body.replace( /\+/g, ' ' ) ) ;
      callback( obj );
    });
   getPostParams( req, function( obj ) {
       var r = redis.createClient();
 
-      r.stream.addListener( 'connect', function() {
+      r.stream.on( 'connect', function() {
         r.incr( 'nextid' , function( err, id ) {
           r.set( 'snippet:'+id, JSON.stringify( obj ), function() {
             var msg = 'The snippet has been saved at <a href="/'+id+'">'+req.headers.host+'/'+id+'</a>';
 
 var showSnippet = function( req, res, id ) {
     var r = redis.createClient();
-    r.stream.addListener( 'connect', function() {
+    r.stream.on( 'connect', function() {
       r.get( 'snippet:'+id, function( err, data ) {
         if( !data ) {
-          res.sendHeader( 404 );
+          res.writeHead( 404 );
           res.write( "No such snippet" );
           res.end();
           return;
         }
 
-        res.sendHeader( 200, { "Content-Type" : "text/html" } );
+        res.writeHead( 200, { "Content-Type" : "text/html" } );
 
         var obj = JSON.parse( data.toString() );
         var shortcode = languages.filter( function(el) { 
                             "-f", "html",
                             "-O", "full,style=pastie",
                             "-P", "title=Snippet #" + id ] );
-        pyg.stdout.addListener( "data", function( coloured ) {
+        pyg.stdout.on( "data", function( coloured ) {
           if( coloured )
             res.write( coloured );
         } );
 
-        pyg.addListener( 'exit', function() {
+        pyg.on( 'exit', function() {
           res.end();
         });
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.