Skip to content

Commit 536f88e

Browse files
committed
send_command_buf and b_ prefixed commands
1 parent a1755b9 commit 536f88e

File tree

8 files changed

+570
-115
lines changed

8 files changed

+570
-115
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ client.get("missingkey", function(err, reply) {
109109
});
110110
```
111111

112+
If you want receive reply as Buffers instead of Strings just add "b_" prefix before command name:
113+
114+
```js
115+
client.b_get("some key", function(err, buf) {});
116+
```
117+
112118
For a list of Redis commands, see [Redis Command Reference](http://redis.io/commands)
113119

114120
Minimal parsing is done on the replies. Commands that return a integer return JavaScript Numbers, arrays return JavaScript Array. `HGETALL` returns an Object keyed by the hash keys. All strings will either be returned as string or as buffer depending on your setting.
@@ -666,6 +672,10 @@ The command_name has to be lower case.
666672

667673
All commands are sent as multi-bulk commands. `args` can either be an Array of arguments, or omitted / set to undefined.
668674

675+
## client.send_command_buf(command_name[, [args][, callback]])
676+
677+
The same as client.send_command but sending reply as Buffers instead of Strings.
678+
669679
## client.connected
670680

671681
Boolean tracking the state of the connection to the Redis server.

index.js

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,6 @@ if (typeof EventEmitter !== 'function') {
2727

2828
function noop () {}
2929

30-
function handle_detect_buffers_reply (reply, command, buffer_args) {
31-
if (buffer_args === false || this.message_buffers) {
32-
// If detect_buffers option was specified, then the reply from the parser will be a buffer.
33-
// If this command did not use Buffer arguments, then convert the reply to Strings here.
34-
reply = utils.reply_to_strings(reply);
35-
}
36-
37-
if (command === 'hgetall') {
38-
reply = utils.reply_to_object(reply);
39-
}
40-
return reply;
41-
}
42-
4330
exports.debug_mode = /\bredis\b/i.test(process.env.NODE_DEBUG);
4431

4532
// Attention: The second parameter might be removed at will and is not officially supported.
@@ -112,10 +99,6 @@ function RedisClient (options, stream) {
11299
self.warn('WARNING: You activated return_buffers and detect_buffers at the same time. The return value is always going to be a buffer.');
113100
options.detect_buffers = false;
114101
}
115-
if (options.detect_buffers) {
116-
// We only need to look at the arguments if we do not know what we have to return
117-
this.handle_reply = handle_detect_buffers_reply;
118-
}
119102
this.should_buffer = false;
120103
this.max_attempts = options.max_attempts | 0;
121104
if ('max_attempts' in options) {
@@ -144,18 +127,19 @@ function RedisClient (options, stream) {
144127
this.pub_sub_mode = 0;
145128
this.subscription_set = {};
146129
this.monitoring = false;
147-
this.message_buffers = false;
130+
this.message_buffers = false; // Do we have subscribes on message_buffer event
148131
this.closing = false;
149132
this.server_info = {};
150133
this.auth_pass = options.auth_pass || options.password;
151134
this.selected_db = options.db; // Save the selected db here, used when reconnecting
152135
this.old_state = null;
153136
this.fire_strings = true; // Determine if strings or buffers should be written to the stream
137+
this.cur_command_ret_buf = 0;
154138
this.pipeline = false;
155139
this.sub_commands_left = 0;
156140
this.times_connected = 0;
157141
this.options = options;
158-
this.buffers = options.return_buffers || options.detect_buffers;
142+
this.using_buffer_parser = options.return_buffers || options.detect_buffers;
159143
this.reply = 'ON'; // Returning replies is the default
160144
// Init parser
161145
this.reply_parser = create_parser(this, options);
@@ -172,17 +156,23 @@ function RedisClient (options, stream) {
172156
'The drain event listener is deprecated and will be removed in v.3.0.0.\n' +
173157
'If you want to keep on listening to this event please listen to the stream drain event directly.'
174158
);
175-
} else if (event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer' && !this.buffers) {
159+
} else if (event === 'message_buffer' || event === 'pmessage_buffer' || event === 'messageBuffer' || event === 'pmessageBuffer') {
176160
this.message_buffers = true;
177-
this.handle_reply = handle_detect_buffers_reply;
178-
this.reply_parser = create_parser(this);
161+
if (!this.using_buffer_parser) {
162+
this.switchToBufferParser();
163+
}
179164
}
180165
});
181166
}
182167
util.inherits(RedisClient, EventEmitter);
183168

184169
RedisClient.connection_id = 0;
185170

171+
RedisClient.prototype.switchToBufferParser = function() {
172+
this.using_buffer_parser = true;
173+
this.reply_parser = create_parser(this);
174+
}
175+
186176
function create_parser (self) {
187177
return Parser({
188178
returnReply: function (data) {
@@ -208,7 +198,7 @@ function create_parser (self) {
208198
self.emit('error', err);
209199
self.create_stream();
210200
},
211-
returnBuffers: self.buffers || self.message_buffers,
201+
returnBuffers: self.using_buffer_parser,
212202
name: self.options.parser,
213203
stringNumbers: self.options.string_numbers
214204
});
@@ -304,7 +294,15 @@ RedisClient.prototype.create_stream = function () {
304294
}
305295
};
306296

307-
RedisClient.prototype.handle_reply = function (reply, command) {
297+
RedisClient.prototype.handle_reply = function (reply, command, buffer_reply) {
298+
if (!buffer_reply && this.using_buffer_parser) {
299+
// Reply from parser will be Buffer if:
300+
// 1) return_buffers option set to true
301+
// 2) or detect_buffers option set to true and command used Buffer arguments
302+
// 3) or buffer_reply argument was set to true when calling internal_send_command
303+
reply = utils.reply_to_strings(reply);
304+
}
305+
308306
if (command === 'hgetall') {
309307
reply = utils.reply_to_object(reply);
310308
}
@@ -531,7 +529,7 @@ RedisClient.prototype.ready_check = function () {
531529
RedisClient.prototype.send_offline_queue = function () {
532530
for (var command_obj = this.offline_queue.shift(); command_obj; command_obj = this.offline_queue.shift()) {
533531
debug('Sending offline command: ' + command_obj.command);
534-
this.internal_send_command(command_obj.command, command_obj.args, command_obj.callback, command_obj.call_on_write);
532+
this.internal_send_command(command_obj.command, command_obj.args, command_obj.callback, command_obj.call_on_write, command_obj.buffer_reply);
535533
}
536534
this.drain();
537535
};
@@ -716,7 +714,7 @@ function normal_reply (self, reply) {
716714
var command_obj = self.command_queue.shift();
717715
if (typeof command_obj.callback === 'function') {
718716
if (command_obj.command !== 'exec') {
719-
reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
717+
reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_reply);
720718
}
721719
command_obj.callback(null, reply);
722720
} else {
@@ -728,7 +726,7 @@ function subscribe_unsubscribe (self, reply, type) {
728726
// Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
729727
// The pub sub commands return each argument in a separate return value and have to be handled that way
730728
var command_obj = self.command_queue.get(0);
731-
var buffer = self.options.return_buffers || self.options.detect_buffers && command_obj.buffer_args;
729+
var buffer = command_obj.buffer_reply;
732730
var channel = (buffer || reply[1] === null) ? reply[1] : reply[1].toString();
733731
var count = +reply[2]; // Return the channel counter as number no matter if `string_numbers` is activated or not
734732
debug(type, channel);
@@ -803,7 +801,7 @@ RedisClient.prototype.return_reply = function (reply) {
803801
// the average performance of all other commands in case of no monitor mode
804802
if (this.monitoring) {
805803
var replyStr;
806-
if (this.buffers && Buffer.isBuffer(reply)) {
804+
if (this.using_buffer_parser && Buffer.isBuffer(reply)) {
807805
replyStr = reply.toString();
808806
} else {
809807
replyStr = reply;
@@ -864,9 +862,17 @@ function handle_offline_command (self, command_obj) {
864862
self.should_buffer = true;
865863
}
866864

865+
RedisClient.prototype.internal_send_command_buf = function (command, args, callback, call_on_write) {
866+
return this.internal_send_command(command, args, callback, call_on_write, true);
867+
};
868+
867869
// Do not call internal_send_command directly, if you are not absolutly certain it handles everything properly
868870
// e.g. monitor / info does not work with internal_send_command only
869-
RedisClient.prototype.internal_send_command = function (command, args, callback, call_on_write) {
871+
RedisClient.prototype.internal_send_command = function (command, args, callback, call_on_write, buffer_reply) {
872+
if (this.cur_command_ret_buf) {
873+
buffer_reply = true; //needed for send_command_buf and individual commands
874+
}
875+
870876
var arg, prefix_keys, command_obj;
871877
var i = 0;
872878
var command_str = '';
@@ -881,10 +887,14 @@ RedisClient.prototype.internal_send_command = function (command, args, callback,
881887

882888
if (this.ready === false || this.stream.writable === false) {
883889
// Handle offline commands right away
884-
handle_offline_command(this, new OfflineCommand(command, args, callback, call_on_write));
890+
handle_offline_command(this, new OfflineCommand(command, args, callback, call_on_write, buffer_reply));
885891
return false; // Indicate buffering
886892
}
887893

894+
if (buffer_reply && !this.using_buffer_parser) {
895+
this.switchToBufferParser();
896+
}
897+
888898
for (i = 0; i < len; i += 1) {
889899
if (typeof args[i] === 'string') {
890900
// 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
@@ -929,7 +939,7 @@ RedisClient.prototype.internal_send_command = function (command, args, callback,
929939
}
930940
}
931941
// Pass the original args to make sure in error cases the original arguments are returned
932-
command_obj = new Command(command, args, buffer_args, callback);
942+
command_obj = new Command(command, args, this.options.return_buffers || buffer_args && this.options.detect_buffers || buffer_reply, callback);
933943

934944
if (this.options.prefix) {
935945
prefix_keys = commands.getKeyIndexes(command, args_copy);

lib/command.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
// This Command constructor is ever so slightly faster than using an object literal, but more importantly, using
44
// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
5-
function Command (command, args, buffer_args, callback) {
5+
function Command (command, args, buffer_reply, callback) {
66
this.command = command;
77
this.args = args;
8-
this.buffer_args = buffer_args;
8+
this.buffer_reply = buffer_reply;
99
this.callback = callback;
1010
}
1111

12-
function OfflineCommand (command, args, callback, call_on_write) {
12+
function OfflineCommand (command, args, callback, call_on_write, buffer_reply) {
1313
this.command = command;
1414
this.args = args;
1515
this.callback = callback;
1616
this.call_on_write = call_on_write;
17+
this.buffer_reply = buffer_reply;
1718
}
1819

1920
module.exports = {

0 commit comments

Comments
 (0)