Path: blob/master/lib/rammerhead/src/classes/RammerheadSessionFileCache.js
5253 views
const fs = require('fs');1const path = require('path');2const RammerheadSessionAbstractStore = require('./RammerheadSessionAbstractStore');3const RammerheadSession = require('./RammerheadSession');4const RammerheadLogging = require('../classes/RammerheadLogging');56// rh = rammerhead. extra f to distinguish between rhsession (folder) and rhfsession (file)7const sessionFileExtension = '.rhfsession';89class RammerheadSessionFileCache extends RammerheadSessionAbstractStore {10/**11*12* @param {object} options13* @param {string} options.saveDirectory - all cacheTimeouted sessions will be saved in this folder14* to avoid storing all the sessions in the memory.15* @param {RammerheadLogging|undefined} options.logger16* @param {number} options.cacheTimeout - timeout before saving cache to disk and deleting it from the cache17* @param {number} options.cacheCheckInterval18* @param {boolean} options.deleteUnused - (default: true) if set to true, it deletes unused sessions when saving cache to disk19* @param {boolean} options.deleteCorruptedSessions - (default: true) if set to true, auto-deletes session files that20* give a parse error (happens when nodejs exits abruptly while serializing session to disk)21* @param {object|null} options.staleCleanupOptions - set to null to disable cleaning up stale sessions22* @param {number|null} options.staleCleanupOptions.staleTimeout - stale sessions that are inside saveDirectory that go over23* this timeout will be deleted. Set to null to disable.24* @param {number|null} options.staleCleanupOptions.maxToLive - any created sessions that are older than this will be deleted no matter the usage.25* Set to null to disable.26* @param {number} options.staleCleanupOptions.staleCheckInterval27*/28constructor({29saveDirectory = path.join(__dirname, '../../sessions'),30logger = new RammerheadLogging({ logLevel: 'disabled' }),31cacheTimeout = 1000 * 60 * 20, // 20 minutes32cacheCheckInterval = 1000 * 60 * 10, // 10 minutes,33deleteUnused = true,34deleteCorruptedSessions = true,35staleCleanupOptions = {36staleTimeout: 1000 * 60 * 60 * 24 * 1, // 1 day37maxToLive: 1000 * 60 * 60 * 24 * 4, // four days38staleCheckInterval: 1000 * 60 * 60 * 1 // 1 hour39}40} = {}) {41super();42this.saveDirectory = saveDirectory;43this.logger = logger;44this.deleteUnused = deleteUnused;45this.cacheTimeout = cacheTimeout;46this.deleteCorruptedSessions = deleteCorruptedSessions;47/**48* @type {Map.<string, RammerheadSession>}49*/50this.cachedSessions = new Map();51setInterval(() => this._saveCacheToDisk(), cacheCheckInterval).unref();52if (staleCleanupOptions) {53this._removeStaleSessions(staleCleanupOptions.staleTimeout, staleCleanupOptions.maxToLive);54setInterval(55() => this._removeStaleSessions(staleCleanupOptions.staleTimeout, staleCleanupOptions.maxToLive),56staleCleanupOptions.staleCheckInterval57).unref();58}59}6061keysStore() {62return fs63.readdirSync(this.saveDirectory)64.filter((file) => file.endsWith(sessionFileExtension))65.map((file) => file.slice(0, -sessionFileExtension.length));66}67/**68* @returns {string[]} - list of session ids in store69*/70keys() {71let arr = this.keysStore();72for (const id of this.cachedSessions.keys()) {73if (!arr.includes(id)) arr.push(id);74}75return arr;76}77/**78* @param {string} id79* @returns {boolean}80*/81has(id) {82return this.cachedSessions.has(id) || fs.existsSync(this._getSessionFilePath(id));83}84/**85* @param {string} id86* @param {boolean} updateActiveTimestamp87* @returns {RammerheadSession|undefined}88*/89get(id, updateActiveTimestamp = true, cacheToMemory = true) {90if (!this.has(id)) {91this.logger.debug(`(FileCache.get) ${id} does not exist`);92return;93}9495this.logger.debug(`(FileCache.get) ${id}`);96if (this.cachedSessions.has(id)) {97this.logger.debug(`(FileCache.get) returning memory cached session ${id}`);98return this.cachedSessions.get(id);99}100101let session;102try {103session = RammerheadSession.DeserializeSession(id, fs.readFileSync(this._getSessionFilePath(id)));104} catch (e) {105if (e.name === 'SyntaxError' && e.message.includes('JSON')) {106this.logger.warn(`(FileCache.get) ${id} bad JSON`);107if (this.deleteCorruptedSessions) {108this.delete(id);109this.logger.warn(`(FileCache.get) ${id} deleted because of bad JSON`);110}111return;112}113}114115if (updateActiveTimestamp) {116this.logger.debug(`(FileCache.get) ${id} update active timestamp`);117session.updateLastUsed();118}119120if (cacheToMemory) {121this.cachedSessions.set(id, session);122this.logger.debug(`(FileCache.get) saved ${id} into cache memory`);123}124125return session;126}127/**128* @param {string} id129* @returns {RammerheadSession}130*/131add(id) {132if (this.has(id)) throw new Error(`session ${id} already exists`);133134fs.writeFileSync(this._getSessionFilePath(id), new RammerheadSession().serializeSession());135136this.logger.debug(`FileCache.add ${id}`);137138return this.get(id);139}140/**141* @param {string} id142* @returns {boolean} - returns true when a delete operation is performed143*/144delete(id) {145if (id === RammerheadSession.autocompleteId) return false;146this.logger.debug(`(FileCache.delete) deleting ${id}`);147if (this.has(id)) {148fs.unlinkSync(this._getSessionFilePath(id));149this.cachedSessions.delete(id);150this.logger.debug(`(FileCache.delete) deleted ${id}`);151return true;152}153this.logger.debug(`(FileCache.delete) ${id} does not exist`);154return false;155}156/**157* @param {string} id158* @param {string} serializedSession159*/160addSerializedSession(id, serializedSession) {161this.logger.debug(`(FileCache.addSerializedSession) adding serialized session id ${id} to store`);162const session = RammerheadSession.DeserializeSession(id, serializedSession);163fs.writeFileSync(this._getSessionFilePath(id), session.serializeSession());164this.logger.debug(`(FileCache.addSerializedSession) added ${id} to cache`);165}166close() {167this.logger.debug(`(FileCache.close) calling _saveCacheToDisk`);168this._saveCacheToDisk(true);169}170171/**172* @private173* @param {string} id174* @returns {string} - generated file path to session175*/176_getSessionFilePath(id) {177return path.join(this.saveDirectory, id.replace(/\/|\\/g, '') + sessionFileExtension);178}179/**180* @private181* @param {number|null} staleTimeout182* @param {number|null} maxToLive183*/184_removeStaleSessions(staleTimeout, maxToLive) {185const sessionIds = this.keysStore();186let deleteCount = 0;187this.logger.debug(`(FileCache._removeStaleSessions) Need to go through ${sessionIds.length} sessions in store`);188189const now = Date.now();190for (const id of sessionIds) {191const session = this.get(id, false, false);192if (!session) {193this.logger.debug(`(FileCache._removeStaleSessions) skipping ${id} as .get() returned undefined`);194continue;195}196if (197(staleTimeout && now - session.lastUsed > staleTimeout) ||198(maxToLive && now - session.createdAt > maxToLive)199) {200this.delete(id);201deleteCount++;202this.logger.debug(`(FileCache._removeStaleSessions) deleted ${id}`);203}204}205206this.logger.debug(`(FileCache._removeStaleSessions) Deleted ${deleteCount} sessions from store`);207}208/**209* @private210*/211_saveCacheToDisk(forceSave) {212let deleteCount = 0;213this.logger.debug(`(FileCache._saveCacheToDisk) need to go through ${this.cachedSessions.size} sessions`);214215const now = Date.now();216for (const [sessionId, session] of this.cachedSessions) {217if (forceSave || now - session.lastUsed > this.cacheTimeout) {218if (session.lastUsed === session.createdAt && this.deleteUnused) {219this.cachedSessions.delete(sessionId);220deleteCount++;221this.logger.debug(`(FileCache._saveCacheToDisk) deleted unused ${sessionId} from memory`);222} else {223fs.writeFileSync(this._getSessionFilePath(sessionId), session.serializeSession());224this.cachedSessions.delete(sessionId);225deleteCount++;226this.logger.debug(227`(FileCache._saveCacheToDisk) removed ${sessionId} from memory and saved to store`228);229}230}231}232233this.logger.debug(`(FileCache._saveCacheToDisk) Removed ${deleteCount} sessions from memory`);234}235}236237module.exports = RammerheadSessionFileCache;238239240