| 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; |
|
|