Skip to content

Commit 7a3d7b4

Browse files
committed
Code review: We use a deadline now for connection creation.
1 parent 5c291e4 commit 7a3d7b4

File tree

6 files changed

+142
-75
lines changed

6 files changed

+142
-75
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool.swift

+7-1
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,13 @@ class HTTP1ConnectionProvider {
455455
logger: Logger) -> EventLoopFuture<Channel> {
456456
let connectionID = HTTPConnectionPool.Connection.ID.globalGenerator.next()
457457
let eventLoop = preference.bestEventLoop ?? self.eventLoop
458-
return self.factory.makeChannel(connectionID: connectionID, eventLoop: eventLoop, logger: logger).flatMapThrowing {
458+
let deadline = NIODeadline.now() + (self.configuration.timeout.connect ?? .seconds(10))
459+
return self.factory.makeChannel(
460+
connectionID: connectionID,
461+
deadline: deadline,
462+
eventLoop: eventLoop,
463+
logger: logger
464+
).flatMapThrowing {
459465
(channel, _) -> Channel in
460466

461467
// add the http1.1 channel handlers

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift

+64-48
Original file line numberDiff line numberDiff line change
@@ -29,74 +29,83 @@ extension HTTPConnectionPool {
2929
}
3030

3131
final class ConnectionFactory {
32-
struct Timeouts {
33-
/// the `TimeAmount` waited before the TCP connection creation is failed with a timeout error
34-
var connect: TimeAmount = .seconds(10)
35-
36-
/// the `TimeAmount` waited before the SOCKS proxy connection creation is failed with a timeout error
37-
var socksProxyHandshake: TimeAmount = .seconds(10)
38-
39-
/// the `TimeAmount` waited before the HTTP proxy connection creation is failed with a timeout error
40-
var httpProxyHandshake: TimeAmount = .seconds(10)
41-
42-
/// the `TimeAmount` waited before we the TLS handshake is failed with a timeout error
43-
var tlsHandshake: TimeAmount = .seconds(10)
44-
}
45-
4632
let key: ConnectionPool.Key
4733
let clientConfiguration: HTTPClient.Configuration
4834
let tlsConfiguration: TLSConfiguration
4935
let sslContextCache: SSLContextCache
50-
let timeouts: Timeouts
5136

5237
init(key: ConnectionPool.Key,
5338
tlsConfiguration: TLSConfiguration?,
5439
clientConfiguration: HTTPClient.Configuration,
55-
sslContextCache: SSLContextCache,
56-
timeouts: Timeouts = .init()) {
40+
sslContextCache: SSLContextCache) {
5741
self.key = key
5842
self.clientConfiguration = clientConfiguration
5943
self.sslContextCache = sslContextCache
6044
self.tlsConfiguration = tlsConfiguration ?? clientConfiguration.tlsConfiguration ?? .forClient()
61-
62-
var timeouts = timeouts
63-
if let connect = clientConfiguration.timeout.connect {
64-
timeouts.connect = connect
65-
}
66-
self.timeouts = timeouts
6745
}
6846
}
6947
}
7048

7149
extension HTTPConnectionPool.ConnectionFactory {
72-
func makeChannel(connectionID: HTTPConnectionPool.Connection.ID, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {
50+
func makeChannel(
51+
connectionID: HTTPConnectionPool.Connection.ID,
52+
deadline: NIODeadline,
53+
eventLoop: EventLoop,
54+
logger: Logger
55+
) -> EventLoopFuture<(Channel, HTTPVersion)> {
56+
let channelFuture: EventLoopFuture<(Channel, HTTPVersion)>
57+
7358
if self.key.scheme.isProxyable, let proxy = self.clientConfiguration.proxy {
7459
switch proxy.type {
7560
case .socks:
76-
return self.makeSOCKSProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)
61+
channelFuture = self.makeSOCKSProxyChannel(
62+
proxy,
63+
connectionID: connectionID,
64+
deadline: deadline,
65+
eventLoop: eventLoop,
66+
logger: logger)
7767
case .http:
78-
return self.makeHTTPProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)
68+
channelFuture = self.makeHTTPProxyChannel(
69+
proxy,
70+
connectionID: connectionID,
71+
deadline: deadline,
72+
eventLoop: eventLoop,
73+
logger: logger)
7974
}
8075
} else {
81-
return self.makeNonProxiedChannel(eventLoop: eventLoop, logger: logger)
76+
channelFuture = self.makeNonProxiedChannel(deadline: deadline, eventLoop: eventLoop, logger: logger)
77+
}
78+
79+
// let's map `ChannelError.connectTimeout` into a `HTTPClientError.connectTimeout`
80+
return channelFuture.flatMapErrorThrowing { error throws -> (Channel, HTTPVersion) in
81+
switch error {
82+
case ChannelError.connectTimeout:
83+
throw HTTPClientError.connectTimeout
84+
default:
85+
throw error
86+
}
8287
}
8388
}
8489

85-
private func makeNonProxiedChannel(eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {
90+
private func makeNonProxiedChannel(
91+
deadline: NIODeadline,
92+
eventLoop: EventLoop,
93+
logger: Logger
94+
) -> EventLoopFuture<(Channel, HTTPVersion)> {
8695
switch self.key.scheme {
8796
case .http, .http_unix, .unix:
88-
return self.makePlainChannel(eventLoop: eventLoop).map { ($0, .http1_1) }
97+
return self.makePlainChannel(deadline: deadline, eventLoop: eventLoop).map { ($0, .http1_1) }
8998
case .https, .https_unix:
90-
return self.makeTLSChannel(eventLoop: eventLoop, logger: logger).flatMapThrowing {
99+
return self.makeTLSChannel(deadline: deadline, eventLoop: eventLoop, logger: logger).flatMapThrowing {
91100
channel, negotiated in
92101

93102
(channel, try self.matchALPNToHTTPVersion(negotiated))
94103
}
95104
}
96105
}
97106

98-
private func makePlainChannel(eventLoop: EventLoop) -> EventLoopFuture<Channel> {
99-
let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)
107+
private func makePlainChannel(deadline: NIODeadline, eventLoop: EventLoop) -> EventLoopFuture<Channel> {
108+
let bootstrap = self.makePlainBootstrap(deadline: deadline, eventLoop: eventLoop)
100109

101110
switch self.key.scheme {
102111
case .http:
@@ -111,21 +120,22 @@ extension HTTPConnectionPool.ConnectionFactory {
111120
private func makeHTTPProxyChannel(
112121
_ proxy: HTTPClient.Configuration.Proxy,
113122
connectionID: HTTPConnectionPool.Connection.ID,
123+
deadline: NIODeadline,
114124
eventLoop: EventLoop,
115125
logger: Logger
116126
) -> EventLoopFuture<(Channel, HTTPVersion)> {
117127
// A proxy connection starts with a plain text connection to the proxy server. After
118128
// the connection has been established with the proxy server, the connection might be
119129
// upgraded to TLS before we send our first request.
120-
let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)
130+
let bootstrap = self.makePlainBootstrap(deadline: deadline, eventLoop: eventLoop)
121131
return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in
122132
let encoder = HTTPRequestEncoder()
123133
let decoder = ByteToMessageHandler(HTTPResponseDecoder(leftOverBytesStrategy: .dropBytes))
124134
let proxyHandler = HTTP1ProxyConnectHandler(
125135
targetHost: self.key.host,
126136
targetPort: self.key.port,
127137
proxyAuthorization: proxy.authorization,
128-
deadline: .now() + self.timeouts.httpProxyHandshake
138+
deadline: deadline
129139
)
130140

131141
do {
@@ -145,24 +155,25 @@ extension HTTPConnectionPool.ConnectionFactory {
145155
}
146156
}
147157
}.flatMap {
148-
self.setupTLSInProxyConnectionIfNeeded(channel, logger: logger)
158+
self.setupTLSInProxyConnectionIfNeeded(channel, deadline: deadline, logger: logger)
149159
}
150160
}
151161
}
152162

153163
private func makeSOCKSProxyChannel(
154164
_ proxy: HTTPClient.Configuration.Proxy,
155165
connectionID: HTTPConnectionPool.Connection.ID,
166+
deadline: NIODeadline,
156167
eventLoop: EventLoop,
157168
logger: Logger
158169
) -> EventLoopFuture<(Channel, HTTPVersion)> {
159170
// A proxy connection starts with a plain text connection to the proxy server. After
160171
// the connection has been established with the proxy server, the connection might be
161172
// upgraded to TLS before we send our first request.
162-
let bootstrap = self.makePlainBootstrap(eventLoop: eventLoop)
173+
let bootstrap = self.makePlainBootstrap(deadline: deadline, eventLoop: eventLoop)
163174
return bootstrap.connect(host: proxy.host, port: proxy.port).flatMap { channel in
164175
let socksConnectHandler = SOCKSClientHandler(targetAddress: .domain(self.key.host, port: self.key.port))
165-
let socksEventHandler = SOCKSEventsHandler(deadline: .now() + self.timeouts.socksProxyHandshake)
176+
let socksEventHandler = SOCKSEventsHandler(deadline: deadline)
166177

167178
do {
168179
try channel.pipeline.syncOperations.addHandler(socksConnectHandler)
@@ -178,12 +189,16 @@ extension HTTPConnectionPool.ConnectionFactory {
178189
channel.pipeline.removeHandler(socksConnectHandler)
179190
}
180191
}.flatMap {
181-
self.setupTLSInProxyConnectionIfNeeded(channel, logger: logger)
192+
self.setupTLSInProxyConnectionIfNeeded(channel, deadline: deadline, logger: logger)
182193
}
183194
}
184195
}
185196

186-
private func setupTLSInProxyConnectionIfNeeded(_ channel: Channel, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {
197+
private func setupTLSInProxyConnectionIfNeeded(
198+
_ channel: Channel,
199+
deadline: NIODeadline,
200+
logger: Logger
201+
) -> EventLoopFuture<(Channel, HTTPVersion)> {
187202
switch self.key.scheme {
188203
case .unix, .http_unix, .https_unix:
189204
preconditionFailure("Unexpected scheme. Not supported for proxy!")
@@ -193,7 +208,7 @@ extension HTTPConnectionPool.ConnectionFactory {
193208
var tlsConfig = self.tlsConfiguration
194209
// since we can support h2, we need to advertise this in alpn
195210
tlsConfig.applicationProtocols = ["http/1.1" /* , "h2" */ ]
196-
let tlsEventHandler = TLSEventsHandler(deadline: .now() + self.timeouts.tlsHandshake)
211+
let tlsEventHandler = TLSEventsHandler(deadline: deadline)
197212

198213
let sslContextFuture = self.sslContextCache.sslContext(
199214
tlsConfiguration: tlsConfig,
@@ -224,11 +239,11 @@ extension HTTPConnectionPool.ConnectionFactory {
224239
}
225240
}
226241

227-
private func makePlainBootstrap(eventLoop: EventLoop) -> NIOClientTCPBootstrapProtocol {
242+
private func makePlainBootstrap(deadline: NIODeadline, eventLoop: EventLoop) -> NIOClientTCPBootstrapProtocol {
228243
#if canImport(Network)
229244
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) {
230245
return tsBootstrap
231-
.connectTimeout(self.timeouts.connect)
246+
.connectTimeout(deadline - NIODeadline.now())
232247
.channelInitializer { channel in
233248
do {
234249
try channel.pipeline.syncOperations.addHandler(HTTPClient.NWErrorHandler())
@@ -242,14 +257,15 @@ extension HTTPConnectionPool.ConnectionFactory {
242257

243258
if let nioBootstrap = ClientBootstrap(validatingGroup: eventLoop) {
244259
return nioBootstrap
245-
.connectTimeout(self.timeouts.connect)
260+
.connectTimeout(deadline - NIODeadline.now())
246261
}
247262

248263
preconditionFailure("No matching bootstrap found")
249264
}
250265

251-
private func makeTLSChannel(eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, String?)> {
266+
private func makeTLSChannel(deadline: NIODeadline, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, String?)> {
252267
let bootstrapFuture = self.makeTLSBootstrap(
268+
deadline: deadline,
253269
eventLoop: eventLoop,
254270
logger: logger
255271
)
@@ -285,7 +301,7 @@ extension HTTPConnectionPool.ConnectionFactory {
285301
return channelFuture
286302
}
287303

288-
private func makeTLSBootstrap(eventLoop: EventLoop, logger: Logger)
304+
private func makeTLSBootstrap(deadline: NIODeadline, eventLoop: EventLoop, logger: Logger)
289305
-> EventLoopFuture<NIOClientTCPBootstrapProtocol> {
290306
// since we can support h2, we need to advertise this in alpn
291307
var tlsConfig = self.tlsConfiguration
@@ -298,7 +314,7 @@ extension HTTPConnectionPool.ConnectionFactory {
298314
options -> NIOClientTCPBootstrapProtocol in
299315

300316
tsBootstrap
301-
.connectTimeout(self.timeouts.connect)
317+
.connectTimeout(deadline - NIODeadline.now())
302318
.tlsOptions(options)
303319
.channelInitializer { channel in
304320
do {
@@ -327,7 +343,7 @@ extension HTTPConnectionPool.ConnectionFactory {
327343
)
328344

329345
let bootstrap = ClientBootstrap(group: eventLoop)
330-
.connectTimeout(self.timeouts.connect)
346+
.connectTimeout(deadline - NIODeadline.now())
331347
.channelInitializer { channel in
332348
sslContextFuture.flatMap { (sslContext) -> EventLoopFuture<Void> in
333349
do {
@@ -336,7 +352,7 @@ extension HTTPConnectionPool.ConnectionFactory {
336352
context: sslContext,
337353
serverHostname: hostname
338354
)
339-
let tlsEventHandler = TLSEventsHandler(deadline: .now() + self.timeouts.tlsHandshake)
355+
let tlsEventHandler = TLSEventsHandler(deadline: deadline)
340356

341357
try sync.addHandler(sslHandler)
342358
try sync.addHandler(tlsEventHandler)

Diff for: Sources/AsyncHTTPClient/HTTPClient.swift

+3
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
912912
case bodyLengthMismatch
913913
case writeAfterRequestSent
914914
case incompatibleHeaders
915+
case connectTimeout
915916
case socksHandshakeTimeout
916917
case httpProxyHandshakeTimeout
917918
case tlsHandshakeTimeout
@@ -972,6 +973,8 @@ public struct HTTPClientError: Error, Equatable, CustomStringConvertible {
972973
public static let writeAfterRequestSent = HTTPClientError(code: .writeAfterRequestSent)
973974
/// Incompatible headers specified, for example `Transfer-Encoding` and `Content-Length`.
974975
public static let incompatibleHeaders = HTTPClientError(code: .incompatibleHeaders)
976+
/// Creating a new tcp connection timed out
977+
public static let connectTimeout = HTTPClientError(code: .connectTimeout)
975978
/// The socks handshake timed out.
976979
public static let socksHandshakeTimeout = HTTPClientError(code: .socksHandshakeTimeout)
977980
/// The http proxy connection creation timed out.

Diff for: Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ class HTTPClientNIOTSTests: XCTestCase {
8888
let port = httpBin.port
8989
XCTAssertNoThrow(try httpBin.shutdown())
9090

91-
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(port)/get").wait()) { error in
92-
XCTAssertEqual(.connectTimeout(.milliseconds(100)), error as? ChannelError)
91+
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(port)/get").wait()) {
92+
XCTAssertEqual($0 as? HTTPClientError, .connectTimeout)
9393
}
9494
}
9595

Diff for: Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+9-15
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,8 @@ class HTTPClientTests: XCTestCase {
587587
}
588588

589589
// This must throw as 198.51.100.254 is reserved for documentation only
590-
XCTAssertThrowsError(try httpClient.get(url: "http://198.51.100.254:65535/get").wait()) { error in
591-
XCTAssertEqual(.connectTimeout(.milliseconds(100)), error as? ChannelError)
590+
XCTAssertThrowsError(try httpClient.get(url: "http://198.51.100.254:65535/get").wait()) {
591+
XCTAssertEqual($0 as? HTTPClientError, .connectTimeout)
592592
}
593593
}
594594

@@ -1841,15 +1841,9 @@ class HTTPClientTests: XCTestCase {
18411841

18421842
XCTAssertThrowsError(try localClient.get(url: "http://localhost:\(port)").wait()) { error in
18431843
if isTestingNIOTS() {
1844-
guard case ChannelError.connectTimeout = error else {
1845-
XCTFail("Unexpected error: \(error)")
1846-
return
1847-
}
1844+
XCTAssertEqual(error as? HTTPClientError, .connectTimeout)
18481845
} else {
1849-
guard error is NIOConnectionError else {
1850-
XCTFail("Unexpected error: \(error)")
1851-
return
1852-
}
1846+
XCTAssert(error is NIOConnectionError, "Unexpected error: \(error)")
18531847
}
18541848
}
18551849
}
@@ -2505,9 +2499,9 @@ class HTTPClientTests: XCTestCase {
25052499
let request = try HTTPClient.Request(url: "http://198.51.100.254:65535/get")
25062500
let delegate = TestDelegate()
25072501

2508-
XCTAssertThrowsError(try httpClient.execute(request: request, delegate: delegate).wait()) { error in
2509-
XCTAssertEqual(.connectTimeout(.milliseconds(10)), error as? ChannelError)
2510-
XCTAssertEqual(.connectTimeout(.milliseconds(10)), delegate.error as? ChannelError)
2502+
XCTAssertThrowsError(try httpClient.execute(request: request, delegate: delegate).wait()) {
2503+
XCTAssertEqual(.connectTimeout, $0 as? HTTPClientError)
2504+
XCTAssertEqual(.connectTimeout, delegate.error as? HTTPClientError)
25112505
}
25122506
}
25132507

@@ -2733,7 +2727,7 @@ class HTTPClientTests: XCTestCase {
27332727

27342728
XCTAssertThrowsError(try task.wait()) { error in
27352729
if isTestingNIOTS() {
2736-
XCTAssertEqual(error as? ChannelError, .connectTimeout(.milliseconds(100)))
2730+
XCTAssertEqual(error as? HTTPClientError, .connectTimeout)
27372731
} else {
27382732
switch error as? NIOSSLError {
27392733
case .some(.handshakeFailed(.sslError(_))): break
@@ -2780,7 +2774,7 @@ class HTTPClientTests: XCTestCase {
27802774

27812775
XCTAssertThrowsError(try task.wait()) { error in
27822776
if isTestingNIOTS() {
2783-
XCTAssertEqual(error as? ChannelError, .connectTimeout(.milliseconds(200)))
2777+
XCTAssertEqual(error as? HTTPClientError, .connectTimeout)
27842778
} else {
27852779
switch error as? NIOSSLError {
27862780
case .some(.handshakeFailed(.sslError(_))): break

0 commit comments

Comments
 (0)