From b34b40a4dfce91ca833110f370d7a9c8b2df53ca Mon Sep 17 00:00:00 2001 From: David Terei Date: Thu, 28 Apr 2016 23:23:39 -0700 Subject: [PATCH 1/3] Fix various lint issues --- lib/memjs/header.js | 13 +++++++------ lib/memjs/memjs.js | 15 ++++++++++----- lib/memjs/protocol.js | 2 ++ lib/memjs/server.js | 2 +- lib/memjs/utils.js | 12 +++++++++--- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/lib/memjs/header.js b/lib/memjs/header.js index 6602276..d2c68fd 100644 --- a/lib/memjs/header.js +++ b/lib/memjs/header.js @@ -1,4 +1,7 @@ -var fromBuffer = function(headerBuf) { +// # MemJS Memcache binary protocol header + +// fromBuffer converts a serialized header to a JS object. +exports.fromBuffer = function(headerBuf) { if (!headerBuf) { return {}; } @@ -15,7 +18,8 @@ var fromBuffer = function(headerBuf) { }; }; -var toBuffer = function(header) { +// toBuffer converts a JS memcache header object to a binary memcache header +exports.toBuffer = function(header) { var headerBuf = new Buffer(24); headerBuf.fill(); headerBuf.writeUInt8(header.magic, 0); @@ -29,11 +33,8 @@ var toBuffer = function(header) { if (header.cas) { header.cas.copy(headerBuf, 16); } else { - headerBuf.fill('\0', 16); + headerBuf.fill('\x00', 16); } return headerBuf; }; -exports.fromBuffer = fromBuffer; -exports.toBuffer = toBuffer; - diff --git a/lib/memjs/memjs.js b/lib/memjs/memjs.js index 55d21ab..2500583 100644 --- a/lib/memjs/memjs.js +++ b/lib/memjs/memjs.js @@ -335,6 +335,7 @@ Client.prototype.flush = function(callback) { var count = this.servers.length; var result = {}; var lastErr = null; + var i; var handleFlush = function(seq, serv) { serv.onResponse(seq, function(/* response */) { @@ -355,7 +356,7 @@ Client.prototype.flush = function(callback) { serv.write(seq, request); }; - for (var i in this.servers) { + for (i = 0; i < this.servers.length; i++) { handleFlush(this.seq, this.servers[i]); } }; @@ -373,6 +374,7 @@ Client.prototype.statsWithKey = function(key, callback) { this.seq++; var request = makeRequestBuffer(0x10, key, '', '', this.seq); var logger = this.options.logger; + var i; var handleStats = function(seq, serv) { var result = {}; @@ -405,7 +407,7 @@ Client.prototype.statsWithKey = function(key, callback) { serv.write(seq, request); }; - for (var i in this.servers) { + for (i = 0; i < this.servers.length; i++) { handleStats(this.seq, this.servers[i]); } }; @@ -447,6 +449,8 @@ Client.prototype.quit = function() { // TODO: Nicer perhaps to do QUITQ (0x17) but need a new callback for when // write is done. var request = makeRequestBuffer(0x07, '', '', '', this.seq); // QUIT + var serv; + var i; var handleQuit = function(seq, serv) { serv.onResponse(seq, function(/* response */) { @@ -458,8 +462,8 @@ Client.prototype.quit = function() { serv.write(seq, request); }; - for (var i in this.servers) { - var serv = this.servers[i]; + for (i = 0; i < this.servers.length; i++) { + serv = this.servers[i]; handleQuit(this.seq, serv); } }; @@ -468,7 +472,8 @@ Client.prototype.quit = function() { // // Closes (abruptly) connections to all the servers. Client.prototype.close = function() { - for (var i in this.servers) { + var i; + for (i = 0; i < this.servers.length; i++) { this.servers[i].close(); } }; diff --git a/lib/memjs/protocol.js b/lib/memjs/protocol.js index 44e8ef3..8ca370c 100644 --- a/lib/memjs/protocol.js +++ b/lib/memjs/protocol.js @@ -1,3 +1,5 @@ +// # MemJS Memcache binary protocol errors + exports.errors = {}; exports.errors[0x0000] = 'No error'; exports.errors[0x0001] = 'Key not found'; diff --git a/lib/memjs/server.js b/lib/memjs/server.js index 3a3048b..de2417b 100644 --- a/lib/memjs/server.js +++ b/lib/memjs/server.js @@ -73,7 +73,7 @@ Server.prototype.listSasl = function() { }; Server.prototype.saslAuth = function() { - var authStr = '\0' + this.username + '\0' + this.password; + var authStr = '\x00' + this.username + '\x00' + this.password; var buf = makeRequestBuffer(0x21, 'PLAIN', '', authStr); this.writeSASL(buf); }; diff --git a/lib/memjs/utils.js b/lib/memjs/utils.js index 335e8fe..dedbd60 100644 --- a/lib/memjs/utils.js +++ b/lib/memjs/utils.js @@ -1,3 +1,5 @@ +// # MemJS utility functions + var header = require('./header'); var bufferify = function(val) { @@ -54,13 +56,17 @@ exports.parseMessage = function(dataBuf) { return false; } var responseHeader = header.fromBuffer(dataBuf); - if (dataBuf.length < responseHeader.totalBodyLength + 24 || responseHeader.totalBodyLength < responseHeader.keyLength + responseHeader.extrasLength) { + if (dataBuf.length < responseHeader.totalBodyLength + 24 || + responseHeader.totalBodyLength < + responseHeader.keyLength + responseHeader.extrasLength) { return false; } var pointer = 24; - var extras = dataBuf.slice(pointer, (pointer += responseHeader.extrasLength)); - var key = dataBuf.slice(pointer, (pointer += responseHeader.keyLength)); + var extras = dataBuf.slice(pointer, pointer + responseHeader.extrasLength); + pointer += responseHeader.extrasLength; + var key = dataBuf.slice(pointer, pointer + responseHeader.keyLength); + pointer += responseHeader.keyLength; var val = dataBuf.slice(pointer, 24 + responseHeader.totalBodyLength); return {header: responseHeader, key: key, extras: extras, val: val}; From d714e7b53161d7c46e258f7ea74eef459dfff8b9 Mon Sep 17 00:00:00 2001 From: David Terei Date: Thu, 28 Apr 2016 23:25:30 -0700 Subject: [PATCH 2/3] Add support for append and prepend operations --- README.md | 1 - lib/memjs/memjs.js | 64 ++++++++++++++++++++++ test/client_test.js | 126 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 167 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7f98381..d54fc99 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,6 @@ can also implement a feature not a list if you think it would be good. ### TODOS ### * Support flags -* Support prepend, append * Support multi commands * Support touch * Support CAS diff --git a/lib/memjs/memjs.js b/lib/memjs/memjs.js index 2500583..dd8b262 100644 --- a/lib/memjs/memjs.js +++ b/lib/memjs/memjs.js @@ -320,6 +320,70 @@ Client.prototype.decrement = function(key, amount, callback, expires, initial) { }); }; +// APPEND +// +// Append the given _value_ to the value associated with the given _key_ in +// memcache. The operation only succeeds if the key is already present. The +// callback signature is: +// +// callback(err, success) +Client.prototype.append = function(key, value, callback) { + this.seq++; + var request = makeRequestBuffer(0x0E, key, '', value, this.seq); + + var logger = this.options.logger; + this.perform(key, request, function(err, response) { + if (err) { + if (callback) { callback(err, null); } + return; + } + switch (response.header.status) { + case 0: + if (callback) { callback(null, true); } + break; + case 1: + if (callback) { callback(null, false); } + break; + default: + var errorMessage = 'MemJS APPEND: ' + errors[response.header.status]; + logger.log(errorMessage); + if (callback) { callback(new Error(errorMessage), null); } + } + }); +}; + +// PREPEND +// +// Prepend the given _value_ to the value associated with the given _key_ in +// memcache. The operation only succeeds if the key is already present. The +// callback signature is: +// +// callback(err, success) +Client.prototype.prepend = function(key, value, callback) { + this.seq++; + var request = makeRequestBuffer(0x0E, key, '', value, this.seq); + + var logger = this.options.logger; + this.perform(key, request, function(err, response) { + if (err) { + if (callback) { callback(err, null); } + return; + } + switch (response.header.status) { + case 0: + if (callback) { callback(null, true); } + break; + case 1: + if (callback) { callback(null, false); } + break; + default: + var errorMessage = 'MemJS PREPEND: ' + errors[response.header.status]; + logger.log(errorMessage); + if (callback) { callback(new Error(errorMessage), null); } + } + }); +}; + // FLUSH // // Flushes the cache on each connected server. The callback signature is: diff --git a/test/client_test.js b/test/client_test.js index 4d496c9..fb4b3a2 100644 --- a/test/client_test.js +++ b/test/client_test.js @@ -19,7 +19,7 @@ test('GetSuccessful', function(t) { t.equal('world', val); t.equal('flagshere', flags); t.equal(null, err); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure get is called'); t.end(); }); }); @@ -39,7 +39,7 @@ test('GetNotFound', function(t) { client.get('hello', function(val, flags) { t.equal(null, val); t.equal(null, flags); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure get is called'); t.end(); }); }); @@ -59,7 +59,7 @@ test('SetSuccessful', function(t) { client.set('hello', 'world', function(err, val) { t.equal(true, val); t.equal(null, err); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure set is called'); t.end(); }); }); @@ -80,7 +80,7 @@ test('SetWithExpiration', function(t) { client.set('hello', 'world', function(err, val) { t.equal(null, err); t.equal(true, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure set is called'); t.end(); }); }); @@ -100,7 +100,7 @@ test('SetUnsuccessful', function(t) { client.set('hello', 'world', function(err, val) { t.equal(null, val); t.equal('MemJS SET: ' + errors[3], err.message); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure set is called'); t.end(); }); }); @@ -121,7 +121,7 @@ test('SetError', function(t) { t.notEqual(null, err); t.equal('This is an expected error.', err.message); t.equal(null, val); - t.equal(2, n, 'Ensure set is retried once'); + t.equal(2, n, 'Ensure set is retried once'); t.end(); }); }); @@ -180,13 +180,13 @@ test('SetErrorConcurrent', function(t) { return function() { called += 1; if (called < 2) return; - t.equal(2, n, 'Ensure error is sent'); - t.equal(1, callbn1, 'Ensure callback 1 is called once'); - t.equal(1, callbn2, 'Ensure callback 2 is called once'); + t.equal(2, n, 'Ensure error is sent'); + t.equal(1, callbn1, 'Ensure callback 1 is called once'); + t.equal(1, callbn2, 'Ensure callback 2 is called once'); process.nextTick(function() { - t.equal(1, callbn1, 'Ensure callback 1 is called once'); - t.equal(1, callbn2, 'Ensure callback 2 is called once'); - t.equal(4, n, 'Ensure error sent again'); + t.equal(1, callbn1, 'Ensure callback 1 is called once'); + t.equal(1, callbn2, 'Ensure callback 2 is called once'); + t.equal(4, n, 'Ensure error sent again'); t.end(); }); }; @@ -207,7 +207,7 @@ test('SetUnicode', function(t) { var client = new MemJS.Client([dummyServer]); client.set('hello', 'éééoào', function(err, val) { t.equal(true, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure set is called'); t.end(); }); }); @@ -228,7 +228,7 @@ test('AddSuccessful', function(t) { client.add('hello', 'world', function(err, val) { t.equal(null, err); t.equal(true, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure add is called'); t.end(); }); }); @@ -248,7 +248,7 @@ test('AddKeyExists', function(t) { client.add('hello', 'world', function(err, val) { t.equal(null, err); t.equal(false, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure add is called'); t.end(); }); }); @@ -269,7 +269,7 @@ test('ReplaceSuccessful', function(t) { client.replace('hello', 'world', function(err, val) { t.equal(null, err); t.equal(true, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure replace is called'); t.end(); }); }); @@ -289,7 +289,7 @@ test('ReplaceKeyDNE', function(t) { client.replace('hello', 'world', function(err, val) { t.equal(null, err); t.equal(false, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure replace is called'); t.end(); }); }); @@ -308,7 +308,7 @@ test('DeleteSuccessful', function(t) { client.delete('hello', function(err, val) { t.equal(null, err); t.equal(true, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure delete is called'); t.end(); }); }); @@ -327,7 +327,7 @@ test('DeleteKeyDNE', function(t) { client.delete('hello', function(err, val) { t.equal(null, err); t.equal(false, val); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure delete is called'); t.end(); }); }); @@ -378,7 +378,7 @@ test('Stats', function(t) { t.equal('1432', stats.bytes); t.equal('5432', stats.count); t.equal('myhostname:5544', server); - t.equal(1, n, 'Ensure set is called'); + t.equal(1, n, 'Ensure stats is called'); t.end(); }); }); @@ -430,8 +430,8 @@ test('IncrementSuccessful', function(t) { return function() { called += 1; if (called < 2) return; - t.equal(2, n, 'Ensure increment is called twice'); - t.equal(2, callbn, 'Ensure callback is called twice'); + t.equal(2, n, 'Ensure increment is called twice'); + t.equal(2, callbn, 'Ensure callback is called twice'); t.end(); }; })(); @@ -455,7 +455,87 @@ test('DecrementSuccessful', function(t) { client.decrement('number-decrement-test', 5, function(err, val){ t.equal(true, val); t.equal(null, err); - t.equal(1, n, 'Ensure decr is called'); + t.equal(1, n, 'Ensure decr is called'); + t.end(); + }); +}); + +test('AppendSuccessful', function(t) { + var n = 0; + var dummyServer = new MemJS.Server(); + dummyServer.write = function(seq, requestBuf) { + var request = MemJS.Utils.parseMessage(requestBuf); + t.equal('hello', request.key.toString()); + t.equal('world', request.val.toString()); + n += 1; + dummyServer.respond({header: {status: 0, opaque: request.header.opaque}}); + }; + + var client = new MemJS.Client([dummyServer], {expires: 1024}); + client.append('hello', 'world', function(err, val) { + t.equal(null, err); + t.equal(true, val); + t.equal(1, n, 'Ensure append is called'); + t.end(); + }); +}); + +test('AppendKeyDNE', function(t) { + var n = 0; + var dummyServer = new MemJS.Server(); + dummyServer.write = function(seq, requestBuf) { + var request = MemJS.Utils.parseMessage(requestBuf); + t.equal('hello', request.key.toString()); + t.equal('world', request.val.toString()); + n += 1; + dummyServer.respond({header: {status: 1, opaque: request.header.opaque}}); + }; + + var client = new MemJS.Client([dummyServer]); + client.append('hello', 'world', function(err, val) { + t.equal(null, err); + t.equal(false, val); + t.equal(1, n, 'Ensure append is called'); + t.end(); + }); +}); + +test('PrependSuccessful', function(t) { + var n = 0; + var dummyServer = new MemJS.Server(); + dummyServer.write = function(seq, requestBuf) { + var request = MemJS.Utils.parseMessage(requestBuf); + t.equal('hello', request.key.toString()); + t.equal('world', request.val.toString()); + n += 1; + dummyServer.respond({header: {status: 0, opaque: request.header.opaque}}); + }; + + var client = new MemJS.Client([dummyServer], {expires: 1024}); + client.prepend('hello', 'world', function(err, val) { + t.equal(null, err); + t.equal(true, val); + t.equal(1, n, 'Ensure prepend is called'); + t.end(); + }); +}); + +test('PrependKeyDNE', function(t) { + var n = 0; + var dummyServer = new MemJS.Server(); + dummyServer.write = function(seq, requestBuf) { + var request = MemJS.Utils.parseMessage(requestBuf); + t.equal('hello', request.key.toString()); + t.equal('world', request.val.toString()); + n += 1; + dummyServer.respond({header: {status: 1, opaque: request.header.opaque}}); + }; + + var client = new MemJS.Client([dummyServer]); + client.prepend('hello', 'world', function(err, val) { + t.equal(null, err); + t.equal(false, val); + t.equal(1, n, 'Ensure prepend is called'); t.end(); }); }); From 13de9b5d45120aac0739ef6b232c996c799ca722 Mon Sep 17 00:00:00 2001 From: David Terei Date: Thu, 28 Apr 2016 23:51:04 -0700 Subject: [PATCH 3/3] Add support for touch --- README.md | 1 - lib/memjs/memjs.js | 33 +++++++++++++++++++++++++++++++++ test/client_test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d54fc99..1188a96 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,6 @@ can also implement a feature not a list if you think it would be good. * Support flags * Support multi commands -* Support touch * Support CAS * Consistent hashing for keys and/or pluggable hashing algorithm diff --git a/lib/memjs/memjs.js b/lib/memjs/memjs.js index dd8b262..947d4a6 100644 --- a/lib/memjs/memjs.js +++ b/lib/memjs/memjs.js @@ -384,6 +384,39 @@ Client.prototype.prepend = function(key, value, callback) { }); }; +// TOUCH +// +// Touch sets an expiration value, given by _expires_, on the given _key_ in +// memcache. The operation only succeeds if the key is already present. The +// callback signature is: +// +// callback(err, success) +Client.prototype.touch = function(key, expires, callback) { + this.seq++; + var extras = makeExpiration(expires || this.options.expires); + var request = makeRequestBuffer(0x1C, key, extras, '', this.seq); + + var logger = this.options.logger; + this.perform(key, request, function(err, response) { + if (err) { + if (callback) { callback(err, null); } + return; + } + switch (response.header.status) { + case 0: + if (callback) { callback(null, true); } + break; + case 1: + if (callback) { callback(null, false); } + break; + default: + var errorMessage = 'MemJS TOUCH: ' + errors[response.header.status]; + logger.log(errorMessage); + if (callback) { callback(new Error(errorMessage), null); } + } + }); +}; + // FLUSH // // Flushes the cache on each connected server. The callback signature is: diff --git a/test/client_test.js b/test/client_test.js index fb4b3a2..fa2dafc 100644 --- a/test/client_test.js +++ b/test/client_test.js @@ -540,6 +540,48 @@ test('PrependKeyDNE', function(t) { }); }); +test('TouchSuccessful', function(t) { + var n = 0; + var dummyServer = new MemJS.Server(); + dummyServer.write = function(seq, requestBuf) { + var request = MemJS.Utils.parseMessage(requestBuf); + t.equal('hello', request.key.toString()); + t.equal('', request.val.toString()); + t.equal('\0\0\4\0', request.extras.toString()); + n += 1; + dummyServer.respond({header: {status: 0, opaque: request.header.opaque}}); + }; + + var client = new MemJS.Client([dummyServer]); + client.touch('hello', 1024, function(err, val) { + t.equal(null, err); + t.equal(true, val); + t.equal(1, n, 'Ensure touch is called'); + t.end(); + }); +}); + +test('TouchKeyDNE', function(t) { + var n = 0; + var dummyServer = new MemJS.Server(); + dummyServer.write = function(seq, requestBuf) { + var request = MemJS.Utils.parseMessage(requestBuf); + t.equal('hello', request.key.toString()); + t.equal('', request.val.toString()); + t.equal('\0\0\4\0', request.extras.toString()); + n += 1; + dummyServer.respond({header: {status: 1, opaque: request.header.opaque}}); + }; + + var client = new MemJS.Client([dummyServer]); + client.touch('hello', 1024, function(err, val) { + t.equal(null, err); + t.equal(false, val); + t.equal(1, n, 'Ensure ptouch is called'); + t.end(); + }); +}); + test('Failover', function(t) { var n1 = 0; var n2 = 0;