rsshub
Version:
Make RSS Great Again!
79 lines (66 loc) • 2.88 kB
text/typescript
import xxhash from 'xxhash-wasm';
import type { MiddlewareHandler } from 'hono';
import { config } from '@/config';
import RequestInProgressError from '@/errors/types/request-in-progress';
import cacheModule from '@/utils/cache/index';
import { Data } from '@/types';
const bypassList = new Set(['/', '/robots.txt', '/logo.png', '/favicon.ico']);
// 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 limit = ctx.req.query('limit') ? `:${ctx.req.query('limit')}` : '';
const { h64ToString } = await xxhash();
const key = 'rsshub:koa-redis-cache:' + h64ToString(requestPath + limit);
const controlKey = 'rsshub:path-requested:' + h64ToString(requestPath + 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) {
// 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!');
}
}
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;
}
// Doesn't hit the cache? We need to let others know!
await cacheModule.globalCache.set(controlKey, '1', config.cache.requestTimeout);
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);
}
// We need to let it go, even no cache set.
// Wait to set cache so the next request could be handled correctly
await cacheModule.globalCache.set(controlKey, '0', config.cache.requestTimeout);
};
export default middleware;