Initial commit

This commit is contained in:
DrMint 2023-09-02 21:37:45 +02:00
commit c1028ae6b1
8 changed files with 133 additions and 0 deletions

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
PURGE_TOKEN=my-super-token
TARGET_HOST=http://localhost:8080 # Do not include the trailing /
OUTGOING_PORT=8081

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
cache

10
README.md Normal file
View File

@ -0,0 +1,10 @@
# middleware.accords-library.com
This middleware handles caching of the website.
## Install
```
bun install
bun run dev
```

BIN
bun.lockb Executable file

Binary file not shown.

11
package.json Normal file
View File

@ -0,0 +1,11 @@
{
"scripts": {
"start": "bun run --watch src/index.ts"
},
"dependencies": {
"hono": "^3.5.4"
},
"devDependencies": {
"bun-types": "^0.6.2"
}
}

66
src/cache.ts Normal file
View File

@ -0,0 +1,66 @@
import { BunFile } from "bun";
import fs from "fs/promises";
type SerializableResponse = {
body: string;
headers: Record<string, string>;
status: number;
statusText: string;
};
export const purgeCache = async (request: Request): Promise<void> => {
const url = new URL(request.url);
if (url.pathname.endsWith("/*")) {
const pathnameWithoutWildcard = url.pathname.slice(0, -2);
await fs.rm(getFolderPath(pathnameWithoutWildcard), {
recursive: true,
force: true,
});
} else {
await fs.rm(getFilePath(url.pathname), { force: true });
}
};
export const getResponse = async (request: Request): Promise<Response> => {
const url = new URL(request.url);
const file = Bun.file(getFilePath(url.pathname));
let serializableResponse;
if (fileExists(file)) {
serializableResponse = await file.json<SerializableResponse>();
console.log("🟢 Retrieved response from cache for", request.url);
} else {
serializableResponse = await createCache(request);
console.log("🟠 Generated response for", request.url);
}
const { body, headers, status, statusText } = serializableResponse;
return new Response(body, { headers, status, statusText });
};
const createCache = async (request: Request): Promise<SerializableResponse> => {
const url = new URL(request.url);
const response = await fetch(
`${Bun.env.TARGET_HOST}${url.pathname}${url.search}`
);
const serializableReponse: SerializableResponse = {
body: await response.text(),
headers: Object.fromEntries(response.headers.entries()),
status: response.status,
statusText: response.statusText,
};
// Only save cache is status is ok
if (response.ok) {
await fs.mkdir(getFolderPath(url.pathname), { recursive: true });
await Bun.write(
getFilePath(url.pathname),
JSON.stringify(serializableReponse)
);
}
return serializableReponse;
};
const fileExists = (file: BunFile): boolean => file.size > 0;
const getFolderPath = (pathname: string): string => `./cache${pathname}`;
const getFilePath = (pathname: string): string =>
`${getFolderPath(pathname)}/index.res`;

22
src/index.ts Normal file
View File

@ -0,0 +1,22 @@
import { Hono } from "hono";
import { getResponse, purgeCache } from "./cache";
const app = new Hono();
app.on("PURGE", "*", async ({ req }) => {
if (req.headers.get("Authorization") !== `Bearer ${Bun.env.PURGE_TOKEN}`) {
console.log("🛑 Purge request rejected for", req.url);
return new Response(null, { status: 403 });
}
console.log("🔥 Purge request accepted for", req.url);
await purgeCache(req.raw);
return new Response(null, { status: 200 });
});
app.get(async ({ req }) => await getResponse(req.raw));
const server = Bun.serve({
port: Bun.env.OUTGOING_PORT,
fetch: app.fetch,
});
console.log(`👂 Listening on http://${server.hostname}:${server.port}`);

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"esModuleInterop": true,
"strict": true,
"types": ["bun-types"]
}
}