Merge branch 'revert-53bee767' into 'main'
Revert "Merge branch 'rework-cache' into 'main'" See merge request soapbox-pub/ditto!166
This commit is contained in:
commit
f6945ecd1c
|
@ -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({ expires: Time.minutes(5) }), instanceController);
|
app.get('/api/v1/instance', cache({ cacheName: 'web', expires: Time.minutes(5) }), 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', cache({ expires: Time.minutes(5) }), frontendConfigController);
|
app.get('/api/pleroma/frontend_configurations', frontendConfigController);
|
||||||
|
|
||||||
app.get('/api/v1/trends/tags', cache({ expires: Time.minutes(15) }), trendingTagsController);
|
app.get('/api/v1/trends/tags', cache({ cacheName: 'web', expires: Time.minutes(15) }), trendingTagsController);
|
||||||
app.get('/api/v1/trends', cache({ expires: Time.minutes(15) }), trendingTagsController);
|
app.get('/api/v1/trends', cache({ cacheName: 'web', 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);
|
||||||
|
|
|
@ -1,45 +1,26 @@
|
||||||
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');
|
||||||
|
|
||||||
interface CacheOpts {
|
export const cache = (options: {
|
||||||
expires: number;
|
cacheName: string;
|
||||||
}
|
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 now = Date.now();
|
const key = c.req.url.replace('http://', 'https://');
|
||||||
const expired = now > expires;
|
const cache = new ExpiringCache(await caches.open(options.cacheName));
|
||||||
|
const response = await cache.match(key);
|
||||||
async function updateCache() {
|
if (!response) {
|
||||||
await next();
|
|
||||||
const res = c.res.clone();
|
|
||||||
if (res.status < 500) {
|
|
||||||
const old = response;
|
|
||||||
response = res;
|
|
||||||
old?.text(); // Prevent memory leaks.
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response && !expired) {
|
|
||||||
debug('Serving page from cache', c.req.url);
|
|
||||||
return response.clone();
|
|
||||||
} else {
|
|
||||||
expires = Date.now() + opts.expires;
|
|
||||||
if (response && expired) {
|
|
||||||
debug('Serving stale cache, rebuilding', c.req.url);
|
|
||||||
const stale = response.clone();
|
|
||||||
updateCache();
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
||||||
return stale;
|
|
||||||
}
|
|
||||||
debug('Building cache for page', c.req.url);
|
debug('Building cache for page', c.req.url);
|
||||||
return await updateCache();
|
await next();
|
||||||
|
const response = c.res.clone();
|
||||||
|
if (response.status < 500) {
|
||||||
|
await cache.putExpiring(key, response, options.expires ?? 0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('Serving page from cache', c.req.url);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
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();
|
||||||
|
});
|
|
@ -0,0 +1,68 @@
|
||||||
|
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;
|
Loading…
Reference in New Issue