From 6af26a9ee8bd3edc556f71917fd4de063b3c3903 Mon Sep 17 00:00:00 2001 From: savioruz Date: Wed, 23 Sep 2026 09:11:50 +0700 Subject: [PATCH] fix(proxy-pools): lossless header forwarding for relay pools Preserve request headers through the Vercel/Cloudflare/Deno relay. - proxyFetch: normalize options.headers via Object.fromEntries before spreading into the relay headers. Spreading a Headers instance yields {} and silently dropped every entry (auth + content-type), which is why the same pool worked on one path and failed on another. - vercel relay: build the forwarded header object from req.headers.entries() instead of new Headers(req.headers), avoiding edge-runtime normalization of casing/duplicate keys that some providers reject. --- open-sse/utils/proxyFetch.js | 5 ++++- src/app/api/proxy-pools/vercel-deploy/route.js | 11 ++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/open-sse/utils/proxyFetch.js b/open-sse/utils/proxyFetch.js index 341b8158..b5403fde 100644 --- a/open-sse/utils/proxyFetch.js +++ b/open-sse/utils/proxyFetch.js @@ -298,8 +298,11 @@ export async function proxyAwareFetch(url, options = {}, proxyOptions = null) { const vercelRelayUrl = normalizeString(proxyOptions?.vercelRelayUrl); if (vercelRelayUrl) { const parsed = new URL(targetUrl); + const baseHeaders = options.headers instanceof Headers + ? Object.fromEntries(options.headers.entries()) + : { ...(options.headers || {}) }; const relayHeaders = { - ...options.headers, + ...baseHeaders, "x-relay-target": `${parsed.protocol}//${parsed.host}`, "x-relay-path": `${parsed.pathname}${parsed.search}`, }; diff --git a/src/app/api/proxy-pools/vercel-deploy/route.js b/src/app/api/proxy-pools/vercel-deploy/route.js index e87390f5..f6501331 100644 --- a/src/app/api/proxy-pools/vercel-deploy/route.js +++ b/src/app/api/proxy-pools/vercel-deploy/route.js @@ -20,14 +20,15 @@ export default async function handler(req) { const targetUrl = target.replace(/\\/$/, "") + relayPath; - const headers = new Headers(req.headers); - headers.delete("x-relay-target"); - headers.delete("x-relay-path"); - headers.delete("host"); + const rawHeaders = {}; + for (const [k, v] of req.headers.entries()) rawHeaders[k] = v; + delete rawHeaders["x-relay-target"]; + delete rawHeaders["x-relay-path"]; + delete rawHeaders["host"]; const response = await fetch(targetUrl, { method: req.method, - headers, + headers: rawHeaders, body: req.method !== "GET" && req.method !== "HEAD" ? req.body : undefined, duplex: "half", });