@@ -457,6 +457,75 @@ class HTTP1ClientChannelHandlerTests: XCTestCase {
457
457
XCTAssertEqual ( embedded. isActive, false )
458
458
}
459
459
}
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
+ }
460
529
}
461
530
462
531
class TestBackpressureWriter {
@@ -636,3 +705,19 @@ class ReadEventHitHandler: ChannelOutboundHandler {
636
705
context. read ( )
637
706
}
638
707
}
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