import { NextRequest, NextResponse } from "next/server"; import { getClientInfo, logProxyAccess, shouldLogProxyError } from "./proxy-logging"; import { buildFallbackTargetUrl, buildProxyRequestHeaders, buildTargetUrl, fetchWithOptionalFallback, getForwardedSearchParams, isAbortError, ProxyBodyTooLargeError, proxyRequestBodyLimit, readProxyRequestBody, } from "./proxy-fetch"; import { toProxyNextResponse } from "./proxy-response"; import { resolveProxyTarget } from "./proxy-target"; export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> }, ) { const { path } = await params; return handleRequest(request, "GET", path); } export async function POST( request: NextRequest, { params }: { params: Promise<{ path: string[] }> }, ) { const { path } = await params; return handleRequest(request, "POST", path); } export async function PUT( request: NextRequest, { params }: { params: Promise<{ path: string[] }> }, ) { const { path } = await params; return handleRequest(request, "PUT", path); } export async function DELETE( request: NextRequest, { params }: { params: Promise<{ path: string[] }> }, ) { const { path } = await params; return handleRequest(request, "DELETE", path); } 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; // Never forward credentials to the controller as query params. 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 toProxyNextResponse(response, { client, invalidateOverride: usedFallback || target.blockedOverrideCleared, method, path, }); } 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 NextResponse.json({ error: "Backend request timed out" }, { status: 504 }); } if (error instanceof ProxyBodyTooLargeError) { return NextResponse.json({ error: error.message }, { status: 413 }); } return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } }