You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
const BOT_ORIGIN = 'http://127.0.0.1:3007';
|
|
const WEB_ORIGIN = 'http://127.0.0.1:3008';
|
|
|
|
function shouldRouteToBot(pathname: string): boolean {
|
|
if (pathname === '/metrics' || pathname.startsWith('/metrics/')) return true;
|
|
if (pathname === '/webhook' || pathname.startsWith('/webhook/')) return true;
|
|
return false;
|
|
}
|
|
|
|
function buildForwardHeaders(req: Request): Headers {
|
|
const headers = new Headers(req.headers);
|
|
try {
|
|
const proto = headers.get('x-forwarded-proto') || 'https';
|
|
const fwdFor = headers.get('x-forwarded-for');
|
|
headers.set('x-forwarded-proto', proto);
|
|
headers.set('x-forwarded-for', fwdFor ? `${fwdFor}, 127.0.0.1` : '127.0.0.1');
|
|
const host = headers.get('host') || '';
|
|
if (!host) headers.set('host', 'localhost');
|
|
} catch {}
|
|
return headers;
|
|
}
|
|
|
|
Bun.serve({
|
|
port: Number(process.env.PORT || process.env.ROUTER_PORT || 3000),
|
|
fetch: async (req) => {
|
|
const url = new URL(req.url);
|
|
|
|
// Health local para el contenedor (evita 404 en healthcheck)
|
|
if (url.pathname === '/health') {
|
|
return new Response('ok', { status: 200, headers: { 'content-type': 'text/plain' } });
|
|
}
|
|
|
|
const routeToBot = shouldRouteToBot(url.pathname);
|
|
const targetOrigin = routeToBot ? BOT_ORIGIN : WEB_ORIGIN;
|
|
const targetUrl = targetOrigin + url.pathname + url.search;
|
|
|
|
const init: RequestInit = {
|
|
method: req.method,
|
|
headers: buildForwardHeaders(req),
|
|
body: req.method === 'GET' || req.method === 'HEAD' ? undefined : req.body,
|
|
redirect: 'manual',
|
|
};
|
|
|
|
const started = Date.now();
|
|
try {
|
|
const res = await fetch(targetUrl, init);
|
|
const ms = Date.now() - started;
|
|
try {
|
|
console.log(`[proxy] ${req.method} ${url.pathname}${url.search} -> ${routeToBot ? 'bot' : 'web'} ${res.status} (${ms}ms)`);
|
|
} catch {}
|
|
// Devuelve la respuesta (incluye Set-Cookie, Location, etc.), asegurando Content-Type en assets por si faltase
|
|
const passthroughHeaders = new Headers(res.headers);
|
|
if (!passthroughHeaders.get('content-type')) {
|
|
if (url.pathname.endsWith('.js')) passthroughHeaders.set('content-type', 'application/javascript; charset=utf-8');
|
|
if (url.pathname.endsWith('.css')) passthroughHeaders.set('content-type', 'text/css; charset=utf-8');
|
|
}
|
|
return new Response(res.body, { status: res.status, headers: passthroughHeaders });
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
console.error(`[proxy] ${req.method} ${url.pathname}${url.search} -> ERROR: ${msg}`);
|
|
return new Response(`Proxy error: ${msg}\n`, { status: 502 });
|
|
}
|
|
},
|
|
});
|