"use strict"; /** * Middleware to cache responses. * * @module */ var fs = require("fs"); const util = require("util"); var cacheManager = require("cache-manager"); var fse = require("fs-extra"); var fsStore = require("cache-manager-fs"); var U = require("glace-utils"); var CONF = require("../config"); var LOG = U.logger; if (!CONF.cache.existing && fs.existsSync(CONF.cache.folder)) { fse.removeSync(CONF.cache.folder); }; fse.mkdirsSync(CONF.cache.folder); var diskCache; var inited; var init = opts => { opts = U.defVal(opts, {}); opts.force = U.defVal(opts.force, false); if (!inited || opts.force) { inited = new Promise(resolve => { var fillcb = null; if (CONF.cache.existing) fillcb = resolve; diskCache = cacheManager.caching({ store: fsStore, options: { ttl: CONF.cache.ttl, maxsize: CONF.cache.size, path: CONF.cache.folder, preventfill: !CONF.cache.existing, fillcallback: fillcb } }); if (!CONF.cache.existing) resolve(); }); }; return inited; }; init(); /** * Middleware to cache server responses and reply them. * * @function * @this BaseProxy * @return {boolean} - `true` if response was retrieved from cache, `false` * otherwise. */ var cache = module.exports = async function () { if (!this.useCache) return false; if (!diskCache) return false; for (var skipped of cache.skipped.req) { if (skipped(this.req)) return false; }; var req = this.req; var res = this.res; var result; if (await fromCache(req, res)) { result = true; } else { toCache(req, res); result = false; }; return result; }; /** * @prop {object} skipped - Filters to skip caching. * @prop {function[]} [skipped.req=[]] - List of filters to skip by request. * @prop {function[]} [skipped.res=[]] - List of filters to skip by response. */ cache.skipped = { req: [], res: [] }; /** * Initializes cache. Does nothing if it is initialized already and no force option. * * @function * @arg {object} [opts] - Initialization options. * @arg {boolean} [opts.force=false] - Force reinitialize cache. * @return {Promise} */ cache.init = init; /** * Patches http response to send response from cache, if it is there. * * @function * @arg {object} req - http(s) request * @arg {object} res - http(s) response * @return {Promise.<boolean>} - `true` if response is in cache and * was patched, otherwise `false` */ var fromCache = (req, res) => { return new Promise((resolve, reject) => { diskCache.get(U.getReqKey(req), (err, result) => { if (err) return reject(err); resolve(result); }); }).then(cached => { if (!cached) return false; LOG.silly(util.format("[cache] <<<<", U.getReqKey(req))); var response = JSON.parse(cached); res.writeHead(response.code, response.headers); res.end(Buffer.from(response.data)); return true; }); }; /** * Patches http response to put response to cache. * * @function * @arg {object} req - http(s) request * @arg {object} res - http(s) response */ var toCache = (req, res) => { var resWrite = res.write; var resEnd = res.end; var chunks = []; res.write = function (chunk) { if (chunk instanceof Buffer) chunks.push(chunk); resWrite.apply(this, arguments); }; res.end = function (chunk) { if (chunk instanceof Buffer) chunks.push(chunk); var data = Buffer.concat(chunks).toJSON().data; if (res.statusCode !== 200 || data.length === 0) { return resEnd.apply(this, arguments); }; for (var skipped of cache.skipped.res) { if (skipped(res)) { return resEnd.apply(this, arguments); }; }; var resData = JSON.stringify({ data: data, code: res.statusCode, headers: res.headers }); LOG.silly(util.format("[cache] >>>>", U.getReqKey(req))); diskCache.set(U.getReqKey(req), resData); resEnd.apply(this, arguments); }; };