From 7e07e3f6291a42bfa56a5c4a7528c645f9bba863 Mon Sep 17 00:00:00 2001 From: Abhay Date: Tue, 6 Oct 2026 18:24:16 +0530 Subject: [PATCH] fix(proxy): preserve binary request bodies and forward DELETE request bodies (#157) --- routes/proxy.ts | 7 ++- tests/routes/proxy-binary-body.test.ts | 60 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/routes/proxy-binary-body.test.ts diff --git a/routes/proxy.ts b/routes/proxy.ts index eb7848a..2d435d2 100644 --- a/routes/proxy.ts +++ b/routes/proxy.ts @@ -24,8 +24,11 @@ export default withRouteSpec({ } let body = undefined - if (["POST", "PUT", "PATCH"].includes(req.method)) { - body = await req.clone().text() + if (!["GET", "HEAD"].includes(req.method)) { + const rawBody = await req.clone().arrayBuffer() + if (rawBody.byteLength > 0) { + body = rawBody + } } const headers = new Headers(req.headers) diff --git a/tests/routes/proxy-binary-body.test.ts b/tests/routes/proxy-binary-body.test.ts new file mode 100644 index 0000000..afe2a64 --- /dev/null +++ b/tests/routes/proxy-binary-body.test.ts @@ -0,0 +1,60 @@ +import { expect, test } from "bun:test" +import { getTestServer } from "../fixtures/get-test-server" + +test.each(["POST", "PUT", "PATCH", "DELETE"])( + "proxy preserves binary request bytes for %s", + async (method) => { + const { url } = await getTestServer() + const payload = new Uint8Array([0x00, 0xff, 0xfe, 0x80, 0x41]) + const upstream = Bun.serve({ + port: 0, + async fetch(req) { + return new Response(await req.arrayBuffer(), { + headers: { "Content-Type": "application/octet-stream" }, + }) + }, + }) + + try { + const response = await fetch(`${url}/proxy`, { + method, + headers: { + "X-Target-Url": `http://127.0.0.1:${upstream.port}`, + "Content-Type": "application/octet-stream", + }, + body: payload, + signal: AbortSignal.timeout(3000), + }) + expect(response.status).toBe(200) + expect(new Uint8Array(await response.arrayBuffer())).toEqual(payload) + } finally { + upstream.stop(true) + } + }, +) + +test("proxy handles OPTIONS method without body", async () => { + const { url } = await getTestServer() + const upstream = Bun.serve({ + port: 0, + async fetch(req) { + return new Response(null, { + status: 204, + headers: { Allow: "GET, POST, OPTIONS" }, + }) + }, + }) + + try { + const response = await fetch(`${url}/proxy`, { + method: "OPTIONS", + headers: { + "X-Target-Url": `http://127.0.0.1:${upstream.port}`, + }, + }) + expect(response.status).toBe(204) + expect(response.headers.get("Allow")).toBe("GET, POST, OPTIONS") + } finally { + upstream.stop(true) + } +})