diff --git a/src/http.zig b/src/http.zig index 7c9659e0eb..517a470f83 100644 --- a/src/http.zig +++ b/src/http.zig @@ -542,6 +542,7 @@ pub fn buildRequest(this: *HTTPClient, body_len: usize) picohttp.Request { var override_accept_encoding = false; var override_accept_header = false; var override_host_header = false; + var override_connection_header = false; var override_user_agent = false; var add_transfer_encoding = true; var original_content_length: ?string = null; @@ -560,8 +561,10 @@ pub fn buildRequest(this: *HTTPClient, body_len: usize) picohttp.Request { continue; }, hashHeaderConst("Connection") => { - if (!this.flags.disable_keepalive) { - continue; + override_connection_header = true; + const connection_value = this.headerStr(header_values[i]); + if (std.ascii.eqlIgnoreCase(connection_value, "close")) { + this.flags.disable_keepalive = true; } }, hashHeaderConst("if-modified-since") => { @@ -609,7 +612,7 @@ pub fn buildRequest(this: *HTTPClient, body_len: usize) picohttp.Request { header_count += 1; } - if (!this.flags.disable_keepalive) { + if (!override_connection_header and !this.flags.disable_keepalive) { request_headers_buf[header_count] = connection_header; header_count += 1; } diff --git a/test/js/web/fetch/fetch-connection-header.test.ts b/test/js/web/fetch/fetch-connection-header.test.ts new file mode 100644 index 0000000000..52e3ca8fe5 --- /dev/null +++ b/test/js/web/fetch/fetch-connection-header.test.ts @@ -0,0 +1,93 @@ +import { serve } from "bun"; +import { describe, expect, it, test } from "bun:test"; + +describe("fetch Connection header", () => { + // Helper function to capture headers from a request + const captureHeadersFromRequest = async (fetchOptions: RequestInit): Promise> => { + return new Promise((resolve, reject) => { + // Create a temporary server to capture headers + const tempServer = serve({ + port: 0, + fetch(req) { + const capturedHeaders: Record = {}; + for (const [name, value] of req.headers.entries()) { + capturedHeaders[name.toLowerCase()] = value; + } + tempServer.stop(); + resolve(capturedHeaders); + return new Response("OK"); + }, + }); + + const tempPort = tempServer.port; + const url = `http://localhost:${tempPort}/test`; + + // Make the request to temp server + fetch(url, fetchOptions) + .then(response => { + if (response.status !== 200) { + tempServer.stop(); + reject(new Error(`Expected status 200, got ${response.status}`)); + } + }) + .catch(error => { + tempServer.stop(); + reject(error); + }); + }); + }; + + test.each([ + ["close", "close"], + ["keep-alive", "keep-alive"], + ["upgrade", "upgrade"], + ["Upgrade", "Upgrade"], // Test case preservation + ])("should respect Connection: %s header", async (inputValue, expectedValue) => { + const headers = await captureHeadersFromRequest({ + headers: { Connection: inputValue }, + }); + + expect(headers.connection).toBe(expectedValue); + }); + + test.each([ + ["connection", "close"], + ["Connection", "close"], + ["CONNECTION", "close"], + ])("should respect case-insensitive header name: %s", async (headerName, expectedValue) => { + const headers = await captureHeadersFromRequest({ + headers: { [headerName]: expectedValue }, + }); + + expect(headers.connection).toBe(expectedValue); + }); + + it("should respect Connection header in Request object", async () => { + const headers = await captureHeadersFromRequest({ + headers: { Connection: "close" }, + }); + expect(headers.connection).toBe("close"); + }); + + it("should default to keep-alive when no Connection header provided", async () => { + const headers = await captureHeadersFromRequest({}); + expect(headers.connection).toBe("keep-alive"); + }); + + it("should handle multiple headers including Connection", async () => { + const headers = await captureHeadersFromRequest({ + headers: { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "accept-language": "en-US", + "connection": "close", + "user-agent": "test-agent", + "x-test-header": "test-value", + }, + }); + + expect(headers.connection).toBe("close"); + expect(headers.accept).toBe("application/json"); + expect(headers["x-test-header"]).toBe("test-value"); + }); +}); diff --git a/test/no-validate-exceptions.txt b/test/no-validate-exceptions.txt index fc4d24346b..db4370fec0 100644 --- a/test/no-validate-exceptions.txt +++ b/test/no-validate-exceptions.txt @@ -519,6 +519,7 @@ test/js/web/fetch/client-fetch.test.ts test/js/web/fetch/content-length.test.js test/js/web/fetch/cookies.test.ts test/js/web/fetch/fetch-args.test.ts +test/js/web/fetch/fetch-connection-header.test.ts test/js/web/fetch/fetch-gzip.test.ts test/js/web/fetch/fetch-preconnect.test.ts test/js/web/fetch/fetch-redirect.test.ts