@@ -457,6 +457,75 @@ class HTTP1ClientChannelHandlerTests: XCTestCase {
457457 XCTAssertEqual ( embedded. isActive, false )
458458 }
459459 }
460+
461+ func testHandlerClosesChannelIfLastActionIsSendEndAndItFails( ) {
462+ let embedded = EmbeddedChannel ( )
463+ let testWriter = TestBackpressureWriter ( eventLoop: embedded. eventLoop, parts: 5 )
464+ var maybeTestUtils : HTTP1TestTools ?
465+ XCTAssertNoThrow ( maybeTestUtils = try embedded. setupHTTP1Connection ( ) )
466+ guard let testUtils = maybeTestUtils else { return XCTFail ( " Expected connection setup works " ) }
467+
468+ var maybeRequest : HTTPClient . Request ?
469+ XCTAssertNoThrow ( maybeRequest = try HTTPClient . Request ( url: " http://localhost/ " , method: . POST, body: . stream( length: 10 ) { writer in
470+ testWriter. start ( writer: writer)
471+ } ) )
472+ guard let request = maybeRequest else { return XCTFail ( " Expected to be able to create a request " ) }
473+
474+ let delegate = ResponseAccumulator ( request: request)
475+ var maybeRequestBag : RequestBag < ResponseAccumulator > ?
476+ XCTAssertNoThrow ( maybeRequestBag = try RequestBag (
477+ request: request,
478+ eventLoopPreference: . delegate( on: embedded. eventLoop) ,
479+ task: . init( eventLoop: embedded. eventLoop, logger: testUtils. logger) ,
480+ redirectHandler: nil ,
481+ connectionDeadline: . now( ) + . seconds( 30 ) ,
482+ requestOptions: . forTests( idleReadTimeout: . milliseconds( 200 ) ) ,
483+ delegate: delegate
484+ ) )
485+ guard let requestBag = maybeRequestBag else { return XCTFail ( " Expected to be able to create a request bag " ) }
486+
487+ XCTAssertNoThrow ( try embedded. pipeline. addHandler ( FailEndHandler ( ) , position: . first) . wait ( ) )
488+
489+ // Execute the request and we'll receive the head.
490+ testWriter. writabilityChanged ( true )
491+ testUtils. connection. executeRequest ( requestBag)
492+ XCTAssertNoThrow ( try embedded. receiveHeadAndVerify {
493+ XCTAssertEqual ( $0. method, . POST)
494+ XCTAssertEqual ( $0. uri, " / " )
495+ XCTAssertEqual ( $0. headers. first ( name: " host " ) , " localhost " )
496+ XCTAssertEqual ( $0. headers. first ( name: " content-length " ) , " 10 " )
497+ } )
498+ // We're going to immediately send the response head and end.
499+ let responseHead = HTTPResponseHead ( version: . http1_1, status: . ok)
500+ XCTAssertNoThrow ( try embedded. writeInbound ( HTTPClientResponsePart . head ( responseHead) ) )
501+ embedded. read ( )
502+
503+ // Send the end and confirm the connection is still live.
504+ XCTAssertNoThrow ( try embedded. writeInbound ( HTTPClientResponsePart . end ( nil ) ) )
505+ XCTAssertEqual ( testUtils. connectionDelegate. hitConnectionClosed, 0 )
506+ XCTAssertEqual ( testUtils. connectionDelegate. hitConnectionReleased, 0 )
507+
508+ // Ok, now we can process some reads. We expect 5 reads, but we do _not_ expect an .end, because
509+ // the `FailEndHandler` is going to fail it.
510+ embedded. embeddedEventLoop. run ( )
511+ XCTAssertEqual ( testWriter. written, 5 )
512+ for _ in 0 ..< 5 {
513+ XCTAssertNoThrow ( try embedded. receiveBodyAndVerify {
514+ XCTAssertEqual ( $0. readableBytes, 2 )
515+ } )
516+ }
517+
518+ embedded. embeddedEventLoop. run ( )
519+ XCTAssertNil ( try embedded. readOutbound ( as: HTTPClientRequestPart . self) )
520+
521+ // We should have seen the connection close, and the request is complete.
522+ XCTAssertEqual ( testUtils. connectionDelegate. hitConnectionClosed, 1 )
523+ XCTAssertEqual ( testUtils. connectionDelegate. hitConnectionReleased, 0 )
524+
525+ XCTAssertThrowsError ( try requestBag. task. futureResult. wait ( ) ) { error in
526+ XCTAssertTrue ( error is FailEndHandler . Error )
527+ }
528+ }
460529}
461530
462531class TestBackpressureWriter {
@@ -636,3 +705,19 @@ class ReadEventHitHandler: ChannelOutboundHandler {
636705 context. read ( )
637706 }
638707}
708+
709+ final class FailEndHandler : ChannelOutboundHandler {
710+ typealias OutboundIn = HTTPClientRequestPart
711+ typealias OutboundOut = HTTPClientRequestPart
712+
713+ struct Error : Swift . Error { }
714+
715+ func write( context: ChannelHandlerContext , data: NIOAny , promise: EventLoopPromise < Void > ? ) {
716+ if case . end = self . unwrapOutboundIn ( data) {
717+ // We fail this.
718+ promise? . fail ( Self . Error ( ) )
719+ } else {
720+ context. write ( data, promise: promise)
721+ }
722+ }
723+ }
0 commit comments