Skip to content

Commit aac4357

Browse files
authored
notify delegate about connect errors (#245)
1 parent 5c7a317 commit aac4357

File tree

6 files changed

+86
-11
lines changed

6 files changed

+86
-11
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -513,13 +513,13 @@ class HTTP1ConnectionProvider {
513513
}
514514

515515
private func makeChannel(preference: HTTPClient.EventLoopPreference) -> EventLoopFuture<Channel> {
516-
let eventLoop = preference.bestEventLoop ?? self.eventLoop
516+
let channelEventLoop = preference.bestEventLoop ?? self.eventLoop
517517
let requiresTLS = self.key.scheme.requiresTLS
518518
let bootstrap: NIOClientTCPBootstrap
519519
do {
520-
bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: eventLoop, host: self.key.host, port: self.key.port, requiresTLS: requiresTLS, configuration: self.configuration)
520+
bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: channelEventLoop, host: self.key.host, port: self.key.port, requiresTLS: requiresTLS, configuration: self.configuration)
521521
} catch {
522-
return eventLoop.makeFailedFuture(error)
522+
return channelEventLoop.makeFailedFuture(error)
523523
}
524524

525525
let channel: EventLoopFuture<Channel>
@@ -564,7 +564,7 @@ class HTTP1ConnectionProvider {
564564
error = HTTPClient.NWErrorHandler.translateError(error)
565565
}
566566
#endif
567-
return self.eventLoop.makeFailedFuture(error)
567+
return channelEventLoop.makeFailedFuture(error)
568568
}
569569
}
570570

Diff for: Sources/AsyncHTTPClient/HTTPClient.swift

+14-7
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,14 @@ public class HTTPClient {
545545
deadline: deadline,
546546
setupComplete: setupComplete.futureResult,
547547
logger: logger)
548+
549+
let taskHandler = TaskHandler(task: task,
550+
kind: request.kind,
551+
delegate: delegate,
552+
redirectHandler: redirectHandler,
553+
ignoreUncleanSSLShutdown: self.configuration.ignoreUncleanSSLShutdown,
554+
logger: logger)
555+
548556
connection.flatMap { connection -> EventLoopFuture<Void> in
549557
logger.debug("got connection for request",
550558
metadata: ["ahc-connection": "\(connection)",
@@ -561,12 +569,6 @@ public class HTTPClient {
561569
}
562570

563571
return future.flatMap {
564-
let taskHandler = TaskHandler(task: task,
565-
kind: request.kind,
566-
delegate: delegate,
567-
redirectHandler: redirectHandler,
568-
ignoreUncleanSSLShutdown: self.configuration.ignoreUncleanSSLShutdown,
569-
logger: logger)
570572
return channel.pipeline.addHandler(taskHandler)
571573
}.flatMap {
572574
task.setConnection(connection)
@@ -590,7 +592,12 @@ public class HTTPClient {
590592
}
591593
}.always { _ in
592594
setupComplete.succeed(())
593-
}.cascadeFailure(to: task.promise)
595+
}.whenFailure { error in
596+
taskHandler.callOutToDelegateFireAndForget { task in
597+
delegate.didReceiveError(task: task, error)
598+
}
599+
task.promise.fail(error)
600+
}
594601

595602
return task
596603
}

Diff for: Tests/AsyncHTTPClientTests/HTTPClientInternalTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ extension HTTPClientInternalTests {
4343
("testUploadStreamingIsCalledOnTaskEL", testUploadStreamingIsCalledOnTaskEL),
4444
("testWeCanActuallyExactlySetTheEventLoops", testWeCanActuallyExactlySetTheEventLoops),
4545
("testTaskPromiseBoundToEL", testTaskPromiseBoundToEL),
46+
("testConnectErrorCalloutOnCorrectEL", testConnectErrorCalloutOnCorrectEL),
4647
("testInternalRequestURI", testInternalRequestURI),
4748
]
4849
}

Diff for: Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift

+39
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,45 @@ class HTTPClientInternalTests: XCTestCase {
960960
XCTAssertNoThrow(try task.wait())
961961
}
962962

963+
func testConnectErrorCalloutOnCorrectEL() throws {
964+
class TestDelegate: HTTPClientResponseDelegate {
965+
typealias Response = Void
966+
967+
let expectedEL: EventLoop
968+
var receivedError: Bool = false
969+
970+
init(expectedEL: EventLoop) {
971+
self.expectedEL = expectedEL
972+
}
973+
974+
func didFinishRequest(task: HTTPClient.Task<Void>) throws {}
975+
976+
func didReceiveError(task: HTTPClient.Task<Void>, _ error: Error) {
977+
self.receivedError = true
978+
XCTAssertTrue(self.expectedEL.inEventLoop)
979+
}
980+
}
981+
982+
let elg = getDefaultEventLoopGroup(numberOfThreads: 2)
983+
let el1 = elg.next()
984+
let el2 = elg.next()
985+
986+
let httpBin = HTTPBin(refusesConnections: true)
987+
let client = HTTPClient(eventLoopGroupProvider: .shared(elg))
988+
989+
defer {
990+
XCTAssertNoThrow(try client.syncShutdown())
991+
XCTAssertNoThrow(try elg.syncShutdownGracefully())
992+
}
993+
994+
let request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/get")
995+
let delegate = TestDelegate(expectedEL: el1)
996+
XCTAssertNoThrow(try httpBin.shutdown())
997+
let task = client.execute(request: request, delegate: delegate, eventLoop: .init(.testOnly_exact(channelOn: el2, delegateOn: el1)))
998+
XCTAssertThrowsError(try task.wait())
999+
XCTAssertTrue(delegate.receivedError)
1000+
}
1001+
9631002
func testInternalRequestURI() throws {
9641003
let request1 = try Request(url: "https://someserver.com:8888/some/path?foo=bar")
9651004
XCTAssertEqual(request1.kind, .host)

Diff for: Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ extension HTTPClientTests {
114114
("testNothingIsLoggedAtInfoOrHigher", testNothingIsLoggedAtInfoOrHigher),
115115
("testAllMethodsLog", testAllMethodsLog),
116116
("testClosingIdleConnectionsInPoolLogsInTheBackground", testClosingIdleConnectionsInPoolLogsInTheBackground),
117+
("testConnectErrorPropagatedToDelegate", testConnectErrorPropagatedToDelegate),
117118
("testDelegateCallinsTolerateRandomEL", testDelegateCallinsTolerateRandomEL),
118119
("testContentLengthTooLongFails", testContentLengthTooLongFails),
119120
("testContentLengthTooShortFails", testContentLengthTooShortFails),

Diff for: Tests/AsyncHTTPClientTests/HTTPClientTests.swift

+27
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,33 @@ class HTTPClientTests: XCTestCase {
23742374
self.defaultClient = nil // so it doesn't get shut down again.
23752375
}
23762376

2377+
func testConnectErrorPropagatedToDelegate() throws {
2378+
class TestDelegate: HTTPClientResponseDelegate {
2379+
typealias Response = Void
2380+
var error: Error?
2381+
func didFinishRequest(task: HTTPClient.Task<Void>) throws {}
2382+
func didReceiveError(task: HTTPClient.Task<Response>, _ error: Error) {
2383+
self.error = error
2384+
}
2385+
}
2386+
2387+
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
2388+
configuration: .init(timeout: .init(connect: .milliseconds(10))))
2389+
2390+
defer {
2391+
XCTAssertNoThrow(try httpClient.syncShutdown())
2392+
}
2393+
2394+
// This must throw as 198.51.100.254 is reserved for documentation only
2395+
let request = try HTTPClient.Request(url: "http://198.51.100.254:65535/get")
2396+
let delegate = TestDelegate()
2397+
2398+
XCTAssertThrowsError(try httpClient.execute(request: request, delegate: delegate).wait()) { error in
2399+
XCTAssertEqual(.connectTimeout(.milliseconds(10)), error as? ChannelError)
2400+
XCTAssertEqual(.connectTimeout(.milliseconds(10)), delegate.error as? ChannelError)
2401+
}
2402+
}
2403+
23772404
func testDelegateCallinsTolerateRandomEL() throws {
23782405
class TestDelegate: HTTPClientResponseDelegate {
23792406
typealias Response = Void

0 commit comments

Comments
 (0)