@@ -47,6 +47,7 @@ protocol HTTPConnectionRequester {
4747 func http1ConnectionCreated( _: HTTP1Connection )
4848 func http2ConnectionCreated( _: HTTP2Connection , maximumStreams: Int )
4949 func failedToCreateHTTPConnection( _: HTTPConnectionPool . Connection . ID , error: Error )
50+ func waitingForConnectivity( _: HTTPConnectionPool . Connection . ID , error: Error )
5051}
5152
5253extension HTTPConnectionPool . ConnectionFactory {
@@ -62,7 +63,7 @@ extension HTTPConnectionPool.ConnectionFactory {
6263 var logger = logger
6364 logger [ metadataKey: " ahc-connection-id " ] = " \( connectionID) "
6465
65- self . makeChannel ( connectionID: connectionID, deadline: deadline, eventLoop: eventLoop, logger: logger) . whenComplete { result in
66+ self . makeChannel ( requester : requester , connectionID: connectionID, deadline: deadline, eventLoop: eventLoop, logger: logger) . whenComplete { result in
6667 switch result {
6768 case . success( . http1_1( let channel) ) :
6869 do {
@@ -104,13 +105,15 @@ extension HTTPConnectionPool.ConnectionFactory {
104105 case http2( Channel )
105106 }
106107
107- func makeHTTP1Channel(
108+ func makeHTTP1Channel< Requester: HTTPConnectionRequester > (
109+ requester: Requester ,
108110 connectionID: HTTPConnectionPool . Connection . ID ,
109111 deadline: NIODeadline ,
110112 eventLoop: EventLoop ,
111113 logger: Logger
112114 ) -> EventLoopFuture < Channel > {
113115 self . makeChannel (
116+ requester: requester,
114117 connectionID: connectionID,
115118 deadline: deadline,
116119 eventLoop: eventLoop,
@@ -137,7 +140,8 @@ extension HTTPConnectionPool.ConnectionFactory {
137140 }
138141 }
139142
140- func makeChannel(
143+ func makeChannel< Requester: HTTPConnectionRequester > (
144+ requester: Requester ,
141145 connectionID: HTTPConnectionPool . Connection . ID ,
142146 deadline: NIODeadline ,
143147 eventLoop: EventLoop ,
@@ -150,6 +154,7 @@ extension HTTPConnectionPool.ConnectionFactory {
150154 case . socks:
151155 channelFuture = self . makeSOCKSProxyChannel (
152156 proxy,
157+ requester: requester,
153158 connectionID: connectionID,
154159 deadline: deadline,
155160 eventLoop: eventLoop,
@@ -158,14 +163,15 @@ extension HTTPConnectionPool.ConnectionFactory {
158163 case . http:
159164 channelFuture = self . makeHTTPProxyChannel (
160165 proxy,
166+ requester: requester,
161167 connectionID: connectionID,
162168 deadline: deadline,
163169 eventLoop: eventLoop,
164170 logger: logger
165171 )
166172 }
167173 } else {
168- channelFuture = self . makeNonProxiedChannel ( deadline: deadline, eventLoop: eventLoop, logger: logger)
174+ channelFuture = self . makeNonProxiedChannel ( requester : requester , connectionID : connectionID , deadline: deadline, eventLoop: eventLoop, logger: logger)
169175 }
170176
171177 // let's map `ChannelError.connectTimeout` into a `HTTPClientError.connectTimeout`
@@ -179,30 +185,38 @@ extension HTTPConnectionPool.ConnectionFactory {
179185 }
180186 }
181187
182- private func makeNonProxiedChannel(
188+ private func makeNonProxiedChannel< Requester: HTTPConnectionRequester > (
189+ requester: Requester ,
190+ connectionID: HTTPConnectionPool . Connection . ID ,
183191 deadline: NIODeadline ,
184192 eventLoop: EventLoop ,
185193 logger: Logger
186194 ) -> EventLoopFuture < NegotiatedProtocol > {
187195 switch self . key. scheme {
188196 case . http, . httpUnix, . unix:
189- return self . makePlainChannel ( deadline: deadline, eventLoop: eventLoop) . map { . http1_1( $0) }
197+ return self . makePlainChannel ( requester : requester , connectionID : connectionID , deadline: deadline, eventLoop: eventLoop) . map { . http1_1( $0) }
190198 case . https, . httpsUnix:
191- return self . makeTLSChannel ( deadline: deadline, eventLoop: eventLoop, logger: logger) . flatMapThrowing {
199+ return self . makeTLSChannel ( requester : requester , connectionID : connectionID , deadline: deadline, eventLoop: eventLoop, logger: logger) . flatMapThrowing {
192200 channel, negotiated in
193201
194202 try self . matchALPNToHTTPVersion ( negotiated, channel: channel)
195203 }
196204 }
197205 }
198206
199- private func makePlainChannel( deadline: NIODeadline , eventLoop: EventLoop ) -> EventLoopFuture < Channel > {
207+ private func makePlainChannel< Requester: HTTPConnectionRequester > (
208+ requester: Requester ,
209+ connectionID: HTTPConnectionPool . Connection . ID ,
210+ deadline: NIODeadline ,
211+ eventLoop: EventLoop
212+ ) -> EventLoopFuture < Channel > {
200213 precondition ( !self . key. scheme. usesTLS, " Unexpected scheme " )
201- return self . makePlainBootstrap ( deadline: deadline, eventLoop: eventLoop) . connect ( target: self . key. connectionTarget)
214+ return self . makePlainBootstrap ( requester : requester , connectionID : connectionID , deadline: deadline, eventLoop: eventLoop) . connect ( target: self . key. connectionTarget)
202215 }
203216
204- private func makeHTTPProxyChannel(
217+ private func makeHTTPProxyChannel< Requester : HTTPConnectionRequester > (
205218 _ proxy: HTTPClient . Configuration . Proxy ,
219+ requester: Requester ,
206220 connectionID: HTTPConnectionPool . Connection . ID ,
207221 deadline: NIODeadline ,
208222 eventLoop: EventLoop ,
@@ -211,7 +225,7 @@ extension HTTPConnectionPool.ConnectionFactory {
211225 // A proxy connection starts with a plain text connection to the proxy server. After
212226 // the connection has been established with the proxy server, the connection might be
213227 // upgraded to TLS before we send our first request.
214- let bootstrap = self . makePlainBootstrap ( deadline: deadline, eventLoop: eventLoop)
228+ let bootstrap = self . makePlainBootstrap ( requester : requester , connectionID : connectionID , deadline: deadline, eventLoop: eventLoop)
215229 return bootstrap. connect ( host: proxy. host, port: proxy. port) . flatMap { channel in
216230 let encoder = HTTPRequestEncoder ( )
217231 let decoder = ByteToMessageHandler ( HTTPResponseDecoder ( leftOverBytesStrategy: . dropBytes) )
@@ -243,8 +257,9 @@ extension HTTPConnectionPool.ConnectionFactory {
243257 }
244258 }
245259
246- private func makeSOCKSProxyChannel(
260+ private func makeSOCKSProxyChannel< Requester : HTTPConnectionRequester > (
247261 _ proxy: HTTPClient . Configuration . Proxy ,
262+ requester: Requester ,
248263 connectionID: HTTPConnectionPool . Connection . ID ,
249264 deadline: NIODeadline ,
250265 eventLoop: EventLoop ,
@@ -253,7 +268,7 @@ extension HTTPConnectionPool.ConnectionFactory {
253268 // A proxy connection starts with a plain text connection to the proxy server. After
254269 // the connection has been established with the proxy server, the connection might be
255270 // upgraded to TLS before we send our first request.
256- let bootstrap = self . makePlainBootstrap ( deadline: deadline, eventLoop: eventLoop)
271+ let bootstrap = self . makePlainBootstrap ( requester : requester , connectionID : connectionID , deadline: deadline, eventLoop: eventLoop)
257272 return bootstrap. connect ( host: proxy. host, port: proxy. port) . flatMap { channel in
258273 let socksConnectHandler = SOCKSClientHandler ( targetAddress: SOCKSAddress ( self . key. connectionTarget) )
259274 let socksEventHandler = SOCKSEventsHandler ( deadline: deadline)
@@ -331,14 +346,21 @@ extension HTTPConnectionPool.ConnectionFactory {
331346 }
332347 }
333348
334- private func makePlainBootstrap( deadline: NIODeadline , eventLoop: EventLoop ) -> NIOClientTCPBootstrapProtocol {
349+ private func makePlainBootstrap< Requester: HTTPConnectionRequester > (
350+ requester: Requester ,
351+ connectionID: HTTPConnectionPool . Connection . ID ,
352+ deadline: NIODeadline ,
353+ eventLoop: EventLoop
354+ ) -> NIOClientTCPBootstrapProtocol {
335355 #if canImport(Network)
336356 if #available( OSX 10 . 14 , iOS 12 . 0 , tvOS 12 . 0 , watchOS 6 . 0 , * ) , let tsBootstrap = NIOTSConnectionBootstrap ( validatingGroup: eventLoop) {
337357 return tsBootstrap
358+ . channelOption ( NIOTSChannelOptions . waitForActivity, value: self . clientConfiguration. networkFrameworkWaitForConnectivity)
338359 . connectTimeout ( deadline - NIODeadline. now ( ) )
339360 . channelInitializer { channel in
340361 do {
341362 try channel. pipeline. syncOperations. addHandler ( HTTPClient . NWErrorHandler ( ) )
363+ try channel. pipeline. syncOperations. addHandler ( NWWaitingHandler ( requester: requester, connectionID: connectionID) )
342364 return channel. eventLoop. makeSucceededVoidFuture ( )
343365 } catch {
344366 return channel. eventLoop. makeFailedFuture ( error)
@@ -355,9 +377,17 @@ extension HTTPConnectionPool.ConnectionFactory {
355377 preconditionFailure ( " No matching bootstrap found " )
356378 }
357379
358- private func makeTLSChannel( deadline: NIODeadline , eventLoop: EventLoop , logger: Logger ) -> EventLoopFuture < ( Channel , String ? ) > {
380+ private func makeTLSChannel< Requester: HTTPConnectionRequester > (
381+ requester: Requester ,
382+ connectionID: HTTPConnectionPool . Connection . ID ,
383+ deadline: NIODeadline ,
384+ eventLoop: EventLoop ,
385+ logger: Logger
386+ ) -> EventLoopFuture < ( Channel , String ? ) > {
359387 precondition ( self . key. scheme. usesTLS, " Unexpected scheme " )
360388 let bootstrapFuture = self . makeTLSBootstrap (
389+ requester: requester,
390+ connectionID: connectionID,
361391 deadline: deadline,
362392 eventLoop: eventLoop,
363393 logger: logger
@@ -387,8 +417,13 @@ extension HTTPConnectionPool.ConnectionFactory {
387417 return channelFuture
388418 }
389419
390- private func makeTLSBootstrap( deadline: NIODeadline , eventLoop: EventLoop , logger: Logger )
391- -> EventLoopFuture < NIOClientTCPBootstrapProtocol > {
420+ private func makeTLSBootstrap< Requester: HTTPConnectionRequester > (
421+ requester: Requester ,
422+ connectionID: HTTPConnectionPool . Connection . ID ,
423+ deadline: NIODeadline ,
424+ eventLoop: EventLoop ,
425+ logger: Logger
426+ ) -> EventLoopFuture < NIOClientTCPBootstrapProtocol > {
392427 var tlsConfig = self . tlsConfiguration
393428 switch self . clientConfiguration. httpVersion. configuration {
394429 case . automatic:
@@ -408,11 +443,13 @@ extension HTTPConnectionPool.ConnectionFactory {
408443 options -> NIOClientTCPBootstrapProtocol in
409444
410445 tsBootstrap
446+ . channelOption ( NIOTSChannelOptions . waitForActivity, value: self . clientConfiguration. networkFrameworkWaitForConnectivity)
411447 . connectTimeout ( deadline - NIODeadline. now ( ) )
412448 . tlsOptions ( options)
413449 . channelInitializer { channel in
414450 do {
415451 try channel. pipeline. syncOperations. addHandler ( HTTPClient . NWErrorHandler ( ) )
452+ try channel. pipeline. syncOperations. addHandler ( NWWaitingHandler ( requester: requester, connectionID: connectionID) )
416453 // we don't need to set a TLS deadline for NIOTS connections, since the
417454 // TLS handshake is part of the TS connection bootstrap. If the TLS
418455 // handshake times out the complete connection creation will be failed.
0 commit comments