fix: bypass image proxy for browser image loads
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-29 16:48:30 +08:00
parent 166cc184d1
commit e1e4c205e1

View File

@ -90,6 +90,80 @@ function buildEtag(input: string): string {
return `"${crypto.createHash("sha1").update(input).digest("hex")}"`;
}
function isBrowserImageRequest(request: NextRequest): boolean {
return request.headers.get("sec-fetch-dest") === "image";
}
function isAliyunOssHost(hostname: string): boolean {
const normalized = hostname.toLowerCase();
return normalized.endsWith(".aliyuncs.com") && normalized.includes(".oss-");
}
function appendAliyunImageProcess(
target: URL,
width: number | null,
quality: number | null,
requestedFormat: string | null
): URL | null {
if (target.searchParams.has("x-oss-process")) {
return width || quality || requestedFormat ? null : target;
}
const processSteps: string[] = ["image"];
if (width) {
processSteps.push(`resize,w_${width}`);
}
if (quality) {
processSteps.push(`quality,q_${quality}`);
}
if (requestedFormat) {
const format = requestedFormat === "jpeg" ? "jpg" : requestedFormat;
if (format !== "webp" && format !== "jpg" && format !== "png") {
return null;
}
processSteps.push(`format,${format}`);
}
const redirected = new URL(target.toString());
redirected.searchParams.set("x-oss-process", processSteps.join("/"));
return redirected;
}
function resolveDirectImageTarget(
request: NextRequest,
target: URL,
width: number | null,
requestedQuality: number | null,
requestedFormat: string | null
): URL | null {
if (!isBrowserImageRequest(request)) {
return null;
}
const hasTransformRequest = Boolean(width || requestedQuality || requestedFormat);
if (!hasTransformRequest) {
return target;
}
if (isAliyunOssHost(target.hostname)) {
return appendAliyunImageProcess(target, width, requestedQuality, requestedFormat);
}
return null;
}
function redirectToDirectImage(target: URL): NextResponse {
const response = NextResponse.redirect(target, 307);
response.headers.set(
"Cache-Control",
"public, max-age=31536000, immutable, stale-while-revalidate=86400"
);
response.headers.set("X-Image-Proxy-Bypass", "direct-image-request");
return response;
}
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const rawUrl = (searchParams.get("url") || "").trim();
@ -116,8 +190,20 @@ export async function GET(request: NextRequest) {
const requestedQuality = parsePositiveInt(searchParams.get("q"));
const width = requestedWidth ? clamp(requestedWidth, MIN_WIDTH, MAX_WIDTH) : null;
const quality = requestedQuality ? clamp(requestedQuality, MIN_QUALITY, MAX_QUALITY) : DEFAULT_QUALITY;
const directQuality = requestedQuality ? clamp(requestedQuality, MIN_QUALITY, MAX_QUALITY) : null;
const requestedFormat = searchParams.get("f");
const directImageTarget = resolveDirectImageTarget(
request,
target,
width,
directQuality,
requestedFormat
);
if (directImageTarget) {
return redirectToDirectImage(directImageTarget);
}
const cacheKey = `${target.toString()}|${width || "auto"}|${quality}|${requestedFormat || "auto"}`;
const etag = buildEtag(cacheKey);
const ifNoneMatch = request.headers.get("if-none-match");