UNPKG

@gis-ag/oniyi-http-plugin-cache-redis

Version:

Plugin responsible for caching responses into redis db

153 lines (129 loc) 4.75 kB
'use strict'; // 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;