UNPKG

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

Version:

Plugin responsible for caching responses into redis db

154 lines (132 loc) 5.81 kB
'use strict'; // node core modules // 3rd party modules const _ = require('lodash'); const debug = require('debug')('oniyi-http-plugin:cache:phase-lists:response:transform-response-data'); // internal modules const serializeData = data => (_.isString(data) ? data : JSON.stringify(data)); const isTtlValid = (ttl, minTtl) => _.isFinite(ttl) && ttl >= minTtl; // ttl should not be less then provided min value const defaultRegexParser = (regexResult, name) => (regexResult && regexResult[1] ? { [name]: parseInt(regexResult[1], 10) } : {}); /** * Method that applies multiple Regex patterns on a single text source * * @param {String} textToMatch Text used for regex matching * @param {Object} regexMap Map of regex patterns which are applied on textToMatch param * @param {Function} regexParser Parser method that can be provided in order to handle matched Array response */ const multipleRegexCompiler = (textToMatch, regexMap, regexParser = defaultRegexParser) => _.reduce( regexMap, (result, regex, name) => { const regexResult = textToMatch.match(regex); return regexResult ? Object.assign(result, regexParser(regexResult, name)) : result; }, {} ); /** * Method that calculates expiration time by validating different params in this exact order: * 1. Check within the cache-control header param * 2. Look into ttl param * 3. Look into expires header param * 4. Return null if 1-3 fails, which sets infinite expiration time for current data * @param {*} data Data that requires caching * @param {Boolean} isResponsePrivate Displays whether response is marked as private or not * @param {Number} ttl Time to Live indicator, provided by either initial params, * or by a single http request * @param {Number} minTtl Minimum time to live, provided by plugin options * @param {String} expires Date provided by response headers * @param {String} cacheControl Value provided by response headers, * where max-age and s-max-age are extracted from * @return {Number|null} */ const getExpirationTime = (data, isResponsePrivate, ttl, minTtl, expires, cacheControl) => { const now = Math.round(Date.now() / 1000); if (_.isString(cacheControl)) { // we have a cache-control header, check for s-maxage and maxage const { sMaxAge, maxAge } = multipleRegexCompiler(cacheControl, { sMaxAge: /s-max-age=([0-9]+)/, maxAge: /max-age=([0-9]+)/, }); // if we have a private cache-control, we are only interested in maxAge time. if (isResponsePrivate && maxAge) { return now + maxAge; } // otherwise get bot sMaxAge and maxAge (sMaxAge has higher priority) if (sMaxAge || maxAge) { return now + (sMaxAge || maxAge); } } // validate ttl, if present if (isTtlValid(ttl, minTtl)) { return now + ttl; } // if all previous fails, look into "expires" param if (_.isString(expires)) { return Math.round(Date.parse(expires) / 1000); } debug('Expiration time not provided/found \nWARNING: This data will have no expiration time -> %o', data); return null; }; /** *Method used for transforming response data into cacheable version. * * @param {*} data Response data which is being transformed * @param {Boolean} storeMultiResponse Indicates that multiple responses were combined and * response header should be aware of this information * @param {Object} response Original http response received from origin server * @param {Object} cachedResponse Cached version of an http response * @param {String} hash Hashed key used for caching transformed response data * @param {Boolean} isResponsePrivate Displays whether response is marked as private or not * @param {Number} ttl Time to Live indicator, provided by either initial params, * or by a single http request * @param {Number} minTtl Minimum time to live, provided by plugin options * * @return {Object} */ const transformResponseData = ({ data, storeMultiResponse, response, cachedResponse, hash, isResponsePrivate, ttl, minTtl, }) => { const { headers: { expires, 'cache-control': cacheControl } = {}, statusCode } = response; const raw = serializeData(data); const expireAt = getExpirationTime(data, isResponsePrivate, ttl, minTtl, expires, cacheControl); // if we have received data that was build by combining multiple http responses, // we want to handle it differently from storing single response data. if (storeMultiResponse) { return { expireAt, hash, raw, response: _.defaultsDeep({ headers: { storeMultiResponse } }, response), }; } const buildDataByStatusCode = { // 200: OK -> indicates that we should build a full data object 200: { expireAt, hash, raw, response, }, // 304: Not Modified -> Indicates that requested resource was not modified, // we can use the cached version, and we should only update expireAt field 304: { expireAt, hash, }, }; // only read statusCode when we have received the data from the cache, // and if cacheControl requires re-validation // otherwise force building of full data object by providing "200". return _.isString(cacheControl) && !(cacheControl.match(/must-revalidate|no-cache/) && cachedResponse) ? buildDataByStatusCode[200] : buildDataByStatusCode[statusCode]; }; module.exports = transformResponseData;