Skip to content

Commit

Permalink
Merge pull request #9 from vivid-planet/cleanup
Browse files Browse the repository at this point in the history
Implement cache cleanup
  • Loading branch information
nsams authored Sep 11, 2023
2 parents efeb268 + b036bfa commit 9d31782
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 4 deletions.
29 changes: 29 additions & 0 deletions src/__tests__/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,35 @@ describe("Proxy Server E2E Tests", () => {
}
});

it("if-none-match should work correclty", async () => {
let etag: string | undefined = undefined;
{
// first request
const res = await request(`http://localhost:${proxyServerPort}`).get("/ifmodified");
expect(res.status).toBe(200);
expect(res.text).toBe("foo");
expect(res.header["x-cache"]).toBe("MISS");
expect(res.header["etag"]).toBeDefined();
etag = res.header["etag"] as string;
}
await timeout(100);
{
// second request with if-none-match should return 304 not modified
const res = await request(`http://localhost:${proxyServerPort}`).get("/ifmodified").set("If-None-Match", etag);
expect(res.status).toBe(304);
expect(res.text).toBe("");
expect(res.header["x-cache"]).toBe("HIT");
}
await timeout(2000);
{
// request with if-none-match should return 304 not modified
const res = await request(`http://localhost:${proxyServerPort}`).get("/ifmodified").set("If-None-Match", etag);
expect(res.status).toBe(304);
expect(res.text).toBe("");
expect(res.header["x-cache"]).toBe("MISS");
}
});

it("via header is set", async () => {
const res = await request(`http://localhost:${proxyServerPort}`).get("/hello");
expect(res.status).toBe(200);
Expand Down
55 changes: 53 additions & 2 deletions src/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,60 @@ export interface CacheBackend {
}

export class FilesystemCacheBackend implements CacheBackend {
constructor(private cacheDir: string) {
fs.ensureDirSync(cacheDir);
constructor(private cacheDir: string, private sizeLimit: number | null) {
//create chacheDir if it doesn't exist
fs.mkdir(cacheDir, { recursive: true });

this.cleanup(); // don't await
setInterval(this.cleanup, 1000 * 60 * 15);
}

private async cleanup() {
console.log("cleanup started");
const cleanupStart = new Date().getTime();
const stats = {
deletedOutdated: 0,
deletedOverSizeLimit: 0,
};
let entries = [];
let sumSize = 0;
const dir = await fs.opendir(this.cacheDir);
for await (const file of dir) {
if (file.name.endsWith("--meta")) {
const meta = JSON.parse(await fs.readFile(file.path, "utf-8"));
const contentFilePath = file.path.substring(0, file.path.length - "--meta".length);
if (meta.mtime + meta.maxAge < new Date().getTime()) {
stats.deletedOutdated++;
await fs.unlink(file.path);
if (meta.hasBody) {
await fs.unlink(contentFilePath);
}
} else {
const statMeta = await fs.stat(file.path);
let size = statMeta.size;
if (meta.hasBody) {
const statContent = await fs.stat(contentFilePath);
size += statContent.size;
}
sumSize += size;
entries.push({ path: file.path, size, mtime: meta.mtime, hasBody: meta.hasBody });
}
}
}
entries = entries.sort((a, b) => b.mtime - a.mtime); // oldest last
while (this.sizeLimit && sumSize > this.sizeLimit) {
const oldest = entries.pop();
if (!oldest) break;
stats.deletedOverSizeLimit++;
await fs.unlink(oldest.path);
if (oldest.hasBody) {
await fs.unlink(oldest.path.substring(0, oldest.path.length - "--meta".length));
}
sumSize -= oldest.size;
}
console.log("cleanup finished in", new Date().getTime() - cleanupStart, "sec", stats, "entries", entries.length, "size", sumSize);
}

async get(key: string): Promise<[CacheMetaWithMtime, ReadableStream | null] | null> {
const cacheFilePath = path.join(this.cacheDir, encodeURIComponent(key));
if (!(await fs.exists(`${cacheFilePath}--meta`))) return null;
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ program
.name("swr-cache-proxy")
.argument("<origin>", "Origin server URL")
.option("--port <port>", "port to listen on", "3000")
.option("--cacheSizeLimitHint <megabytes>", "maximum cache size hint", "500")
.option("--cacheDir <dir>", "directory cache files will be written into", "cache")
.action((origin: string, { port, cacheDir }: { port: string; cacheDir: string }) => {
.action((origin: string, { port, cacheDir, cacheSizeLimitHint }: { port: string; cacheDir: string; cacheSizeLimitHint: string }) => {
// Ensure the cache directory exists
const cache = new FilesystemCacheBackend(cacheDir);
const cache = new FilesystemCacheBackend(cacheDir, parseInt(cacheSizeLimitHint) /* * 1024 * 1024*/);

console.log(`Starting proxy Server`);
http.createServer(function (req, res) {
Expand Down
8 changes: 8 additions & 0 deletions src/test/test-origin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ app.get("/via", async (req, res) => {
res.send("foo");
});

app.all("/long", async (req, res) => {
res.appendHeader("Cache-Control", "max-age=60, stale-while-revalidate=120");
res.send("long");
});
app.all("/long2", async (req, res) => {
res.appendHeader("Cache-Control", "max-age=60, stale-while-revalidate=120");
res.send("long2");
});
app.listen(port, () => {
console.log(`test origin server is running on port ${port}`);
});

0 comments on commit 9d31782

Please sign in to comment.