/** 代理核心:鉴权透传、请求转发、SSE 流式透传与错误映射。 * 照壁不拥有业务规则(service-boundaries.md §3):能否看到的语义一律来自灵台; * 这里只做传输与形状处理——透传凭证/幂等/续传头,流式响应禁缓冲直通。 */ import { Readable } from "node:stream"; import type { FastifyReply, FastifyRequest } from "fastify"; /** 需要原样透传的请求头(小写)。 */ const FORWARD_REQUEST_HEADERS = [ "authorization", "content-type", "accept", "idempotency-key", "last-event-id", "x-trace-id", ] as const; /** 需要透传的响应头(SSE 关键头由流式分支显式设置)。 */ const FORWARD_RESPONSE_HEADERS = ["content-type", "x-trace-id", "retry-after"] as const; export interface UpstreamError { statusCode: number; code: string; message: string; } /** 判定上游响应是否为 SSE 流。 */ function isEventStream(contentType: string | null): boolean { return (contentType ?? "").includes("text/event-stream"); } export async function proxyRequest( req: FastifyRequest, reply: FastifyReply, backendUrl: string, ): Promise { const headers: Record = {}; for (const name of FORWARD_REQUEST_HEADERS) { const value = req.headers[name]; if (value != null) { headers[name] = Array.isArray(value) ? value.join(", ") : value; } } const method = req.method; const body = method === "GET" || method === "HEAD" ? undefined : (req.body as Buffer); let upstream: Response; try { upstream = await fetch(`${backendUrl}${req.url}`, { method, headers, body: body && body.length > 0 ? new Uint8Array(body) : undefined, redirect: "manual", }); } catch (err) { // 上游不可达:HJ-3001(API 契约 §5 错误结构) req.log.warn({ err: String(err) }, "上游不可达"); await reply.code(502).send({ error: { code: "HJ-3001", message: "上游服务不可达", request_id: req.id }, }); return; } if (upstream.body === null) { await writeBuffered(reply, upstream); return; } if (isEventStream(upstream.headers.get("content-type"))) { writeEventStream(req, reply, upstream); return; } await writeBuffered(reply, upstream); } async function writeBuffered(reply: FastifyReply, upstream: Response): Promise { for (const name of FORWARD_RESPONSE_HEADERS) { const value = upstream.headers.get(name); if (value != null) { reply.header(name, value); } } const payload = Buffer.from(await upstream.arrayBuffer()); await reply.code(upstream.status).send(payload.length > 0 ? payload : null); } function writeEventStream(req: FastifyRequest, reply: FastifyReply, upstream: Response): void { // 禁缓冲直通:hijack 后绕过 Fastify 序列化,逐块写底层连接 reply.hijack(); const raw = reply.raw; raw.writeHead(upstream.status, { "content-type": upstream.headers.get("content-type") ?? "text/event-stream; charset=utf-8", "cache-control": "no-cache", "x-accel-buffering": "no", }); const nodeStream = Readable.fromWeb(upstream.body as Parameters[0]); nodeStream.on("error", (err) => { req.log.warn({ err: String(err) }, "上游流中断"); raw.end(); }); nodeStream.pipe(raw); }