Skip to content

Commit d82c461

Browse files
committed
Clarify test server error code
Motivation The NIOHTTP1TestServer had a weird codepath where we could hit errors configuring the channel pipeline, but we weren't sure why. I've investigated, and this happens because a Channel that had been accepted earlier was closed while we were waiting to start handling it. This is totally benign: the code works just fine in that situation. To be clear, though, let's document the behaviour and add a test. Modifications - Code comment to explain the behaviour - Added a regression test Result More confidence.
1 parent d7a8ff6 commit d82c461

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

Sources/NIOTestUtils/NIOHTTP1TestServer.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ public final class NIOHTTP1TestServer {
272272
try channel.pipeline.syncOperations.addHandler(TransformerHandler())
273273
_ = try channel.syncOptions!.setOption(.autoRead, value: true)
274274
} catch {
275-
print("Channel initialization failed with: \(error)")
275+
// This happens when the channel has been closed while it was waiting in
276+
// the pipeline. It's benign: the closure passed to the close future above will
277+
// have executed already, and started working on getting the next channel.
276278
channel.close(promise: nil)
277279
}
278280
}

Tests/NIOTestUtilsTests/NIOHTTP1TestServerTest.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,53 @@ class NIOHTTP1TestServerTest: XCTestCase {
458458
XCTAssertNotNil(channel)
459459
XCTAssertNoThrow(try channel.closeFuture.wait())
460460
}
461+
462+
func testCloseChannelWhileItIsWaiting() throws {
463+
let testServer = NIOHTTP1TestServer(group: self.group, aggregateBody: false)
464+
let firstResponsePromise = self.group.next().makePromise(of: String.self)
465+
let firstChannel = try self.connect(serverPort: testServer.serverPort, responsePromise: firstResponsePromise).wait()
466+
467+
// Send a request head and wait for it to be sent, and received at the test server so we know the connection is well underway.
468+
let requestHead = HTTPRequestHead(version: .http1_1, method: .POST, uri: "/uri")
469+
firstChannel.writeAndFlush(SendableRequestPart.head(requestHead), promise: nil)
470+
XCTAssertNoThrow(
471+
try testServer.receiveHeadAndVerify { head in
472+
XCTAssertEqual(head.uri, "/uri")
473+
}
474+
)
475+
476+
// Create a second channel now, and again send a request head. We can't wait for the test server to receive it, because it hasn't yet.
477+
let secondResponsePromise = self.group.next().makePromise(of: String.self)
478+
let secondChannel = try self.connect(serverPort: testServer.serverPort, responsePromise: secondResponsePromise).wait()
479+
480+
// To burn a little time and convince ourselves that things are going fairly well, we can send a body payload on the first channel
481+
// and confirm it comes through.
482+
firstChannel.writeAndFlush(
483+
SendableRequestPart.body(ByteBuffer(string: "ping")),
484+
promise: nil
485+
)
486+
XCTAssertNoThrow(
487+
try testServer.receiveBodyAndVerify { buffer in
488+
XCTAssertEqual(String(buffer: buffer), "ping")
489+
}
490+
)
491+
492+
// Now, close the second channel.
493+
try secondChannel.close().wait()
494+
495+
// Now we can complete the transaction.
496+
firstChannel.writeAndFlush(SendableRequestPart.end(nil), promise: nil)
497+
XCTAssertNoThrow(
498+
try testServer.receiveEndAndVerify { trailers in
499+
XCTAssertNil(trailers)
500+
}
501+
)
502+
XCTAssertNoThrow(try testServer.writeOutbound(.head(.init(version: .http1_1, status: .ok))))
503+
XCTAssertNoThrow(try testServer.writeOutbound(.end(nil)))
504+
505+
// The promise for the second should error.
506+
XCTAssertThrowsError(try secondResponsePromise.futureResult.wait())
507+
}
461508
}
462509

463510
private final class TestHTTPHandler: ChannelInboundHandler {

0 commit comments

Comments
 (0)