sucess pt. 9 proxy amq cdn

This commit is contained in:
2026-02-05 04:29:44 -08:00
parent 356ca922f9
commit ef9453cbc0
2 changed files with 194 additions and 1 deletions

View File

@@ -39,7 +39,7 @@
} }
function audioUrl(fileName: string) { function audioUrl(fileName: string) {
return `https://nawdist.animemusicquiz.com/${fileName}`; return `/cdn/${encodeURIComponent(fileName)}`;
} }
async function load() { async function load() {

View File

@@ -0,0 +1,193 @@
import { error } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
const UPSTREAM_ORIGIN = "https://nawdist.animemusicquiz.com";
const ALLOWED_METHODS = ["GET", "HEAD"] as const;
// Basic filename hygiene: avoid path traversal and weird encodings.
// If you need to support subdirectories later, relax this and validate segments.
function isSafeFilename(name: string) {
// Disallow slashes/backslashes and any ".."
if (
!name ||
name.includes("/") ||
name.includes("\\") ||
name.includes("..")
) {
return false;
}
// Keep it reasonably strict; CDN filenames are typically simple.
// Allow common audio/container extensions and typical characters.
return /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(name);
}
function pickForwardHeader(request: Request, headerName: string) {
const v = request.headers.get(headerName);
return v ?? undefined;
}
export const GET: RequestHandler = async ({
params,
request,
fetch,
setHeaders,
}) => {
const filename = params.filename;
if (!isSafeFilename(filename)) {
throw error(400, "Invalid filename");
}
// Forward Range so audio seeking works
const range = pickForwardHeader(request, "range");
const ifNoneMatch = pickForwardHeader(request, "if-none-match");
const ifModifiedSince = pickForwardHeader(request, "if-modified-since");
const upstreamUrl = `${UPSTREAM_ORIGIN}/${encodeURIComponent(filename)}`;
const upstreamRes = await fetch(upstreamUrl, {
method: "GET",
headers: {
...(range ? { range } : {}),
...(ifNoneMatch ? { "if-none-match": ifNoneMatch } : {}),
...(ifModifiedSince ? { "if-modified-since": ifModifiedSince } : {}),
},
});
// Map some upstream statuses sensibly
if (upstreamRes.status === 404) throw error(404, "File not found");
if (upstreamRes.status === 416) {
// Range Not Satisfiable (e.g. stale range) should pass through
return new Response(upstreamRes.body, {
status: upstreamRes.status,
statusText: upstreamRes.statusText,
headers: upstreamRes.headers,
});
}
if (
!upstreamRes.ok &&
upstreamRes.status !== 206 &&
upstreamRes.status !== 304
) {
throw error(upstreamRes.status, `Upstream error (${upstreamRes.status})`);
}
// Pass through headers that matter for media + caching.
// Avoid copying hop-by-hop headers.
const passthrough = new Headers();
const contentType = upstreamRes.headers.get("content-type");
if (contentType) passthrough.set("content-type", contentType);
const contentLength = upstreamRes.headers.get("content-length");
if (contentLength) passthrough.set("content-length", contentLength);
const acceptRanges = upstreamRes.headers.get("accept-ranges");
if (acceptRanges) passthrough.set("accept-ranges", acceptRanges);
const contentRange = upstreamRes.headers.get("content-range");
if (contentRange) passthrough.set("content-range", contentRange);
const etag = upstreamRes.headers.get("etag");
if (etag) passthrough.set("etag", etag);
const lastModified = upstreamRes.headers.get("last-modified");
if (lastModified) passthrough.set("last-modified", lastModified);
const cacheControl = upstreamRes.headers.get("cache-control");
if (cacheControl) passthrough.set("cache-control", cacheControl);
else passthrough.set("cache-control", "public, max-age=86400");
// CORS: allow your app to fetch this endpoint from anywhere you deploy it
passthrough.set("access-control-allow-origin", "*");
passthrough.set("access-control-allow-methods", ALLOWED_METHODS.join(", "));
passthrough.set(
"access-control-allow-headers",
"range, if-none-match, if-modified-since",
);
// Tell browsers/proxies that Range affects the response
passthrough.set("vary", "range");
// Also apply via SvelteKit helper so adapters that manage headers behave
setHeaders(Object.fromEntries(passthrough.entries()));
// 304 responses have no body
if (upstreamRes.status === 304) {
return new Response(null, { status: 304, headers: passthrough });
}
return new Response(upstreamRes.body, {
status: upstreamRes.status,
statusText: upstreamRes.statusText,
headers: passthrough,
});
};
// HEAD support for players/browsers that probe first
export const HEAD: RequestHandler = async (event) => {
// Reuse GET logic but avoid streaming a body.
// We still want upstream to evaluate Range/ETag, so call upstream with HEAD.
const { params, request, fetch, setHeaders } = event;
const filename = params.filename;
if (!isSafeFilename(filename)) {
throw error(400, "Invalid filename");
}
const range = pickForwardHeader(request, "range");
const ifNoneMatch = pickForwardHeader(request, "if-none-match");
const ifModifiedSince = pickForwardHeader(request, "if-modified-since");
const upstreamUrl = `${UPSTREAM_ORIGIN}/${encodeURIComponent(filename)}`;
const upstreamRes = await fetch(upstreamUrl, {
method: "HEAD",
headers: {
...(range ? { range } : {}),
...(ifNoneMatch ? { "if-none-match": ifNoneMatch } : {}),
...(ifModifiedSince ? { "if-modified-since": ifModifiedSince } : {}),
},
});
if (upstreamRes.status === 404) throw error(404, "File not found");
if (
!upstreamRes.ok &&
upstreamRes.status !== 206 &&
upstreamRes.status !== 304
) {
throw error(upstreamRes.status, `Upstream error (${upstreamRes.status})`);
}
const headers = new Headers();
const copy = (h: string) => {
const v = upstreamRes.headers.get(h);
if (v) headers.set(h, v);
};
copy("content-type");
copy("content-length");
copy("accept-ranges");
copy("content-range");
copy("etag");
copy("last-modified");
copy("cache-control");
if (!headers.has("cache-control")) {
headers.set("cache-control", "public, max-age=86400");
}
headers.set("access-control-allow-origin", "*");
headers.set("access-control-allow-methods", ALLOWED_METHODS.join(", "));
headers.set(
"access-control-allow-headers",
"range, if-none-match, if-modified-since",
);
headers.set("vary", "range");
setHeaders(Object.fromEntries(headers.entries()));
return new Response(null, { status: upstreamRes.status, headers });
};