diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index fa0f730..4f9c5f4 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -5,11 +5,17 @@ import { type ProxyOutgoingMiddleware, defineProxyOutgoingMiddleware } from "./_ const redirectRegex = /^201|30([1278])$/; /** - * Remove chunked transfer-encoding for HTTP/1.0 and HTTP/2 requests + * Remove chunked transfer-encoding for HTTP/1.0, HTTP/2, and bodyless (204/304) responses */ export const removeChunked = defineProxyOutgoingMiddleware((req, res, proxyRes) => { // HTTP/1.0 and HTTP/2 do not have transfer-encoding: chunked - if (req.httpVersion === "1.0" || req.httpVersionMajor >= 2) { + // 204 and 304 responses MUST NOT contain a message body (RFC 9110) + if ( + req.httpVersion === "1.0" || + req.httpVersionMajor >= 2 || + proxyRes.statusCode === 204 || + proxyRes.statusCode === 304 + ) { delete proxyRes.headers["transfer-encoding"]; } }); diff --git a/test/middleware/web-outgoing.test.ts b/test/middleware/web-outgoing.test.ts index cc22f25..85603aa 100644 --- a/test/middleware/web-outgoing.test.ts +++ b/test/middleware/web-outgoing.test.ts @@ -692,18 +692,68 @@ describe("middleware:web-outgoing", () => { }); }); - it("#removeChunked", () => { - const proxyRes = { - headers: { - "transfer-encoding": "hello", - }, - }; - webOutgoing.removeChunked( - stubIncomingMessage({ httpVersion: "1.0" }), - stubServerResponse(), - proxyRes as any, - stubMiddlewareOptions(), - ); - expect(proxyRes.headers["transfer-encoding"]).to.eql(undefined); + describe("#removeChunked", () => { + it("removes transfer-encoding on HTTP/1.0", () => { + const proxyRes = { + headers: { + "transfer-encoding": "hello", + }, + }; + webOutgoing.removeChunked( + stubIncomingMessage({ httpVersion: "1.0" }), + stubServerResponse(), + proxyRes as any, + stubMiddlewareOptions(), + ); + expect(proxyRes.headers["transfer-encoding"]).to.eql(undefined); + }); + + it("removes transfer-encoding on 204 response", () => { + const proxyRes = { + statusCode: 204, + headers: { + "transfer-encoding": "chunked", + }, + }; + webOutgoing.removeChunked( + stubIncomingMessage({ httpVersion: "1.1" }), + stubServerResponse(), + proxyRes as any, + stubMiddlewareOptions(), + ); + expect(proxyRes.headers["transfer-encoding"]).to.eql(undefined); + }); + + it("removes transfer-encoding on 304 response", () => { + const proxyRes = { + statusCode: 304, + headers: { + "transfer-encoding": "chunked", + }, + }; + webOutgoing.removeChunked( + stubIncomingMessage({ httpVersion: "1.1" }), + stubServerResponse(), + proxyRes as any, + stubMiddlewareOptions(), + ); + expect(proxyRes.headers["transfer-encoding"]).to.eql(undefined); + }); + + it("preserves transfer-encoding on normal HTTP/1.1 responses", () => { + const proxyRes = { + statusCode: 200, + headers: { + "transfer-encoding": "chunked", + }, + }; + webOutgoing.removeChunked( + stubIncomingMessage({ httpVersion: "1.1" }), + stubServerResponse(), + proxyRes as any, + stubMiddlewareOptions(), + ); + expect(proxyRes.headers["transfer-encoding"]).to.eql("chunked"); + }); }); });