11const CacheManager = require ( 'cache-manager' )
22const iu = require ( 'middleware-if-unless' ) ( )
3+ const { parse : cacheControl } = require ( '@tusbar/cache-control' )
34const ms = require ( 'ms' )
45const onEnd = require ( 'on-http-end' )
56const getKeys = require ( './get-keys' )
67
78const X_CACHE_EXPIRE = 'x-cache-expire'
89const X_CACHE_TIMEOUT = 'x-cache-timeout'
910const X_CACHE_HIT = 'x-cache-hit'
11+ const CACHE_ETAG = 'etag'
12+ const CACHE_CONTROL = 'cache-control'
13+ const CACHE_IF_NONE_MATCH = 'if-none-match'
1014
1115const middleware = ( opts ) => async ( req , res , next ) => {
1216 opts = Object . assign ( {
@@ -31,6 +35,16 @@ const middleware = (opts) => async (req, res, next) => {
3135 if ( cached ) {
3236 // respond from cache if there is a hit
3337 let { status, headers, data } = JSON . parse ( cached )
38+
39+ // pre-checking If-None-Match header
40+ if ( req . headers [ CACHE_IF_NONE_MATCH ] === headers [ CACHE_ETAG ] ) {
41+ res . setHeader ( 'content-length' , '0' )
42+ res . statusCode = 304
43+ res . end ( )
44+
45+ return // exit because client cache state matches
46+ }
47+
3448 if ( typeof data === 'object' && data . type === 'Buffer' ) {
3549 data = Buffer . from ( data . data )
3650 }
@@ -53,17 +67,28 @@ const middleware = (opts) => async (req, res, next) => {
5367 const keysPattern = payload . headers [ X_CACHE_EXPIRE ] . replace ( / \s / g, '' )
5468 const patterns = keysPattern . split ( ',' )
5569 // delete keys on all cache tiers
56- patterns . forEach ( pattern =>
57- opts . stores . forEach ( cache =>
58- getKeys ( cache , pattern ) . then ( keys =>
59- mcache . del ( keys ) ) ) )
60- } else if ( payload . headers [ X_CACHE_TIMEOUT ] ) {
61- // we need to cache response
62- mcache . set ( req . cacheKey , JSON . stringify ( payload ) , {
63- // @NOTE : cache-manager uses seconds as TTL unit
64- // restrict to min value "1 second"
65- ttl : Math . max ( ms ( payload . headers [ X_CACHE_TIMEOUT ] ) , 1000 ) / 1000
66- } )
70+ patterns . forEach ( pattern => opts . stores . forEach ( store => getKeys ( store , pattern ) . then ( keys => mcache . del ( keys ) ) ) )
71+ } else if ( payload . headers [ X_CACHE_TIMEOUT ] || payload . headers [ CACHE_CONTROL ] ) {
72+ // extract cache ttl
73+ let ttl = 0
74+ if ( payload . headers [ CACHE_CONTROL ] ) {
75+ ttl = cacheControl ( payload . headers [ CACHE_CONTROL ] ) . maxAge
76+ }
77+ if ( ! ttl ) {
78+ ttl = Math . max ( ms ( payload . headers [ X_CACHE_TIMEOUT ] ) , 1000 ) / 1000 // min value: 1 second
79+ }
80+
81+ // setting cache-control header if absent
82+ if ( ! payload . headers [ CACHE_CONTROL ] ) {
83+ payload . headers [ CACHE_CONTROL ] = `private, no-cache, max-age=${ ttl } `
84+ }
85+ // setting ETag if absent
86+ if ( ! payload . headers [ CACHE_ETAG ] ) {
87+ payload . headers [ CACHE_ETAG ] = '1'
88+ }
89+
90+ // cache response
91+ mcache . set ( req . cacheKey , JSON . stringify ( payload ) , { ttl } )
6792 }
6893 } )
6994
0 commit comments