UNPKG

tastypie

Version:

Tastypie is a webservice API framework for Node.js based on Django's Tastypie Framework. It provides a convenient, yet powerful and highly customizable, abstraction for creating REST-style interfaces

242 lines (166 loc) 5.81 kB
'use strict'; // Load modules const Hoek = require('hoek'); // Declare internals const internals = {}; internals.defaults = { 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, allowMixedContent) { let 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.stored = Date.now(); this.ttl = ttl; // Approximate cache entry size without value: 144 bytes this.byteSize = 144 + valueByteSize + Buffer.byteLength(key.segment) + Buffer.byteLength(key.id); this.timeoutId = null; }; 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; }; internals.Connection.prototype.start = function (callback) { callback = Hoek.nextTick(callback); if (!this.cache) { this.cache = {}; this.byteSize = 0; } return callback(); }; internals.Connection.prototype.stop = function () { // Clean up pending eviction timers. if (this.cache) { const segments = Object.keys(this.cache); for (let i = 0; i < segments.length; ++i) { const segment = segments[i]; const keys = Object.keys(this.cache[segment]); for (let j = 0; j < keys.length; ++j) { const key = keys[j]; clearTimeout(this.cache[segment][key].timeoutId); } } } this.cache = null; this.byteSize = 0; return; }; internals.Connection.prototype.isReady = function () { return !!this.cache; }; internals.Connection.prototype.validateSegmentName = function (name) { if (!name) { return new Error('Empty string'); } if (name.indexOf('\u0000') !== -1) { return new Error('Includes null character'); } return null; }; internals.Connection.prototype.get = function (key, callback) { callback = Hoek.nextTick(callback); if (!this.cache) { return callback(new Error('Connection not started')); } const segment = this.cache[key.segment]; if (!segment) { return callback(null, null); } const envelope = segment[key.id]; if (!envelope) { return callback(null, null); } let value = null; if (Buffer.isBuffer(envelope.item)) { value = envelope.item; } else { value = internals.parseJSON(envelope.item); if (value instanceof Error) { return callback(new Error('Bad value content')); } } const result = { item: value, stored: envelope.stored, ttl: envelope.ttl }; return callback(null, result); }; internals.Connection.prototype.set = function (key, value, ttl, callback) { callback = Hoek.nextTick(callback); if (!this.cache) { return callback(new Error('Connection not started')); } if (ttl > 2147483647) { // Math.pow(2, 31) return callback(new Error('Invalid ttl (greater than 2147483647)')); } let envelope = null; try { envelope = new internals.MemoryCacheEntry(key, value, ttl, this.settings.allowMixedContent); } catch (err) { return callback(err); } this.cache[key.segment] = this.cache[key.segment] || new internals.MemoryCacheSegment(); const segment = this.cache[key.segment]; const cachedItem = segment[key.id]; if (cachedItem && cachedItem.timeoutId) { clearTimeout(cachedItem.timeoutId); this.byteSize -= cachedItem.byteSize; // If the item existed, decrement the byteSize as the value could be different } if (this.settings.maxByteSize) { if (this.byteSize + envelope.byteSize > this.settings.maxByteSize) { return callback(new Error('Cache size limit reached')); } } const timeoutId = setTimeout(() => { this.drop(key, () => {}); }, ttl); envelope.timeoutId = timeoutId; segment[key.id] = envelope; this.byteSize += envelope.byteSize; return callback(null); }; internals.Connection.prototype.drop = function (key, callback) { callback = Hoek.nextTick(callback); if (!this.cache) { return callback(new Error('Connection not started')); } const segment = this.cache[key.segment]; if (segment) { const item = segment[key.id]; if (item) { clearTimeout(item.timeoutId); this.byteSize -= item.byteSize; } delete segment[key.id]; } return callback(); }; internals.parseJSON = function (json) { let obj = null; try { obj = JSON.parse(json); } catch (err) { obj = err; } return obj; };