All files / lib/middleware cache.js

36% Statements 27/75
20.59% Branches 7/34
20% Functions 2/10
39.39% Lines 26/66

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169              1x 1x   1x 1x 1x 1x   1x   1x   1x     1x         1x   1x 1x   1x 1x   1x 1x   1x                 1x     1x   1x                 1x                                                 1x                 1x                   1x                                                     1x                                                                        
"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;
 
Iif (!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);
 
    Eif (!inited || opts.force) {
        inited = new Promise(resolve => {
 
            var fillcb = null;
            Iif (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 } });
 
            Eif (!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);
    };
};