88 lines
2.2 KiB
TypeScript
88 lines
2.2 KiB
TypeScript
/**
|
|
* BFF 通用代理服务(仅服务端使用)。
|
|
*
|
|
* 封装请求转发逻辑:构建请求头、注入 Bearer token、响应头透传过滤。
|
|
* proxy route handler 只负责编排:读路径 → 转发 → 401 重试 → 返回。
|
|
*/
|
|
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
|
|
/** 客户端 → 后端允许透传的请求头白名单(剔除 host / cookie / cf-* 等环境头)。 */
|
|
const FORWARD_HEADERS = [
|
|
"content-type",
|
|
"accept",
|
|
"accept-language",
|
|
"idempotency-key",
|
|
];
|
|
|
|
/**
|
|
* 构建转发给后端的请求头:白名单透传 + 注入 Bearer。
|
|
*/
|
|
export function buildHeaders(
|
|
req: NextRequest,
|
|
token: string | null,
|
|
): Record<string, string> {
|
|
const headers: Record<string, string> = {};
|
|
for (const key of FORWARD_HEADERS) {
|
|
const v = req.headers.get(key);
|
|
if (v) headers[key] = v;
|
|
}
|
|
if (token) headers["authorization"] = `Bearer ${token}`;
|
|
return headers;
|
|
}
|
|
|
|
/**
|
|
* 转发请求到后端目标地址。
|
|
* body 为已缓冲的 ArrayBuffer,可在 401 重试时复用。
|
|
*/
|
|
export function forward(
|
|
target: string,
|
|
req: NextRequest,
|
|
token: string | null,
|
|
body: ArrayBuffer | null,
|
|
): Promise<Response> {
|
|
const init: RequestInit = {
|
|
method: req.method,
|
|
headers: buildHeaders(req, token),
|
|
cache: "no-store",
|
|
};
|
|
if (body && req.method !== "GET" && req.method !== "HEAD") {
|
|
init.body = body;
|
|
}
|
|
return fetch(target, init);
|
|
}
|
|
|
|
/**
|
|
* 响应头透传,过滤 set-cookie(避免后端 cookie 干扰 BFF 的 cookie 管理)。
|
|
*/
|
|
export function passthroughHeaders(src: Headers): Headers {
|
|
const h = new Headers();
|
|
src.forEach((value, key) => {
|
|
if (key.toLowerCase() === "set-cookie") return;
|
|
h.set(key, value);
|
|
});
|
|
return h;
|
|
}
|
|
|
|
/**
|
|
* 构建后端目标 URL。
|
|
*/
|
|
export function buildTargetUrl(
|
|
backend: string,
|
|
path: string[],
|
|
search: string,
|
|
): string {
|
|
return `${backend}/${path.join("/")}${search}`;
|
|
}
|
|
|
|
/**
|
|
* 构造代理响应的 NextResponse。
|
|
*/
|
|
export function proxyResponse(resp: Response): NextResponse {
|
|
return new NextResponse(resp.body, {
|
|
status: resp.status,
|
|
statusText: resp.statusText,
|
|
headers: passthroughHeaders(resp.headers),
|
|
});
|
|
}
|