├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── COPYING ├── Makefile ├── README.md ├── appveyor.yml ├── bench.js ├── binding.gyp ├── deps └── hiredis.gyp ├── hiredis.js ├── package.json ├── src ├── hiredis.cc ├── reader.cc └── reader.h └── test ├── reader.js ├── testlib.js └── writer.js /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .lock-wscript 3 | tmp 4 | node_modules 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/hiredis"] 2 | path = deps/hiredis 3 | url = git://github.com/redis/hiredis.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | env: 4 | - CXX=g++-4.8 5 | addons: 6 | apt: 7 | sources: 8 | - ubuntu-toolchain-r-test 9 | packages: 10 | - g++-4.8 11 | 12 | 13 | before_install: 14 | - node --version | grep -q 'v0.8' && npm install -g npm@2 || true 15 | 16 | node_js: 17 | - "6" 18 | - "5" 19 | - "4" 20 | - "0.12" 21 | - "0.10" 22 | 23 | notifications: 24 | email: false 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.5.0 (2016-04-xy) 2 | 3 | * Dropping support for EOL Node versions. 4 | * This does not mean it breaks right now, but we're not testing against v0.8 and iojs anymore. 5 | * Do not cast a potentially non-String value to String (#117, iamstolis) 6 | * Upgrade to new nan version (#119, nicolashenry), avoiding deprecation warnings in Node v6 7 | 8 | ### 0.4.1 (2015-08-22) 9 | 10 | * Upgrade to latest nan to be compatible with io.js (Thanks, Benjamin Byholm) 11 | 12 | ### 0.4.0 (2015-05-25) 13 | 14 | * Upgrade to latest nan to be compatible with io.js 15 | * Update license attribute 16 | 17 | ### 0.3.0 (2015-04-03) 18 | 19 | * Update to latest hiredis including basic windows support 20 | * Refactor to use only the parser from hiredis source. 21 | 22 | ### 0.2.0 (2015-02-08) 23 | 24 | * Update to use new hiredis 0.12 25 | * Update nan to latest version to get io.js support for free (thanks @jonathanong) 26 | * Bufferify writeCommand, to support unicode and non-string arguments and make it even faster (thanks @stephank) 27 | * Remove support for Node 0.6 28 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2012, Pieter Noordhuis 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Redis nor the names of its contributors may be used to 15 | endorse or promote products derived from this software without specific prior 16 | written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | node-gyp configure build 3 | 4 | clean: 5 | node-gyp clean 6 | 7 | temp: 8 | rm -rf tmp/hiredis 9 | mkdir -p tmp/hiredis 10 | cp -r README* COPYING *.js* binding.gyp src deps test tmp/hiredis 11 | cd tmp/hiredis && rm -rf deps/*/.git* deps/*/*.o deps/*/*.a 12 | 13 | package: temp 14 | cd tmp && tar -czvf hiredis.tgz hiredis 15 | 16 | check: 17 | npm test 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Use node-redis](https://github.com/NodeRedis/node_redis) 2 | 3 | `hiredis-node` is deprecated, unmaintained and not updated in forever. [Use node-redis](https://github.com/NodeRedis/node_redis). 4 | 5 | --- 6 | 7 | [![Build Status](https://travis-ci.org/redis/hiredis-node.png?branch=master)](https://travis-ci.org/redis/hiredis-node) 8 | 9 | # hiredis-node 10 | 11 | Node extension that wraps [hiredis][hiredis]. 12 | Because Node is already good at doing I/O, hiredis-node only provides 13 | bindings to the protocol parser. 14 | The hiredis protocol parser is faster than JavaScript protocol parsers, 15 | but the speedup only becomes noticeable for large replies. 16 | If you use Redis for simple SET/GET operations, there won't be a big 17 | benefit to using hiredis. 18 | If you use Redis for big SUNION/SINTER/LRANGE/ZRANGE operations, the 19 | benefit to using hiredis-node can be significant. 20 | 21 | [hiredis]: http://github.com/redis/hiredis 22 | 23 | ## Install 24 | 25 | Install with [NPM][npm]: 26 | 27 | ``` 28 | npm install hiredis 29 | ``` 30 | 31 | This requires: 32 | * `gcc` / `g++` 4.8 or newer. 33 | * `python` 2.7 or any newer 2.x version. `python` 3.x is not supported. 34 | 35 | For running on Travis check the bundled [.travis.yml](.travis.yml). 36 | 37 | [npm]: https://npmjs.org/ 38 | 39 | ## Contribute 40 | 41 | To work on the code, first fetch the bundled hiredis submodule, then build hiredis and run the tests. 42 | 43 | ``` 44 | git submodule update --init 45 | npm install 46 | npm test 47 | ``` 48 | 49 | ## Usage 50 | 51 | hiredis-node works out of the box with Matt Ranney's [node_redis][node_redis]. 52 | The latter has an optional dependency on hiredis-node, so maybe you're 53 | already using it without knowing. 54 | 55 | Alternatively, you can use it directly: 56 | 57 | ```javascript 58 | var hiredis = require("hiredis"), 59 | reader = new hiredis.Reader(); 60 | 61 | // Data comes in 62 | reader.feed("$5\r\nhello\r\n"); 63 | 64 | // Reply comes out 65 | reader.get() // => "hello" 66 | ``` 67 | 68 | Instead of returning strings for bulk payloads, it can also return 69 | buffers: 70 | 71 | ```javascript 72 | var hiredis = require("hiredis"), 73 | reader = new hiredis.Reader({ return_buffers: true }); 74 | 75 | // Data comes in 76 | reader.feed("$5\r\nhello\r\n"); 77 | 78 | // Reply comes out 79 | reader.get() // => 80 | ``` 81 | 82 | [node_redis]: http://github.com/mranney/node_redis 83 | 84 | ## Windows 85 | 86 | Since Version 0.3.0 hiredis-node officially supports Windows. 87 | A simple `npm install hiredis` should just work. 88 | If not, please open a bug report. 89 | 90 | There's also a [Windows fork][windows_fork] by Dmitry Gorbunos (@fuwaneko), which should now be unnecessary. 91 | 92 | [windows_fork]: https://github.com/fuwaneko/hiredis-node 93 | 94 | ## License 95 | 96 | This code is released under the BSD license, after the license of hiredis. 97 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf input 3 | 4 | environment: 5 | matrix: 6 | - nodejs_version: 6 7 | - nodejs_version: 5 8 | - nodejs_version: 4 9 | - nodejs_version: 0.12 10 | - nodejs_version: 0.10 11 | 12 | platform: 13 | - x86 14 | - x64 15 | 16 | install: 17 | - ps: Install-Product node $env:nodejs_version $env:platform 18 | - git submodule update --init --recursive 19 | - npm install --msvs_version=2013 20 | 21 | build: off 22 | 23 | test_script: 24 | - node --version 25 | - npm --version 26 | - "node test/reader.js" 27 | - "node test/writer.js" 28 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | var hiredis = require("./hiredis"), 2 | num_clients = 10, 3 | active_clients = 0, 4 | pipeline = 0, 5 | num_requests = parseInt(process.argv[2]) || 20000, 6 | issued_requests = 0, 7 | test_start; 8 | 9 | var tests = []; 10 | tests.push({ 11 | descr: "PING", 12 | command: ["PING"] 13 | }); 14 | tests.push({ 15 | descr: "SET", 16 | command: ["SET", "foo", "bar"] 17 | }); 18 | tests.push({ 19 | descr: "GET", 20 | command: ["GET", "foo"] 21 | }); 22 | tests.push({ 23 | descr: "LPUSH 8 bytes", 24 | command: ["LPUSH", "mylist-8", new Buffer(Array(8).join("-"))] 25 | }); 26 | tests.push({ 27 | descr: "LPUSH 64 bytes", 28 | command: ["LPUSH", "mylist-64", new Buffer(Array(64).join("-"))] 29 | }); 30 | tests.push({ 31 | descr: "LPUSH 512 bytes", 32 | command: ["LPUSH", "mylist-512", new Buffer(Array(512).join("-"))] 33 | }); 34 | tests.push({ 35 | descr: "LRANGE 10 elements, 8 bytes", 36 | command: ["LRANGE", "mylist-8", "0", "9"] 37 | }); 38 | tests.push({ 39 | descr: "LRANGE 100 elements, 8 bytes", 40 | command: ["LRANGE", "mylist-8", "0", "99"] 41 | }); 42 | tests.push({ 43 | descr: "LRANGE 100 elements, 64 bytes", 44 | command: ["LRANGE", "mylist-64", "0", "99"] 45 | }); 46 | tests.push({ 47 | descr: "LRANGE 100 elements, 512 bytes", 48 | command: ["LRANGE", "mylist-512", "0", "99"] 49 | }); 50 | 51 | function call(client, test) { 52 | client.on("reply", function() { 53 | if (issued_requests < num_requests) { 54 | request(); 55 | } else { 56 | client.end(); 57 | if (--active_clients == 0) 58 | done(test); 59 | } 60 | }); 61 | 62 | function request() { 63 | issued_requests++; 64 | client.write.apply(client,test.command); 65 | }; 66 | 67 | request(); 68 | } 69 | 70 | function done(test) { 71 | var time = (new Date - test_start); 72 | var op_rate = (num_requests/(time/1000.0)).toFixed(2); 73 | console.log(test.descr + ": " + op_rate + " ops/sec"); 74 | next(); 75 | } 76 | 77 | function concurrent_test(test) { 78 | var i = num_clients; 79 | var client; 80 | 81 | issued_requests = 0; 82 | test_start = new Date; 83 | while(i-- && issued_requests < num_requests) { 84 | active_clients++; 85 | client = hiredis.createConnection(); 86 | call(client, test); 87 | } 88 | } 89 | 90 | function pipelined_test(test) { 91 | var client = hiredis.createConnection(); 92 | var received_replies = 0; 93 | 94 | issued_requests = 0; 95 | while (issued_requests < num_requests) { 96 | issued_requests++; 97 | client.write.apply(client,test.command); 98 | } 99 | 100 | test_start = new Date; 101 | client.on("reply", function() { 102 | if (++received_replies == num_requests) { 103 | client.end(); 104 | done(test); 105 | } 106 | }); 107 | } 108 | 109 | function next() { 110 | var test = tests.shift(); 111 | if (test) { 112 | if (pipeline) { 113 | pipelined_test(test); 114 | } else { 115 | concurrent_test(test); 116 | } 117 | } 118 | } 119 | 120 | next(); 121 | 122 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'hiredis', 5 | 'sources': [ 6 | 'src/hiredis.cc' 7 | , 'src/reader.cc' 8 | ], 9 | 'include_dirs': ["", 7 | "contributors": [ 8 | "Pieter Noordhuis " 9 | ], 10 | "main": "hiredis", 11 | "scripts": { 12 | "test": "node test/reader.js && node test/writer.js" 13 | }, 14 | "dependencies": { 15 | "bindings": "^1.2.1", 16 | "nan": "^2.3.4" 17 | }, 18 | "engines": { 19 | "node": ">= 0.10.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/redis/hiredis-node.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/redis/hiredis-node/issues" 27 | }, 28 | "license": "BSD-3-Clause" 29 | } 30 | -------------------------------------------------------------------------------- /src/hiredis.cc: -------------------------------------------------------------------------------- 1 | #include "reader.h" 2 | 3 | using namespace v8; 4 | 5 | extern "C" { 6 | static NAN_MODULE_INIT(init) { 7 | hiredis::Reader::Initialize(target); 8 | } 9 | NODE_MODULE(hiredis, init) 10 | } 11 | -------------------------------------------------------------------------------- /src/reader.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "reader.h" 4 | 5 | using namespace hiredis; 6 | 7 | static void *tryParentize(const redisReadTask *task, const Local &v) { 8 | Nan::HandleScope scope; 9 | 10 | Reader *r = reinterpret_cast(task->privdata); 11 | size_t pidx, vidx; 12 | 13 | if (task->parent != NULL) { 14 | pidx = (size_t)task->parent->obj; 15 | assert(pidx > 0 && pidx < 9); 16 | 17 | /* When there is a parent, it should be an array. */ 18 | Local lvalue = Nan::New(r->handle[pidx]); 19 | assert(lvalue->IsArray()); 20 | Local larray = lvalue.As(); 21 | larray->Set(task->idx,v); 22 | 23 | /* Store the handle when this is an inner array. Otherwise, hiredis 24 | * doesn't care about the return value as long as the value is set in 25 | * its parent array. */ 26 | vidx = pidx+1; 27 | if (v->IsArray()) { 28 | r->handle[vidx].Reset(v); 29 | return (void*)vidx; 30 | } else { 31 | /* Return value doesn't matter for inner value, as long as it is 32 | * not NULL (which means OOM for hiredis). */ 33 | return (void*)0xcafef00d; 34 | } 35 | } else { 36 | /* There is no parent, so this value is the root object. */ 37 | r->handle[1].Reset(v); 38 | return (void*)1; 39 | } 40 | } 41 | 42 | static void *createArray(const redisReadTask *task, int size) { 43 | Nan::HandleScope scope; 44 | 45 | return tryParentize(task, Nan::New(size)); 46 | } 47 | 48 | static void *createString(const redisReadTask *task, char *str, size_t len) { 49 | Nan::HandleScope scope; 50 | 51 | Reader *r = reinterpret_cast(task->privdata); 52 | Local v(r->createString(str,len)); 53 | 54 | if (task->type == REDIS_REPLY_ERROR) 55 | v = Exception::Error(v->ToString()); 56 | return tryParentize(task,v); 57 | } 58 | 59 | static void *createInteger(const redisReadTask *task, long long value) { 60 | Nan::HandleScope scope; 61 | return tryParentize(task, Nan::New(value)); 62 | } 63 | 64 | static void *createNil(const redisReadTask *task) { 65 | Nan::HandleScope scope; 66 | return tryParentize(task, Nan::Null()); 67 | } 68 | 69 | static redisReplyObjectFunctions v8ReplyFunctions = { 70 | createString, 71 | createArray, 72 | createInteger, 73 | createNil, 74 | 0 /* No free function: cleanup is done in Reader::Get. */ 75 | }; 76 | 77 | Reader::Reader(bool return_buffers) : 78 | return_buffers(return_buffers) 79 | { 80 | Nan::HandleScope scope; 81 | 82 | reader = redisReaderCreateWithFunctions(&v8ReplyFunctions); 83 | reader->privdata = this; 84 | 85 | #if _USE_CUSTOM_BUFFER_POOL 86 | if (return_buffers) { 87 | Local global = Context::GetCurrent()->Global(); 88 | Local bv = global->Get(String::NewSymbol("Buffer")); 89 | assert(bv->IsFunction()); 90 | Local bf = Local::Cast(bv); 91 | buffer_fn = Persistent::New(bf); 92 | 93 | buffer_pool_length = 8*1024; /* Same as node */ 94 | buffer_pool_offset = 0; 95 | 96 | node::Buffer *b = node::Buffer::New(buffer_pool_length); 97 | buffer_pool = Persistent::New(b->handle_); 98 | } 99 | #endif 100 | } 101 | 102 | Reader::~Reader() { 103 | redisReaderFree(reader); 104 | } 105 | 106 | /* Don't declare an extra scope here, so the objects are created within the 107 | * scope inherited from the caller (Reader::Get) and we don't have to the pay 108 | * the overhead. */ 109 | inline Local Reader::createString(char *str, size_t len) { 110 | if (return_buffers) { 111 | #if _USE_CUSTOM_BUFFER_POOL 112 | if (len > buffer_pool_length) { 113 | node::Buffer *b = node::Buffer::New(str,len); 114 | return Local::New(b->handle_); 115 | } else { 116 | return createBufferFromPool(str,len); 117 | } 118 | #else 119 | return Nan::CopyBuffer(str,len).ToLocalChecked(); 120 | #endif 121 | } else { 122 | return Nan::New(str,len).ToLocalChecked(); 123 | } 124 | } 125 | 126 | #if _USE_CUSTOM_BUFFER_POOL 127 | Local Reader::createBufferFromPool(char *str, size_t len) { 128 | HandleScope scope; 129 | Local argv[3]; 130 | Local instance; 131 | 132 | assert(len <= buffer_pool_length); 133 | if (buffer_pool_length - buffer_pool_offset < len) { 134 | node::Buffer *b = node::Buffer::New(buffer_pool_length); 135 | buffer_pool.Dispose(); 136 | buffer_pool = Persistent::New(b->handle_); 137 | buffer_pool_offset = 0; 138 | } 139 | 140 | memcpy(node::Buffer::Data(buffer_pool)+buffer_pool_offset,str,len); 141 | 142 | argv[0] = Local::New(buffer_pool); 143 | argv[1] = Integer::New(len); 144 | argv[2] = Integer::New(buffer_pool_offset); 145 | instance = buffer_fn->NewInstance(3,argv); 146 | buffer_pool_offset += len; 147 | return scope.Close(instance); 148 | } 149 | #endif 150 | 151 | NAN_METHOD(Reader::New) { 152 | bool return_buffers = false; 153 | 154 | if (info.Length() > 0 && info[0]->IsObject()) { 155 | Local bv = Nan::Get(info[0].As(), Nan::New("return_buffers").ToLocalChecked()).ToLocalChecked(); 156 | if (bv->IsBoolean()) 157 | return_buffers = Nan::To(bv).FromJust(); 158 | } 159 | 160 | Reader *r = new Reader(return_buffers); 161 | r->Wrap(info.This()); 162 | info.GetReturnValue().Set(info.This()); 163 | } 164 | 165 | NAN_MODULE_INIT(Reader::Initialize) { 166 | Nan::HandleScope scope; 167 | 168 | Local t = Nan::New(New); 169 | 170 | t->InstanceTemplate()->SetInternalFieldCount(1); 171 | Nan::SetPrototypeMethod(t, "feed", Feed); 172 | Nan::SetPrototypeMethod(t, "get", Get); 173 | Nan::Set(target, Nan::New("Reader").ToLocalChecked(), Nan::GetFunction(t).ToLocalChecked()); 174 | } 175 | 176 | NAN_METHOD(Reader::Feed) { 177 | Reader *r = Nan::ObjectWrap::Unwrap(info.This()); 178 | 179 | if (info.Length() == 0) { 180 | Nan::ThrowTypeError("First argument must be a string or buffer"); 181 | } else { 182 | if (node::Buffer::HasInstance(info[0])) { 183 | Local buffer_object = info[0].As(); 184 | char *data; 185 | size_t length; 186 | 187 | data = node::Buffer::Data(buffer_object); 188 | length = node::Buffer::Length(buffer_object); 189 | 190 | /* Can't handle OOM for now. */ 191 | assert(redisReaderFeed(r->reader, data, length) == REDIS_OK); 192 | } else if (info[0]->IsString()) { 193 | Nan::Utf8String str(info[0].As()); 194 | redisReplyReaderFeed(r->reader, *str, str.length()); 195 | } else { 196 | Nan::ThrowError("Invalid argument"); 197 | } 198 | } 199 | 200 | info.GetReturnValue().Set(info.This()); 201 | } 202 | 203 | NAN_METHOD(Reader::Get) { 204 | Reader *r = Nan::ObjectWrap::Unwrap(info.This()); 205 | void *index = NULL; 206 | Local reply; 207 | int i; 208 | 209 | if (redisReaderGetReply(r->reader,&index) == REDIS_OK) { 210 | if (index == 0) { 211 | return; 212 | } else { 213 | /* Complete replies should always have a root object at index 1. */ 214 | assert((size_t)index == 1); 215 | reply = Nan::New(r->handle[1]); 216 | 217 | /* Dispose and clear used handles. */ 218 | for (i = 1; i < 3; i++) { 219 | r->handle[i].Reset(); 220 | } 221 | } 222 | } else { 223 | Nan::ThrowError(r->reader->errstr); 224 | } 225 | 226 | info.GetReturnValue().Set(reply); 227 | } 228 | -------------------------------------------------------------------------------- /src/reader.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if NODE_MODULE_VERSION < NODE_0_12_MODULE_VERSION 5 | #define _USE_CUSTOM_BUFFER_POOL 1 6 | #else 7 | #define _USE_CUSTOM_BUFFER_POOL 0 8 | #endif 9 | 10 | namespace hiredis { 11 | 12 | using namespace v8; 13 | 14 | class Reader : public Nan::ObjectWrap { 15 | public: 16 | Reader(bool); 17 | ~Reader(); 18 | 19 | static NAN_MODULE_INIT(Initialize); 20 | static NAN_METHOD(New); 21 | static NAN_METHOD(Feed); 22 | static NAN_METHOD(Get); 23 | 24 | /* Objects created by the reply object functions need to get back to the 25 | * reader when the reply is requested via Reader::Get(). Keep temporary 26 | * objects in this handle. Use an array of handles because replies may 27 | * include nested multi bulks and child-elements need to be added to the 28 | * right respective parent. handle[0] will be unused, so the real index of 29 | * an object in this array can be returned from the reply object functions. 30 | * The returned value needs to be non-zero to distinguish complete replies 31 | * from incomplete replies. These are persistent handles because 32 | * Reader::Get might not return a full reply and the objects need to be 33 | * kept around for subsequent calls. */ 34 | Nan::Persistent handle[9]; 35 | 36 | /* Helper function to create string/buffer objects. */ 37 | Local createString(char *str, size_t len); 38 | 39 | private: 40 | redisReader *reader; 41 | 42 | /* Determines whether to return strings or buffers for single line and bulk 43 | * replies. This defaults to false, so strings are returned by default. */ 44 | bool return_buffers; 45 | 46 | #if _USE_CUSTOM_BUFFER_POOL 47 | Local createBufferFromPool(char *str, size_t len); 48 | Persistent buffer_fn; 49 | Persistent buffer_pool; 50 | size_t buffer_pool_length; 51 | size_t buffer_pool_offset; 52 | #endif 53 | }; 54 | 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /test/reader.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"), 2 | test = require("./testlib")(), 3 | hiredis = require("../hiredis"); 4 | 5 | test("CreateReader", function() { 6 | var reader = new hiredis.Reader(); 7 | assert.notEqual(reader, null); 8 | }); 9 | 10 | test("StatusReply", function() { 11 | var reader = new hiredis.Reader(); 12 | reader.feed("+OK\r\n"); 13 | assert.equal("OK", reader.get()); 14 | }); 15 | 16 | test("StatusReplyAsBuffer", function() { 17 | var reader = new hiredis.Reader({ return_buffers: true }); 18 | reader.feed("+OK\r\n"); 19 | var reply = reader.get(); 20 | assert.ok(Buffer.isBuffer(reply)); 21 | assert.equal("OK", reply.toString()); 22 | }); 23 | 24 | test("IntegerReply", function() { 25 | var reader = new hiredis.Reader(); 26 | reader.feed(":1\r\n"); 27 | assert.equal(1, reader.get()); 28 | }); 29 | 30 | test("LargeIntegerReply", function() { 31 | var reader = new hiredis.Reader(); 32 | reader.feed(":9223372036854775807\r\n"); 33 | // We test for a different value here, as JavaScript has no 64-bit integers, 34 | // only IEEE double precision floating point numbers 35 | assert.equal("9223372036854776000", String(reader.get())); 36 | }); 37 | 38 | test("ErrorReply", function() { 39 | var reader = new hiredis.Reader(); 40 | reader.feed("-ERR foo\r\n"); 41 | var reply = reader.get(); 42 | assert.equal(Error, reply.constructor); 43 | assert.equal("ERR foo", reply.message); 44 | }); 45 | 46 | test("ErrorReplyWithReturnBuffers", function() { 47 | var reader = new hiredis.Reader({ return_buffers: true }); 48 | reader.feed("-ERR foo\r\n"); 49 | var reply = reader.get(); 50 | assert.equal(Error, reply.constructor); 51 | assert.equal("ERR foo", reply.message); 52 | }); 53 | 54 | test("NullBulkReply", function() { 55 | var reader = new hiredis.Reader(); 56 | reader.feed("$-1\r\n"); 57 | assert.equal(null, reader.get()); 58 | }); 59 | 60 | test("EmptyBulkReply", function() { 61 | var reader = new hiredis.Reader(); 62 | reader.feed("$0\r\n\r\n"); 63 | assert.equal("", reader.get()); 64 | }); 65 | 66 | test("BulkReply", function() { 67 | var reader = new hiredis.Reader(); 68 | reader.feed("$3\r\nfoo\r\n"); 69 | assert.equal("foo", reader.get()); 70 | }); 71 | 72 | test("BulkReplyAsBuffer", function() { 73 | var reader = new hiredis.Reader({ return_buffers: true }); 74 | reader.feed("$3\r\nfoo\r\n"); 75 | var reply = reader.get(); 76 | assert.ok(Buffer.isBuffer(reply)); 77 | assert.equal("foo", reply.toString()); 78 | }); 79 | 80 | test("BulkReplyWithEncoding", function() { 81 | var reader = new hiredis.Reader(); 82 | reader.feed("$" + Buffer.byteLength("☃") + "\r\n☃\r\n"); 83 | assert.equal("☃", reader.get()); 84 | }); 85 | 86 | test("NullMultiBulkReply", function() { 87 | var reader = new hiredis.Reader(); 88 | reader.feed("*-1\r\n"); 89 | assert.equal(null, reader.get()); 90 | }); 91 | 92 | test("EmptyMultiBulkReply", function() { 93 | var reader = new hiredis.Reader(); 94 | reader.feed("*0\r\n"); 95 | assert.deepEqual([], reader.get()); 96 | }); 97 | 98 | test("MultiBulkReply", function() { 99 | var reader = new hiredis.Reader(); 100 | reader.feed("*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"); 101 | assert.deepEqual(["foo", "bar"], reader.get()); 102 | }); 103 | 104 | test("NestedMultiBulkReply", function() { 105 | var reader = new hiredis.Reader(); 106 | reader.feed("*2\r\n*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$3\r\nqux\r\n"); 107 | assert.deepEqual([["foo", "bar"], "qux"], reader.get()); 108 | }); 109 | 110 | test("DeeplyNestedMultiBulkReply", function() { 111 | var i; 112 | var reader = new hiredis.Reader(); 113 | var expected = 1; 114 | 115 | for (i = 0; i < 8; i++) { 116 | reader.feed("*1\r\n"); 117 | expected = [expected]; 118 | } 119 | 120 | reader.feed(":1\r\n"); 121 | 122 | assert.deepEqual(reader.get(), expected); 123 | }); 124 | 125 | test("TooDeeplyNestedMultiBulkReply", function() { 126 | var i; 127 | var reader = new hiredis.Reader(); 128 | 129 | for (i = 0; i < 9; i++) { 130 | reader.feed("*1\r\n"); 131 | } 132 | 133 | reader.feed(":1\r\n"); 134 | 135 | assert.throws( 136 | function() { 137 | reader.get(); 138 | }, 139 | /nested multi/ 140 | ); 141 | }); 142 | 143 | test("MultiBulkReplyWithNonStringValues", function() { 144 | var reader = new hiredis.Reader(); 145 | reader.feed("*3\r\n:1\r\n+OK\r\n$-1\r\n"); 146 | assert.deepEqual([1, "OK", null], reader.get()); 147 | }); 148 | 149 | test("FeedWithBuffer", function() { 150 | var reader = new hiredis.Reader(); 151 | reader.feed(new Buffer("$3\r\nfoo\r\n")); 152 | assert.deepEqual("foo", reader.get()); 153 | }); 154 | 155 | test("UndefinedReplyOnIncompleteFeed", function() { 156 | var reader = new hiredis.Reader(); 157 | reader.feed("$3\r\nfoo"); 158 | assert.deepEqual(undefined, reader.get()); 159 | reader.feed("\r\n"); 160 | assert.deepEqual("foo", reader.get()); 161 | }); 162 | 163 | test("Leaks", function() { 164 | /* The "leaks" utility is only available on OSX. */ 165 | if (process.platform != "darwin") return; 166 | 167 | var done = 0; 168 | var leaks = require('child_process').spawn("leaks", [process.pid]); 169 | leaks.stdout.on("data", function(data) { 170 | var str = data.toString(); 171 | var notice = "Node 0.2.5 always leaks 16 bytes (this is " + process.versions.node + ")"; 172 | var matches; 173 | if ((matches = /(\d+) leaks?/i.exec(str)) != null) { 174 | if (parseInt(matches[1]) > 0) { 175 | console.log(str); 176 | console.log('\x1B[31mNotice: ' + notice + '\x1B[0m'); 177 | } 178 | } 179 | done = 1; 180 | }); 181 | 182 | process.on('exit', function() { 183 | assert.ok(done, "Leaks test should have completed"); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/testlib.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | function test(str, fn) { 3 | try { 4 | fn(); 5 | test.passed++; 6 | } catch (err) { 7 | console.log("\x1B[1;31m" + str + " failed!\x1B[0m"); 8 | console.log(err.stack + "\n"); 9 | test.failed++; 10 | } 11 | } 12 | 13 | test.passed = 0; 14 | test.failed = 0; 15 | 16 | return test; 17 | } 18 | -------------------------------------------------------------------------------- /test/writer.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"), 2 | test = require("./testlib")(), 3 | hiredis = require("../hiredis"); 4 | 5 | test("WriteCommand", function() { 6 | var reader = new hiredis.Reader(); 7 | reader.feed(hiredis.writeCommand("hello", "world")); 8 | assert.deepEqual(["hello", "world"], reader.get()); 9 | }); 10 | 11 | test("WriteUnicode", function() { 12 | var reader = new hiredis.Reader(); 13 | reader.feed(hiredis.writeCommand("béép")); 14 | assert.deepEqual(["béép"], reader.get()); 15 | }); 16 | 17 | test("WriteBuffer", function() { 18 | var reader = new hiredis.Reader({ return_buffers: true }); 19 | reader.feed(hiredis.writeCommand(new Buffer([0xC3, 0x28]))); 20 | var command = reader.get(); 21 | assert.equal(0xC3, command[0][0]); 22 | assert.equal(0x28, command[0][1]); 23 | }); 24 | 25 | test("WriteNumber", function() { 26 | var reader = new hiredis.Reader(); 27 | reader.feed(hiredis.writeCommand(3)); 28 | assert.deepEqual(["3"], reader.get()); 29 | }); 30 | --------------------------------------------------------------------------------