-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathindex.js
116 lines (94 loc) · 2.67 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
'use strict'
const fp = require('fastify-plugin')
const Keyv = require('keyv')
const BPromise = require('bluebird')
const crypto = require('crypto')
const cache = new Keyv()
const CACHEABLE_METHODS = ['GET']
const INTERVAL = 200
const X_RESPONSE_CACHE = 'x-response-cache'
const X_RESPONSE_CACHE_HIT = 'hit'
const X_RESPONSE_CACHE_MISS = 'miss'
const isCacheableRequest = (req) => {
return CACHEABLE_METHODS.includes(req.raw.method)
}
const buildCacheKey = (req, {headers}) => {
const {url, headers: requestHeaders} = req.raw
const additionalCondition = headers.reduce((acc, header) => {
return `${acc}__${header}:${requestHeaders[header] || ''}`
}, '')
const data = `${url}__${additionalCondition}`
const encrytedData = crypto.createHash('md5').update(data)
const key = encrytedData.digest('hex')
return key
}
const waitForCacheFulfilled = async (key, timeout) => {
let cachedString = await cache.get(key)
let waitedFor = 0
while (!cachedString) {
await BPromise.delay(INTERVAL)
cachedString = await cache.get(key)
waitedFor += INTERVAL
if (!cachedString && waitedFor > timeout) {
return
}
}
return cachedString
}
const createOnRequestHandler = ({
ttl,
additionalCondition: {headers},
}) => async (req, res) => {
if (!isCacheableRequest(req)) {
return
}
const key = buildCacheKey(req, {headers})
const requestKey = `${key}__requested`
const isRequestExisted = await cache.get(requestKey)
if (isRequestExisted) {
const cachedString = await waitForCacheFulfilled(key, ttl)
if (cachedString) {
const cached = JSON.parse(cachedString)
res.header(X_RESPONSE_CACHE, X_RESPONSE_CACHE_HIT)
return res.code(cached.statusCode).send(cached.payload)
} else {
res.header(X_RESPONSE_CACHE, X_RESPONSE_CACHE_MISS)
}
} else {
await cache.set(requestKey, 'cached', ttl)
res.header(X_RESPONSE_CACHE, X_RESPONSE_CACHE_MISS)
}
}
const createOnSendHandler = ({ttl, additionalCondition: {headers}}) => async (
req,
res,
payload,
) => {
if (!isCacheableRequest(req)) {
return
}
const key = buildCacheKey(req, {headers})
await cache.set(
key,
JSON.stringify({
statusCode: res.statusCode,
payload,
}),
ttl,
)
}
const responseCachingPlugin = (
instance,
{ttl = 1000, additionalCondition = {}},
next,
) => {
const headers = additionalCondition.headers || []
const opts = {ttl, additionalCondition: {headers}}
instance.addHook('onRequest', createOnRequestHandler(opts))
instance.addHook('onSend', createOnSendHandler(opts))
return next()
}
module.exports = fp(responseCachingPlugin, {
fastify: '3.x',
name: 'fastify-response-caching',
})