diff --git a/README.md b/README.md index de299fe..2e2ce39 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,13 @@ objects instead of JavaScript Strings. `createClient()` returns a `RedisClient` object that is named `client` in all of the examples here. +## client.auth(password, callback) + +When connecting to Redis servers that require authentication, the `AUTH` command must be sent as the +first command after connecting. This can be tricky to coordinate with reconnections, the ready check, +etc. To make this easier, `client.auth()` stashes `password` and will send it after each connection, +including reconnections. `callback` is invoked only once, after the response to the very first +`AUTH` command sent. ## client.end() diff --git a/changelog.md b/changelog.md index 671e046..b3d7da3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,13 @@ Changelog ========= +## v0.5.7 - February 27, 2011 + +Add magical auth command. + +Authentication is now remembered by the client and will be automatically sent to the server +on every connection, including any reconnections. + ## v0.5.6 - February 22, 2011 Fix bug in ready check with `return_buffers` set to `true`. diff --git a/examples/auth.js b/examples/auth.js index 06b9058..6c0a563 100644 --- a/examples/auth.js +++ b/examples/auth.js @@ -1,13 +1,5 @@ -// Note - Eventually this functionality will be built in to the client library - var redis = require("redis"), client = redis.createClient(); -// whenever the client connects, make sure to auth -client.on("connect", function () { - client.auth("somepass", redis.print); -}); - +// This command is magical. Client stashes the password and will issue on every connect. client.auth("somepass"); - -// then do whatever you want diff --git a/examples/pub_sub.js b/examples/pub_sub.js index 595cae6..aa508d6 100644 --- a/examples/pub_sub.js +++ b/examples/pub_sub.js @@ -30,10 +30,9 @@ client1.on("message", function (channel, message) { } }); -client1.incr("did a thing"); - client1.on("ready", function () { // if you need auth, do it here + client1.incr("did a thing"); client1.subscribe("a nice channel", "another one"); }); diff --git a/index.js b/index.js index 4df8beb..bbb19a6 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,7 @@ function RedisClient(stream, options) { this.subscriptions = false; this.closing = false; this.server_info = {}; + this.auth_pass = null; var parser_module, self = this; @@ -90,30 +91,9 @@ function RedisClient(stream, options) { }); this.stream.on("connect", function () { - if (exports.debug_mode) { - console.log("Stream connected fd " + self.stream.fd); - } - self.connected = true; - self.ready = false; - self.connections += 1; - self.command_queue = new Queue(); - self.emitted_end = false; - - self.retry_timer = null; - self.retry_delay = 250; - self.stream.setNoDelay(); - self.stream.setTimeout(0); - - self.emit("connect"); - - if (self.options.no_ready_check) { - self.ready = true; - self.send_offline_queue(); - } else { - self.ready_check(); - } + self.on_connect(); }); - + this.stream.on("data", function (buffer_from_socket) { self.on_data(buffer_from_socket); }); @@ -164,6 +144,55 @@ function RedisClient(stream, options) { util.inherits(RedisClient, events.EventEmitter); exports.RedisClient = RedisClient; +RedisClient.prototype.on_connect = function () { + if (exports.debug_mode) { + console.log("Stream connected " + this.host + ":" + this.port + " fd " + this.stream.fd); + } + var self = this; + + this.connected = true; + this.ready = false; + this.connections += 1; + this.command_queue = new Queue(); + this.emitted_end = false; + this.retry_timer = null; + this.retry_delay = 250; + this.stream.setNoDelay(); + this.stream.setTimeout(0); + + if (this.auth_pass) { + if (exports.debug_mode) { + console.log("Sending auth to " + this.host + ":" + this.port + " fd " + this.stream.fd); + } + self.send_anyway = true; + self.send_command("auth", this.auth_pass, function (err, res) { + if (err) { + return self.emit("error", "Auth error: " + err); + } + if (res.toString() !== "OK") { + return self.emit("error", "Auth failed: " + res.toString()); + } + if (exports.debug_mode) { + console.log("Auth succeeded " + self.host + ":" + self.port + " fd " + self.stream.fd); + } + if (self.auth_callback) { + self.auth_callback(err, res); + self.auth_callback = null; + } + }); + self.send_anyway = false; + } + + this.emit("connect"); + + if (this.options.no_ready_check) { + this.ready = true; + this.send_offline_queue(); + } else { + this.ready_check(); + } +}; + RedisClient.prototype.ready_check = function () { var self = this; @@ -175,8 +204,7 @@ RedisClient.prototype.ready_check = function () { self.send_anyway = true; // secret flag to send_command to send something even if not "ready" self.info(function (err, res) { if (err) { - self.emit("error", "Ready check failed: " + err); - return; + return self.emit("error", "Ready check failed: " + err); } var lines = res.toString().split("\r\n"), obj = {}, retry_time; @@ -291,7 +319,7 @@ RedisClient.prototype.connection_gone = function (why) { RedisClient.prototype.on_data = function (data) { if (exports.debug_mode) { - console.log("net read fd " + this.stream.fd + ": " + data.toString()); + console.log("net read " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + data.toString()); } try { @@ -480,7 +508,7 @@ RedisClient.prototype.send_command = function () { command_str += "$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"; } if (exports.debug_mode) { - console.log("send fd " + this.stream.fd + ": " + command_str); + console.log("send " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + command_str); } stream.write(command_str); } else { @@ -547,7 +575,7 @@ function Multi(client, args) { //bit commands "getbit", "setbit", "getrange", "setrange", // misc - "getset", "mset", "msetnx", "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "auth", "ping", "echo", + "getset", "mset", "msetnx", "randomkey", "select", "move", "rename", "renamenx", "expire", "expireat", "keys", "dbsize", "ping", "echo", "save", "bgsave", "bgwriteaof", "shutdown", "lastsave", "type", "sync", "flushdb", "flushall", "sort", "info", "monitor", "ttl", "persist", "slaveof", "debug", "config", "subscribe", "unsubscribe", "psubscribe", "punsubscribe", "publish", "watch", "unwatch", "quit" @@ -568,6 +596,22 @@ function Multi(client, args) { Multi.prototype[command.toUpperCase()] = Multi.prototype[command]; }); +// Stash auth for connect and reconnect. Send immediately if already connected. +RedisClient.prototype.auth = function () { + var args = to_array(arguments); + this.auth_pass = args[0]; + this.auth_callback = args[1]; + if (exports.debug_mode) { + console.log("Saving auth as " + this.auth_pass); + } + + if (this.connected) { + args.unshift("auth"); + this.send_command.apply(this, args); + } +}; +RedisClient.prototype.AUTH = RedisClient.prototype.auth; + RedisClient.prototype.hmset = function () { var args = to_array(arguments), tmp_args; if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") { diff --git a/package.json b/package.json index 03db43b..4d74363 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "name" : "redis", - "version" : "0.5.6", + "version" : "0.5.7", "description" : "Redis client library", "author": "Matt Ranney ", "contributors": [ diff --git a/test.js b/test.js index b6bb734..7147f0f 100644 --- a/test.js +++ b/test.js @@ -3,6 +3,7 @@ var redis = require("./index"), client = redis.createClient(), client2 = redis.createClient(), client3 = redis.createClient(), + client4 = redis.createClient(9006, "filefish.redistogo.com"), assert = require("assert"), util = require("./lib/util").util, test_db_num = 15, // this DB will be flushed and used for testing @@ -1049,6 +1050,7 @@ function run_next_test() { console.log('\n completed \x1b[32m%d\x1b[0m tests in \x1b[33m%d\x1b[0m ms\n', test_count, new Date() - all_start); client.quit(); client2.quit(); + client4.quit(); } } @@ -1066,6 +1068,14 @@ client.on('end', function () { ended = true; }); +// TODO - need a better way to test auth, maybe auto-config a local Redis server? +client4.auth("664b1b6aaf134e1ec281945a8de702a9", function (err, res) { + if (err) { + assert.fail(err, name); + } + assert.strictEqual("OK", res.toString(), "auth"); +}); + // Exit immediately on connection failure, which triggers "exit", below, which fails the test client.on("error", function (err) { console.error("client: " + err.stack);