Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,29 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
_ = try await httpClient.execute(request, deadline: .now() + .seconds(2))
}

func testFollowingRedirectsWithBodiesThatAreNotReadDoesNotFailTheNextRequest() {
XCTAsyncTest {
let bin = HTTPBin(.http1_1(compress: false))
defer { XCTAssertNoThrow(try bin.shutdown()) }
var config = HTTPClient.Configuration()
config.redirectConfiguration = .follow(max: 5, allowCycles: false)
let client = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config)
defer { XCTAssertNoThrow(try client.syncShutdown()) }

// The body of a redirect is dropped without being read. This is repeated because what could go wrong is
// up to timing: the next request must not get a connection that is not usable.
for _ in 0..<150 {
let response = try await client.execute(
HTTPClientRequest(url: "http://localhost:\(bin.port)/redirect/302-with-body?size=4096"),
deadline: .now() + .seconds(10)
)
XCTAssertEqual(response.status, .ok)
XCTAssertEqual(response.history.count, 2)
_ = try await response.body.collect(upTo: 1024 * 1024)
}
}
}

func testRedirectChangesHostHeader() {
XCTAsyncTest {
let bin = HTTPBin(.http2(compress: false))
Expand Down
11 changes: 10 additions & 1 deletion Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,9 @@ internal struct HTTPResponseBuilder {
var requestBodyByteCount: Int
let responseBodyIsRequestBodyByteCount: Bool
let trailers: HTTPHeaders?
/// Sends `body` as it is, instead of wrapping it into the JSON description of the request that other endpoints
/// answer with. Needed to answer with a body that has exactly the length that the response head announces.
var sendsBodyVerbatim = false

init(
_ version: HTTPVersion = HTTPVersion(major: 1, minor: 1),
Expand Down Expand Up @@ -1064,6 +1067,10 @@ internal final class HTTPBinHandler: ChannelInboundHandler {
headers.replaceOrAdd(name: "content-length", value: "\(size)")
var builder = HTTPResponseBuilder(status: .found, headers: headers)
builder.body = ByteBuffer(repeating: UInt8(ascii: "x"), count: size)
// The head announces `size` bytes, the body must have exactly that many. Wrapped into the JSON description of
// the request it would be longer, and the bytes after the announced length would be taken for the start of
// the next response.
builder.sendsBodyVerbatim = true
self.resps.append(builder)
return
case "/percent%20encoded":
Expand Down Expand Up @@ -1160,7 +1167,9 @@ internal final class HTTPBinHandler: ChannelInboundHandler {
var response = self.resps.removeFirst()
response.head.headers.add(contentsOf: self.responseHeaders)
context.write(wrapOutboundOut(.head(response.head)), promise: nil)
if let body = response.body {
if response.sendsBodyVerbatim, let body = response.body {
context.write(wrapOutboundOut(.body(.byteBuffer(body))), promise: nil)
} else if let body = response.body {
let requestInfo = RequestInfo(
data: String(buffer: body),
requestNumber: self.requestId,
Expand Down
Loading