123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- /*!
- * Connect - Redis
- * Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
- * MIT Licensed
- */
- var debug = require('debug')('connect:redis');
- var redis = require('redis');
- var util = require('util');
- var noop = function(){};
- /**
- * One day in seconds.
- */
- var oneDay = 86400;
- function getTTL(store, sess, sid) {
- if (typeof store.ttl === 'number' || typeof store.ttl === 'string') return store.ttl;
- if (typeof store.ttl === 'function') return store.ttl(store, sess, sid);
- if (store.ttl) throw new TypeError('`store.ttl` must be a number or function.');
- var maxAge = sess.cookie.maxAge;
- return (typeof maxAge === 'number'
- ? Math.floor(maxAge / 1000)
- : oneDay);
- }
- /**
- * Return the `RedisStore` extending `express`'s session Store.
- *
- * @param {object} express session
- * @return {Function}
- * @api public
- */
- module.exports = function (session) {
- /**
- * Express's session Store.
- */
- var Store = session.Store;
- /**
- * Initialize RedisStore with the given `options`.
- *
- * @param {Object} options
- * @api public
- */
- function RedisStore (options) {
- if (!(this instanceof RedisStore)) {
- throw new TypeError('Cannot call RedisStore constructor as a function');
- }
- var self = this;
- options = options || {};
- Store.call(this, options);
- this.prefix = options.prefix == null
- ? 'sess:'
- : options.prefix;
- delete options.prefix;
- this.scanCount = Number(options.scanCount) || 100;
- delete options.scanCount;
- this.serializer = options.serializer || JSON;
- if (options.url) {
- options.socket = options.url;
- }
- // convert to redis connect params
- if (options.client) {
- this.client = options.client;
- }
- else if (options.socket) {
- this.client = redis.createClient(options.socket, options);
- }
- else {
- this.client = redis.createClient(options);
- }
- // logErrors
- if(options.logErrors){
- // if options.logErrors is function, allow it to override. else provide default logger. useful for large scale deployment
- // which may need to write to a distributed log
- if(typeof options.logErrors != 'function'){
- options.logErrors = function (err) {
- console.error('Warning: connect-redis reported a client error: ' + err);
- };
- }
- this.client.on('error', options.logErrors);
- }
- if (options.pass) {
- this.client.auth(options.pass, function (err) {
- if (err) {
- throw err;
- }
- });
- }
- this.ttl = options.ttl;
- this.disableTTL = options.disableTTL;
- if (options.unref) this.client.unref();
- if ('db' in options) {
- if (typeof options.db !== 'number') {
- console.error('Warning: connect-redis expects a number for the "db" option');
- }
- self.client.select(options.db);
- self.client.on('connect', function () {
- self.client.select(options.db);
- });
- }
- self.client.on('error', function (er) {
- debug('Redis returned err', er);
- self.emit('disconnect', er);
- });
- self.client.on('connect', function () {
- self.emit('connect');
- });
- }
- /**
- * Inherit from `Store`.
- */
- util.inherits(RedisStore, Store);
- /**
- * Attempt to fetch session by the given `sid`.
- *
- * @param {String} sid
- * @param {Function} fn
- * @api public
- */
- RedisStore.prototype.get = function (sid, fn) {
- var store = this;
- var psid = store.prefix + sid;
- if (!fn) fn = noop;
- debug('GET "%s"', sid);
- store.client.get(psid, function (er, data) {
- if (er) return fn(er);
- if (!data) return fn();
- var result;
- data = data.toString();
- debug('GOT %s', data);
- try {
- result = store.serializer.parse(data);
- }
- catch (er) {
- return fn(er);
- }
- return fn(null, result);
- });
- };
- /**
- * Commit the given `sess` object associated with the given `sid`.
- *
- * @param {String} sid
- * @param {Session} sess
- * @param {Function} fn
- * @api public
- */
- RedisStore.prototype.set = function (sid, sess, fn) {
- var store = this;
- var args = [store.prefix + sid];
- if (!fn) fn = noop;
- try {
- var jsess = store.serializer.stringify(sess);
- }
- catch (er) {
- return fn(er);
- }
- args.push(jsess);
- if (!store.disableTTL) {
- var ttl = getTTL(store, sess, sid);
- args.push('EX', ttl);
- debug('SET "%s" %s ttl:%s', sid, jsess, ttl);
- } else {
- debug('SET "%s" %s', sid, jsess);
- }
- store.client.set(args, function (er) {
- if (er) return fn(er);
- debug('SET complete');
- fn.apply(null, arguments);
- });
- };
- /**
- * Destroy the session associated with the given `sid`.
- *
- * @param {String} sid
- * @api public
- */
- RedisStore.prototype.destroy = function (sid, fn) {
- debug('DEL "%s"', sid);
- if (Array.isArray(sid)) {
- var multi = this.client.multi();
- var prefix = this.prefix;
- sid.forEach(function (s) {
- multi.del(prefix + s);
- });
- multi.exec(fn);
- } else {
- sid = this.prefix + sid;
- this.client.del(sid, fn);
- }
- };
- /**
- * Refresh the time-to-live for the session with the given `sid`.
- *
- * @param {String} sid
- * @param {Session} sess
- * @param {Function} fn
- * @api public
- */
- RedisStore.prototype.touch = function (sid, sess, fn) {
- var store = this;
- var psid = store.prefix + sid;
- if (!fn) fn = noop;
- if (store.disableTTL) return fn();
- var ttl = getTTL(store, sess);
- debug('EXPIRE "%s" ttl:%s', sid, ttl);
- store.client.expire(psid, ttl, function (er) {
- if (er) return fn(er);
- debug('EXPIRE complete');
- fn.apply(this, arguments);
- });
- };
- /**
- * Fetch all sessions' Redis keys using non-blocking SCAN command
- *
- * @param {Function} fn
- * @api private
- */
- function allKeys (store, cb) {
- var keysObj = {}; // Use an object to dedupe as scan can return duplicates
- var pattern = store.prefix + '*';
- var scanCount = store.scanCount;
- debug('SCAN "%s"', pattern);
- (function nextBatch (cursorId) {
- store.client.scan(cursorId, 'match', pattern, 'count', scanCount, function (err, result) {
- if (err) return cb(err);
- var nextCursorId = result[0];
- var keys = result[1];
- debug('SCAN complete (next cursor = "%s")', nextCursorId);
- keys.forEach(function (key) {
- keysObj[key] = 1;
- });
- if (nextCursorId != 0) {
- // next batch
- return nextBatch(nextCursorId);
- }
- // end of cursor
- return cb(null, Object.keys(keysObj));
- });
- })(0);
- }
- /**
- * Fetch all sessions' ids
- *
- * @param {Function} fn
- * @api public
- */
- RedisStore.prototype.ids = function (fn) {
- var store = this;
- var prefixLength = store.prefix.length;
- if (!fn) fn = noop;
- allKeys(store, function (err, keys) {
- if (err) return fn(err);
- keys = keys.map(function (key) {
- return key.substr(prefixLength);
- });
- return fn(null, keys);
- });
- };
- /**
- * Fetch all sessions
- *
- * @param {Function} fn
- * @api public
- */
- RedisStore.prototype.all = function (fn) {
- var store = this;
- var prefixLength = store.prefix.length;
- if (!fn) fn = noop;
- allKeys(store, function (err, keys) {
- if (err) return fn(err);
- if (keys.length === 0) return fn(null,[]);
- store.client.mget(keys, function (err, sessions) {
- if (err) return fn(err);
- var result;
- try {
- result = sessions.map(function (data, index) {
- data = data.toString();
- data = store.serializer.parse(data);
- data.id = keys[index].substr(prefixLength);
- return data;
- });
- } catch (e) {
- err = e;
- }
- return fn(err, result);
- });
- });
- };
- return RedisStore;
- };
|