Allow overriding the s-maxage cache header (#6294)

* Allow overriding the s-maxage cache header

* Only load expiry / set headers when cache exists
This commit is contained in:
Rijk van Zanten
2021-06-15 17:11:29 -04:00
committed by GitHub
parent 895e6b4f0c
commit d56f02697a
5 changed files with 64 additions and 33 deletions

View File

@@ -50,6 +50,7 @@ const defaults: Record<string, any> = {
CACHE_TTL: '10m',
CACHE_NAMESPACE: 'system-cache',
CACHE_AUTO_PURGE: false,
CACHE_CONTROL_S_MAXAGE: '0',
OAUTH_PROVIDERS: '',

View File

@@ -2,6 +2,7 @@ import { RequestHandler } from 'express';
import cache from '../cache';
import env from '../env';
import asyncHandler from '../utils/async-handler';
import { getCacheControlHeader } from '../utils/get-cache-headers';
import { getCacheKey } from '../utils/get-cache-key';
const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next) => {
@@ -17,18 +18,11 @@ const checkCacheMiddleware: RequestHandler = asyncHandler(async (req, res, next)
const cachedData = await cache.get(key);
if (cachedData) {
// Set cache-control header, but only for the public role
if (env.CACHE_AUTO_PURGE !== true && !!req.accountability?.role === false) {
const expiresAt = await cache.get(`${key}__expires_at`);
const maxAge = `max-age=${expiresAt - Date.now()}`;
res.setHeader('Cache-Control', `public, ${maxAge}`);
} else {
// This indicates that the browser/proxy is allowed to cache, but has to revalidate with
// the server before use. At this point, we don't include Last-Modified, so it'll always
// recreate the local cache. This does NOT mean that cache is disabled all together, as
// Directus is still pulling the value from it's internal cache.
res.setHeader('Cache-Control', 'no-cache');
}
const cacheExpiryDate = (await cache.get(`${key}__expires_at`)) as number | null;
const cacheTTL = cacheExpiryDate ? cacheExpiryDate - Date.now() : null;
res.setHeader('Cache-Control', getCacheControlHeader(req, cacheTTL));
res.setHeader('Vary', 'Origin, Cache-Control');
return res.json(cachedData);
} else {

View File

@@ -7,6 +7,7 @@ import env from '../env';
import asyncHandler from '../utils/async-handler';
import { getCacheKey } from '../utils/get-cache-key';
import { parse as toXML } from 'js2xmlparser';
import { getCacheControlHeader } from '../utils/get-cache-headers';
export const respond: RequestHandler = asyncHandler(async (req, res) => {
if (
@@ -19,20 +20,12 @@ export const respond: RequestHandler = asyncHandler(async (req, res) => {
const key = getCacheKey(req);
await cache.set(key, res.locals.payload, ms(env.CACHE_TTL as string));
await cache.set(`${key}__expires_at`, Date.now() + ms(env.CACHE_TTL as string));
const noCacheRequested =
req.headers['cache-control']?.includes('no-cache') || req.headers['Cache-Control']?.includes('no-cache');
// Set cache-control header
if (env.CACHE_AUTO_PURGE !== true && noCacheRequested === false) {
const maxAge = `max-age=${ms(env.CACHE_TTL as string)}`;
const access = !!req.accountability?.role === false ? 'public' : 'private';
res.setHeader('Cache-Control', `${access}, ${maxAge}`);
}
if (noCacheRequested) {
res.setHeader('Cache-Control', 'no-cache');
}
res.setHeader('Cache-Control', getCacheControlHeader(req, ms(env.CACHE_TTL as string)));
res.setHeader('Vary', 'Origin, Cache-Control');
} else {
// Don't cache anything by default
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Vary', 'Origin, Cache-Control');
}
if (req.sanitizedQuery.export) {

View File

@@ -0,0 +1,42 @@
import env from '../env';
import { Request } from 'express';
/**
* Returns the Cache-Control header for the current request
*
* @param req Express request object
* @param ttl TTL of the cache in ms
*/
export function getCacheControlHeader(req: Request, ttl: number | null): string {
// When the resource / current request isn't cached
if (ttl === null) return 'no-cache';
// When the API cache can invalidate at any moment
if (env.CACHE_AUTO_PURGE === true) return 'no-cache';
const noCacheRequested =
req.headers['cache-control']?.includes('no-cache') || req.headers['Cache-Control']?.includes('no-cache');
// When the user explicitly asked to skip the cache
if (noCacheRequested) return 'no-cache';
// Cache control header uses seconds for everything
const ttlSeconds = Math.round(ttl / 1000);
const access = !!req.accountability?.role === false ? 'public' : 'private';
let headerValue = `${access}, max-age=${ttlSeconds}`;
// When the s-maxage flag should be included
if (env.CACHE_CONTROL_S_MAXAGE !== false) {
// Default to regular max-age flag when true
if (env.CACHE_CONTROL_S_MAXAGE === true) {
headerValue += `, s-maxage=${ttlSeconds}`;
} else {
// Set to custom value
headerValue += `, s-maxage=${env.CACHE_CONTROL_S_MAXAGE}`;
}
}
return headerValue;
}

View File

@@ -125,13 +125,14 @@ needs, you can extend the above environment variables to configure any of
## Cache
| Variable | Description | Default Value |
| ------------------ | ----------------------------------------------------------------------- | ---------------- |
| `CACHE_ENABLED` | Whether or not caching is enabled. | `false` |
| `CACHE_TTL` | How long the cache is persisted. | `30m` |
| `CACHE_AUTO_PURGE` | Automatically purge the cache on `create`/`update`/`delete` actions. | `false` |
| `CACHE_NAMESPACE` | How to scope the cache data. | `directus-cache` |
| `CACHE_STORE` | Where to store the cache data. Either `memory`, `redis`, or `memcache`. | `memory` |
| Variable | Description | Default Value |
| ------------------------ | -------------------------------------------------------------------------------------- | ---------------- |
| `CACHE_ENABLED` | Whether or not caching is enabled. | `false` |
| `CACHE_TTL` | How long the cache is persisted. | `30m` |
| `CACHE_CONTROL_S_MAXAGE` | Whether to not to add the s-maxage expiration flag. Set to a number for a custom value | `0` |
| `CACHE_AUTO_PURGE` | Automatically purge the cache on `create`/`update`/`delete` actions. | `false` |
| `CACHE_NAMESPACE` | How to scope the cache data. | `directus-cache` |
| `CACHE_STORE` | Where to store the cache data. Either `memory`, `redis`, or `memcache`. | `memory` |
Based on the `CACHE_STORE` used, you must also provide the following configurations: