/** * A bucket in memory, served over HTTP for the joint public-steal test: koh's own S3 backend and * Bun's S3Client both talk to it, so Kepr and the two koh machines share one bucket. * * It runs as its own process because the test drives koh with `Bun.spawnSync`, which would block * the event loop this server needs. It prints its endpoint on the first line of stdout. * * Only what the two clients use is implemented: GET, HEAD, PUT (with `If-None-Match: *` for the * one conditional write that matters, `head`), DELETE, and ListObjectsV2. Signatures are ignored * rather than verified — the one way this is easier than a real bucket. */ const BUCKET = "kohbucket"; const objects = new Map(); async function handle(request: Request): Promise { const url = new URL(request.url); const [, bucket = "", ...rest] = url.pathname.split("/"); if (bucket !== BUCKET) return new Response("", { status: 404 }); if (url.searchParams.get("list-type") === "2") { const prefix = url.searchParams.get("prefix") ?? ""; const delimiter = url.searchParams.get("delimiter"); const keys = [...objects.keys()].filter((key) => key.startsWith(prefix)).sort(); // With a delimiter, keys sharing a folder after the prefix roll up into one CommonPrefixes. const folders = new Set(); const listed = keys.filter((key) => { const at = delimiter ? key.indexOf(delimiter, prefix.length) : -1; if (at >= 0) folders.add(key.slice(0, at + 1)); return at < 0; }); const contents = listed .map( (key) => `${key}${objects.get(key)?.length ?? 0}`, ) .join(""); const common = [...folders] .map((folder) => `${folder}`) .join(""); return new Response( `false${contents}${common}`, { headers: { "content-type": "application/xml" } }, ); } const key = rest.join("/"); if (request.method === "PUT") { const body = new Uint8Array(await request.arrayBuffer()); if (request.headers.get("if-none-match") === "*" && objects.has(key)) { return new Response("", { status: 412 }); } objects.set(key, body); return new Response("", { status: 200, headers: { etag: `"${body.length}"` } }); } if (request.method === "GET" || request.method === "HEAD") { const body = objects.get(key); if (!body) return new Response("", { status: 404 }); return new Response(request.method === "HEAD" ? null : body, { status: 200, headers: { etag: `"${body.length}"`, "content-length": String(body.length) }, }); } if (request.method === "DELETE") { objects.delete(key); return new Response("", { status: 204 }); } return new Response("", { status: 404 }); } const server = Bun.serve({ port: 0, fetch: handle }); console.log(`http://127.0.0.1:${server.port}`);