From 0306aee487dd37c04991b5784a56727f4989026a Mon Sep 17 00:00:00 2001 From: Xyan Bhatnagar Date: Mon, 9 Mar 2026 10:25:40 -0700 Subject: [PATCH 1/3] Fix bug with Raw Server causing AHC tests to fail on Linux The Raw Server handler would `return` every time it received a non-head portion of the request. Unfortunately, every request has a `.end` portion in addition to the `.head` portion, meaning that the server would be tearing down the connection after one response despite not including `Connection: close`. To fix this, allow the connection to handle multiple requests by making this a `continue` instead of a `return`. --- .../HTTPServerForTesting/RawHTTPServer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift index 95f2a4d..1731c99 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift @@ -122,7 +122,7 @@ actor RawHTTPServer { // Wait for a request header. // Ignore request bodies for now. guard case .head(let head) = requestPart else { - return + continue } // Get the response from the handler From d40ab59ce9ef05f58623f341929bf87c165b50b1 Mon Sep 17 00:00:00 2001 From: Xyan Bhatnagar Date: Wed, 11 Mar 2026 08:17:53 -0700 Subject: [PATCH 2/3] Avoid connection reuse for raw server Connection re-use doesn't work correctly for cases where the server is expected to write incomplete un-chunked bodies. Connection re-use would mean that neither the server nor the client close the connection and both are waiting for more data. This causes indefinite hangs. The raw server now receives the headers from a request, writes the response and closes the connection immediately. --- .../HTTPServerForTesting/RawHTTPServer.swift | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift index 1731c99..9b26140 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift @@ -40,13 +40,24 @@ func handler(request: HTTPRequestHead) -> Data { case "/not_http": return "FOOBAR".data(using: .ascii)! case "/http_case": - return "Http/1.1 200 OK\r\n\r\n".data(using: .ascii)! + return linesToData([ + "Http/1.1 200 OK", + "Connection: close", + "", + "", + ]) case "/no_reason": - return "HTTP/1.1 200\r\n\r\n".data(using: .ascii)! + return linesToData([ + "HTTP/1.1 200", + "Connection: close", + "", + "", + ]) case "/204_with_cl": return linesToData([ "HTTP/1.1 204 No Content", "Content-Length: 1000", + "Connection: close", "", "", ]) @@ -54,6 +65,7 @@ func handler(request: HTTPRequestHead) -> Data { return linesToData([ "HTTP/1.1 304 Not Modified", "Content-Length: 1000", + "Connection: close", "", "", ]) @@ -61,12 +73,14 @@ func handler(request: HTTPRequestHead) -> Data { return linesToData([ "HTTP/1.1 200 OK", "Content-Length: 1000", + "Connection: close", "", "1234", ]) case "/no_length_hint": return linesToData([ "HTTP/1.1 200 OK", + "Connection: close", "", "1234", ]) @@ -74,11 +88,17 @@ func handler(request: HTTPRequestHead) -> Data { return linesToData([ "HTTP/1.1 200 OK", "Content-Length: 10, 4", + "Connection: close", "", "1234", ]) default: - return "HTTP/1.1 500 Internal Server Error\r\n\r\n".data(using: .ascii)! + return linesToData([ + "HTTP/1.1 500 Internal Server Error", + "Connection: close", + "", + "", + ]) } } @@ -117,21 +137,30 @@ actor RawHTTPServer { func run(handler: @Sendable @escaping (HTTPRequestHead) async throws -> Data) async throws { try await server_channel.executeThenClose { inbound in for try await httpChannel in inbound { - try await httpChannel.executeThenClose { inbound, outbound in - for try await requestPart in inbound { - // Wait for a request header. - // Ignore request bodies for now. + do { + try await httpChannel.executeThenClose { inbound, outbound in + // Read exactly one part of the request + var iter = inbound.makeAsyncIterator() + let requestPart = try await iter.next() + + // It must be the header. We don't care about the rest. guard case .head(let head) = requestPart else { - continue + print("Server unexpectedly received non-header as first part of request: \(requestPart)") + return } // Get the response from the handler let response = try await handler(head) - // Write the response out + // Write the response out and then close the channel. + // This server is a simple one, it won't reuse connections. let data = IOData.byteBuffer(ByteBuffer(bytes: response)) try await outbound.write(data) } + } catch { + // Do not let one connection take down the entire server. + // Print the error from the connection and move on. + print("Server caught error handling client: \(error)") } } } From fce7881628e024325c3d4f3c4c52391f79f0fdb7 Mon Sep 17 00:00:00 2001 From: Xyan Bhatnagar Date: Wed, 11 Mar 2026 08:28:11 -0700 Subject: [PATCH 3/3] Add a warning about the raw server endpoints --- .../HTTPServerForTesting/RawHTTPServer.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift b/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift index 9b26140..bd01174 100644 --- a/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift +++ b/Sources/HTTPClientConformance/HTTPServerForTesting/RawHTTPServer.swift @@ -36,6 +36,8 @@ func linesToData(_ lines: [String]) -> Data { } func handler(request: HTTPRequestHead) -> Data { + /// **WARNING**: All endpoints must add a `Connection: close` header because + /// this server does not allow connection reuse. switch request.uri { case "/not_http": return "FOOBAR".data(using: .ascii)!