├── .gitignore ├── index.js ├── Makefile ├── package.json ├── example.js ├── README.md ├── test └── test-memcache.js └── lib └── memcache.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.swp 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/memcache'); -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE = node 2 | TEST = expresso 3 | TESTS ?= test/*.js 4 | 5 | test: 6 | @CONNECT_ENV=test $(TEST) \ 7 | -I lib \ 8 | $(TEST_FLAGS) $(TESTS) 9 | 10 | test-cov: 11 | @$(MAKE) test TEST_FLAGS="--cov" 12 | 13 | .PHONY: test test-cov 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memcache", 3 | "description": "simple memcache client", 4 | "version": "0.3.0", 5 | "homepage": "https://github.com/elbart/node-memcache", 6 | "repository": "git://github.com/elbart/node-memcache.git", 7 | "author": "Tim Eggert ", 8 | "main": "./lib/memcache", 9 | "devDependencies": { 10 | "expresso": ">=0.7.0" 11 | }, 12 | "directories": { 13 | "lib": "lib", 14 | "test": "test" 15 | }, 16 | "scripts": { 17 | "test": "make test", 18 | "test-cov": "make test-cov" 19 | }, 20 | "engines": { 21 | "node": ">=0.6.0" 22 | }, 23 | "keywords": [ "memcache", "memcached" ] 24 | } 25 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var memcache = require('./lib/memcache'); 3 | 4 | function microtime(get_as_float) { 5 | var now = new Date().getTime() / 1000; 6 | var s = parseInt(now); 7 | return (get_as_float) ? now : (Math.round((now - s) * 1000) / 1000) + ' ' + s; 8 | } 9 | 10 | var onConnect = function() { 11 | mcClient.get('test', function(err, data) { 12 | util.debug(data); 13 | mcClient.close(); 14 | }); 15 | }; 16 | 17 | var benchmark = function() { 18 | var count = 20000; 19 | start = microtime(true); 20 | var x = 0; 21 | 22 | for (var i=0; i<=count; i++) { 23 | mcClient.get('test', function(err, data) { 24 | x += 1; 25 | if (x == count) { 26 | end = microtime(true); 27 | util.debug('total time: ' + (end - start)); 28 | } 29 | }); 30 | } 31 | 32 | mcClient.close(); 33 | }; 34 | 35 | var setKey = function() { 36 | mcClient.set('test', 'hello \r\n node-memcache', function(err, response) { 37 | mcClient.get('test', function(err, data) { 38 | util.debug(data); 39 | mcClient.close(); 40 | }); 41 | }); 42 | }; 43 | 44 | var version = function() { 45 | mcClient.version(function(err, version) { 46 | util.debug(version); 47 | mcClient.close(); 48 | }); 49 | }; 50 | 51 | var incr = function() { 52 | mcClient.increment('x', 2, function(err, new_value) { 53 | util.debug(new_value); 54 | mcClient.close(); 55 | }); 56 | }; 57 | 58 | var decr = function() { 59 | mcClient.decrement('x', 1, function(err, new_value) { 60 | util.debug(new_value); 61 | mcClient.close(); 62 | }); 63 | }; 64 | 65 | mcClient = new memcache.Client(); 66 | mcClient.connect(); 67 | mcClient.addHandler(onConnect); 68 | 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node.js memcached client 2 | ======================== 3 | 4 | A pure-JavaScript memcached library for node. 5 | 6 | 7 | Tests 8 | ----- 9 | 10 | To run the test suite, first insall expresso, 11 | then run make test. 12 | 13 | If you have node-jscoverage you can 14 | also make test-cov for coverage, but that's pretty nerdy. 15 | 16 | 17 | Usage 18 | ----- 19 | 20 | Create a Client object to start working. 21 | Host and port can be passed to the constructor or set afterwards. 22 | They have sensible defaults. 23 | 24 | var memcache = require('./memcache'); 25 | 26 | var client = new memcache.Client(port, host); 27 | client.port = 11211; 28 | client.host = 'localhost'; 29 | 30 | The Client object emits 4 important events - connect, close, timeout and error. 31 | 32 | client.on('connect', function(){ 33 | // no arguments - we've connected 34 | }); 35 | 36 | client.on('close', function(){ 37 | // no arguments - connection has been closed 38 | }); 39 | 40 | client.on('timeout', function(){ 41 | // no arguments - socket timed out 42 | }); 43 | 44 | client.on('error', function(e){ 45 | // there was an error - exception is 1st argument 46 | }); 47 | 48 | // connect to the memcache server after subscribing to some or all of these events 49 | client.connect() 50 | 51 | After connecting, you can start to make requests. 52 | 53 | client.get('key', function(error, result){ 54 | 55 | // all of the callbacks have two arguments. 56 | // 'result' may contain things which aren't great, but 57 | // aren't really errors, like 'NOT_STORED' 58 | 59 | }); 60 | 61 | client.set('key', 'value', function(error, result){ 62 | 63 | // lifetime is optional. the default is 64 | // to never expire (0) 65 | 66 | }, lifetime); 67 | 68 | client.delete('key', function(error, result){ 69 | 70 | // delete a key from cache. 71 | }); 72 | 73 | client.version(function(error, result)){ 74 | 75 | // grab the server version 76 | }); 77 | 78 | 79 | There are all the commands you would expect. 80 | 81 | // all of the different "store" operations 82 | // (lifetime & flags are both optional) 83 | client.set(key, value, callback, lifetime, flags); 84 | client.add(key, value, callback, lifetime, flags); 85 | client.replace(key, value, callback, lifetime, flags); 86 | client.append(key, value, callback, lifetime, flags); 87 | client.prepend(key, value, callback, lifetime, flags); 88 | client.cas(key, value, unique, callback, lifetime, flags); 89 | 90 | // increment and decrement (named differently to the server commands - for now!) 91 | // (value is optional, defaults to 1) 92 | client.increment('key', value, callback); 93 | client.decrement('key', value, callback); 94 | 95 | // statistics. the success argument to the callback 96 | // is a key=>value object 97 | client.stats(callback); 98 | client.stats('settings', callback); 99 | client.stats('items', callback); 100 | client.stats('mongeese', callback); 101 | 102 | Once you're done, close the connection. 103 | 104 | client.close(); 105 | 106 | There might be bugs. I'd like to know about them. 107 | 108 | I bet you also want to read the memcached 109 | protocol doc. It's exciting! It also explains possible error messages. 110 | -------------------------------------------------------------------------------- /test/test-memcache.js: -------------------------------------------------------------------------------- 1 | /* 2 | tests for expresso 3 | */ 4 | 5 | var util = require('util'), 6 | memcache = require('memcache'), 7 | assert = require('assert'), 8 | port = 11211; 9 | 10 | mc = new memcache.Client(port); 11 | mc.on('error', function(e){ 12 | 13 | if (e.errno == 111){ 14 | exports['startup test'] = function(){ 15 | 16 | assert.ok(false, "You need to have a memcache server running on localhost:11211 for these tests to run"); 17 | } 18 | return; 19 | } 20 | 21 | exports['startup test'] = function(){ 22 | assert.ok(false, "Unexpected error during connection: "+util.inspect(e)); 23 | } 24 | }); 25 | mc.connect(); 26 | 27 | 28 | mc.addHandler(function() { 29 | 30 | // test nonexistent key is null 31 | exports['test null value'] = function(beforeExit) { 32 | var n = 0; 33 | mc.get('no such key', function(err, r) { 34 | assert.equal(null, r); 35 | n++; 36 | }); 37 | 38 | beforeExit(function() { 39 | assert.equal(1, n); 40 | }); 41 | }; 42 | 43 | // test set, get and expires 44 | exports['test set, get, and expires'] = function(beforeExit) { 45 | var n = 0; 46 | // set key 47 | mc.set('set1', 'asdf1', function() { 48 | n++; 49 | mc.get('set1', function(err, r) { 50 | // assert key is found 51 | assert.equal('asdf1', r); 52 | n++; 53 | // assert key expires after 1s 54 | setTimeout(function() { 55 | mc.get('set1', function(r) { 56 | mc.close(); 57 | assert.equal(null, r); 58 | n++; 59 | }); 60 | }, 1000); 61 | }); 62 | }, 1); 63 | 64 | beforeExit(function() { 65 | assert.equal(3, n); 66 | }); 67 | }; 68 | 69 | exports['test set get with integer value'] = function(beforeExit) { 70 | mc.set('testKey', 123, function() { 71 | mc.get('testKey', function(err, r) { 72 | assert.equal(123,r); 73 | }); 74 | }); 75 | }; 76 | 77 | // test set and delete 78 | exports['test set del'] = function(beforeExit) { 79 | var n = 0; 80 | // set key 81 | mc.set('set2', 'asdf2', function() { 82 | n++; 83 | mc.get('set2', function(err, r) { 84 | // assert key is found 85 | assert.equal('asdf2', r); 86 | n++; 87 | // delete key 88 | mc.delete('set2', function() { 89 | mc.get('set2', function(err, r) { 90 | // assert key is null 91 | assert.equal(null, r); 92 | n++; 93 | }); 94 | }); 95 | }); 96 | }, 0); 97 | 98 | beforeExit(function() { 99 | assert.equal(3, n); 100 | }); 101 | }; 102 | 103 | // test utf8 handling 104 | exports['utf8'] = function(beforeExit) { 105 | mc.set('key1', 'привет', function() { 106 | mc.get('key1', function(err, r) { 107 | assert.equal('привет', r); 108 | }); 109 | }); 110 | }; 111 | 112 | 113 | // test connecting and disconnecting 114 | exports['con disco'] = function(beforeExit) { 115 | 116 | var n = 0; 117 | 118 | var mc2 = new memcache.Client(port); 119 | mc2.on('connect', function(){ 120 | n++; 121 | mc2.close(); 122 | }); 123 | mc2.on('close', function(){ 124 | n++; 125 | }); 126 | 127 | mc2.connect(); 128 | 129 | beforeExit(function() { 130 | assert.equal(2, n); 131 | }); 132 | }; 133 | 134 | // increment / decrement 135 | exports['inc dec'] = function(beforeExit){ 136 | 137 | var n = 0; 138 | 139 | mc.set('inc_bad', 'HELLO', function(err, response){ 140 | assert.equal(response, 'STORED'); 141 | n++; 142 | mc.increment('inc_bad', 2, function(err, ok){ 143 | n++; 144 | assert.match(err, /^CLIENT_ERROR/); 145 | assert.equal(ok, null); 146 | }); 147 | mc.decrement('inc_bad', 3, function(err, ok){ 148 | n++; 149 | assert.match(err, /^CLIENT_ERROR/); 150 | assert.equal(ok, null); 151 | }); 152 | mc.increment('inc_bad', null, function(err, ok){ 153 | n++; 154 | assert.match(err, /^CLIENT_ERROR/); 155 | assert.equal(ok, null); 156 | }); 157 | mc.decrement('inc_bad', null, function(err, ok){ 158 | n++; 159 | assert.match(err, /^CLIENT_ERROR/); 160 | assert.equal(ok, null); 161 | }); 162 | }); 163 | 164 | mc.set('inc_good', '5', function(err, response){ 165 | assert.equal(response, 'STORED'); 166 | n++; 167 | mc.increment('inc_good', 2, function(err, response){ 168 | n++; 169 | assert.equal(response, 7); 170 | mc.increment('inc_good', function(err, response){ 171 | n++; 172 | assert.equal(response, 8); 173 | mc.decrement('inc_good', function(err, response){ 174 | n++; 175 | assert.equal(response, 7); 176 | mc.decrement('inc_good', 4, function(err, response){ 177 | n++; 178 | assert.equal(response, 3); 179 | }); 180 | }); 181 | }); 182 | }); 183 | }); 184 | 185 | beforeExit(function(){ 186 | assert.equal(10, n); 187 | }); 188 | 189 | }; 190 | 191 | exports['version'] = function(beforeExit){ 192 | var n = 0; 193 | 194 | mc.version(function(error, success){ 195 | n++; 196 | assert.equal(error, null); 197 | assert.length(success, 5); 198 | }); 199 | 200 | beforeExit(function(){ 201 | assert.equal(1, n); 202 | }); 203 | }; 204 | 205 | exports['stats'] = function(beforeExit){ 206 | var n = 0; 207 | 208 | mc.stats(function(error, success){ 209 | n++; 210 | assert.ok(success.pid, "server has a pid"); 211 | }); 212 | 213 | mc.stats('settings', function(error, success){ 214 | n++; 215 | assert.ok(success.maxconns); 216 | }); 217 | 218 | mc.stats('items', function(error, success){ n++; assert.ok(num_keys(success)); }); 219 | mc.stats('sizes', function(error, success){ n++; assert.ok(num_keys(success)); }); 220 | mc.stats('slabs', function(error, success){ n++; assert.ok(num_keys(success)); }); 221 | 222 | mc.stats('notreal', function(error, success){ 223 | n++; 224 | assert.equal(error, 'ERROR'); 225 | }); 226 | 227 | beforeExit(function(){ 228 | assert.equal(6, n); 229 | }); 230 | }; 231 | 232 | }); 233 | 234 | function num_keys(a){ 235 | var i=0; 236 | for (var k in a) i++; 237 | return i; 238 | } 239 | -------------------------------------------------------------------------------- /lib/memcache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2011 Tim Eggert 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * @author Tim Eggert 23 | * @license http://www.opensource.org/licenses/mit-license.html MIT License 24 | */ 25 | 26 | var tcp = require('net'), 27 | util = require('util'); 28 | 29 | var crlf = "\r\n"; 30 | var crlf_len = crlf.length; 31 | 32 | var error_replies = ['ERROR', 'NOT_FOUND', 'CLIENT_ERROR', 'SERVER_ERROR']; 33 | 34 | var Client = exports.Client = function(port, host) { 35 | this.port = port || 11211; 36 | this.host = host || 'localhost'; 37 | this.buffer = ''; 38 | this.conn = null; 39 | this.sends = 0; 40 | this.replies = 0; 41 | this.callbacks = []; 42 | this.handles = []; 43 | }; 44 | 45 | util.inherits(Client, process.EventEmitter); 46 | 47 | Client.prototype.connect = function () { 48 | if (!this.conn) { 49 | this.conn = new tcp.createConnection(this.port, this.host); 50 | var self = this; 51 | this.conn.addListener("connect", function () { 52 | this.setTimeout(0); // try to stay connected. 53 | this.setNoDelay(); 54 | self.emit("connect"); 55 | self.dispatchHandles(); 56 | }); 57 | 58 | this.conn.addListener("data", function (data) { 59 | self.buffer += data; 60 | // util.debug(data); 61 | self.recieves += 1; 62 | self.handle_received_data(); 63 | }); 64 | 65 | this.conn.addListener("end", function () { 66 | if (self.conn && self.conn.readyState) { 67 | self.conn.end(); 68 | self.conn = null; 69 | } 70 | }); 71 | 72 | this.conn.addListener("close", function () { 73 | self.conn = null; 74 | self.emit("close"); 75 | }); 76 | 77 | this.conn.addListener("timeout", function () { 78 | self.conn = null; 79 | self.emit("timeout"); 80 | }); 81 | 82 | this.conn.addListener("error", function (ex) { 83 | self.conn = null; 84 | self.emit("error", ex); 85 | }); 86 | } 87 | }; 88 | 89 | Client.prototype.addHandler = function(callback) { 90 | this.handles.push(callback); 91 | 92 | if (this.conn.readyState == 'open') { 93 | this.dispatchHandles(); 94 | } 95 | }; 96 | 97 | Client.prototype.dispatchHandles = function() { 98 | for (var i in this.handles) { 99 | var handle = this.handles.shift(); 100 | // util.debug('dispatching handle ' + handle); 101 | if (typeof handle !== 'undefined') { 102 | handle(); 103 | } 104 | } 105 | }; 106 | 107 | Client.prototype.query = function(query, type, callback) { 108 | this.callbacks.push({ type: type, fun: callback }); 109 | this.sends++; 110 | this.conn.write(query + crlf); 111 | }; 112 | 113 | Client.prototype.close = function() { 114 | if (this.conn && this.conn.readyState === "open") { 115 | this.conn.end(); 116 | this.conn = null; 117 | } 118 | }; 119 | 120 | Client.prototype.get = function(key, callback) { 121 | return this.query('get ' + key, 'get', callback); 122 | }; 123 | 124 | 125 | // all of these store ops (everything bu "cas") have the same format 126 | Client.prototype.set = function(key, value, callback, lifetime, flags) { return this.store('set', key, value, callback, lifetime, flags); } 127 | Client.prototype.add = function(key, value, callback, lifetime, flags) { return this.store('add', key, value, callback, lifetime, flags); } 128 | Client.prototype.replace = function(key, value, callback, lifetime, flags) { return this.store('replace', key, value, callback, lifetime, flags); } 129 | Client.prototype.append = function(key, value, callback, lifetime, flags) { return this.store('append', key, value, callback, lifetime, flags); } 130 | Client.prototype.prepend = function(key, value, callback, lifetime, flags) { return this.store('prepend', key, value, callback, lifetime, flags); } 131 | Client.prototype.store = function(cmd, key, value, callback, lifetime, flags) { 132 | 133 | if (typeof(callback) != 'function') { 134 | lifetime = callback; 135 | callback = null; 136 | } 137 | 138 | var set_flags = flags || 0; 139 | var exp_time = lifetime || 0; 140 | var tml_buf = new Buffer(value.toString()); 141 | var value_len = tml_buf.length || 0; 142 | var query = [cmd, key, set_flags, exp_time, value_len]; 143 | 144 | return this.query(query.join(' ') + crlf + value, 'simple', callback); 145 | }; 146 | 147 | // "cas" is a store op that takes an extra "unique" argument 148 | Client.prototype.cas = function(key, value, unique, callback, lifetime, flags) { 149 | 150 | if (typeof(callback) != 'function') { 151 | lifetime = callback; 152 | callback = null; 153 | } 154 | 155 | var set_flags = flags || 0; 156 | var exp_time = lifetime || 0; 157 | var value_len = value.length || 0; 158 | var query = ['cas', key, set_flags, exp_time, value_len, unique]; 159 | 160 | return this.query(query.join(' ') + crlf + value, 'simple', callback); 161 | }; 162 | 163 | 164 | Client.prototype.del = function(key, callback){ 165 | util.error("mc.del() is deprecated - use mc.delete() instead"); 166 | return this.delete(key, callback); 167 | }; 168 | 169 | Client.prototype.delete = function(key, callback){ 170 | return this.query('delete ' + key, 'simple', callback); 171 | }; 172 | 173 | Client.prototype.version = function(callback) { 174 | return this.query('version', 'version', callback); 175 | }; 176 | 177 | Client.prototype.increment = function(key, value, callback) { 178 | 179 | if (typeof(value) == 'function') { 180 | callback = value; 181 | value = 1;; 182 | } 183 | 184 | value = value || 1; 185 | return this.query('incr ' + key + ' ' + value, 'simple', callback); 186 | }; 187 | 188 | Client.prototype.decrement = function(key, value, callback) { 189 | 190 | if (typeof(value) == 'function') { 191 | callback = value; 192 | value = 1;; 193 | } 194 | 195 | value = value || 1; 196 | return this.query('decr ' + key + ' ' + value, 'simple', callback); 197 | }; 198 | 199 | Client.prototype.stats = function(type, callback){ 200 | 201 | if (typeof(type) == 'function'){ 202 | callback = type; 203 | type = null; 204 | } 205 | 206 | if (type){ 207 | return this.query('stats '+type, 'stats', callback); 208 | }else{ 209 | return this.query('stats', 'stats', callback); 210 | } 211 | } 212 | 213 | Client.prototype.handle_received_data = function(){ 214 | 215 | while (this.buffer.length > 0){ 216 | 217 | var result = this.determine_reply_handler(this.buffer); 218 | 219 | if (result == null){ 220 | break; 221 | } 222 | 223 | var result_value = result[0]; 224 | var next_result_at = result[1]; 225 | var result_error = result[2]; 226 | 227 | // does the current message need more data than we have? 228 | // (this is how "get" ops ensure we've gotten all the data) 229 | if (next_result_at > this.buffer.length){ 230 | break; 231 | } 232 | 233 | this.buffer = this.buffer.substring(next_result_at); 234 | 235 | var callback = this.callbacks.shift(); 236 | if (callback != null && callback.fun){ 237 | this.replies++; 238 | callback.fun(result_error, result_value); 239 | } 240 | } 241 | }; 242 | 243 | Client.prototype.determine_reply_handler = function (buffer){ 244 | 245 | // check we have a whole line in the buffer 246 | var crlf_at = buffer.indexOf(crlf); 247 | if (crlf_at == -1){ 248 | return null; 249 | } 250 | 251 | // determine errors 252 | for (var error_idx in error_replies){ 253 | var error_indicator = error_replies[error_idx]; 254 | if (buffer.indexOf(error_indicator) == 0) { 255 | return this.handle_error(buffer); 256 | } 257 | } 258 | 259 | // call the handler for the current message type 260 | var type = this.callbacks[0].type; 261 | if (type){ 262 | return this['handle_' + type](buffer); 263 | } 264 | 265 | return null; 266 | }; 267 | 268 | Client.prototype.handle_get = function(buffer) { 269 | var next_result_at = 0; 270 | var result_value = null; 271 | var end_indicator_len = 3; 272 | var result_len = 0; 273 | 274 | if (buffer.indexOf('END') == 0) { 275 | return [result_value, end_indicator_len + crlf_len]; 276 | } else if (buffer.indexOf('VALUE') == 0 && buffer.indexOf('END') != -1) { 277 | first_line_len = buffer.indexOf(crlf) + crlf_len; 278 | var end_indicator_start = buffer.indexOf('END'); 279 | result_len = end_indicator_start - first_line_len - crlf_len; 280 | result_value = buffer.substr(first_line_len, result_len); 281 | return [result_value, first_line_len + parseInt(result_len, 10) + crlf_len + end_indicator_len + crlf_len] 282 | } else { 283 | var first_line_len = buffer.indexOf(crlf) + crlf_len; 284 | var result_len = buffer.substr(0, first_line_len).split(' ')[3]; 285 | result_value = buffer.substr(first_line_len, result_len); 286 | 287 | return [result_value, first_line_len + parseInt(result_len ) + crlf_len + end_indicator_len + crlf_len]; 288 | } 289 | }; 290 | 291 | Client.prototype.handle_stats = function(buffer){ 292 | 293 | // special case - no stats at all 294 | if (buffer.indexOf('END') == 0){ 295 | return [{}, 5]; 296 | } 297 | 298 | // find the terminator 299 | var idx = buffer.indexOf('\r\nEND\r\n'); 300 | if (idx == -1){ 301 | // wait for more data if we don't have an end yet 302 | return null; 303 | } 304 | 305 | // read the lines 306 | var our_data = buffer.substr(0, idx+2); 307 | var out = {}; 308 | var line = null; 309 | var i=0; 310 | while (line = readLine(our_data)){ 311 | our_data = our_data.substr(line.length + 2); 312 | if (line.substr(0, 5) == 'STAT '){ 313 | var idx2 = line.indexOf(' ', 5); 314 | var k = line.substr(5, idx2-5); 315 | var v = line.substr(idx2+1); 316 | out[k] = v; 317 | } 318 | } 319 | 320 | return [out, idx + 7, null]; 321 | }; 322 | 323 | Client.prototype.handle_simple = function(buffer){ 324 | var line = readLine(buffer); 325 | return [line, (line.length + crlf_len), null]; 326 | }; 327 | 328 | Client.prototype.handle_version = function(buffer){ 329 | var line_len = buffer.indexOf(crlf); 330 | var indicator_len = 'VERSION '.length; 331 | var result_value = buffer.substr(indicator_len, (line_len - indicator_len)); 332 | return [result_value, line_len + crlf_len, null]; 333 | }; 334 | 335 | Client.prototype.handle_error = function(buffer){ 336 | var line = readLine(buffer); 337 | return [null, (line.length + crlf_len), line]; 338 | }; 339 | 340 | readLine = function(string){ 341 | var line_len = string.indexOf(crlf); 342 | return string.substr(0, line_len); 343 | }; 344 | 345 | --------------------------------------------------------------------------------