sucess pt. 9 proxy amq cdn
This commit is contained in:
@@ -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() {
|
||||||
|
|||||||
193
src/routes/cdn/[filename]/+server.ts
Normal file
193
src/routes/cdn/[filename]/+server.ts
Normal 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 });
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user