Rework cache middleware to use in-memory cache, remove ExpiringCache module

This commit is contained in:
Alex Gleason 2024-04-13 14:00:21 -05:00
parent a738ed3d4d
commit 6ab3a640bf
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
4 changed files with 21 additions and 103 deletions

View File

@ -134,7 +134,7 @@ app.get('/users/:username', actorController);
app.get('/nodeinfo/:version', nodeInfoSchemaController); app.get('/nodeinfo/:version', nodeInfoSchemaController);
app.get('/api/v1/instance', cache({ cacheName: 'web', expires: Time.minutes(5) }), instanceController); app.get('/api/v1/instance', cache({ expires: Time.seconds(3) }), instanceController);
app.get('/api/v1/apps/verify_credentials', appCredentialsController); app.get('/api/v1/apps/verify_credentials', appCredentialsController);
app.post('/api/v1/apps', createAppController); app.post('/api/v1/apps', createAppController);
@ -185,10 +185,10 @@ app.get('/api/v1/preferences', preferencesController);
app.get('/api/v1/search', searchController); app.get('/api/v1/search', searchController);
app.get('/api/v2/search', searchController); app.get('/api/v2/search', searchController);
app.get('/api/pleroma/frontend_configurations', frontendConfigController); app.get('/api/pleroma/frontend_configurations', cache({ expires: Time.minutes(5) }), frontendConfigController);
app.get('/api/v1/trends/tags', cache({ cacheName: 'web', expires: Time.minutes(15) }), trendingTagsController); app.get('/api/v1/trends/tags', cache({ expires: Time.minutes(15) }), trendingTagsController);
app.get('/api/v1/trends', cache({ cacheName: 'web', expires: Time.minutes(15) }), trendingTagsController); app.get('/api/v1/trends', cache({ expires: Time.minutes(15) }), trendingTagsController);
app.get('/api/v1/notifications', requirePubkey, notificationsController); app.get('/api/v1/notifications', requirePubkey, notificationsController);
app.get('/api/v1/favourites', requirePubkey, favouritesController); app.get('/api/v1/favourites', requirePubkey, favouritesController);

View File

@ -1,26 +1,30 @@
import { Debug, type MiddlewareHandler } from '@/deps.ts'; import { Debug, type MiddlewareHandler } from '@/deps.ts';
import ExpiringCache from '@/utils/expiring-cache.ts';
const debug = Debug('ditto:middleware:cache'); const debug = Debug('ditto:middleware:cache');
export const cache = (options: { interface CacheOpts {
cacheName: string; expires: number;
expires?: number; }
}): MiddlewareHandler => {
/** In-memory cache middleware. */
export const cache = (opts: CacheOpts): MiddlewareHandler => {
let response: Response | undefined;
let expires = Date.now() + opts.expires;
return async (c, next) => { return async (c, next) => {
const key = c.req.url.replace('http://', 'https://'); if (!response || (Date.now() > expires)) {
const cache = new ExpiringCache(await caches.open(options.cacheName));
const response = await cache.match(key);
if (!response) {
debug('Building cache for page', c.req.url); debug('Building cache for page', c.req.url);
expires = Date.now() + opts.expires;
await next(); await next();
const response = c.res.clone();
if (response.status < 500) { const res = c.res.clone();
await cache.putExpiring(key, response, options.expires ?? 0); if (res.status < 500) {
response = res;
} }
} else { } else {
debug('Serving page from cache', c.req.url); debug('Serving page from cache', c.req.url);
return response; return response.clone();
} }
}; };
}; };

View File

@ -1,18 +0,0 @@
import { assert } from '@/deps-test.ts';
import ExpiringCache from './expiring-cache.ts';
Deno.test('ExpiringCache', async () => {
const cache = new ExpiringCache(await caches.open('test'));
await cache.putExpiring('http://mostr.local/1', new Response('hello world'), 300);
await cache.putExpiring('http://mostr.local/2', new Response('hello world'), -1);
// const resp1 = await cache.match('http://mostr.local/1');
const resp2 = await cache.match('http://mostr.local/2');
// assert(resp1!.headers.get('Expires'));
assert(!resp2);
// await resp1!.text();
});

View File

@ -1,68 +0,0 @@
class ExpiringCache implements Cache {
#cache: Cache;
constructor(cache: Cache) {
this.#cache = cache;
}
add(request: RequestInfo | URL): Promise<void> {
return this.#cache.add(request);
}
addAll(requests: RequestInfo[]): Promise<void> {
return this.#cache.addAll(requests);
}
keys(request?: RequestInfo | URL | undefined, options?: CacheQueryOptions | undefined): Promise<readonly Request[]> {
return this.#cache.keys(request, options);
}
matchAll(
request?: RequestInfo | URL | undefined,
options?: CacheQueryOptions | undefined,
): Promise<readonly Response[]> {
return this.#cache.matchAll(request, options);
}
put(request: RequestInfo | URL, response: Response): Promise<void> {
return this.#cache.put(request, response);
}
putExpiring(request: RequestInfo | URL, response: Response, expiresIn: number): Promise<void> {
const expires = Date.now() + expiresIn;
const clone = new Response(response.body, {
status: response.status,
headers: {
expires: new Date(expires).toUTCString(),
...Object.fromEntries(response.headers.entries()),
},
});
return this.#cache.put(request, clone);
}
async match(request: RequestInfo | URL, options?: CacheQueryOptions | undefined): Promise<Response | undefined> {
const response = await this.#cache.match(request, options);
const expires = response?.headers.get('Expires');
if (response && expires) {
if (new Date(expires).getTime() > Date.now()) {
return response;
} else {
await Promise.all([
this.delete(request),
response.text(), // Prevent memory leaks
]);
}
} else if (response) {
return response;
}
}
delete(request: RequestInfo | URL, options?: CacheQueryOptions | undefined): Promise<boolean> {
return this.#cache.delete(request, options);
}
}
export default ExpiringCache;