diff --git a/services/oembed/src/app.js b/services/oembed/src/app.js index 264dcce33..7fb1c8914 100644 --- a/services/oembed/src/app.js +++ b/services/oembed/src/app.js @@ -3,6 +3,7 @@ const helmet = require('helmet'); const { json } = require('body-parser'); const { asyncRoute } = require('@parameter1/base-cms-utils'); const embedly = require('./embedly'); +const cache = require('./cache'); const app = express(); const dev = process.env.NODE_ENV === 'development'; @@ -12,15 +13,28 @@ app.use(json()); app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']); app.post('/', asyncRoute(async (req, res) => { - const { body } = req; - const data = await embedly.oembed(body.url, body.params); + const { url, ...params } = req.body; + const data = await embedly.oembed(url, params); + // post will set to cache, but not read from it + await cache.setFor({ url, params, data }); res.json(data); })); app.get('/', asyncRoute(async (req, res) => { - const { query } = req; - const data = await embedly.oembed(query.url, query); - res.json(data); + const { url, ...params } = req.query; + const cacheControl = req.headers['cache-control']; + const noCache = cacheControl && /no-cache/i.test(cacheControl); + + // allow for fresh data retrieval + const cached = noCache ? null : await cache.getFor({ url, params }); + res.set('X-Cache', cached ? 'hit' : 'miss'); + if (cached) { + res.set('Age', cached.age); + return res.json(cached.data); + } + const data = await embedly.oembed(url, params); + await cache.setFor({ url, params, data }); + return res.json(data); })); // eslint-disable-next-line no-unused-vars diff --git a/services/oembed/src/cache.js b/services/oembed/src/cache.js new file mode 100644 index 000000000..6bc2568dd --- /dev/null +++ b/services/oembed/src/cache.js @@ -0,0 +1,30 @@ +const { createHash } = require('crypto'); +const redis = require('./redis'); + +const createCacheKey = ({ url, params } = {}) => { + const hash = createHash('sha256').update(JSON.stringify({ url, params })).digest('hex'); + return `base_oembed:${hash}`; +}; + +const getFor = async ({ url, params } = {}) => { + const key = createCacheKey({ url, params }); + const serialized = await redis.get(key); + if (!serialized) return null; + const cacheValue = JSON.parse(serialized); + const age = Math.round((Date.now() - cacheValue.cacheTime) / 1000); + return { ...cacheValue, age }; +}; + +const setFor = async ({ url, params, data } = {}) => { + const key = createCacheKey({ url, params }); + const cacheValue = { data, cacheTime: Date.now() }; + const ttl = 60 * 60 * 24 * 3; // cache for 72 hours + await redis.set(key, JSON.stringify(cacheValue), 'EX', ttl); +}; + + +module.exports = { + createCacheKey, + getFor, + setFor, +};