222 lines
6.4 KiB
TypeScript
222 lines
6.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8888";
|
|
|
|
// #region agent log
|
|
function debugLog(hypothesisId: string, message: string, data: Record<string, unknown>) {
|
|
fetch("http://localhost:7687/ingest/ee56ea93-ad22-4673-ab77-595d83a9b3c5", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
"X-Debug-Session-Id": "6b638a",
|
|
},
|
|
body: JSON.stringify({
|
|
sessionId: "6b638a",
|
|
runId: "login-400-debug",
|
|
hypothesisId,
|
|
location: "frontend/app/api/[...path]/route.ts:debugLog",
|
|
message,
|
|
data,
|
|
timestamp: Date.now(),
|
|
}),
|
|
}).catch(() => {});
|
|
}
|
|
// #endregion
|
|
|
|
export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
|
return proxyRequest(req, ctx);
|
|
}
|
|
|
|
export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
|
return proxyRequest(req, ctx);
|
|
}
|
|
|
|
export async function PUT(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
|
return proxyRequest(req, ctx);
|
|
}
|
|
|
|
export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
|
return proxyRequest(req, ctx);
|
|
}
|
|
|
|
export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
|
|
return proxyRequest(req, ctx);
|
|
}
|
|
|
|
async function proxyRequest(
|
|
req: NextRequest,
|
|
ctx: { params: Promise<{ path: string[] }> }
|
|
) {
|
|
const { path } = await ctx.params;
|
|
const search = req.nextUrl.search || "";
|
|
const proxiedPathRaw = req.nextUrl.pathname.replace(/^\/api\//, "");
|
|
const normalizedPathRaw = proxiedPathRaw.startsWith("api/")
|
|
? proxiedPathRaw
|
|
: `api/${proxiedPathRaw}`;
|
|
const proxiedPath = normalizedPathRaw.endsWith("/") ? normalizedPathRaw : `${normalizedPathRaw}/`;
|
|
const target = `${API_BASE}/${proxiedPath}${search}`;
|
|
// #region agent log
|
|
debugLog("H1_H2_H3", "proxy_request_start", {
|
|
method: req.method,
|
|
path,
|
|
proxiedPath,
|
|
originalPathname: req.nextUrl.pathname,
|
|
search,
|
|
apiBase: API_BASE,
|
|
target,
|
|
});
|
|
// #endregion
|
|
|
|
const headers = new Headers(req.headers);
|
|
headers.delete("host");
|
|
headers.delete("content-length");
|
|
|
|
const hasBody = !["GET", "HEAD"].includes(req.method);
|
|
const contentType = headers.get("content-type");
|
|
const isMultipart = (contentType || "").toLowerCase().includes("multipart/form-data");
|
|
let clonedBodyText = "";
|
|
let requestBodyLength = 0;
|
|
let requestBodyJsonKeys: string[] = [];
|
|
let requestBodyReadError: string | null = null;
|
|
|
|
if (hasBody && !isMultipart) {
|
|
try {
|
|
clonedBodyText = await req.clone().text();
|
|
requestBodyLength = clonedBodyText.length;
|
|
if (contentType?.includes("application/json")) {
|
|
const parsed = JSON.parse(clonedBodyText || "{}");
|
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
requestBodyJsonKeys = Object.keys(parsed).sort();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
requestBodyReadError = error instanceof Error ? error.message : "unknown_error";
|
|
}
|
|
}
|
|
// #region agent log
|
|
debugLog("H1_H3_H4", "proxy_request_body_probe", {
|
|
method: req.method,
|
|
target,
|
|
hasBody,
|
|
isMultipart,
|
|
contentType,
|
|
requestBodyLength,
|
|
requestBodyJsonKeys,
|
|
requestBodyReadError,
|
|
hasUsernameKey: requestBodyJsonKeys.includes("username"),
|
|
hasPasswordKey: requestBodyJsonKeys.includes("password"),
|
|
});
|
|
// #endregion
|
|
|
|
const init: RequestInit & { duplex?: "half" } = {
|
|
method: req.method,
|
|
headers,
|
|
cache: "no-store",
|
|
};
|
|
|
|
if (hasBody) {
|
|
if (isMultipart) {
|
|
// Multipart precisa seguir como stream para preservar boundary e arquivos.
|
|
init.body = req.body;
|
|
init.duplex = "half";
|
|
debugLog("H9", "proxy_request_forward_mode", {
|
|
method: req.method,
|
|
target,
|
|
forwardMode: "stream_multipart",
|
|
contentType,
|
|
});
|
|
} else {
|
|
// JSON/urlencoded segue buffer textual para evitar perda de stream no proxy.
|
|
init.body = clonedBodyText;
|
|
debugLog("H9", "proxy_request_forward_mode", {
|
|
method: req.method,
|
|
target,
|
|
forwardMode: "buffered_text",
|
|
forwardedBodyLength: clonedBodyText.length,
|
|
contentType,
|
|
});
|
|
}
|
|
}
|
|
|
|
let res: Response;
|
|
try {
|
|
res = await fetch(target, init);
|
|
} catch (error) {
|
|
// #region agent log
|
|
debugLog("H2", "proxy_request_fetch_exception", {
|
|
target,
|
|
error: error instanceof Error ? error.message : "unknown_error",
|
|
});
|
|
// #endregion
|
|
return NextResponse.json({ message: "Falha de conexão no proxy." }, { status: 502 });
|
|
}
|
|
// #region agent log
|
|
debugLog("H5", "proxy_request_first_attempt_done", {
|
|
target,
|
|
status: res.status,
|
|
needsTrailingSlashRetry: res.status === 404 && !target.endsWith("/"),
|
|
});
|
|
// #endregion
|
|
|
|
if (res.status === 404 && !target.endsWith("/")) {
|
|
const targetWithSlash = `${target}/`;
|
|
// #region agent log
|
|
debugLog("H5", "proxy_request_retry_with_trailing_slash", {
|
|
from: target,
|
|
to: targetWithSlash,
|
|
});
|
|
// #endregion
|
|
try {
|
|
const retried = await fetch(targetWithSlash, init);
|
|
// #region agent log
|
|
debugLog("H5", "proxy_request_retry_response", {
|
|
targetWithSlash,
|
|
status: retried.status,
|
|
statusText: retried.statusText,
|
|
});
|
|
// #endregion
|
|
res = retried;
|
|
} catch (error) {
|
|
// #region agent log
|
|
debugLog("H5", "proxy_request_retry_exception", {
|
|
targetWithSlash,
|
|
error: error instanceof Error ? error.message : "unknown_error",
|
|
});
|
|
// #endregion
|
|
}
|
|
}
|
|
// #region agent log
|
|
debugLog("H1_H3", "proxy_request_response", {
|
|
target,
|
|
status: res.status,
|
|
statusText: res.statusText,
|
|
});
|
|
// #endregion
|
|
if (res.status >= 400) {
|
|
let responsePreview = "";
|
|
try {
|
|
responsePreview = (await res.clone().text()).slice(0, 300);
|
|
} catch {
|
|
responsePreview = "unreadable_response_body";
|
|
}
|
|
// #region agent log
|
|
debugLog("H2_H4", "proxy_request_error_response_preview", {
|
|
target,
|
|
status: res.status,
|
|
statusText: res.statusText,
|
|
responsePreview,
|
|
});
|
|
// #endregion
|
|
}
|
|
|
|
const responseHeaders = new Headers();
|
|
res.headers.forEach((value, key) => {
|
|
responseHeaders.set(key, value);
|
|
});
|
|
|
|
return new NextResponse(res.body, {
|
|
status: res.status,
|
|
statusText: res.statusText,
|
|
headers: responseHeaders,
|
|
});
|
|
} |