73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
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' },
|
|
});
|
|
}
|
|
};
|