@gis-ag/oniyi-http-plugin-cache-redis
Version:
Plugin responsible for caching responses into redis db
153 lines (129 loc) • 4.75 kB
JavaScript
;
// node core modules
// 3rd party modules
const _ = require('lodash');
const debug = require('debug')('oniyi-http-plugin:cache:phase-lists:response:hooks:add-to-cache');
// internal modules
const { constants: { PHASE_NAME } } = require('./utils');
const transformResponseData = require('./../transform-response-data');
const addToCache = (cache, pluginOptions) => ({
phaseName: PHASE_NAME,
handler: (ctx, next) => {
const {
options: { plugins = {} },
response = {},
responseBody,
hookState: {
cache: {
hashedId,
privateHashedId,
response: cachedResponse,
body: cachedBody,
abortCaching,
isResponsePrivate,
} = {},
},
} = ctx;
const { statusCode } = response;
// while receiving response with statusCode = 304, it's body is empty by default.
// we should check if there is a cached version of that response/body
// and update the context object if so.
if (cachedResponse && cachedBody && statusCode === 304) {
Object.assign(ctx, {
response: cachedResponse,
responseBody: cachedBody,
});
debug(
'Response updated from the cache -> %o \nResponse body updated from the cache -> %o',
cachedResponse,
cachedBody
);
}
if (abortCaching) {
// Reason for aborting logged by previous plugin hook handler. Just skip plugin operations
next();
return;
}
// we don't want to let single http request mutates minTtl provided by plugin options
const { ttl, delayCaching } = _.merge({}, pluginOptions, _.omit(plugins.cache, ['minTtl']));
const hash = isResponsePrivate ? privateHashedId : hashedId;
/**
* Method that performs storing provided data into cache.
*
* @param {*} data Response data that should be transformed before cached
* @param {Boolean} [storeMultiResponse] Indicates that multiple responses were combined and
* should be stored as such
*/
const storeData = ({ data = {}, storeMultiResponse }) => {
debug('Preparing to cache the data -> %o', data);
const transformedData = transformResponseData({
data,
storeMultiResponse,
response,
cachedResponse,
hash,
isResponsePrivate,
ttl,
minTtl: pluginOptions.minTtl,
});
if (!transformedData) {
debug(
'Unable to perform caching for response -> %o \nReason: Unsupported status code: [%s]',
response,
response.statusCode
);
return;
}
debug('Data is ready for caching -> %o', _.omit(transformedData, ['response']));
// response not present -> update expireAt only
if (!transformedData.response) {
cache.putWithCommand('expire', { hash: transformedData.hash, data: transformedData.expireAt }, (err) => {
if (err) {
debug(
'While trying to update the expiration time for id [%s], an error has occurred -> %o',
transformedData.hash,
err
);
return;
}
const expirationTime = transformedData.expireAt - Math.round(Date.now() / 1000);
debug('Stored cache [%s] will expire in [%d] seconds', transformedData.hash, expirationTime);
});
return;
}
cache.put(transformedData, (err) => {
if (err) {
debug(
'While trying to store the data in cache for id [%s], an error has occurred -> %o',
transformedData.hash,
err
);
return;
}
const expirationTime = transformedData.expireAt - Math.round(Date.now() / 1000);
debug('Stored cache [%s] will expire in [%d] seconds', transformedData.hash, expirationTime);
});
};
// if this option is true, it means that we want to cache the response data "later"
if (delayCaching) {
response.once('addToCache', storeData);
next();
return;
}
// otherwise, set the data for caching and continue with next phaseList handler
storeData({ data: responseBody });
// provide option to remove currently cached data
response.once('removeFromCache', () => {
debug('Preparing to purge the data with id: [%s]', hash);
cache.purge(hash, (err, success) => {
if (err) {
debug('An error has occurred while trying to remove the data from the cache for id [%s]', hash);
return;
}
debug('Data has been removed: [%s]', success);
});
});
next();
},
});
module.exports = addToCache;