import type { APIRoute } from 'astro'; const FORBIDDEN_HEADERS = new Set([ 'host', 'connection', 'content-length', 'transfer-encoding', 'origin', 'referer', 'accept-encoding', ]); // Node fetch auto-decompresses the response body, so any encoding/length // headers from the upstream no longer match what we forward to the browser. const FORBIDDEN_RESPONSE_HEADERS = new Set([ 'content-encoding', 'content-length', 'transfer-encoding', ]); export const ALL: APIRoute = async ({ request, params }) => { const API_URL = process.env.PUBLIC_API_URL || 'http://backend:3000'; const path = params.path; if (!path) { return new Response(JSON.stringify({ error: 'Missing path' }), { status: 400 }); } const url = new URL(`${API_URL}/api/${path}`); const requestUrl = new URL(request.url); url.search = requestUrl.search; const headers = new Headers(); request.headers.forEach((value, key) => { if (!FORBIDDEN_HEADERS.has(key.toLowerCase())) { headers.set(key, value); } }); try { const fetchOptions: RequestInit = { method: request.method, headers, }; if (request.method !== 'GET' && request.method !== 'HEAD' && request.body) { fetchOptions.body = request.body; // @ts-expect-error — required by Node fetch when body is a stream fetchOptions.duplex = 'half'; } const response = await fetch(url.toString(), fetchOptions); const responseHeaders = new Headers(); response.headers.forEach((value, key) => { const k = key.toLowerCase(); // Set-Cookie can repeat and must NOT be merged. Handle it separately below. if (k === 'set-cookie') return; if (FORBIDDEN_RESPONSE_HEADERS.has(k)) return; responseHeaders.set(key, value); }); // @ts-expect-error — getSetCookie is on Node fetch's Headers const setCookies: string[] = response.headers.getSetCookie?.() ?? []; for (const c of setCookies) { responseHeaders.append('set-cookie', c); } return new Response(response.body, { status: response.status, headers: responseHeaders, }); } catch (e) { console.error(`[Proxy] ${request.method} ${url} failed:`, e); return new Response(JSON.stringify({ error: 'Proxy connection failed' }), { status: 502, headers: { 'Content-Type': 'application/json' }, }); } };