import { NextRequest } from "next/server"; import { relayResponse } from "@/app/api/_lib/relay-response"; import { getClientInfo, logProxyAccess, shouldLogProxyError } from "./proxy-logging"; import { buildFallbackTargetUrl, buildProxyRequestHeaders, buildTargetUrl, fetchWithOptionalFallback, getForwardedSearchParams, isAbortError, ProxyBodyTooLargeError, proxyRequestBodyLimit, readProxyRequestBody, } from "./proxy-fetch"; import { clearBackendOverrideHeaders, resolveProxyTarget } from "./proxy-target"; const route = (request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) => params.then(({ path }) => handleRequest(request, request.method, path)); export const GET = route; export const POST = route; export const PUT = route; export const DELETE = route; async function handleRequest(request: NextRequest, method: string, path: string[]) { const startTime = Date.now(); const client = getClientInfo(request); try { const target = await resolveProxyTarget(request, client); if ("blockedResponse" in target) return target.blockedResponse; const { apiKeyQuery, searchParams } = getForwardedSearchParams(request); const targetUrl = buildTargetUrl(target.backendUrl, path, searchParams); const fallbackTargetUrl = buildFallbackTargetUrl({ defaultBackendUrl: target.defaultBackendUrl, overrideUrl: target.overrideUrl, path, searchParams, }); const hasAuth = Boolean(request.headers.get("authorization")); logProxyAccess({ client, hasAuth, method, overrideUrl: target.overrideUrl, path }); const body = await readProxyRequestBody(request, method, proxyRequestBodyLimit(path)); const headers = buildProxyRequestHeaders( request, target.apiKey, apiKeyQuery, Boolean(target.overrideUrl), ); const { response, usedFallback } = await fetchWithOptionalFallback( targetUrl, fallbackTargetUrl, { method, headers, body }, { client, method, path, overrideUsed: Boolean(target.overrideUrl), strictOverride: target.strictOverride, }, ); return relayResponse(response, { headers: usedFallback || target.blockedOverrideCleared ? clearBackendOverrideHeaders() : undefined, onStreamError: (error) => { if (shouldLogProxyError(method, path, error)) { console.warn( `[PROXY STREAM CLOSED] ip=${client.ip} | country=${client.country} | method=${method} | path=/${path.join("/")} | error=${String(error)}`, ); } }, }); } catch (error) { const duration = Date.now() - startTime; if (shouldLogProxyError(method, path, error)) { console.error( `[PROXY ERROR] ip=${client.ip} | country=${client.country} | method=${method} | path=/${path.join("/")} | duration=${duration}ms | error=${String(error)}`, ); } if (isAbortError(error)) { return Response.json({ error: "Backend request timed out" }, { status: 504 }); } if (error instanceof ProxyBodyTooLargeError) { return Response.json({ error: error.message }, { status: 413 }); } return Response.json({ error: "Internal server error" }, { status: 500 }); } }