Skip to content
This repository has been archived by the owner on Dec 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request #200 from zarathustra323/oembed-cache
Browse files Browse the repository at this point in the history
Add Redis caching to embed requests
  • Loading branch information
solocommand authored Nov 30, 2021
2 parents 2b666eb + d319c31 commit a9ca8bc
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 5 deletions.
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,11 @@ services:
environment:
<<: *env
<<: *env-newrelic
REDIS_CACHE_DSN: ${REDIS_CACHE_DSN-redis://redis}
EMBEDLY_API_KEY: ${EMBEDLY_API_KEY-}
EXPOSED_PORT: 10402
depends_on:
- redis
ports:
- "10402:80"

Expand Down
1 change: 1 addition & 0 deletions services/oembed/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"helmet": "^3.23.3",
"ioredis": "^4.27.10",
"newrelic": "^6.14.0"
}
}
24 changes: 19 additions & 5 deletions services/oembed/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const cors = require('cors');
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';
Expand All @@ -21,15 +22,28 @@ app.use(CORS);
app.options('*', CORS);

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
Expand Down
30 changes: 30 additions & 0 deletions services/oembed/src/cache.js
Original file line number Diff line number Diff line change
@@ -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,
};
1 change: 1 addition & 0 deletions services/oembed/src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = cleanEnv(process.env, {
NEW_RELIC_LICENSE_KEY: nonemptystr({ desc: 'The license key for New Relic.', devDefault: '(unset)' }),
EXPOSED_HOST: str({ desc: 'The external host to run on.', default: 'localhost' }),
EXPOSED_PORT: port({ desc: 'The external port to run on.', default: 10013 }),
REDIS_CACHE_DSN: nonemptystr({ desc: 'The Redis DSN where cache values should be saved.' }),
TERMINUS_TIMEOUT: num({ desc: 'Number of milliseconds before forceful exiting', default: 1000 }),
TERMINUS_SHUTDOWN_DELAY: num({ desc: 'Number of milliseconds before the HTTP server starts its shutdown', default: 10000 }),
});
4 changes: 4 additions & 0 deletions services/oembed/src/redis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const Redis = require('ioredis');
const { REDIS_CACHE_DSN } = require('./env');

module.exports = new Redis(REDIS_CACHE_DSN);

0 comments on commit a9ca8bc

Please sign in to comment.