Skip to content

Commit c1c97ce

Browse files
committed
Translate NWError into easier to use structs
Added NWTLSError, NWDNSError and NWPOSIXError to wrap Network.framework errors. The reason to do this was to make it easier to read Errors thrown from the Network.framework.
1 parent d3362c4 commit c1c97ce

File tree

3 files changed

+145
-21
lines changed

3 files changed

+145
-21
lines changed

Sources/AsyncHTTPClient/ConnectionPool.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,13 @@ final class ConnectionPool {
397397
channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise)
398398
return handshakePromise.futureResult.flatMap {
399399
channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes)
400+
}.flatMap {
401+
#if canImport(Network)
402+
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap {
403+
return channel.pipeline.addHandler(NWErrorHandler(), position: .first)
404+
}
405+
#endif
406+
return eventLoop.makeSucceededFuture(())
400407
}.map {
401408
let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool)
402409
connection.isLeased = true
@@ -406,6 +413,12 @@ final class ConnectionPool {
406413
self.configureCloseCallback(of: connection)
407414
return connection
408415
}.flatMapError { error in
416+
var error = error
417+
#if canImport(Network)
418+
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap {
419+
error = NWErrorHandler.translateError(error)
420+
}
421+
#endif
409422
// This promise may not have been completed if we reach this
410423
// so we fail it to avoid any leak
411424
handshakePromise.fail(error)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
#if canImport(Network)
16+
17+
import Network
18+
import NIO
19+
import NIOHTTP1
20+
import NIOTransportServices
21+
22+
public struct NWDNSError: Error {
23+
24+
/// DNS error type. Error type list is defined in <dns_sd.h>
25+
public let errorType: DNSServiceErrorType
26+
27+
/// actual reason, in human readable form
28+
private let reason: String
29+
30+
/// Initialise a NWDNSError
31+
/// - Parameters:
32+
/// - errorType: DNS error type
33+
/// - reason: String describing reason for error
34+
public init(_ errorType: DNSServiceErrorType, reason: String) {
35+
self.errorType = errorType
36+
self.reason = reason
37+
}
38+
}
39+
40+
extension NWDNSError: CustomStringConvertible {
41+
public var description: String { return reason }
42+
}
43+
44+
public struct NWPOSIXError: Error {
45+
46+
/// POSIX error code (enum)
47+
public let errorCode: POSIXErrorCode
48+
49+
/// actual reason, in human readable form
50+
private let reason: String
51+
52+
/// Initialise a NWPOSIXError
53+
/// - Parameters:
54+
/// - errorType: posix error type
55+
/// - reason: String describing reason for error
56+
public init(_ errorCode: POSIXErrorCode, reason: String) {
57+
self.errorCode = errorCode
58+
self.reason = reason
59+
}
60+
}
61+
62+
extension NWPOSIXError: CustomStringConvertible {
63+
public var description: String { return reason }
64+
}
65+
66+
public struct NWTLSError: Error {
67+
68+
/// TLS error status. List of TLS errors can be found in <Security/SecureTransport.h>
69+
public let status: OSStatus
70+
71+
/// actual reason, in human readable form
72+
private let reason: String
73+
74+
/// initialise a NWTLSError
75+
/// - Parameters:
76+
/// - status: TLS status
77+
/// - reason: String describing reason for error
78+
public init(_ status: OSStatus, reason: String) {
79+
self.status = status
80+
self.reason = reason
81+
}
82+
}
83+
84+
extension NWTLSError: CustomStringConvertible {
85+
public var description: String { return reason }
86+
}
87+
88+
@available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *)
89+
class NWErrorHandler: ChannelInboundHandler {
90+
typealias InboundIn = HTTPClientResponsePart
91+
92+
func errorCaught(context: ChannelHandlerContext, error: Error) {
93+
context.fireErrorCaught(NWErrorHandler.translateError(error))
94+
}
95+
96+
static func translateError(_ error: Error) -> Error {
97+
98+
if let error = error as? NWError {
99+
switch error {
100+
case .dns(let errorType):
101+
return NWDNSError(errorType, reason: error.localizedDescription)
102+
case .tls(let status):
103+
return NWTLSError(status, reason: error.localizedDescription)
104+
case .posix(let errorCode):
105+
return NWPOSIXError(errorCode, reason: error.localizedDescription)
106+
default:
107+
return error
108+
}
109+
}
110+
return error
111+
}
112+
}
113+
114+
115+
#endif

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ class HTTPClientTests: XCTestCase {
538538
}
539539

540540
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/noresponse").wait(), "Should fail") { error in
541-
guard case let error = error as? NIOSSLError, error == .uncleanShutdown else {
541+
guard case let sslError = error as? NIOSSLError, sslError == .uncleanShutdown else {
542542
return XCTFail("Should fail with NIOSSLError.uncleanShutdown")
543543
}
544544
}
@@ -555,7 +555,7 @@ class HTTPClientTests: XCTestCase {
555555
}
556556

557557
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/noresponse").wait(), "Should fail") { error in
558-
guard case let error = error as? NIOSSLError, error == .uncleanShutdown else {
558+
guard case let sslError = error as? NIOSSLError, sslError == .uncleanShutdown else {
559559
return XCTFail("Should fail with NIOSSLError.uncleanShutdown")
560560
}
561561
}
@@ -572,7 +572,7 @@ class HTTPClientTests: XCTestCase {
572572
}
573573

574574
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/wrongcontentlength").wait(), "Should fail") { error in
575-
guard case let error = error as? NIOSSLError, error == .uncleanShutdown else {
575+
guard case let sslError = error as? NIOSSLError, sslError == .uncleanShutdown else {
576576
return XCTFail("Should fail with NIOSSLError.uncleanShutdown")
577577
}
578578
}
@@ -1017,21 +1017,19 @@ class HTTPClientTests: XCTestCase {
10171017
XCTFail("Shouldn't succeed")
10181018
continue
10191019
case .failure(let error):
1020-
#if canImport(Network)
10211020
if isTestingNIOTS() {
1022-
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
1023-
guard let clientError = error as? NWError, case NWError.tls(let status) = clientError else {
1024-
XCTFail("Unexpected error: \(error)")
1025-
continue
1026-
}
1027-
XCTAssertEqual(status, errSSLHandshakeFail)
1021+
#if canImport(Network)
1022+
guard let clientError = error as? NWTLSError else {
1023+
XCTFail("Unexpected error: \(error)")
1024+
continue
1025+
}
1026+
XCTAssertEqual(clientError.status, errSSLHandshakeFail)
1027+
#endif
1028+
} else {
1029+
guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else {
1030+
XCTFail("Unexpected error: \(error)")
1031+
continue
10281032
}
1029-
continue
1030-
}
1031-
#endif
1032-
guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else {
1033-
XCTFail("Unexpected error: \(error)")
1034-
continue
10351033
}
10361034
}
10371035
}
@@ -1675,15 +1673,13 @@ class HTTPClientTests: XCTestCase {
16751673
}
16761674

16771675
XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)").wait()) { error in
1678-
#if canImport(Network)
1679-
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), isTestingNIOTS() {
1680-
guard let nwError = error as? NWError, case NWError.posix(let posixErrorCode) = nwError, posixErrorCode == .ECONNREFUSED else {
1676+
if isTestingNIOTS() {
1677+
guard let ioError = error as? IOError, ioError.errnoCode == ECONNREFUSED else {
16811678
XCTFail("Unexpected error: \(error)")
16821679
return
16831680
}
16841681
return
16851682
}
1686-
#endif
16871683
guard error is NIOConnectionError else {
16881684
XCTFail("Unexpected error: \(error)")
16891685
return
@@ -1710,7 +1706,7 @@ class HTTPClientTests: XCTestCase {
17101706
XCTAssertNoThrow(try httpClient.syncShutdown())
17111707
}
17121708
#if canImport(Network)
1713-
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
1709+
if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
17141710
XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup)
17151711
return
17161712
}

0 commit comments

Comments
 (0)