Repository: redis/hiredis-node Branch: master Commit: daa2eacb4e37 Files: 19 Total size: 26.6 KB Directory structure: gitextract_wx808x3g/ ├── .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 ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ /build .lock-wscript tmp node_modules ================================================ FILE: .gitmodules ================================================ [submodule "deps/hiredis"] path = deps/hiredis url = git://github.com/redis/hiredis.git ================================================ FILE: .travis.yml ================================================ language: node_js sudo: false env: - CXX=g++-4.8 addons: apt: sources: - ubuntu-toolchain-r-test packages: - g++-4.8 before_install: - node --version | grep -q 'v0.8' && npm install -g npm@2 || true node_js: - "6" - "5" - "4" - "0.12" - "0.10" notifications: email: false ================================================ FILE: CHANGELOG.md ================================================ ### 0.5.0 (2016-04-xy) * Dropping support for EOL Node versions. * This does not mean it breaks right now, but we're not testing against v0.8 and iojs anymore. * Do not cast a potentially non-String value to String (#117, iamstolis) * Upgrade to new nan version (#119, nicolashenry), avoiding deprecation warnings in Node v6 ### 0.4.1 (2015-08-22) * Upgrade to latest nan to be compatible with io.js (Thanks, Benjamin Byholm) ### 0.4.0 (2015-05-25) * Upgrade to latest nan to be compatible with io.js * Update license attribute ### 0.3.0 (2015-04-03) * Update to latest hiredis including basic windows support * Refactor to use only the parser from hiredis source. ### 0.2.0 (2015-02-08) * Update to use new hiredis 0.12 * Update nan to latest version to get io.js support for free (thanks @jonathanong) * Bufferify writeCommand, to support unicode and non-string arguments and make it even faster (thanks @stephank) * Remove support for Node 0.6 ================================================ FILE: COPYING ================================================ Copyright (c) 2010-2012, Pieter Noordhuis All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ all: node-gyp configure build clean: node-gyp clean temp: rm -rf tmp/hiredis mkdir -p tmp/hiredis cp -r README* COPYING *.js* binding.gyp src deps test tmp/hiredis cd tmp/hiredis && rm -rf deps/*/.git* deps/*/*.o deps/*/*.a package: temp cd tmp && tar -czvf hiredis.tgz hiredis check: npm test ================================================ FILE: README.md ================================================ # [Use node-redis](https://github.com/NodeRedis/node_redis) `hiredis-node` is deprecated, unmaintained and not updated in forever. [Use node-redis](https://github.com/NodeRedis/node_redis). --- [![Build Status](https://travis-ci.org/redis/hiredis-node.png?branch=master)](https://travis-ci.org/redis/hiredis-node) # hiredis-node Node extension that wraps [hiredis][hiredis]. Because Node is already good at doing I/O, hiredis-node only provides bindings to the protocol parser. The hiredis protocol parser is faster than JavaScript protocol parsers, but the speedup only becomes noticeable for large replies. If you use Redis for simple SET/GET operations, there won't be a big benefit to using hiredis. If you use Redis for big SUNION/SINTER/LRANGE/ZRANGE operations, the benefit to using hiredis-node can be significant. [hiredis]: http://github.com/redis/hiredis ## Install Install with [NPM][npm]: ``` npm install hiredis ``` This requires: * `gcc` / `g++` 4.8 or newer. * `python` 2.7 or any newer 2.x version. `python` 3.x is not supported. For running on Travis check the bundled [.travis.yml](.travis.yml). [npm]: https://npmjs.org/ ## Contribute To work on the code, first fetch the bundled hiredis submodule, then build hiredis and run the tests. ``` git submodule update --init npm install npm test ``` ## Usage hiredis-node works out of the box with Matt Ranney's [node_redis][node_redis]. The latter has an optional dependency on hiredis-node, so maybe you're already using it without knowing. Alternatively, you can use it directly: ```javascript var hiredis = require("hiredis"), reader = new hiredis.Reader(); // Data comes in reader.feed("$5\r\nhello\r\n"); // Reply comes out reader.get() // => "hello" ``` Instead of returning strings for bulk payloads, it can also return buffers: ```javascript var hiredis = require("hiredis"), reader = new hiredis.Reader({ return_buffers: true }); // Data comes in reader.feed("$5\r\nhello\r\n"); // Reply comes out reader.get() // => ``` [node_redis]: http://github.com/mranney/node_redis ## Windows Since Version 0.3.0 hiredis-node officially supports Windows. A simple `npm install hiredis` should just work. If not, please open a bug report. There's also a [Windows fork][windows_fork] by Dmitry Gorbunos (@fuwaneko), which should now be unnecessary. [windows_fork]: https://github.com/fuwaneko/hiredis-node ## License This code is released under the BSD license, after the license of hiredis. ================================================ FILE: appveyor.yml ================================================ init: - git config --global core.autocrlf input environment: matrix: - nodejs_version: 6 - nodejs_version: 5 - nodejs_version: 4 - nodejs_version: 0.12 - nodejs_version: 0.10 platform: - x86 - x64 install: - ps: Install-Product node $env:nodejs_version $env:platform - git submodule update --init --recursive - npm install --msvs_version=2013 build: off test_script: - node --version - npm --version - "node test/reader.js" - "node test/writer.js" ================================================ FILE: bench.js ================================================ var hiredis = require("./hiredis"), num_clients = 10, active_clients = 0, pipeline = 0, num_requests = parseInt(process.argv[2]) || 20000, issued_requests = 0, test_start; var tests = []; tests.push({ descr: "PING", command: ["PING"] }); tests.push({ descr: "SET", command: ["SET", "foo", "bar"] }); tests.push({ descr: "GET", command: ["GET", "foo"] }); tests.push({ descr: "LPUSH 8 bytes", command: ["LPUSH", "mylist-8", new Buffer(Array(8).join("-"))] }); tests.push({ descr: "LPUSH 64 bytes", command: ["LPUSH", "mylist-64", new Buffer(Array(64).join("-"))] }); tests.push({ descr: "LPUSH 512 bytes", command: ["LPUSH", "mylist-512", new Buffer(Array(512).join("-"))] }); tests.push({ descr: "LRANGE 10 elements, 8 bytes", command: ["LRANGE", "mylist-8", "0", "9"] }); tests.push({ descr: "LRANGE 100 elements, 8 bytes", command: ["LRANGE", "mylist-8", "0", "99"] }); tests.push({ descr: "LRANGE 100 elements, 64 bytes", command: ["LRANGE", "mylist-64", "0", "99"] }); tests.push({ descr: "LRANGE 100 elements, 512 bytes", command: ["LRANGE", "mylist-512", "0", "99"] }); function call(client, test) { client.on("reply", function() { if (issued_requests < num_requests) { request(); } else { client.end(); if (--active_clients == 0) done(test); } }); function request() { issued_requests++; client.write.apply(client,test.command); }; request(); } function done(test) { var time = (new Date - test_start); var op_rate = (num_requests/(time/1000.0)).toFixed(2); console.log(test.descr + ": " + op_rate + " ops/sec"); next(); } function concurrent_test(test) { var i = num_clients; var client; issued_requests = 0; test_start = new Date; while(i-- && issued_requests < num_requests) { active_clients++; client = hiredis.createConnection(); call(client, test); } } function pipelined_test(test) { var client = hiredis.createConnection(); var received_replies = 0; issued_requests = 0; while (issued_requests < num_requests) { issued_requests++; client.write.apply(client,test.command); } test_start = new Date; client.on("reply", function() { if (++received_replies == num_requests) { client.end(); done(test); } }); } function next() { var test = tests.shift(); if (test) { if (pipeline) { pipelined_test(test); } else { concurrent_test(test); } } } next(); ================================================ FILE: binding.gyp ================================================ { 'targets': [ { 'target_name': 'hiredis', 'sources': [ 'src/hiredis.cc' , 'src/reader.cc' ], 'include_dirs': ["", "contributors": [ "Pieter Noordhuis " ], "main": "hiredis", "scripts": { "test": "node test/reader.js && node test/writer.js" }, "dependencies": { "bindings": "^1.2.1", "nan": "^2.3.4" }, "engines": { "node": ">= 0.10.0" }, "repository": { "type": "git", "url": "https://github.com/redis/hiredis-node.git" }, "bugs": { "url": "https://github.com/redis/hiredis-node/issues" }, "license": "BSD-3-Clause" } ================================================ FILE: src/hiredis.cc ================================================ #include "reader.h" using namespace v8; extern "C" { static NAN_MODULE_INIT(init) { hiredis::Reader::Initialize(target); } NODE_MODULE(hiredis, init) } ================================================ FILE: src/reader.cc ================================================ #include #include #include "reader.h" using namespace hiredis; static void *tryParentize(const redisReadTask *task, const Local &v) { Nan::HandleScope scope; Reader *r = reinterpret_cast(task->privdata); size_t pidx, vidx; if (task->parent != NULL) { pidx = (size_t)task->parent->obj; assert(pidx > 0 && pidx < 9); /* When there is a parent, it should be an array. */ Local lvalue = Nan::New(r->handle[pidx]); assert(lvalue->IsArray()); Local larray = lvalue.As(); larray->Set(task->idx,v); /* Store the handle when this is an inner array. Otherwise, hiredis * doesn't care about the return value as long as the value is set in * its parent array. */ vidx = pidx+1; if (v->IsArray()) { r->handle[vidx].Reset(v); return (void*)vidx; } else { /* Return value doesn't matter for inner value, as long as it is * not NULL (which means OOM for hiredis). */ return (void*)0xcafef00d; } } else { /* There is no parent, so this value is the root object. */ r->handle[1].Reset(v); return (void*)1; } } static void *createArray(const redisReadTask *task, int size) { Nan::HandleScope scope; return tryParentize(task, Nan::New(size)); } static void *createString(const redisReadTask *task, char *str, size_t len) { Nan::HandleScope scope; Reader *r = reinterpret_cast(task->privdata); Local v(r->createString(str,len)); if (task->type == REDIS_REPLY_ERROR) v = Exception::Error(v->ToString()); return tryParentize(task,v); } static void *createInteger(const redisReadTask *task, long long value) { Nan::HandleScope scope; return tryParentize(task, Nan::New(value)); } static void *createNil(const redisReadTask *task) { Nan::HandleScope scope; return tryParentize(task, Nan::Null()); } static redisReplyObjectFunctions v8ReplyFunctions = { createString, createArray, createInteger, createNil, 0 /* No free function: cleanup is done in Reader::Get. */ }; Reader::Reader(bool return_buffers) : return_buffers(return_buffers) { Nan::HandleScope scope; reader = redisReaderCreateWithFunctions(&v8ReplyFunctions); reader->privdata = this; #if _USE_CUSTOM_BUFFER_POOL if (return_buffers) { Local global = Context::GetCurrent()->Global(); Local bv = global->Get(String::NewSymbol("Buffer")); assert(bv->IsFunction()); Local bf = Local::Cast(bv); buffer_fn = Persistent::New(bf); buffer_pool_length = 8*1024; /* Same as node */ buffer_pool_offset = 0; node::Buffer *b = node::Buffer::New(buffer_pool_length); buffer_pool = Persistent::New(b->handle_); } #endif } Reader::~Reader() { redisReaderFree(reader); } /* Don't declare an extra scope here, so the objects are created within the * scope inherited from the caller (Reader::Get) and we don't have to the pay * the overhead. */ inline Local Reader::createString(char *str, size_t len) { if (return_buffers) { #if _USE_CUSTOM_BUFFER_POOL if (len > buffer_pool_length) { node::Buffer *b = node::Buffer::New(str,len); return Local::New(b->handle_); } else { return createBufferFromPool(str,len); } #else return Nan::CopyBuffer(str,len).ToLocalChecked(); #endif } else { return Nan::New(str,len).ToLocalChecked(); } } #if _USE_CUSTOM_BUFFER_POOL Local Reader::createBufferFromPool(char *str, size_t len) { HandleScope scope; Local argv[3]; Local instance; assert(len <= buffer_pool_length); if (buffer_pool_length - buffer_pool_offset < len) { node::Buffer *b = node::Buffer::New(buffer_pool_length); buffer_pool.Dispose(); buffer_pool = Persistent::New(b->handle_); buffer_pool_offset = 0; } memcpy(node::Buffer::Data(buffer_pool)+buffer_pool_offset,str,len); argv[0] = Local::New(buffer_pool); argv[1] = Integer::New(len); argv[2] = Integer::New(buffer_pool_offset); instance = buffer_fn->NewInstance(3,argv); buffer_pool_offset += len; return scope.Close(instance); } #endif NAN_METHOD(Reader::New) { bool return_buffers = false; if (info.Length() > 0 && info[0]->IsObject()) { Local bv = Nan::Get(info[0].As(), Nan::New("return_buffers").ToLocalChecked()).ToLocalChecked(); if (bv->IsBoolean()) return_buffers = Nan::To(bv).FromJust(); } Reader *r = new Reader(return_buffers); r->Wrap(info.This()); info.GetReturnValue().Set(info.This()); } NAN_MODULE_INIT(Reader::Initialize) { Nan::HandleScope scope; Local t = Nan::New(New); t->InstanceTemplate()->SetInternalFieldCount(1); Nan::SetPrototypeMethod(t, "feed", Feed); Nan::SetPrototypeMethod(t, "get", Get); Nan::Set(target, Nan::New("Reader").ToLocalChecked(), Nan::GetFunction(t).ToLocalChecked()); } NAN_METHOD(Reader::Feed) { Reader *r = Nan::ObjectWrap::Unwrap(info.This()); if (info.Length() == 0) { Nan::ThrowTypeError("First argument must be a string or buffer"); } else { if (node::Buffer::HasInstance(info[0])) { Local buffer_object = info[0].As(); char *data; size_t length; data = node::Buffer::Data(buffer_object); length = node::Buffer::Length(buffer_object); /* Can't handle OOM for now. */ assert(redisReaderFeed(r->reader, data, length) == REDIS_OK); } else if (info[0]->IsString()) { Nan::Utf8String str(info[0].As()); redisReplyReaderFeed(r->reader, *str, str.length()); } else { Nan::ThrowError("Invalid argument"); } } info.GetReturnValue().Set(info.This()); } NAN_METHOD(Reader::Get) { Reader *r = Nan::ObjectWrap::Unwrap(info.This()); void *index = NULL; Local reply; int i; if (redisReaderGetReply(r->reader,&index) == REDIS_OK) { if (index == 0) { return; } else { /* Complete replies should always have a root object at index 1. */ assert((size_t)index == 1); reply = Nan::New(r->handle[1]); /* Dispose and clear used handles. */ for (i = 1; i < 3; i++) { r->handle[i].Reset(); } } } else { Nan::ThrowError(r->reader->errstr); } info.GetReturnValue().Set(reply); } ================================================ FILE: src/reader.h ================================================ #include #include #if NODE_MODULE_VERSION < NODE_0_12_MODULE_VERSION #define _USE_CUSTOM_BUFFER_POOL 1 #else #define _USE_CUSTOM_BUFFER_POOL 0 #endif namespace hiredis { using namespace v8; class Reader : public Nan::ObjectWrap { public: Reader(bool); ~Reader(); static NAN_MODULE_INIT(Initialize); static NAN_METHOD(New); static NAN_METHOD(Feed); static NAN_METHOD(Get); /* Objects created by the reply object functions need to get back to the * reader when the reply is requested via Reader::Get(). Keep temporary * objects in this handle. Use an array of handles because replies may * include nested multi bulks and child-elements need to be added to the * right respective parent. handle[0] will be unused, so the real index of * an object in this array can be returned from the reply object functions. * The returned value needs to be non-zero to distinguish complete replies * from incomplete replies. These are persistent handles because * Reader::Get might not return a full reply and the objects need to be * kept around for subsequent calls. */ Nan::Persistent handle[9]; /* Helper function to create string/buffer objects. */ Local createString(char *str, size_t len); private: redisReader *reader; /* Determines whether to return strings or buffers for single line and bulk * replies. This defaults to false, so strings are returned by default. */ bool return_buffers; #if _USE_CUSTOM_BUFFER_POOL Local createBufferFromPool(char *str, size_t len); Persistent buffer_fn; Persistent buffer_pool; size_t buffer_pool_length; size_t buffer_pool_offset; #endif }; }; ================================================ FILE: test/reader.js ================================================ var assert = require("assert"), test = require("./testlib")(), hiredis = require("../hiredis"); test("CreateReader", function() { var reader = new hiredis.Reader(); assert.notEqual(reader, null); }); test("StatusReply", function() { var reader = new hiredis.Reader(); reader.feed("+OK\r\n"); assert.equal("OK", reader.get()); }); test("StatusReplyAsBuffer", function() { var reader = new hiredis.Reader({ return_buffers: true }); reader.feed("+OK\r\n"); var reply = reader.get(); assert.ok(Buffer.isBuffer(reply)); assert.equal("OK", reply.toString()); }); test("IntegerReply", function() { var reader = new hiredis.Reader(); reader.feed(":1\r\n"); assert.equal(1, reader.get()); }); test("LargeIntegerReply", function() { var reader = new hiredis.Reader(); reader.feed(":9223372036854775807\r\n"); // We test for a different value here, as JavaScript has no 64-bit integers, // only IEEE double precision floating point numbers assert.equal("9223372036854776000", String(reader.get())); }); test("ErrorReply", function() { var reader = new hiredis.Reader(); reader.feed("-ERR foo\r\n"); var reply = reader.get(); assert.equal(Error, reply.constructor); assert.equal("ERR foo", reply.message); }); test("ErrorReplyWithReturnBuffers", function() { var reader = new hiredis.Reader({ return_buffers: true }); reader.feed("-ERR foo\r\n"); var reply = reader.get(); assert.equal(Error, reply.constructor); assert.equal("ERR foo", reply.message); }); test("NullBulkReply", function() { var reader = new hiredis.Reader(); reader.feed("$-1\r\n"); assert.equal(null, reader.get()); }); test("EmptyBulkReply", function() { var reader = new hiredis.Reader(); reader.feed("$0\r\n\r\n"); assert.equal("", reader.get()); }); test("BulkReply", function() { var reader = new hiredis.Reader(); reader.feed("$3\r\nfoo\r\n"); assert.equal("foo", reader.get()); }); test("BulkReplyAsBuffer", function() { var reader = new hiredis.Reader({ return_buffers: true }); reader.feed("$3\r\nfoo\r\n"); var reply = reader.get(); assert.ok(Buffer.isBuffer(reply)); assert.equal("foo", reply.toString()); }); test("BulkReplyWithEncoding", function() { var reader = new hiredis.Reader(); reader.feed("$" + Buffer.byteLength("☃") + "\r\n☃\r\n"); assert.equal("☃", reader.get()); }); test("NullMultiBulkReply", function() { var reader = new hiredis.Reader(); reader.feed("*-1\r\n"); assert.equal(null, reader.get()); }); test("EmptyMultiBulkReply", function() { var reader = new hiredis.Reader(); reader.feed("*0\r\n"); assert.deepEqual([], reader.get()); }); test("MultiBulkReply", function() { var reader = new hiredis.Reader(); reader.feed("*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"); assert.deepEqual(["foo", "bar"], reader.get()); }); test("NestedMultiBulkReply", function() { var reader = new hiredis.Reader(); reader.feed("*2\r\n*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$3\r\nqux\r\n"); assert.deepEqual([["foo", "bar"], "qux"], reader.get()); }); test("DeeplyNestedMultiBulkReply", function() { var i; var reader = new hiredis.Reader(); var expected = 1; for (i = 0; i < 8; i++) { reader.feed("*1\r\n"); expected = [expected]; } reader.feed(":1\r\n"); assert.deepEqual(reader.get(), expected); }); test("TooDeeplyNestedMultiBulkReply", function() { var i; var reader = new hiredis.Reader(); for (i = 0; i < 9; i++) { reader.feed("*1\r\n"); } reader.feed(":1\r\n"); assert.throws( function() { reader.get(); }, /nested multi/ ); }); test("MultiBulkReplyWithNonStringValues", function() { var reader = new hiredis.Reader(); reader.feed("*3\r\n:1\r\n+OK\r\n$-1\r\n"); assert.deepEqual([1, "OK", null], reader.get()); }); test("FeedWithBuffer", function() { var reader = new hiredis.Reader(); reader.feed(new Buffer("$3\r\nfoo\r\n")); assert.deepEqual("foo", reader.get()); }); test("UndefinedReplyOnIncompleteFeed", function() { var reader = new hiredis.Reader(); reader.feed("$3\r\nfoo"); assert.deepEqual(undefined, reader.get()); reader.feed("\r\n"); assert.deepEqual("foo", reader.get()); }); test("Leaks", function() { /* The "leaks" utility is only available on OSX. */ if (process.platform != "darwin") return; var done = 0; var leaks = require('child_process').spawn("leaks", [process.pid]); leaks.stdout.on("data", function(data) { var str = data.toString(); var notice = "Node 0.2.5 always leaks 16 bytes (this is " + process.versions.node + ")"; var matches; if ((matches = /(\d+) leaks?/i.exec(str)) != null) { if (parseInt(matches[1]) > 0) { console.log(str); console.log('\x1B[31mNotice: ' + notice + '\x1B[0m'); } } done = 1; }); process.on('exit', function() { assert.ok(done, "Leaks test should have completed"); }); }); ================================================ FILE: test/testlib.js ================================================ module.exports = function() { function test(str, fn) { try { fn(); test.passed++; } catch (err) { console.log("\x1B[1;31m" + str + " failed!\x1B[0m"); console.log(err.stack + "\n"); test.failed++; } } test.passed = 0; test.failed = 0; return test; } ================================================ FILE: test/writer.js ================================================ var assert = require("assert"), test = require("./testlib")(), hiredis = require("../hiredis"); test("WriteCommand", function() { var reader = new hiredis.Reader(); reader.feed(hiredis.writeCommand("hello", "world")); assert.deepEqual(["hello", "world"], reader.get()); }); test("WriteUnicode", function() { var reader = new hiredis.Reader(); reader.feed(hiredis.writeCommand("béép")); assert.deepEqual(["béép"], reader.get()); }); test("WriteBuffer", function() { var reader = new hiredis.Reader({ return_buffers: true }); reader.feed(hiredis.writeCommand(new Buffer([0xC3, 0x28]))); var command = reader.get(); assert.equal(0xC3, command[0][0]); assert.equal(0x28, command[0][1]); }); test("WriteNumber", function() { var reader = new hiredis.Reader(); reader.feed(hiredis.writeCommand(3)); assert.deepEqual(["3"], reader.get()); });