@@ -4436,4 +4436,174 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
4436
4436
request. setBasicAuth ( username: " foo " , password: " bar " )
4437
4437
XCTAssertEqual ( request. headers. first ( name: " Authorization " ) , " Basic Zm9vOmJhcg== " )
4438
4438
}
4439
+
4440
+ func runBaseTestForHTTP1ConnectionDebugInitializer( ssl: Bool ) {
4441
+ let connectionDebugInitializerUtil = CountingDebugInitializerUtil ( )
4442
+
4443
+ // Initializing even with just `http1_1ConnectionDebugInitializer` (rather than manually
4444
+ // modifying `config`) to ensure that the matching `init` actually wires up this argument
4445
+ // with the respective property. This is necessary as these parameters are defaulted and can
4446
+ // be easy to miss.
4447
+ var config = HTTPClient . Configuration (
4448
+ http1_1ConnectionDebugInitializer: { channel in
4449
+ connectionDebugInitializerUtil. initialize ( channel: channel)
4450
+ }
4451
+ )
4452
+ config. httpVersion = . http1Only
4453
+
4454
+ if ssl {
4455
+ config. tlsConfiguration = . clientDefault
4456
+ config. tlsConfiguration? . certificateVerification = . none
4457
+ }
4458
+
4459
+ let higherConnectTimeout = CountingDebugInitializerUtil . duration + . milliseconds( 100 )
4460
+ var configWithHigherTimeout = config
4461
+ configWithHigherTimeout. timeout = . init( connect: higherConnectTimeout)
4462
+
4463
+ let clientWithHigherTimeout = HTTPClient (
4464
+ eventLoopGroupProvider: . singleton,
4465
+ configuration: configWithHigherTimeout,
4466
+ backgroundActivityLogger: Logger (
4467
+ label: " HTTPClient " ,
4468
+ factory: StreamLogHandler . standardOutput ( label: )
4469
+ )
4470
+ )
4471
+ defer { XCTAssertNoThrow ( try clientWithHigherTimeout. syncShutdown ( ) ) }
4472
+
4473
+ let bin = HTTPBin ( . http1_1( ssl: ssl, compress: false ) )
4474
+ defer { XCTAssertNoThrow ( try bin. shutdown ( ) ) }
4475
+
4476
+ let scheme = ssl ? " https " : " http "
4477
+
4478
+ for _ in 0 ..< 3 {
4479
+ XCTAssertNoThrow (
4480
+ try clientWithHigherTimeout. get ( url: " \( scheme) ://localhost: \( bin. port) /get " ) . wait ( )
4481
+ )
4482
+ }
4483
+
4484
+ // Even though multiple requests were made, the connection debug initializer must be called
4485
+ // only once.
4486
+ XCTAssertEqual ( connectionDebugInitializerUtil. executionCount, 1 )
4487
+
4488
+ let lowerConnectTimeout = CountingDebugInitializerUtil . duration - . milliseconds( 100 )
4489
+ var configWithLowerTimeout = config
4490
+ configWithLowerTimeout. timeout = . init( connect: lowerConnectTimeout)
4491
+
4492
+ let clientWithLowerTimeout = HTTPClient (
4493
+ eventLoopGroupProvider: . singleton,
4494
+ configuration: configWithLowerTimeout,
4495
+ backgroundActivityLogger: Logger (
4496
+ label: " HTTPClient " ,
4497
+ factory: StreamLogHandler . standardOutput ( label: )
4498
+ )
4499
+ )
4500
+ defer { XCTAssertNoThrow ( try clientWithLowerTimeout. syncShutdown ( ) ) }
4501
+
4502
+ XCTAssertThrowsError (
4503
+ try clientWithLowerTimeout. get ( url: " \( scheme) ://localhost: \( bin. port) /get " ) . wait ( )
4504
+ ) {
4505
+ XCTAssertEqual ( $0 as? HTTPClientError , . connectTimeout)
4506
+ }
4507
+ }
4508
+
4509
+ func testHTTP1PlainTextConnectionDebugInitializer( ) {
4510
+ runBaseTestForHTTP1ConnectionDebugInitializer ( ssl: false )
4511
+ }
4512
+
4513
+ func testHTTP1EncryptedConnectionDebugInitializer( ) {
4514
+ runBaseTestForHTTP1ConnectionDebugInitializer ( ssl: true )
4515
+ }
4516
+
4517
+ func testHTTP2ConnectionAndStreamChannelDebugInitializers( ) {
4518
+ let connectionDebugInitializerUtil = CountingDebugInitializerUtil ( )
4519
+ let streamChannelDebugInitializerUtil = CountingDebugInitializerUtil ( )
4520
+
4521
+ // Initializing even with just `http2ConnectionDebugInitializer` and
4522
+ // `http2StreamChannelDebugInitializer` (rather than manually modifying `config`) to ensure
4523
+ // that the matching `init` actually wires up these arguments with the respective
4524
+ // properties. This is necessary as these parameters are defaulted and can be easy to miss.
4525
+ var config = HTTPClient . Configuration (
4526
+ http2ConnectionDebugInitializer: { channel in
4527
+ connectionDebugInitializerUtil. initialize ( channel: channel)
4528
+ } ,
4529
+ http2StreamChannelDebugInitializer: { channel in
4530
+ streamChannelDebugInitializerUtil. initialize ( channel: channel)
4531
+ }
4532
+ )
4533
+ config. tlsConfiguration = . clientDefault
4534
+ config. tlsConfiguration? . certificateVerification = . none
4535
+ config. httpVersion = . automatic
4536
+
4537
+ let higherConnectTimeout = CountingDebugInitializerUtil . duration + . milliseconds( 100 )
4538
+ var configWithHigherTimeout = config
4539
+ configWithHigherTimeout. timeout = . init( connect: higherConnectTimeout)
4540
+
4541
+ let clientWithHigherTimeout = HTTPClient (
4542
+ eventLoopGroupProvider: . singleton,
4543
+ configuration: configWithHigherTimeout,
4544
+ backgroundActivityLogger: Logger (
4545
+ label: " HTTPClient " ,
4546
+ factory: StreamLogHandler . standardOutput ( label: )
4547
+ )
4548
+ )
4549
+ defer { XCTAssertNoThrow ( try clientWithHigherTimeout. syncShutdown ( ) ) }
4550
+
4551
+ let bin = HTTPBin ( . http2( compress: false ) )
4552
+ defer { XCTAssertNoThrow ( try bin. shutdown ( ) ) }
4553
+
4554
+ let numberOfRequests = 3
4555
+
4556
+ for _ in 0 ..< numberOfRequests {
4557
+ XCTAssertNoThrow (
4558
+ try clientWithHigherTimeout. get ( url: " https://localhost: \( bin. port) /get " ) . wait ( )
4559
+ )
4560
+ }
4561
+
4562
+ // Even though multiple requests were made, the connection debug initializer must be called
4563
+ // only once.
4564
+ XCTAssertEqual ( connectionDebugInitializerUtil. executionCount, 1 )
4565
+
4566
+ // The stream channel debug initializer must be called only as much as the number of
4567
+ // requests made.
4568
+ XCTAssertEqual ( streamChannelDebugInitializerUtil. executionCount, numberOfRequests)
4569
+
4570
+ let lowerConnectTimeout = CountingDebugInitializerUtil . duration - . milliseconds( 100 )
4571
+ var configWithLowerTimeout = config
4572
+ configWithLowerTimeout. timeout = . init( connect: lowerConnectTimeout)
4573
+
4574
+ let clientWithLowerTimeout = HTTPClient (
4575
+ eventLoopGroupProvider: . singleton,
4576
+ configuration: configWithLowerTimeout,
4577
+ backgroundActivityLogger: Logger (
4578
+ label: " HTTPClient " ,
4579
+ factory: StreamLogHandler . standardOutput ( label: )
4580
+ )
4581
+ )
4582
+ defer { XCTAssertNoThrow ( try clientWithLowerTimeout. syncShutdown ( ) ) }
4583
+
4584
+ XCTAssertThrowsError (
4585
+ try clientWithLowerTimeout. get ( url: " https://localhost: \( bin. port) /get " ) . wait ( )
4586
+ ) {
4587
+ XCTAssertEqual ( $0 as? HTTPClientError , . connectTimeout)
4588
+ }
4589
+ }
4590
+ }
4591
+
4592
+ final class CountingDebugInitializerUtil : Sendable {
4593
+ private let _executionCount = NIOLockedValueBox < Int > ( 0 )
4594
+ var executionCount : Int { self . _executionCount. withLockedValue { $0 } }
4595
+
4596
+ /// The minimum time to spend running the debug initializer.
4597
+ static let duration : TimeAmount = . milliseconds( 300 )
4598
+
4599
+ /// The actual debug initializer.
4600
+ func initialize( channel: Channel ) -> EventLoopFuture < Void > {
4601
+ self . _executionCount. withLockedValue { $0 += 1 }
4602
+
4603
+ let someScheduledTask = channel. eventLoop. scheduleTask ( in: Self . duration) {
4604
+ channel. eventLoop. makeSucceededVoidFuture ( )
4605
+ }
4606
+
4607
+ return someScheduledTask. futureResult. flatMap { $0 }
4608
+ }
4439
4609
}
0 commit comments