diff --git a/README.md b/README.md index 8f5d4f1..14cc5d1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ Current version: **1.0.x** [![Build Status](https://api.travis-ci.org/hapijs/cat ### Options - `maxByteSize` - sets an upper limit on the number of bytes that can be stored in the - cached. Once this limit is reached no additional items will be added to the cache + cache. Once this limit is reached no additional items will be added to the cache until some expire. The utilized memory calculation is a rough approximation and must not be relied on. Defaults to `104857600` (100MB). +- `allowMixedContent` - by default, all data is cached as JSON strings, and converted + to an object using `JSON.parse()` on retrieval. By setting this option to `true`, + `Buffer` data can be stored alongside the stringified data. `Buffer`s are not + stringified, and are copied before storage to prevent the value from changing while + in the cache. Defaults to `false`. \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 6071c32..6841c3d 100755 --- a/lib/index.js +++ b/lib/index.js @@ -9,24 +9,35 @@ var internals = {}; internals.defaults = { - maxByteSize: 100 * 1024 * 1024 // 100MB + maxByteSize: 100 * 1024 * 1024, // 100MB + allowMixedContent: false }; // Provides a named reference for memory debugging internals.MemoryCacheSegment = function MemoryCacheSegment () { }; -internals.MemoryCacheEntry = function MemoryCacheEntry (key, value, ttl) { +internals.MemoryCacheEntry = function MemoryCacheEntry (key, value, ttl, allowMixedContent) { - // stringify() to prevent value from changing while in the cache - var stringifiedValue = JSON.stringify(value); + var valueByteSize = 0; + + if (allowMixedContent && Buffer.isBuffer(value)) { + this.item = new Buffer(value.length); + // copy buffer to prevent value from changing while in the cache + value.copy(this.item); + valueByteSize = this.item.length; + } + else { + // stringify() to prevent value from changing while in the cache + this.item = JSON.stringify(value); + valueByteSize = Buffer.byteLength(this.item); + } - this.item = stringifiedValue; this.stored = Date.now(); this.ttl = ttl; // Approximate cache entry size without value: 144 bytes - this.byteSize = 144 + Buffer.byteLength(stringifiedValue) + Buffer.byteLength(key.segment) + Buffer.byteLength(key.id); + this.byteSize = 144 + valueByteSize + Buffer.byteLength(key.segment) + Buffer.byteLength(key.id); this.timeoutId = null; }; @@ -36,6 +47,7 @@ exports = module.exports = internals.Connection = function MemoryCache (options) Hoek.assert(this.constructor === internals.Connection, 'Memory cache client must be instantiated using new'); Hoek.assert(!options || options.maxByteSize === undefined || options.maxByteSize >= 0, 'Invalid cache maxByteSize value'); + Hoek.assert(!options || options.allowMixedContent === undefined || typeof options.allowMixedContent === 'boolean', 'Invalid allowMixedContent value'); this.settings = Hoek.applyToDefaults(internals.defaults, options || {}); this.cache = null; @@ -102,11 +114,16 @@ internals.Connection.prototype.get = function (key, callback) { } var value = null; - try { - value = JSON.parse(envelope.item); + + if (Buffer.isBuffer(envelope.item)) { + value = envelope.item; } - catch (err) { - return callback(new Error('Bad value content')); + else { + value = internals.parseJSON(envelope.item); + + if (value instanceof Error) { + return callback(new Error('Bad value content')); + } } var result = { @@ -135,7 +152,7 @@ internals.Connection.prototype.set = function (key, value, ttl, callback) { var envelope = null; try { - envelope = new internals.MemoryCacheEntry(key, value, ttl); + envelope = new internals.MemoryCacheEntry(key, value, ttl, this.settings.allowMixedContent); } catch (err) { return callback(err); } @@ -192,3 +209,18 @@ internals.Connection.prototype.drop = function (key, callback) { return callback(); }; + + +internals.parseJSON = function (json) { + + var obj = null; + + try { + obj = JSON.parse(json); + } + catch (err) { + obj = err; + } + + return obj; +}; diff --git a/test/index.js b/test/index.js index 54cf11e..2e82f8f 100755 --- a/test/index.js +++ b/test/index.js @@ -74,6 +74,74 @@ describe('Memory', function () { }); }); + it('buffers can be set and retrieved when allowMixedContent is true', function (done) { + + var buffer = new Buffer('string value'); + var client = new Catbox.Client(new Memory({ allowMixedContent: true })); + + client.start(function (err) { + + var key = { id: 'x', segment: 'test' }; + + client.set(key, buffer, 500, function (err) { + + expect(err).to.not.exist; + client.get(key, function (err, result) { + + expect(err).to.not.exist; + expect(result.item instanceof Buffer).to.equal(true); + expect(result.item).to.deep.equal(buffer); + done(); + }); + }); + }); + }); + + it('buffers are copied before storing when allowMixedContent is true', function (done) { + + var buffer = new Buffer('string value'); + var client = new Catbox.Client(new Memory({ allowMixedContent: true })); + + client.start(function (err) { + + var key = { id: 'x', segment: 'test' }; + + client.set(key, buffer, 500, function (err) { + + expect(err).to.not.exist; + client.get(key, function (err, result) { + + expect(err).to.not.exist; + expect(result.item).to.not.equal(buffer); + done(); + }); + }); + }); + }); + + it('buffers are stringified when allowMixedContent is not true', function (done) { + + var buffer = new Buffer('string value'); + var client = new Catbox.Client(new Memory()); + + client.start(function (err) { + + var key = { id: 'x', segment: 'test' }; + + client.set(key, buffer, 500, function (err) { + + expect(err).to.not.exist; + client.get(key, function (err, result) { + + expect(err).to.not.exist; + expect(result.item instanceof Buffer).to.equal(false); + expect(result.item).to.deep.equal(JSON.parse(JSON.stringify(buffer))); + done(); + }); + }); + }); + }); + it('gets an item after setting it (no memory limit)', function (done) { var client = new Catbox.Client(new Memory({ maxByteSize: 0 }));