// Thanks: https://github.com/kwhitley/apicache
// We duplicated the library because it is unmaintened and prevent us to upgrade to recent NodeJS versions
-import * as express from 'express'
+import express from 'express'
import { OutgoingHttpHeaders } from 'http'
import { isTestInstance, parseDurationToMs } from '@server/helpers/core-utils'
import { logger } from '@server/helpers/logger'
import { Redis } from '@server/lib/redis'
import { HttpStatusCode } from '@shared/models'
+import { asyncMiddleware } from '@server/middlewares'
export interface APICacheOptions {
headerBlacklist?: string[]
buildMiddleware (strDuration: string) {
const duration = parseDurationToMs(strDuration)
- return (req: express.Request, res: express.Response, next: express.NextFunction) => {
- const key = Redis.Instance.getPrefix() + 'api-cache-' + req.originalUrl
- const redis = Redis.Instance.getClient()
+ return asyncMiddleware(
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ const key = Redis.Instance.getPrefix() + 'api-cache-' + req.originalUrl
+ const redis = Redis.Instance.getClient()
- if (!redis.connected) return this.makeResponseCacheable(res, next, key, duration)
+ if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration)
- try {
- redis.hgetall(key, (err, obj) => {
- if (!err && obj && obj.response) {
+ try {
+ const obj = await redis.hGetAll(key)
+ if (obj?.response) {
return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration)
}
return this.makeResponseCacheable(res, next, key, duration)
- })
- } catch (err) {
- return this.makeResponseCacheable(res, next, key, duration)
+ } catch (err) {
+ return this.makeResponseCacheable(res, next, key, duration)
+ }
}
- }
+ )
}
private shouldCacheResponse (response: express.Response) {
} as CacheObject
}
- private cacheResponse (key: string, value: object, duration: number) {
+ private async cacheResponse (key: string, value: object, duration: number) {
const redis = Redis.Instance.getClient()
- if (redis.connected) {
- try {
- redis.hset(key, 'response', JSON.stringify(value))
- redis.hset(key, 'duration', duration + '')
+ if (Redis.Instance.isConnected()) {
+ await Promise.all([
+ redis.hSet(key, 'response', JSON.stringify(value)),
+ redis.hSet(key, 'duration', duration + ''),
redis.expire(key, duration / 1000)
- } catch (err) {
- logger.error('Cannot set cache in redis.', { err })
- }
+ ])
}
// add automatic cache clearing from duration, includes max limit on setTimeout
- this.timers[key] = setTimeout(() => this.clear(key), Math.min(duration, 2147483647))
+ this.timers[key] = setTimeout(() => {
+ this.clear(key)
+ .catch(err => logger.error('Cannot clear Redis key %s.', key, { err }))
+ }, Math.min(duration, 2147483647))
}
private accumulateContent (res: express.Response, content: any) {
encoding
)
self.cacheResponse(key, cacheObject, duration)
+ .catch(err => logger.error('Cannot cache response', { err }))
}
}
return response.end(data, cacheObject.encoding)
}
- private clear (target: string) {
+ private async clear (target: string) {
const redis = Redis.Instance.getClient()
if (target) {
delete this.timers[target]
try {
- redis.del(target)
+ await redis.del(target)
} catch (err) {
logger.error('Cannot delete %s in redis cache.', target, { err })
}
delete this.timers[key]
try {
- redis.del(key)
+ await redis.del(key)
} catch (err) {
logger.error('Cannot delete %s in redis cache.', key, { err })
}