import path from "node:path";
import fs from "node:fs/promises";
import type { Stats } from "node:fs";
import type { BunFile, Serve } from "bun";
import * as Sentry from "@sentry/node";
import prom from "bun-prometheus-client";
import log from "loglevel";
import config from "./config";
log.setLevel((import.meta.env["LOG_LEVEL"] || "info") as log.LogLevelDesc);
Sentry.init({
release: `homestead@${import.meta.env["FLY_MACHINE_VERSION"]}`,
tracesSampleRate: 1.0,
});
const expectedHostURL = new URL(
import.meta.env.NODE_ENV === "production"
? config.base_url
: "http://localhost:3000",
);
const defaultHeaders = {
...config.extra.headers,
vary: "Accept-Encoding",
};
type File = {
filename: string;
handle: BunFile;
relPath: string;
headers?: Record;
type: string;
size: number;
mtime: Date;
};
const metrics = {
requests: new prom.Counter({
name: "homestead_requests",
help: "Number of requests by path, status code, and method",
labelNames: [
"path",
"status_code",
"hostname",
"method",
"content_encoding",
] as const,
}),
requestDuration: new prom.Histogram({
name: "homestead_request_duration_seconds",
help: "Request duration in seconds",
labelNames: ["path"] as const,
}),
};
let files = new Map();
function registerFile(
path: string,
pathname: string,
filename: string,
stat: Stats,
): void {
pathname = "/" + (pathname === "." || pathname === "./" ? "" : pathname);
if (files.get(pathname) !== undefined) {
console.warn("File already registered:", pathname);
}
const handle = Bun.file(filename);
files.set(pathname, {
filename,
relPath: "/" + path,
handle: handle,
type: pathname.startsWith("/feed-styles.xsl") ? "text/xsl" : handle.type,
headers:
pathname === "/404.html"
? Object.assign({}, defaultHeaders, { "cache-control": "no-cache" })
: undefined,
size: stat.size,
mtime: stat.mtime,
});
}
async function walkDirectory(root: string) {
for (let relPath of await fs.readdir(root, { recursive: true })) {
const absPath = path.join(root, relPath);
const stat = await fs.stat(absPath);
if (stat.isFile()) {
if (relPath.includes("index.html")) {
const dir = relPath.replace("index.html", "");
registerFile(relPath, dir, absPath, stat);
} else {
registerFile(relPath, relPath, absPath, stat);
}
}
}
}
await walkDirectory("public/");
async function serveFile(
file: File,
statusCode: number = 200,
extraHeaders: Record = {},
): Promise {
return new Response(await file.handle.arrayBuffer(), {
headers: {
"last-modified": file.mtime.toUTCString(),
...extraHeaders,
...(file.headers || defaultHeaders),
},
status: statusCode,
});
}
function parseIfModifiedSinceHeader(header: string | null): number {
return header ? new Date(header).getTime() + 999 : 0;
}
export const metricsServer = {
port: 9091,
fetch: async function (request) {
const pathname = new URL(request.url).pathname;
switch (pathname) {
case "/metrics":
return new Response(await prom.register.metrics());
default:
return new Response("", { status: 404 });
}
},
} satisfies Serve;
export const server = {
fetch: async function (request) {
const url = new URL(request.url);
const pathname = url.pathname.replace(/\/\/+/g, "/");
const hostname = request.headers.get("host")?.toLowerCase() || "unknown";
const endTimer = metrics.requestDuration.startTimer({ path: pathname });
let status;
let newpath;
const transaction = Sentry.startTransaction({
name: pathname,
op: "http.server",
description: `${request.method} ${pathname}`,
tags: {
url: request.url,
"http.host": hostname,
"http.method": request.method,
"http.user_agent": request.headers.get("user-agent"),
},
});
try {
if (pathname === "/health") {
return new Response("OK", { status: (status = 200) });
} else if (
config.redirect_other_hostnames &&
hostname !== expectedHostURL.host
) {
metrics.requests.inc({
method: request.method,
hostname,
content_encoding: "identity",
path: pathname,
status_code: (status = 301),
});
return new Response("", {
status,
headers: {
location: new URL(pathname, expectedHostURL).toString(),
},
});
}
const file = files.get(pathname);
let contentEncoding = "identity";
let suffix = "";
if (file && (await file.handle.exists())) {
if (
parseIfModifiedSinceHeader(
request.headers.get("if-modified-since"),
) >= file?.mtime.getTime()
) {
metrics.requests.inc({
method: request.method,
hostname,
content_encoding: contentEncoding,
path: pathname,
status_code: (status = 304),
});
transaction.setHttpStatus(304);
return new Response("", { status: status, headers: defaultHeaders });
}
const encodings = (request.headers.get("accept-encoding") || "")
.split(",")
.map((x) => x.trim().toLowerCase());
if (encodings.includes("br") && files.has(pathname + ".br")) {
contentEncoding = "br";
suffix = ".br";
} else if (encodings.includes("zstd") && files.has(pathname + ".zst")) {
contentEncoding = "zstd";
suffix = ".zst";
} else if (encodings.includes("gzip") && files.has(pathname + ".gz")) {
contentEncoding = "gzip";
suffix = ".gz";
}
status = 200;
transaction.setHttpStatus(status);
metrics.requests.inc({
method: request.method,
hostname,
path: pathname,
status_code: status,
content_encoding: contentEncoding,
});
const endFile = files.get(pathname + suffix);
if (!endFile) {
throw new Error(`File ${pathname} not found`);
}
return serveFile(endFile, status, {
"content-encoding": contentEncoding,
"content-type": file.type,
});
} else {
if (files.has(pathname + "/")) {
newpath = pathname + "/";
metrics.requests.inc({
method: request.method,
hostname,
path: pathname,
content_encoding: contentEncoding,
status_code: (status = 302),
});
return new Response("", {
status: status,
headers: { location: newpath },
});
} else if (files.has(pathname.replace(/index.html$/, ""))) {
newpath = pathname.replace(/index.html$/, "");
metrics.requests.inc({
method: request.method,
hostname,
path: newpath,
content_encoding: contentEncoding,
status_code: (status = 302),
});
return new Response("", {
status: status,
headers: { location: newpath },
});
}
metrics.requests.inc({
method: request.method,
hostname,
path: pathname,
status_code: (status = 404),
content_encoding: contentEncoding,
});
transaction.setHttpStatus(status);
const notfound = files.get("/404.html");
if (notfound) {
return serveFile(notfound, status, {
"content-type": "text/html; charset=utf-8",
});
} else {
log.warn("404.html not found");
return new Response("404 Not Found", {
status: status,
headers: { "content-type": "text/plain", ...defaultHeaders },
});
}
}
} catch (error) {
transaction.setHttpStatus((status = 503));
metrics.requests.inc({
method: request.method,
hostname,
path: pathname,
status_code: status,
content_encoding: "identity",
});
Sentry.captureException(error);
console.error("Error", error);
return new Response("Something went wrong", { status: status });
} finally {
const seconds = endTimer();
metrics.requestDuration.observe(seconds);
transaction.finish();
console.info(
request.method,
status,
hostname,
pathname,
newpath ? newpath : "",
);
}
},
} satisfies Serve;
export default server;
src/app.ts (view raw)