mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-10-18 22:32:10 +08:00
021f1807d3
Affected files: - lib/middleware/cache.test.ts - lib/middleware/cache.ts
104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
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';
|
|
import { hasItemFilters } from '@/utils/route-limit';
|
|
|
|
const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']);
|
|
|
|
const { h64ToString } = await xxhash();
|
|
// only give cache string, as the `!` condition tricky
|
|
// XXH64 is used to shrink key size
|
|
// plz, write these tips in comments!
|
|
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') || config.format}`;
|
|
const limit = ctx.req.query('limit') ? `:${ctx.req.query('limit')}` : '';
|
|
// Routes may select entries before fetching details according to this option.
|
|
const sortedQuery = ctx.req.query('sorted');
|
|
const sorted = sortedQuery === undefined ? '' : sortedQuery === 'false' ? ':sorted=false' : ':sorted=true';
|
|
const filtered = hasItemFilters(ctx) ? ':filtered' : '';
|
|
const requestKey = h64ToString(requestPath + format + limit + sorted + filtered);
|
|
const key = 'rsshub:koa-redis-cache:' + requestKey;
|
|
const controlKey = 'rsshub:path-requested:' + requestKey;
|
|
|
|
let value = await cacheModule.globalCache.get(key);
|
|
|
|
// Only atomic backends can coordinate fetchers. HTTP/KV may return stale
|
|
// control keys after a completed request, while their feed cache stays useful.
|
|
let isRequesting = false;
|
|
let ownsClaim = false;
|
|
if (!value && cacheModule.globalCache.supportsAtomicClaims) {
|
|
ownsClaim = await cacheModule.globalCache.claim(controlKey, config.cache.requestTimeout);
|
|
isRequesting = !ownsClaim;
|
|
}
|
|
|
|
if (isRequesting) {
|
|
let retryTimes = process.env.NODE_ENV === 'test' ? 1 : 10;
|
|
let bypass = false;
|
|
while (retryTimes > 0) {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await new Promise((resolve) => setTimeout(resolve, process.env.NODE_ENV === 'test' ? 3000 : 6000));
|
|
// eslint-disable-next-line no-await-in-loop
|
|
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!');
|
|
}
|
|
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;
|
|
}
|
|
|
|
if (isRequesting) {
|
|
// waited out a stale claim without finding a cache entry, take over the fetch
|
|
ownsClaim = await cacheModule.globalCache.claim(controlKey, config.cache.requestTimeout);
|
|
if (!ownsClaim) {
|
|
throw new RequestInProgressError('This path is currently fetching, please come back later!');
|
|
}
|
|
}
|
|
|
|
// let routers control cache
|
|
ctx.set('cacheKey', key);
|
|
if (ownsClaim) {
|
|
ctx.set('cacheControlKey', controlKey);
|
|
}
|
|
|
|
try {
|
|
await next();
|
|
|
|
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);
|
|
}
|
|
} finally {
|
|
// Release after writing the feed, including failures after the route ran.
|
|
if (ownsClaim) {
|
|
await cacheModule.globalCache.set(controlKey, '0', config.cache.requestTimeout);
|
|
}
|
|
}
|
|
};
|
|
|
|
export default middleware;
|