| | import type { MiddlewareHandler } from 'hono'; |
| | import xxhash from 'xxhash-wasm'; |
| |
|
| | import { config } from '@/config'; |
| | import RequestInProgressError from '@/errors/types/request-in-progress'; |
| | import type { Data } from '@/types'; |
| | import cacheModule from '@/utils/cache/index'; |
| |
|
| | const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']); |
| | |
| | |
| | |
| | const middleware: MiddlewareHandler = async (ctx, next) => { |
| | if (!cacheModule.status.available || bypassList.has(ctx.req.path)) { |
| | await next(); |
| | return; |
| | } |
| |
|
| | const requestPath = ctx.req.path; |
| | const format = `:${ctx.req.query('format') || 'rss'}`; |
| | const limit = ctx.req.query('limit') ? `:${ctx.req.query('limit')}` : ''; |
| | const { h64ToString } = await xxhash(); |
| | const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + format + limit); |
| | const controlKey = 'rsshub:path-requested:' + h64ToString(requestPath + format + limit); |
| |
|
| | const isRequesting = await cacheModule.globalCache.get(controlKey); |
| |
|
| | if (isRequesting === '1') { |
| | let retryTimes = process.env.NODE_ENV === 'test' ? 1 : 10; |
| | let bypass = false; |
| | while (retryTimes > 0) { |
| | |
| | await new Promise((resolve) => setTimeout(resolve, process.env.NODE_ENV === 'test' ? 3000 : 6000)); |
| | |
| | if ((await cacheModule.globalCache.get(controlKey)) !== '1') { |
| | bypass = true; |
| | break; |
| | } |
| | retryTimes--; |
| | } |
| | if (!bypass) { |
| | throw new RequestInProgressError('This path is currently fetching, please come back later!'); |
| | } |
| | } |
| |
|
| | const value = await cacheModule.globalCache.get(key); |
| |
|
| | if (value) { |
| | ctx.status(200); |
| | ctx.header('RSSHub-Cache-Status', 'HIT'); |
| | ctx.set('data', JSON.parse(value)); |
| | await next(); |
| | return; |
| | } |
| |
|
| | |
| | await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout); |
| |
|
| | |
| | ctx.set('cacheKey', key); |
| | ctx.set('cacheControlKey', controlKey); |
| |
|
| | try { |
| | await next(); |
| | } catch (error) { |
| | await cacheModule.globalCache.set(controlKey, '0', config.cache.requestTimeout); |
| | throw error; |
| | } |
| |
|
| | const data: Data = ctx.get('data'); |
| | if (ctx.res.headers.get('Cache-Control') !== 'no-cache' && data) { |
| | data.lastBuildDate = new Date().toUTCString(); |
| | ctx.set('data', data); |
| | const body = JSON.stringify(data); |
| | await cacheModule.globalCache.set(key, body, config.cache.routeExpire); |
| | } |
| |
|
| | |
| | |
| | await cacheModule.globalCache.set(controlKey, '0', config.cache.requestTimeout); |
| | }; |
| |
|
| | export default middleware; |
| |
|