diff --git a/src/utils/cache.ts b/src/utils/cache.ts new file mode 100644 index 00000000..284b9cc9 --- /dev/null +++ b/src/utils/cache.ts @@ -0,0 +1,50 @@ +import type { CompatibilityEvent } from '../event' + +export interface CacheConditions { + modifiedTime?: Date + maxAge?: number + etag?: string + cacheControls?: string[] +} + +/** + * Check request caching headers (`If-Modified-Since`) and add caching headers (Last-Modified, Cache-Control) + * Note: `public` cache control will be added by default + * @returns `true` when cache headers are matching. When `true` is returned, no reponse should be sent anymore + */ +export function handleCacheHeaders (event: CompatibilityEvent, opts: CacheConditions): boolean { + const cacheControls = ['public'].concat(opts.cacheControls || []) + let cacheMatched = false + + if (opts.maxAge !== undefined) { + opts.cacheControls?.push(`max-age=${+opts.maxAge}`, `s-maxage=${+opts.maxAge}`) + } + + if (opts.modifiedTime) { + const ifModifiedSince = event.req.headers['if-modified-since'] + event.res.setHeader('Last-Modified', +opts.modifiedTime + '') + if (ifModifiedSince) { + if (new Date(ifModifiedSince) >= opts.modifiedTime) { + cacheMatched = true + } + } + } + + if (opts.etag) { + event.res.setHeader('Etag', opts.etag) + const ifNonMatch = event.req.headers['if-none-match'] + if (ifNonMatch === opts.etag) { + cacheMatched = true + } + } + + event.res.setHeader('Cache-Control', cacheControls.join(', ')) + + if (cacheMatched) { + event.res.statusCode = 304 + event.res.end('') + return true + } + + return false +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1181dbe7..9aaae29b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './body' +export * from './cache' export * from './consts' export * from './cookie' export * from './request'