1
1
const CacheManager = require ( 'cache-manager' )
2
2
const iu = require ( 'middleware-if-unless' ) ( )
3
+ const { parse : cacheControl } = require ( '@tusbar/cache-control' )
3
4
const ms = require ( 'ms' )
4
5
const onEnd = require ( 'on-http-end' )
5
6
const getKeys = require ( './get-keys' )
6
7
7
8
const X_CACHE_EXPIRE = 'x-cache-expire'
8
9
const X_CACHE_TIMEOUT = 'x-cache-timeout'
9
10
const 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'
10
14
11
15
const middleware = ( opts ) => async ( req , res , next ) => {
12
16
opts = Object . assign ( {
@@ -31,6 +35,16 @@ const middleware = (opts) => async (req, res, next) => {
31
35
if ( cached ) {
32
36
// respond from cache if there is a hit
33
37
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
+
34
48
if ( typeof data === 'object' && data . type === 'Buffer' ) {
35
49
data = Buffer . from ( data . data )
36
50
}
@@ -53,17 +67,28 @@ const middleware = (opts) => async (req, res, next) => {
53
67
const keysPattern = payload . headers [ X_CACHE_EXPIRE ] . replace ( / \s / g, '' )
54
68
const patterns = keysPattern . split ( ',' )
55
69
// 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 } )
67
92
}
68
93
} )
69
94
0 commit comments