From a138c040d57de9536ccedf341aa17414491d21fb Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 28 Mar 2020 07:02:53 +0000 Subject: [PATCH 01/31] NIO Transport Services Choose NIOTSConnectionBootstrap or ClientBootstrap based on the EventLoop passed to the client. Co-authored-by: Joe Smith Co-authored-by: Johannes Weiss --- Package.swift | 4 +- Sources/AsyncHTTPClient/ConnectionPool.swift | 54 ++++++++++++++++++- Sources/AsyncHTTPClient/HTTPClient.swift | 9 ++++ Sources/AsyncHTTPClient/Utils.swift | 20 ------- .../HTTPClientInternalTests.swift | 4 +- .../HTTPClientTests.swift | 4 +- 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/Package.swift b/Package.swift index f1e14fa34..642e533c4 100644 --- a/Package.swift +++ b/Package.swift @@ -17,6 +17,7 @@ import PackageDescription let package = Package( name: "async-http-client", + platforms: [.iOS(.v12), .tvOS(.v12)], products: [ .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), ], @@ -24,11 +25,12 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "2.13.1"), .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.4.1"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"), + .package(url: "https://github.com/adam-fowler/swift-nio-transport-services.git", .branch("master")), ], targets: [ .target( name: "AsyncHTTPClient", - dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression", "NIOFoundationCompat"] + dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression", "NIOFoundationCompat", "NIOTransportServices"] ), .testTarget( name: "AsyncHTTPClientTests", diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index dfb7d5f4f..61562d9e3 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -16,6 +16,8 @@ import Foundation import NIO import NIOConcurrencyHelpers import NIOHTTP1 +import NIOSSL +import NIOTransportServices import NIOTLS /// A connection pool that manages and creates new connections to hosts respecting the specified preferences @@ -371,11 +373,58 @@ final class ConnectionPool { } } + private func makeNonTSBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + let sslContext = try NIOSSLContext(configuration: tlsConfiguration) + let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: key.host.isIPAddress ? nil : key.host) + return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider) + } + + /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. + private func makeBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { + let bootstrap: NIOClientTCPBootstrap + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { + let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init()) + bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) + } else { + bootstrap = try makeNonTSBootstrap(on: eventLoop) + } + #else + bootstrap = try makeNonTSBootstrap(on: eventLoop) + #endif + + if key.scheme == .https { + return bootstrap.enableTLS() + } + return bootstrap + } + + private func makeHTTPClientBootstrapBase(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { + return try makeBootstrap(on: eventLoop) + .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) + .channelInitializer { channel in + let channelAddedFuture: EventLoopFuture + switch self.configuration.proxy { + case .none: + channelAddedFuture = eventLoop.makeSucceededFuture(()) + case .some: + channelAddedFuture = channel.pipeline.addProxyHandler(host: self.key.host, port: self.key.port, authorization: self.configuration.proxy?.authorization) + } + return channelAddedFuture + } + } + private func makeConnection(on eventLoop: EventLoop) -> EventLoopFuture { self.activityPrecondition(expected: [.opened]) let handshakePromise = eventLoop.makePromise(of: Void.self) - let bootstrap = ClientBootstrap.makeHTTPClientBootstrapBase(group: eventLoop, host: self.key.host, port: self.key.port, configuration: self.configuration) let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy) + let bootstrap : NIOClientTCPBootstrap + do { + bootstrap = try makeHTTPClientBootstrapBase(on: eventLoop) + } catch { + return eventLoop.makeFailedFuture(error) + } let channel: EventLoopFuture switch self.key.scheme { @@ -386,7 +435,8 @@ final class ConnectionPool { } return channel.flatMap { channel -> EventLoopFuture in - channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise) + handshakePromise.succeed(()) +// channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) }.map { diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index eb9833774..35a19ede8 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -18,6 +18,7 @@ import NIOConcurrencyHelpers import NIOHTTP1 import NIOHTTPCompression import NIOSSL +import NIOTransportServices import NIOTLS /// HTTPClient class provides API for request execution. @@ -65,7 +66,15 @@ public class HTTPClient { case .shared(let group): self.eventLoopGroup = group case .createNew: + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + self.eventLoopGroup = NIOTSEventLoopGroup() + } else { + self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + } + #else self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + #endif } self.configuration = configuration self.pool = ConnectionPool(configuration: configuration) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 6e2fedf53..d1400c06e 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -46,26 +46,6 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { } } -extension ClientBootstrap { - static func makeHTTPClientBootstrapBase(group: EventLoopGroup, host: String, port: Int, configuration: HTTPClient.Configuration, channelInitializer: ((Channel) -> EventLoopFuture)? = nil) -> ClientBootstrap { - return ClientBootstrap(group: group) - .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) - - .channelInitializer { channel in - let channelAddedFuture: EventLoopFuture - switch configuration.proxy { - case .none: - channelAddedFuture = group.next().makeSucceededFuture(()) - case .some: - channelAddedFuture = channel.pipeline.addProxyHandler(host: host, port: port, authorization: configuration.proxy?.authorization) - } - return channelAddedFuture.flatMap { (_: Void) -> EventLoopFuture in - channelInitializer?(channel) ?? group.next().makeSucceededFuture(()) - } - } - } -} - extension CircularBuffer { @discardableResult mutating func swapWithFirstAndRemove(at index: Index) -> Element? { diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index f175246c0..9328fafac 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -217,7 +217,9 @@ class HTTPClientInternalTests: XCTestCase { func didFinishRequest(task: HTTPClient.Task) throws {} } - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + // cannot test with NIOTS as `maxMessagesPerRead` is not supported + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) let promise = httpClient.eventLoopGroup.next().makePromise(of: Channel.self) let httpBin = HTTPBin(channelPromise: promise) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index f58170205..382c40853 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -742,7 +742,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.redirectCycleDetected) + XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } @@ -757,7 +757,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in - XCTAssertEqual(error as! HTTPClientError, HTTPClientError.redirectLimitReached) + XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached) } } From 33b6de0f8b4367ec331646d5abf94cfd82e70d3c Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 28 Mar 2020 17:26:48 +0000 Subject: [PATCH 02/31] Tidying up tests - Added getDefaultEventLoopGroup() which will return TS or non TS EventLoopGroup based on environment variable ENABLE_TS_TESTS being set. - Added clientGroup and serverGroup EventLoopGroups to HTTPClientTests. - Stopped using TLS in Redirection tests as it is unnecessary and is hiding if TS version of code is working --- .../HTTPClientInternalTests.swift | 8 +- .../HTTPClientTestUtils.swift | 11 ++ .../HTTPClientTests.swift | 121 +++++++++--------- 3 files changed, 76 insertions(+), 64 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 9328fafac..3de5f51e2 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -356,7 +356,7 @@ class HTTPClientInternalTests: XCTestCase { } } - let group = MultiThreadedEventLoopGroup(numberOfThreads: 3) + let group = getDefaultEventLoopGroup(numberOfThreads: 3) defer { XCTAssertNoThrow(try group.syncShutdownGracefully()) } @@ -549,7 +549,11 @@ class HTTPClientInternalTests: XCTestCase { } let url = "http://127.0.0.1:\(server.localAddress!.port!)" - let client = HTTPClient(eventLoopGroupProvider: .shared(group)) + let elg = getDefaultEventLoopGroup(numberOfThreads: 1) + defer { + XCTAssertNoThrow(try elg.syncShutdownGracefully()) + } + let client = HTTPClient(eventLoopGroupProvider: .shared(elg)) defer { XCTAssertNoThrow(try client.syncShutdown()) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index 765839f93..717aa4ae8 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -19,6 +19,17 @@ import NIOConcurrencyHelpers import NIOHTTP1 import NIOHTTPCompression import NIOSSL +import NIOTransportServices + +func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup { + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), + ProcessInfo.processInfo.environment["ENABLE_TS_TESTS"] != nil { + return NIOTSEventLoopGroup(loopCount: numberOfThreads, defaultQoS: .default) + } + #endif + return MultiThreadedEventLoopGroup(numberOfThreads: numberOfThreads) +} class TestHTTPDelegate: HTTPClientResponseDelegate { typealias Response = Void diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 382c40853..c066a373c 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -20,22 +20,29 @@ import NIOHTTP1 import NIOHTTPCompression import NIOSSL import NIOTestUtils +import NIOTransportServices import XCTest class HTTPClientTests: XCTestCase { typealias Request = HTTPClient.Request - var group: EventLoopGroup! + var clientGroup: EventLoopGroup! + var serverGroup: EventLoopGroup! override func setUp() { - XCTAssertNil(self.group) - self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + XCTAssertNil(self.clientGroup) + XCTAssertNil(self.serverGroup) + self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 1) + self.serverGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) } override func tearDown() { - XCTAssertNotNil(self.group) - XCTAssertNoThrow(try self.group.syncShutdownGracefully()) - self.group = nil + XCTAssertNotNil(self.clientGroup) + XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) + self.clientGroup = nil + XCTAssertNotNil(self.serverGroup) + XCTAssertNoThrow(try self.serverGroup.syncShutdownGracefully()) + self.serverGroup = nil } func testRequestURI() throws { @@ -88,16 +95,13 @@ class HTTPClientTests: XCTestCase { func testGetWithDifferentEventLoopBackpressure() throws { let httpBin = HTTPBin() - let loopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let external = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(loopGroup)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) - XCTAssertNoThrow(try loopGroup.syncShutdownGracefully()) XCTAssertNoThrow(try httpBin.shutdown()) } let request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/events/10/1") - let delegate = TestHTTPDelegate(backpressureEventLoop: external.next()) + let delegate = TestHTTPDelegate(backpressureEventLoop: self.serverGroup.next()) let task = httpClient.execute(request: request, delegate: delegate) try task.wait() } @@ -392,6 +396,10 @@ class HTTPClientTests: XCTestCase { } func testProxyTLS() throws { + guard !(self.clientGroup is NIOTSEventLoopGroup) else { + XCTFail("Disabled test as it crashes"); + return + } let httpBin = HTTPBin(simulateProxy: .tls) let httpClient = HTTPClient( eventLoopGroupProvider: .createNew, @@ -602,12 +610,10 @@ class HTTPClientTests: XCTestCase { func testEventLoopArgument() throws { let httpBin = HTTPBin() - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 5) - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup), + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(redirectConfiguration: .follow(max: 10, allowCycles: true))) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) XCTAssertNoThrow(try httpBin.shutdown()) } @@ -631,7 +637,7 @@ class HTTPClientTests: XCTestCase { } } - let eventLoop = eventLoopGroup.next() + let eventLoop = self.clientGroup.next() let delegate = EventLoopValidatingDelegate(eventLoop: eventLoop) var request = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/get") var response = try httpClient.execute(request: request, delegate: delegate, eventLoop: .delegate(on: eventLoop)).wait() @@ -644,7 +650,10 @@ class HTTPClientTests: XCTestCase { } func testResponseFutureIsOnCorrectEL() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 4) + let group = getDefaultEventLoopGroup(numberOfThreads: 4) + defer { + XCTAssertNoThrow(try group.syncShutdownGracefully()) + } let client = HTTPClient(eventLoopGroupProvider: .shared(group)) let httpBin = HTTPBin() defer { @@ -732,7 +741,7 @@ class HTTPClientTests: XCTestCase { } func testLoopDetectionRedirectLimit() throws { - let httpBin = HTTPBin(ssl: true) + let httpBin = HTTPBin() let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 5, allowCycles: false))) @@ -741,13 +750,13 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try httpBin.shutdown()) } - XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in + XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } func testCountRedirectLimit() throws { - let httpBin = HTTPBin(ssl: true) + let httpBin = HTTPBin() let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 1000, allowCycles: true))) @@ -756,7 +765,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try httpBin.shutdown()) } - XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in + XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached) } } @@ -801,7 +810,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try server?.close().wait()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -828,12 +837,12 @@ class HTTPClientTests: XCTestCase { } func testWorksWith500Error() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -857,12 +866,12 @@ class HTTPClientTests: XCTestCase { } func testWorksWithHTTP10Response() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -886,9 +895,9 @@ class HTTPClientTests: XCTestCase { } func testWorksWhenServerClosesConnectionAfterReceivingRequest() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -909,12 +918,12 @@ class HTTPClientTests: XCTestCase { } func testSubsequentRequestsWorkWithServerSendingConnectionClose() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -942,12 +951,12 @@ class HTTPClientTests: XCTestCase { } func testSubsequentRequestsWorkWithServerAlternatingBetweenKeepAliveAndClose() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -1093,6 +1102,10 @@ class HTTPClientTests: XCTestCase { } func testStressGetClose() throws { + guard !(self.clientGroup is NIOTSEventLoopGroup) else { + XCTFail("Disabled test as it crashes"); + return + } let httpBin = HTTPBin(ssl: false) let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: HTTPClient.Configuration(certificateVerification: .none)) @@ -1158,12 +1171,12 @@ class HTTPClientTests: XCTestCase { } func testRepeatedRequestsWorkWhenServerAlwaysCloses() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -1191,14 +1204,12 @@ class HTTPClientTests: XCTestCase { func testShutdownBeforeTasksCompletion() throws { let httpBin = HTTPBin() - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let client = HTTPClient(eventLoopGroupProvider: .shared(elg)) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let req = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/get", method: .GET, headers: ["X-internal-delay": "500"]) let res = client.execute(request: req) XCTAssertNoThrow(try client.syncShutdown(requiresCleanClose: false)) _ = try? res.timeout(after: .seconds(2)).wait() try httpBin.shutdown() - try elg.syncShutdownGracefully() } /// This test would cause an assertion failure on `HTTPClient` deinit if client doesn't actually shutdown @@ -1213,12 +1224,10 @@ class HTTPClientTests: XCTestCase { func testUncleanShutdownCancelsTasks() throws { let httpBin = HTTPBin() - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let client = HTTPClient(eventLoopGroupProvider: .shared(elg)) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpBin.shutdown()) - XCTAssertNoThrow(try elg.syncShutdownGracefully()) } let responses = (1...100).map { _ in @@ -1227,7 +1236,7 @@ class HTTPClientTests: XCTestCase { try client.syncShutdown(requiresCleanClose: false) - let results = try EventLoopFuture.whenAllComplete(responses, on: elg.next()).timeout(after: .seconds(100)).wait() + let results = try EventLoopFuture.whenAllComplete(responses, on: self.clientGroup.next()).timeout(after: .seconds(100)).wait() for result in results { switch result { @@ -1258,11 +1267,7 @@ class HTTPClientTests: XCTestCase { } func testTaskFailsWhenClientIsShutdown() { - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try elg.syncShutdownGracefully()) - } - let client = HTTPClient(eventLoopGroupProvider: .shared(elg)) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) XCTAssertNoThrow(try client.syncShutdown(requiresCleanClose: true)) do { _ = try client.get(url: "http://localhost/").wait() @@ -1336,7 +1341,7 @@ class HTTPClientTests: XCTestCase { } func testVaryingLoopPreference() throws { - let elg = MultiThreadedEventLoopGroup(numberOfThreads: 2) + let elg = getDefaultEventLoopGroup(numberOfThreads: 2) let first = elg.next() let second = elg.next() XCTAssert(first !== second) @@ -1374,13 +1379,9 @@ class HTTPClientTests: XCTestCase { } func testMakeSecondRequestDuringCancelledCallout() { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) // needs to be 1 thread! - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let el = group.next() + let el = self.clientGroup.next() - let web = NIOHTTP1TestServer(group: el) + let web = NIOHTTP1TestServer(group: self.serverGroup.next()) defer { // This will throw as we've started the request but haven't fulfilled it. XCTAssertThrowsError(try web.stop()) @@ -1424,11 +1425,7 @@ class HTTPClientTests: XCTestCase { } func testMakeSecondRequestDuringSuccessCallout() { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) // needs to be 1 thread! - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let el = group.next() + let el = self.clientGroup.next() let web = HTTPBin() defer { @@ -1451,12 +1448,12 @@ class HTTPClientTests: XCTestCase { } func testMakeSecondRequestWhilstFirstIsOngoing() { - let web = NIOHTTP1TestServer(group: self.group) + let web = NIOHTTP1TestServer(group: self.serverGroup) defer { XCTAssertNoThrow(try web.stop()) } - let client = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } @@ -1531,7 +1528,7 @@ class HTTPClientTests: XCTestCase { func testUseExistingConnectionOnDifferentEL() throws { let threadCount = 16 - let elg = MultiThreadedEventLoopGroup(numberOfThreads: threadCount) + let elg = getDefaultEventLoopGroup(numberOfThreads: threadCount) let httpBin = HTTPBin() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(elg)) defer { @@ -1598,7 +1595,7 @@ class HTTPClientTests: XCTestCase { let sharedStateServerHandler = ServerThatAcceptsThenRejects(requestNumber: requestNumber, connectionNumber: connectionNumber) var maybeServer: Channel? - XCTAssertNoThrow(maybeServer = try ServerBootstrap(group: self.group) + XCTAssertNoThrow(maybeServer = try ServerBootstrap(group: self.serverGroup) .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1) .childChannelInitializer { channel in channel.pipeline.configureHTTPServerPipeline().flatMap { @@ -1618,7 +1615,7 @@ class HTTPClientTests: XCTestCase { } let url = "http://127.0.0.1:\(server.localAddress!.port!)" - let client = HTTPClient(eventLoopGroupProvider: .shared(self.group)) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } From eb81523717d406490e7e096609262b1086ba71d4 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 28 Mar 2020 18:16:37 +0000 Subject: [PATCH 03/31] Make sure ENABLE_TS_TESTS toggles TS on and off for all tests --- .../HTTPClientInternalTests.swift | 29 +++-- .../HTTPClientTestUtils.swift | 2 +- .../HTTPClientTests.swift | 103 +++++++++--------- 3 files changed, 71 insertions(+), 63 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 3de5f51e2..3648a0e16 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -23,6 +23,19 @@ class HTTPClientInternalTests: XCTestCase { typealias Request = HTTPClient.Request typealias Task = HTTPClient.Task + var clientGroup: EventLoopGroup! + + override func setUp() { + XCTAssertNil(self.clientGroup) + self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 1) + } + + override func tearDown() { + XCTAssertNotNil(self.clientGroup) + XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) + self.clientGroup = nil + } + func testHTTPPartsHandler() throws { let channel = EmbeddedChannel() let recorder = RecordingHandler() @@ -105,7 +118,7 @@ class HTTPClientInternalTests: XCTestCase { func testProxyStreaming() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -135,7 +148,7 @@ class HTTPClientInternalTests: XCTestCase { func testProxyStreamingFailure() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -445,7 +458,7 @@ class HTTPClientInternalTests: XCTestCase { func testResponseConnectionCloseGet() throws { let httpBin = HTTPBin(ssl: false) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -549,11 +562,7 @@ class HTTPClientInternalTests: XCTestCase { } let url = "http://127.0.0.1:\(server.localAddress!.port!)" - let elg = getDefaultEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try elg.syncShutdownGracefully()) - } - let client = HTTPClient(eventLoopGroupProvider: .shared(elg)) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } @@ -594,7 +603,7 @@ class HTTPClientInternalTests: XCTestCase { func testWeTolerateConnectionsGoingAwayWhilstPoolIsShuttingDown() { struct NoChannelError: Error {} - let client = HTTPClient(eventLoopGroupProvider: .createNew) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) var maybeServersAndChannels: [(HTTPBin, Channel)]? XCTAssertNoThrow(maybeServersAndChannels = try (0..<10).map { _ in let web = HTTPBin() @@ -676,7 +685,7 @@ class HTTPClientInternalTests: XCTestCase { XCTAssertNoThrow(try web.shutdown()) } - let client = HTTPClient(eventLoopGroupProvider: .createNew) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try client.syncShutdown()) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index 717aa4ae8..d101de84f 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -24,7 +24,7 @@ import NIOTransportServices func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup { #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - ProcessInfo.processInfo.environment["ENABLE_TS_TESTS"] != nil { + ProcessInfo.processInfo.environment["ENABLE_TS_TESTS"] == "true" { return NIOTSEventLoopGroup(loopCount: numberOfThreads, defaultQoS: .default) } #endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index c066a373c..504fe7de9 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -83,7 +83,7 @@ class HTTPClientTests: XCTestCase { func testGet() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -108,7 +108,7 @@ class HTTPClientTests: XCTestCase { func testPost() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -124,7 +124,7 @@ class HTTPClientTests: XCTestCase { func testGetHttps() throws { let httpBin = HTTPBin(ssl: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -137,7 +137,7 @@ class HTTPClientTests: XCTestCase { func testGetHttpsWithIP() throws { let httpBin = HTTPBin(ssl: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -150,7 +150,7 @@ class HTTPClientTests: XCTestCase { func testPostHttps() throws { let httpBin = HTTPBin(ssl: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -170,7 +170,7 @@ class HTTPClientTests: XCTestCase { func testHttpRedirect() throws { let httpBin = HTTPBin(ssl: false) let httpsBin = HTTPBin(ssl: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) defer { @@ -188,7 +188,7 @@ class HTTPClientTests: XCTestCase { func testHttpHostRedirect() throws { let httpBin = HTTPBin(ssl: false) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 10, allowCycles: true))) defer { @@ -212,7 +212,7 @@ class HTTPClientTests: XCTestCase { func testPercentEncoded() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -235,7 +235,7 @@ class HTTPClientTests: XCTestCase { } func testMultipleContentLengthHeaders() throws { - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) } @@ -256,7 +256,7 @@ class HTTPClientTests: XCTestCase { func testStreaming() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -273,7 +273,7 @@ class HTTPClientTests: XCTestCase { func testRemoteClose() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -289,7 +289,7 @@ class HTTPClientTests: XCTestCase { func testReadTimeout() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(read: .milliseconds(150)))) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(timeout: HTTPClient.Configuration.Timeout(read: .milliseconds(150)))) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -305,7 +305,7 @@ class HTTPClientTests: XCTestCase { func testDeadline() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -321,7 +321,7 @@ class HTTPClientTests: XCTestCase { func testCancel() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -345,7 +345,7 @@ class HTTPClientTests: XCTestCase { func testStressCancel() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -384,7 +384,7 @@ class HTTPClientTests: XCTestCase { func testProxyPlaintext() throws { let httpBin = HTTPBin(simulateProxy: .plaintext) let httpClient = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(proxy: .server(host: "localhost", port: httpBin.port)) ) defer { @@ -396,13 +396,12 @@ class HTTPClientTests: XCTestCase { } func testProxyTLS() throws { - guard !(self.clientGroup is NIOTSEventLoopGroup) else { - XCTFail("Disabled test as it crashes"); - return - } + XCTFail("Disabled test as it crashes"); + return + let httpBin = HTTPBin(simulateProxy: .tls) let httpClient = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init( certificateVerification: .none, proxy: .server(host: "localhost", port: httpBin.port) @@ -419,7 +418,7 @@ class HTTPClientTests: XCTestCase { func testProxyPlaintextWithCorrectlyAuthorization() throws { let httpBin = HTTPBin(simulateProxy: .plaintext) let httpClient = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(proxy: .server(host: "localhost", port: httpBin.port, authorization: .basic(username: "aladdin", password: "opensesame"))) ) defer { @@ -433,7 +432,7 @@ class HTTPClientTests: XCTestCase { func testProxyPlaintextWithIncorrectlyAuthorization() throws { let httpBin = HTTPBin(simulateProxy: .plaintext) let httpClient = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(proxy: .server(host: "localhost", port: httpBin.port, authorization: .basic(username: "aladdin", password: "opensesamefoo"))) ) defer { @@ -449,7 +448,7 @@ class HTTPClientTests: XCTestCase { func testUploadStreaming() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -473,7 +472,7 @@ class HTTPClientTests: XCTestCase { func testNoContentLengthForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { @@ -490,7 +489,7 @@ class HTTPClientTests: XCTestCase { func testNoContentLengthWithIgnoreErrorForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, ignoreUncleanSSLShutdown: true)) defer { @@ -508,7 +507,7 @@ class HTTPClientTests: XCTestCase { func testCorrectContentLengthForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { @@ -526,7 +525,7 @@ class HTTPClientTests: XCTestCase { func testNoContentForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { @@ -542,7 +541,7 @@ class HTTPClientTests: XCTestCase { func testNoResponseForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { @@ -559,7 +558,7 @@ class HTTPClientTests: XCTestCase { func testNoResponseWithIgnoreErrorForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, ignoreUncleanSSLShutdown: true)) defer { @@ -576,7 +575,7 @@ class HTTPClientTests: XCTestCase { func testWrongContentLengthForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { @@ -593,7 +592,7 @@ class HTTPClientTests: XCTestCase { func testWrongContentLengthWithIgnoreErrorForSSLUncleanShutdown() throws { let httpBin = HttpBinForSSLUncleanShutdown() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, ignoreUncleanSSLShutdown: true)) defer { @@ -678,7 +677,7 @@ class HTTPClientTests: XCTestCase { func testDecompression() throws { let httpBin = HTTPBin(compress: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: .init(decompression: .enabled(limit: .none))) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .none))) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -714,7 +713,7 @@ class HTTPClientTests: XCTestCase { func testDecompressionLimit() throws { let httpBin = HTTPBin(compress: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: .init(decompression: .enabled(limit: .ratio(10)))) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(decompression: .enabled(limit: .ratio(10)))) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -742,7 +741,7 @@ class HTTPClientTests: XCTestCase { func testLoopDetectionRedirectLimit() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 5, allowCycles: false))) defer { @@ -757,7 +756,7 @@ class HTTPClientTests: XCTestCase { func testCountRedirectLimit() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 1000, allowCycles: true))) defer { @@ -986,7 +985,7 @@ class HTTPClientTests: XCTestCase { func testStressGetHttps() throws { let httpBin = HTTPBin(ssl: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1005,7 +1004,7 @@ class HTTPClientTests: XCTestCase { func testStressGetHttpsSSLError() throws { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1039,7 +1038,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try httpBin.shutdown()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) _ = httpClient.get(url: "http://localhost:\(httpBin.port)/wait") do { try httpClient.syncShutdown(requiresCleanClose: true) @@ -1054,7 +1053,7 @@ class HTTPClientTests: XCTestCase { func testFailingConnectionIsReleased() { let httpBin = HTTPBin(refusesConnections: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) @@ -1072,7 +1071,7 @@ class HTTPClientTests: XCTestCase { func testResponseDelayGet() throws { let httpBin = HTTPBin(ssl: false) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1088,7 +1087,7 @@ class HTTPClientTests: XCTestCase { func testIdleTimeoutNoReuse() throws { let httpBin = HTTPBin(ssl: false) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1107,7 +1106,7 @@ class HTTPClientTests: XCTestCase { return } let httpBin = HTTPBin(ssl: false) - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1135,7 +1134,7 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try httpBin.shutdown()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } @@ -1215,7 +1214,7 @@ class HTTPClientTests: XCTestCase { /// This test would cause an assertion failure on `HTTPClient` deinit if client doesn't actually shutdown func testUncleanShutdownActuallyShutsDown() throws { let httpBin = HTTPBin() - let client = HTTPClient(eventLoopGroupProvider: .createNew) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let req = try HTTPClient.Request(url: "http://localhost:\(httpBin.port)/get", method: .GET, headers: ["X-internal-delay": "200"]) _ = client.execute(request: req) try? client.syncShutdown(requiresCleanClose: true) @@ -1253,7 +1252,7 @@ class HTTPClientTests: XCTestCase { } func testDoubleShutdown() { - let client = HTTPClient(eventLoopGroupProvider: .createNew) + let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) XCTAssertNoThrow(try client.syncShutdown()) do { try client.syncShutdown() @@ -1291,7 +1290,7 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try httpBin.shutdown()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertThrowsError(try httpClient.syncShutdown()) { error in XCTAssertEqual(.alreadyShutdown, error as? HTTPClientError) @@ -1490,7 +1489,7 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try httpBin.shutdown()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } @@ -1511,7 +1510,7 @@ class HTTPClientTests: XCTestCase { defer { XCTAssertNoThrow(try httpBin.shutdown()) } - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } @@ -1637,7 +1636,7 @@ class HTTPClientTests: XCTestCase { func testPoolClosesIdleConnections() { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: .init(maximumAllowedIdleTimeInConnectionPool: .milliseconds(100))) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(maximumAllowedIdleTimeInConnectionPool: .milliseconds(100))) defer { XCTAssertNoThrow(try httpBin.shutdown()) XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1649,7 +1648,7 @@ class HTTPClientTests: XCTestCase { func testRacePoolIdleConnectionsAndGet() { let httpBin = HTTPBin() - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: .init(maximumAllowedIdleTimeInConnectionPool: .milliseconds(10))) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: .init(maximumAllowedIdleTimeInConnectionPool: .milliseconds(10))) defer { XCTAssertNoThrow(try httpBin.shutdown()) XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) @@ -1661,7 +1660,7 @@ class HTTPClientTests: XCTestCase { } func testAvoidLeakingTLSHandshakeCompletionPromise() { - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) let httpBin = HTTPBin() let port = httpBin.port XCTAssertNoThrow(try httpBin.shutdown()) From 0b468f2f4491df490d2839e3e923ac5d7d7b700a Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 28 Mar 2020 18:50:54 +0000 Subject: [PATCH 04/31] Don't set TLS hostname for unix connections --- Sources/AsyncHTTPClient/ConnectionPool.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 61562d9e3..a46e8eda8 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -376,7 +376,7 @@ final class ConnectionPool { private func makeNonTSBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() let sslContext = try NIOSSLContext(configuration: tlsConfiguration) - let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: key.host.isIPAddress ? nil : key.host) + let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: (key.scheme == .unix || key.host.isIPAddress) ? nil : key.host) return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider) } @@ -423,6 +423,7 @@ final class ConnectionPool { do { bootstrap = try makeHTTPClientBootstrapBase(on: eventLoop) } catch { + handshakePromise.fail(error) return eventLoop.makeFailedFuture(error) } @@ -436,7 +437,6 @@ final class ConnectionPool { return channel.flatMap { channel -> EventLoopFuture in handshakePromise.succeed(()) -// channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) }.map { From b06210f0b3e1e80950376868de7c77146cc84a3e Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sun, 29 Mar 2020 08:37:04 +0100 Subject: [PATCH 05/31] Removed Handshake promise as it is not used --- Sources/AsyncHTTPClient/ConnectionPool.swift | 11 ++--------- Tests/AsyncHTTPClientTests/HTTPClientTests.swift | 8 +++++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index a46e8eda8..31fe5bbae 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -417,13 +417,11 @@ final class ConnectionPool { private func makeConnection(on eventLoop: EventLoop) -> EventLoopFuture { self.activityPrecondition(expected: [.opened]) - let handshakePromise = eventLoop.makePromise(of: Void.self) let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy) let bootstrap : NIOClientTCPBootstrap do { bootstrap = try makeHTTPClientBootstrapBase(on: eventLoop) } catch { - handshakePromise.fail(error) return eventLoop.makeFailedFuture(error) } @@ -436,10 +434,8 @@ final class ConnectionPool { } return channel.flatMap { channel -> EventLoopFuture in - handshakePromise.succeed(()) - return handshakePromise.futureResult.flatMap { - channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) - }.map { + + channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes).map { let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool) connection.isLeased = true return connection @@ -448,9 +444,6 @@ final class ConnectionPool { self.configureCloseCallback(of: connection) return connection }.flatMapError { error in - // This promise may not have been completed if we reach this - // so we fail it to avoid any leak - handshakePromise.fail(error) let action = self.parentPool.connectionProvidersLock.withLock { self.stateLock.withLock { self.state.failedConnectionAction() diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 504fe7de9..dea8ed894 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -1101,9 +1101,11 @@ class HTTPClientTests: XCTestCase { } func testStressGetClose() throws { - guard !(self.clientGroup is NIOTSEventLoopGroup) else { - XCTFail("Disabled test as it crashes"); - return + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + guard !(self.clientGroup is NIOTSEventLoopGroup) else { + XCTFail("Disabled test as it crashes"); + return + } } let httpBin = HTTPBin(ssl: false) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), From f40a08463354ec82e60e691c21cfb4496a10dc98 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Mar 2020 10:52:52 +0100 Subject: [PATCH 06/31] Removed iOS platform requirements, swift-nio-ssl from:2.7.0 --- Package.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 642e533c4..a5f494740 100644 --- a/Package.swift +++ b/Package.swift @@ -17,13 +17,12 @@ import PackageDescription let package = Package( name: "async-http-client", - platforms: [.iOS(.v12), .tvOS(.v12)], products: [ .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.13.1"), - .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.4.1"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.7.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"), .package(url: "https://github.com/adam-fowler/swift-nio-transport-services.git", .branch("master")), ], From 64513897dfbb586410cdef0253ac08563ed4bbcc Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Mar 2020 11:30:14 +0100 Subject: [PATCH 07/31] Moved makeHTTPClientBootstrapBase back to Utils.swift It is now a member of NIOClientTCPBootstrap --- Sources/AsyncHTTPClient/ConnectionPool.swift | 44 +----------------- Sources/AsyncHTTPClient/Utils.swift | 48 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 31fe5bbae..ee39c954f 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -373,54 +373,12 @@ final class ConnectionPool { } } - private func makeNonTSBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { - let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() - let sslContext = try NIOSSLContext(configuration: tlsConfiguration) - let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: (key.scheme == .unix || key.host.isIPAddress) ? nil : key.host) - return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider) - } - - /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. - private func makeBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { - let bootstrap: NIOClientTCPBootstrap - #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { - let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init()) - bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) - } else { - bootstrap = try makeNonTSBootstrap(on: eventLoop) - } - #else - bootstrap = try makeNonTSBootstrap(on: eventLoop) - #endif - - if key.scheme == .https { - return bootstrap.enableTLS() - } - return bootstrap - } - - private func makeHTTPClientBootstrapBase(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap { - return try makeBootstrap(on: eventLoop) - .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) - .channelInitializer { channel in - let channelAddedFuture: EventLoopFuture - switch self.configuration.proxy { - case .none: - channelAddedFuture = eventLoop.makeSucceededFuture(()) - case .some: - channelAddedFuture = channel.pipeline.addProxyHandler(host: self.key.host, port: self.key.port, authorization: self.configuration.proxy?.authorization) - } - return channelAddedFuture - } - } - private func makeConnection(on eventLoop: EventLoop) -> EventLoopFuture { self.activityPrecondition(expected: [.opened]) let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy) let bootstrap : NIOClientTCPBootstrap do { - bootstrap = try makeHTTPClientBootstrapBase(on: eventLoop) + bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: eventLoop, host: key.host, port: key.port, requiresTLS: self.key.scheme == .https, configuration: self.configuration) } catch { return eventLoop.makeFailedFuture(error) } diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index d1400c06e..b1ac235b6 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -15,6 +15,8 @@ import NIO import NIOHTTP1 import NIOHTTPCompression +import NIOSSL +import NIOTransportServices internal extension String { var isIPAddress: Bool { @@ -46,6 +48,52 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { } } +extension ClientBootstrap { + fileprivate static func makeBootstrap(on eventLoop: EventLoop, host: String, requiresTLS: Bool, configuration: HTTPClient.Configuration) throws -> NIOClientTCPBootstrap { + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + let sslContext = try NIOSSLContext(configuration: tlsConfiguration) + let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: (!requiresTLS || host.isIPAddress) ? nil : host) + return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider) + } +} + +extension NIOClientTCPBootstrap { + /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. + fileprivate static func makeBootstrap(on eventLoop: EventLoop, host: String, requiresTLS: Bool, configuration: HTTPClient.Configuration) throws -> NIOClientTCPBootstrap { + let bootstrap: NIOClientTCPBootstrap + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { + let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init()) + bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) + } else { + bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) + } + #else + bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) + #endif + + if requiresTLS { + return bootstrap.enableTLS() + } + return bootstrap + } + + static func makeHTTPClientBootstrapBase(on eventLoop: EventLoop, host: String, port: Int, requiresTLS: Bool, configuration: HTTPClient.Configuration) throws -> NIOClientTCPBootstrap { + return try makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) + .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) + .channelInitializer { channel in + let channelAddedFuture: EventLoopFuture + switch configuration.proxy { + case .none: + channelAddedFuture = eventLoop.makeSucceededFuture(()) + case .some: + channelAddedFuture = channel.pipeline.addProxyHandler(host: host, port: port, authorization: configuration.proxy?.authorization) + } + return channelAddedFuture + } + } +} + extension CircularBuffer { @discardableResult mutating func swapWithFirstAndRemove(at index: Index) -> Element? { From a7815d1f01998b120a7402ac869a8ffe144a00f1 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Mar 2020 11:42:42 +0100 Subject: [PATCH 08/31] Updated tests - Shutdown ELG in testUploadStreamingBackpressure - Added isTestingNIOTS() - Added testCorrectEventLoopGroup --- .../HTTPClientInternalTests.swift | 1 + .../HTTPClientTestUtils.swift | 7 +++- .../HTTPClientTests.swift | 32 +++++++++++++------ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift index 3648a0e16..556499d86 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift @@ -239,6 +239,7 @@ class HTTPClientInternalTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) XCTAssertNoThrow(try httpBin.shutdown()) + XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let request = try Request(url: "http://localhost:\(httpBin.port)/custom") diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index d101de84f..962a9c5d6 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -21,10 +21,15 @@ import NIOHTTPCompression import NIOSSL import NIOTransportServices +/// Are we testing NIO Transport services +func isTestingNIOTS() -> Bool { + return ProcessInfo.processInfo.environment["ENABLE_TS_TESTS"] == "true" +} + func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup { #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - ProcessInfo.processInfo.environment["ENABLE_TS_TESTS"] == "true" { + isTestingNIOTS() { return NIOTSEventLoopGroup(loopCount: numberOfThreads, defaultQoS: .default) } #endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index dea8ed894..25c9f5f6c 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -398,7 +398,7 @@ class HTTPClientTests: XCTestCase { func testProxyTLS() throws { XCTFail("Disabled test as it crashes"); return - + let httpBin = HTTPBin(simulateProxy: .tls) let httpClient = HTTPClient( eventLoopGroupProvider: .shared(self.clientGroup), @@ -1101,11 +1101,9 @@ class HTTPClientTests: XCTestCase { } func testStressGetClose() throws { - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { - guard !(self.clientGroup is NIOTSEventLoopGroup) else { - XCTFail("Disabled test as it crashes"); - return - } + guard !isTestingNIOTS() else { + XCTFail("Disabled test as it crashes"); + return } let httpBin = HTTPBin(ssl: false) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), @@ -1679,10 +1677,9 @@ class HTTPClientTests: XCTestCase { } func testAsyncShutdown() { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - let promise = eventLoopGroup.next().makePromise(of: Void.self) - eventLoopGroup.next().execute { + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) + let promise = self.clientGroup.next().makePromise(of: Void.self) + self.clientGroup.next().execute { httpClient.shutdown(queue: DispatchQueue(label: "testAsyncShutdown")) { error in XCTAssertNil(error) promise.succeed(()) @@ -1690,4 +1687,19 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(try promise.futureResult.wait()) } + + + func testCorrectEventLoopGroup() { + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown()) + } + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup) + return + } + #endif + XCTAssertTrue(httpClient.eventLoopGroup is MultiThreadedEventLoopGroup) + } } From 8e18b0bd64ad04dc9d43d53c124af35ac48ca4a8 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Mar 2020 15:03:22 +0100 Subject: [PATCH 09/31] Fixed testProxyTLS() The ssl handler has to be added after the proxy handler has run. Resurrected handshakePromise. --- Sources/AsyncHTTPClient/ConnectionPool.swift | 48 ++++++++++++++++--- Sources/AsyncHTTPClient/Utils.swift | 39 ++++++++++++--- .../HTTPClientTests.swift | 3 -- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index ee39c954f..91dfe6179 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -373,15 +373,42 @@ final class ConnectionPool { } } + // if the configuration includes a proxy and requires TLS, TLS will not have been enabled yet. This + // returns whether we need to call addSSLHandlerIfNeeded(). + private func requiresSSLHandler(on eventLoop: EventLoop) -> Bool { + // if a proxy is not set return false, otherwise for non-TS situation return true, if TS is available and + // either we don't have an NIOTSEventLoop or the scheme is HTTPS then return true + if self.configuration.proxy != nil { + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) + { + if !(eventLoop is NIOTSEventLoop) { + return true + } + if self.key.scheme == .https { + return true + } + } else { + return true + } + #else + return true + #endif + } + return false + } + private func makeConnection(on eventLoop: EventLoop) -> EventLoopFuture { self.activityPrecondition(expected: [.opened]) let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy) + let requiresTLS = self.key.scheme == .https let bootstrap : NIOClientTCPBootstrap do { - bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: eventLoop, host: key.host, port: key.port, requiresTLS: self.key.scheme == .https, configuration: self.configuration) + bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: eventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: self.configuration) } catch { return eventLoop.makeFailedFuture(error) } + let handshakePromise = eventLoop.makePromise(of: Void.self) let channel: EventLoopFuture switch self.key.scheme { @@ -392,16 +419,25 @@ final class ConnectionPool { } return channel.flatMap { channel -> EventLoopFuture in - - channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes).map { - let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool) - connection.isLeased = true - return connection + if self.requiresSSLHandler(on: eventLoop) { + channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise) + } else { + handshakePromise.succeed(()) + } + return handshakePromise.futureResult.flatMap { + channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) + }.map { + let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool) + connection.isLeased = true + return connection } }.map { connection in self.configureCloseCallback(of: connection) return connection }.flatMapError { error in + // This promise may not have been completed if we reach this + // so we fail it to avoid any leak + handshakePromise.fail(error) let action = self.parentPool.connectionProvidersLock.withLock { self.stateLock.withLock { self.state.failedConnectionAction() diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index b1ac235b6..43d31220f 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -49,22 +49,40 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { } extension ClientBootstrap { - fileprivate static func makeBootstrap(on eventLoop: EventLoop, host: String, requiresTLS: Bool, configuration: HTTPClient.Configuration) throws -> NIOClientTCPBootstrap { + fileprivate static func makeBootstrap( + on eventLoop: EventLoop, + host: String, + requiresTLS: Bool, + configuration: HTTPClient.Configuration + ) throws -> NIOClientTCPBootstrap { let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() let sslContext = try NIOSSLContext(configuration: tlsConfiguration) - let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: (!requiresTLS || host.isIPAddress) ? nil : host) + let hostname = (!requiresTLS || host.isIPAddress) ? nil : host + let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname) return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider) } } extension NIOClientTCPBootstrap { /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. - fileprivate static func makeBootstrap(on eventLoop: EventLoop, host: String, requiresTLS: Bool, configuration: HTTPClient.Configuration) throws -> NIOClientTCPBootstrap { + fileprivate static func makeBootstrap( + on eventLoop: EventLoop, + host: String, + requiresTLS: Bool, + configuration: HTTPClient.Configuration + ) throws -> NIOClientTCPBootstrap { let bootstrap: NIOClientTCPBootstrap #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { - let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init()) - bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) + if configuration.proxy != nil, requiresTLS { + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + let sslContext = try NIOSSLContext(configuration: tlsConfiguration) + let hostname = (!requiresTLS || host.isIPAddress) ? nil : host + bootstrap = try NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname)) + } else { + let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init()) + bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) + } } else { bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) } @@ -72,13 +90,20 @@ extension NIOClientTCPBootstrap { bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) #endif - if requiresTLS { + // don't enable TLS if we have a proxy, this will be enabled later on + if requiresTLS, configuration.proxy == nil { return bootstrap.enableTLS() } return bootstrap } - static func makeHTTPClientBootstrapBase(on eventLoop: EventLoop, host: String, port: Int, requiresTLS: Bool, configuration: HTTPClient.Configuration) throws -> NIOClientTCPBootstrap { + static func makeHTTPClientBootstrapBase( + on eventLoop: EventLoop, + host: String, + port: Int, + requiresTLS: Bool, + configuration: HTTPClient.Configuration + ) throws -> NIOClientTCPBootstrap { return try makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) .channelInitializer { channel in diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 25c9f5f6c..b29782457 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -396,9 +396,6 @@ class HTTPClientTests: XCTestCase { } func testProxyTLS() throws { - XCTFail("Disabled test as it crashes"); - return - let httpBin = HTTPBin(simulateProxy: .tls) let httpClient = HTTPClient( eventLoopGroupProvider: .shared(self.clientGroup), From 61c735ed7d121fdcc3392fc2337c7e493f6d935a Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Mar 2020 15:08:23 +0100 Subject: [PATCH 10/31] Got HTTPS tests working, fixed testStressGetHttpsSSLError for TS. Added temporary code to disable certificate verification if required Fixed testStressGetHttpsSSLError() for TS: Need to check for Network.framework specific errors. Maybe NIOTransportServices should be translating these into an NIO error. (The Network.framework errors are not super unfriendly). --- Sources/AsyncHTTPClient/Utils.swift | 44 ++++++++++++++++++- .../HTTPClientTests.swift | 15 +++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 43d31220f..09dca05bc 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -12,6 +12,8 @@ // //===----------------------------------------------------------------------===// +import Foundation +import Network import NIO import NIOHTTP1 import NIOHTTPCompression @@ -63,7 +65,46 @@ extension ClientBootstrap { } } +// Used by temporary code below +let tlsDispatchQueue = DispatchQueue(label: "TLSDispatch") + extension NIOClientTCPBootstrap { + + // TEMPORARY: This is temporary code and an extended version of this should really be in NIOTransportServices. But this allows us to run tests and get results back + // for the TLS tests + #if canImport(Network) + @available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) + fileprivate static func getTLSOptions(tlsConfiguration: TLSConfiguration?, queue: DispatchQueue) -> NWProtocolTLS.Options { + guard let tlsConfiguration = tlsConfiguration else { return .init() } + let options = NWProtocolTLS.Options() + + sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in + let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() + + var error: CFError? + if SecTrustEvaluateWithError(trust, &error) { + sec_protocol_verify_complete(true) + } else { + // check error + var errorCode: CFIndex = 0 + if let userInfo = CFErrorCopyUserInfo(error) { + let userInfoDictionary = userInfo as NSDictionary + let underlyingError = userInfoDictionary[kCFErrorUnderlyingErrorKey!] as! CFError + errorCode = CFErrorGetCode(underlyingError) + } + if tlsConfiguration.certificateVerification == .none || + (tlsConfiguration.certificateVerification == .noHostnameVerification && errorCode == errSecNotTrusted) { + sec_protocol_verify_complete(true) + } else { + sec_protocol_verify_complete(false) + } + } + }, queue) + + return options + } + #endif + /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. fileprivate static func makeBootstrap( on eventLoop: EventLoop, @@ -80,7 +121,8 @@ extension NIOClientTCPBootstrap { let hostname = (!requiresTLS || host.isIPAddress) ? nil : host bootstrap = try NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname)) } else { - let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init()) + let parameters = NIOClientTCPBootstrap.getTLSOptions(tlsConfiguration: configuration.tlsConfiguration, queue: tlsDispatchQueue) + let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) } } else { diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index b29782457..c82b4d022 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -13,6 +13,9 @@ //===----------------------------------------------------------------------===// @testable import AsyncHTTPClient +#if canImport(Network) +import Network +#endif import NIO import NIOConcurrencyHelpers import NIOFoundationCompat @@ -1021,6 +1024,18 @@ class HTTPClientTests: XCTestCase { XCTFail("Shouldn't succeed") continue case .failure(let error): + #if canImport(Network) + if isTestingNIOTS() { + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + guard let clientError = error as? NWError, case NWError.tls(let status) = clientError else { + XCTFail("Unexpected error: \(error)") + continue + } + XCTAssertEqual(status, errSSLHandshakeFail) + } + continue + } + #endif guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { XCTFail("Unexpected error: \(error)") continue From eeea6724939306b67b12f2d3c073c6bd386b0669 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 30 Mar 2020 18:49:04 +0100 Subject: [PATCH 11/31] TLSEventsHandler wasn't getting added This fixes testStressGetHttpsSSLError without TS --- Sources/AsyncHTTPClient/ConnectionPool.swift | 7 ++----- Sources/AsyncHTTPClient/HTTPClient.swift | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 91dfe6179..e43c18c87 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -419,11 +419,8 @@ final class ConnectionPool { } return channel.flatMap { channel -> EventLoopFuture in - if self.requiresSSLHandler(on: eventLoop) { - channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise) - } else { - handshakePromise.succeed(()) - } + + channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: self.requiresSSLHandler(on: eventLoop), handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) }.map { diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 35a19ede8..55deeb7c1 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -681,19 +681,24 @@ extension ChannelPipeline { return addHandlers([encoder, decoder, handler]) } - func addSSLHandlerIfNeeded(for key: ConnectionPool.Key, tlsConfiguration: TLSConfiguration?, handshakePromise: EventLoopPromise) { + func addSSLHandlerIfNeeded(for key: ConnectionPool.Key, tlsConfiguration: TLSConfiguration?, addSSLClient: Bool, handshakePromise: EventLoopPromise) { guard key.scheme == .https else { handshakePromise.succeed(()) return } do { - let tlsConfiguration = tlsConfiguration ?? TLSConfiguration.forClient() - let context = try NIOSSLContext(configuration: tlsConfiguration) - let handlers: [ChannelHandler] = [ - try NIOSSLClientHandler(context: context, serverHostname: key.host.isIPAddress ? nil : key.host), - TLSEventsHandler(completionPromise: handshakePromise), - ] + let handlers: [ChannelHandler] + if addSSLClient { + let tlsConfiguration = tlsConfiguration ?? TLSConfiguration.forClient() + let context = try NIOSSLContext(configuration: tlsConfiguration) + handlers = [ + try NIOSSLClientHandler(context: context, serverHostname: key.host.isIPAddress ? nil : key.host), + TLSEventsHandler(completionPromise: handshakePromise) + ] + } else { + handlers = [TLSEventsHandler(completionPromise: handshakePromise)] + } self.addHandlers(handlers).cascadeFailure(to: handshakePromise) } catch { handshakePromise.fail(error) From 4a1ff5ddbe94d1c404639afb783775f6e047dad3 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 31 Mar 2020 07:29:14 +0100 Subject: [PATCH 12/31] Renabled ssl in redirect tests --- Tests/AsyncHTTPClientTests/HTTPClientTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index c82b4d022..c6b3c245c 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -740,7 +740,7 @@ class HTTPClientTests: XCTestCase { } func testLoopDetectionRedirectLimit() throws { - let httpBin = HTTPBin() + let httpBin = HTTPBin(ssl: true) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 5, allowCycles: false))) @@ -749,13 +749,13 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try httpBin.shutdown()) } - XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in + XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected) } } func testCountRedirectLimit() throws { - let httpBin = HTTPBin() + let httpBin = HTTPBin(ssl: true) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, redirectConfiguration: .follow(max: 1000, allowCycles: true))) @@ -764,7 +764,7 @@ class HTTPClientTests: XCTestCase { XCTAssertNoThrow(try httpBin.shutdown()) } - XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in + XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached) } } From 014cef354ef9234f283b46f5ad017d1ce4fc75a3 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 31 Mar 2020 09:46:35 +0100 Subject: [PATCH 13/31] NIOTS set waitForActivity to false Allow errors to be passed back to AsyncHTTPClient --- Sources/AsyncHTTPClient/Utils.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 09dca05bc..841435c5b 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -115,15 +115,16 @@ extension NIOClientTCPBootstrap { let bootstrap: NIOClientTCPBootstrap #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { + let tsBootstrap = NIOTSConnectionBootstrap(group: eventLoop).channelOption(NIOTSChannelOptions.waitForActivity, value: false) if configuration.proxy != nil, requiresTLS { let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() let sslContext = try NIOSSLContext(configuration: tlsConfiguration) let hostname = (!requiresTLS || host.isIPAddress) ? nil : host - bootstrap = try NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname)) + bootstrap = try NIOClientTCPBootstrap(tsBootstrap, tls: NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname)) } else { let parameters = NIOClientTCPBootstrap.getTLSOptions(tlsConfiguration: configuration.tlsConfiguration, queue: tlsDispatchQueue) let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) - bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider) + bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider) } } else { bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) From d500a0c5af9e0d128342ca7b2fcb71d0d528f8c0 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 31 Mar 2020 10:21:02 +0100 Subject: [PATCH 14/31] Fixed testAvoidLeakingTLSHandshakeCompletionPromise for TS Checking for the correct error. --- Tests/AsyncHTTPClientTests/HTTPClientTests.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index c6b3c245c..1a923e90e 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -1681,6 +1681,15 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)").wait()) { error in + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), isTestingNIOTS() { + guard let nwError = error as? NWError, case NWError.posix(let posixErrorCode) = nwError, posixErrorCode == .ECONNREFUSED else { + XCTFail("Unexpected error: \(error)") + return + } + return + } + #endif guard error is NIOConnectionError else { XCTFail("Unexpected error: \(error)") return From 76b3618d26808c7870a22108a1949dba98d4d1fd Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 31 Mar 2020 19:22:01 +0100 Subject: [PATCH 15/31] Moved my public NIOTSEventLoop to another branch --- Package.swift | 2 +- Sources/AsyncHTTPClient/Utils.swift | 2 ++ Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index a5f494740..d3924b8ff 100644 --- a/Package.swift +++ b/Package.swift @@ -24,7 +24,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-nio.git", from: "2.13.1"), .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.7.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"), - .package(url: "https://github.com/adam-fowler/swift-nio-transport-services.git", .branch("master")), + .package(url: "https://github.com/adam-fowler/swift-nio-transport-services.git", .branch("public-niots-eventloop")), ], targets: [ .target( diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 841435c5b..b4182bbc6 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -13,7 +13,9 @@ //===----------------------------------------------------------------------===// import Foundation +#if canImport(Network) import Network +#endif import NIO import NIOHTTP1 import NIOHTTPCompression diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 8937682a0..713551451 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -98,6 +98,7 @@ extension HTTPClientTests { ("testRacePoolIdleConnectionsAndGet", testRacePoolIdleConnectionsAndGet), ("testAvoidLeakingTLSHandshakeCompletionPromise", testAvoidLeakingTLSHandshakeCompletionPromise), ("testAsyncShutdown", testAsyncShutdown), + ("testCorrectEventLoopGroup", testCorrectEventLoopGroup) ] } } From 8574edda5925a2c243abcda00b68e304a8d801d0 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 31 Mar 2020 20:24:29 +0100 Subject: [PATCH 16/31] Simplified requiresSSLHandler check --- Sources/AsyncHTTPClient/ConnectionPool.swift | 30 ++------------------ 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index e43c18c87..5fd22d512 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -16,7 +16,6 @@ import Foundation import NIO import NIOConcurrencyHelpers import NIOHTTP1 -import NIOSSL import NIOTransportServices import NIOTLS @@ -373,31 +372,6 @@ final class ConnectionPool { } } - // if the configuration includes a proxy and requires TLS, TLS will not have been enabled yet. This - // returns whether we need to call addSSLHandlerIfNeeded(). - private func requiresSSLHandler(on eventLoop: EventLoop) -> Bool { - // if a proxy is not set return false, otherwise for non-TS situation return true, if TS is available and - // either we don't have an NIOTSEventLoop or the scheme is HTTPS then return true - if self.configuration.proxy != nil { - #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) - { - if !(eventLoop is NIOTSEventLoop) { - return true - } - if self.key.scheme == .https { - return true - } - } else { - return true - } - #else - return true - #endif - } - return false - } - private func makeConnection(on eventLoop: EventLoop) -> EventLoopFuture { self.activityPrecondition(expected: [.opened]) let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy) @@ -419,8 +393,8 @@ final class ConnectionPool { } return channel.flatMap { channel -> EventLoopFuture in - - channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: self.requiresSSLHandler(on: eventLoop), handshakePromise: handshakePromise) + let requiresSSLHandler = self.configuration.proxy != nil && self.key.scheme == .https + channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) }.map { From 9a41fc3cba7a5fdcfccfc1d127c257ea9e76b99c Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Thu, 2 Apr 2020 07:39:51 +0100 Subject: [PATCH 17/31] Added TLSConfiguration.getNWProtocolTLSOptions() Supports a very basic set of options at the moment. - min/max TLS version - disabling certificate verification --- .../TLSConfiguration.swift | 127 ++++++++++++++++++ Sources/AsyncHTTPClient/Utils.swift | 50 ++----- 2 files changed, 135 insertions(+), 42 deletions(-) create mode 100644 Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift new file mode 100644 index 000000000..4d4bf6c40 --- /dev/null +++ b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift @@ -0,0 +1,127 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(Network) + +import Foundation +import Network +import NIOSSL +import NIOTransportServices + +internal extension TLSVersion { + /// return Network framework TLS protocol version + var nwTLSProtocolVersion: tls_protocol_version_t { + switch self { + case .tlsv1: + return .TLSv10 + case .tlsv11: + return .TLSv11 + case .tlsv12: + return .TLSv12 + case .tlsv13: + return .TLSv13 + } + } +} + +@available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) +internal extension TLSConfiguration { + + /// Dispatch queue used by Network framework TLS to control certificate verification + static var tlsDispatchQueue = DispatchQueue(label: "TLSDispatch") + + /// create NWProtocolTLS.Options for use with NIOTransportServices from the NIOSSL TLSConfiguration + /// + /// - Parameter queue: Dispatch queue to run `sec_protocol_options_set_verify_block` on. + /// - Returns: Equivalent NWProtocolTLS Options + func getNWProtocolTLSOptions() -> NWProtocolTLS.Options { + let options = NWProtocolTLS.Options() + + // minimum TLS protocol + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, self.minimumTLSVersion.nwTLSProtocolVersion) + } + + // maximum TLS protocol + if let maximumTLSVersion = self.maximumTLSVersion { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + sec_protocol_options_set_max_tls_protocol_version(options.securityProtocolOptions, maximumTLSVersion.nwTLSProtocolVersion) + } else { + precondition(self.maximumTLSVersion != nil, "TLSConfiguration.maximumTLSVersion is not supported") + } + } + + // application protocols + if self.applicationProtocols.count > 0 { + preconditionFailure("TLSConfiguration.applicationProtocols is not supported") + } + /*for applicationProtocol in self.applicationProtocols { + applicationProtocol.utf8.withContiguousStorageIfAvailable { buffer in + guard let opaquePointer = OpaquePointer(buffer.baseAddress) else { return } + let int8Pointer = UnsafePointer(opaquePointer) + sec_protocol_options_add_tls_application_protocol(options.securityProtocolOptions, int8Pointer) + } + }*/ + + // the certificate chain + if self.certificateChain.count > 0 { + preconditionFailure("TLSConfiguration.certificateChain is not supported") + } + + // cipher suites + if self.cipherSuites.count > 0 { + //preconditionFailure("TLSConfiguration.cipherSuites is not supported") + } + + // key log callback + if let _ = self.keyLogCallback { + preconditionFailure("TLSConfiguration.keyLogCallback is not supported") + } + + // private key + if let _ = self.privateKey { + preconditionFailure("TLSConfiguration.privateKey is not supported") + } + + // renegotiation support key is unsupported + + // trust roots + if let trustRoots = self.trustRoots { + guard case .default = trustRoots else { + preconditionFailure("TLSConfiguration.trustRoots != .default is not supported") + } + } + + switch self.certificateVerification { + case .none: + // add verify block to control certificate verification + sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in + //let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() + //var error: CFError? + //if SecTrustEvaluateWithError(trust, &error) { + sec_protocol_verify_complete(true) + }, TLSConfiguration.tlsDispatchQueue) + + case .noHostnameVerification: + precondition(self.certificateVerification != .noHostnameVerification, "TLSConfiguration.certificateVerification = .noHostnameVerification is not supported") + + case .fullVerification: + break + } + + return options + } +} + +#endif diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index b4182bbc6..1569c21ca 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -2,7 +2,7 @@ // // This source file is part of the AsyncHTTPClient open source project // -// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -67,46 +67,8 @@ extension ClientBootstrap { } } -// Used by temporary code below -let tlsDispatchQueue = DispatchQueue(label: "TLSDispatch") - extension NIOClientTCPBootstrap { - // TEMPORARY: This is temporary code and an extended version of this should really be in NIOTransportServices. But this allows us to run tests and get results back - // for the TLS tests - #if canImport(Network) - @available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) - fileprivate static func getTLSOptions(tlsConfiguration: TLSConfiguration?, queue: DispatchQueue) -> NWProtocolTLS.Options { - guard let tlsConfiguration = tlsConfiguration else { return .init() } - let options = NWProtocolTLS.Options() - - sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in - let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() - - var error: CFError? - if SecTrustEvaluateWithError(trust, &error) { - sec_protocol_verify_complete(true) - } else { - // check error - var errorCode: CFIndex = 0 - if let userInfo = CFErrorCopyUserInfo(error) { - let userInfoDictionary = userInfo as NSDictionary - let underlyingError = userInfoDictionary[kCFErrorUnderlyingErrorKey!] as! CFError - errorCode = CFErrorGetCode(underlyingError) - } - if tlsConfiguration.certificateVerification == .none || - (tlsConfiguration.certificateVerification == .noHostnameVerification && errorCode == errSecNotTrusted) { - sec_protocol_verify_complete(true) - } else { - sec_protocol_verify_complete(false) - } - } - }, queue) - - return options - } - #endif - /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. fileprivate static func makeBootstrap( on eventLoop: EventLoop, @@ -116,15 +78,19 @@ extension NIOClientTCPBootstrap { ) throws -> NIOClientTCPBootstrap { let bootstrap: NIOClientTCPBootstrap #if canImport(Network) + // if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { let tsBootstrap = NIOTSConnectionBootstrap(group: eventLoop).channelOption(NIOTSChannelOptions.waitForActivity, value: false) + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + + // if we have a proxy and require TLS then use NIOSSL tls support if configuration.proxy != nil, requiresTLS { - let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() let sslContext = try NIOSSLContext(configuration: tlsConfiguration) let hostname = (!requiresTLS || host.isIPAddress) ? nil : host bootstrap = try NIOClientTCPBootstrap(tsBootstrap, tls: NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname)) } else { - let parameters = NIOClientTCPBootstrap.getTLSOptions(tlsConfiguration: configuration.tlsConfiguration, queue: tlsDispatchQueue) + // create NIOClientTCPBootstrap with NIOTS TLS provider + let parameters = tlsConfiguration.getNWProtocolTLSOptions() let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider) } @@ -141,7 +107,7 @@ extension NIOClientTCPBootstrap { } return bootstrap } - + static func makeHTTPClientBootstrapBase( on eventLoop: EventLoop, host: String, From 639d0d5a23e40ddef9378924bbdf055bbee09c26 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Thu, 2 Apr 2020 16:17:03 +0100 Subject: [PATCH 18/31] 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. Added NWError tests --- Sources/AsyncHTTPClient/ConnectionPool.swift | 13 ++ .../NIOTransportServices/NWErrorHandler.swift | 115 ++++++++++++++++ .../HTTPClientNIOTSTests.swift | 129 ++++++++++++++++++ .../HTTPClientTests+XCTest.swift | 3 +- .../HTTPClientTests.swift | 51 +++---- 5 files changed, 274 insertions(+), 37 deletions(-) create mode 100644 Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift create mode 100644 Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 5fd22d512..885c6ab34 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -397,6 +397,13 @@ final class ConnectionPool { channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) + }.flatMap { + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { + return channel.pipeline.addHandler(NWErrorHandler(), position: .first) + } + #endif + return eventLoop.makeSucceededFuture(()) }.map { let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool) connection.isLeased = true @@ -406,6 +413,12 @@ final class ConnectionPool { self.configureCloseCallback(of: connection) return connection }.flatMapError { error in + var error = error + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { + error = NWErrorHandler.translateError(error) + } + #endif // This promise may not have been completed if we reach this // so we fail it to avoid any leak handshakePromise.fail(error) diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift new file mode 100644 index 000000000..553a60573 --- /dev/null +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -0,0 +1,115 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(Network) + +import Network +import NIO +import NIOHTTP1 +import NIOTransportServices + +public struct NWDNSError: Error { + + /// DNS error type. Error type list is defined in + public let errorType: DNSServiceErrorType + + /// actual reason, in human readable form + private let reason: String + + /// Initialise a NWDNSError + /// - Parameters: + /// - errorType: DNS error type + /// - reason: String describing reason for error + public init(_ errorType: DNSServiceErrorType, reason: String) { + self.errorType = errorType + self.reason = reason + } +} + +extension NWDNSError: CustomStringConvertible { + public var description: String { return reason } +} + +public struct NWPOSIXError: Error { + + /// POSIX error code (enum) + public let errorCode: POSIXErrorCode + + /// actual reason, in human readable form + private let reason: String + + /// Initialise a NWPOSIXError + /// - Parameters: + /// - errorType: posix error type + /// - reason: String describing reason for error + public init(_ errorCode: POSIXErrorCode, reason: String) { + self.errorCode = errorCode + self.reason = reason + } +} + +extension NWPOSIXError: CustomStringConvertible { + public var description: String { return reason } +} + +public struct NWTLSError: Error { + + /// TLS error status. List of TLS errors can be found in + public let status: OSStatus + + /// actual reason, in human readable form + private let reason: String + + /// initialise a NWTLSError + /// - Parameters: + /// - status: TLS status + /// - reason: String describing reason for error + public init(_ status: OSStatus, reason: String) { + self.status = status + self.reason = reason + } +} + +extension NWTLSError: CustomStringConvertible { + public var description: String { return reason } +} + +@available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) +class NWErrorHandler: ChannelInboundHandler { + typealias InboundIn = HTTPClientResponsePart + + func errorCaught(context: ChannelHandlerContext, error: Error) { + context.fireErrorCaught(NWErrorHandler.translateError(error)) + } + + static func translateError(_ error: Error) -> Error { + + if let error = error as? NWError { + switch error { + case .dns(let errorType): + return NWDNSError(errorType, reason: error.localizedDescription) + case .tls(let status): + return NWTLSError(status, reason: error.localizedDescription) + case .posix(let errorCode): + return NWPOSIXError(errorCode, reason: error.localizedDescription) + default: + return error + } + } + return error + } +} + + +#endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift new file mode 100644 index 000000000..4e21ddcc1 --- /dev/null +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if canImport(Network) + +@testable import AsyncHTTPClient +import Network +import NIO +import NIOSSL +import NIOTransportServices +import XCTest + +class HTTPClientNIOTSTests: XCTestCase { + var clientGroup: EventLoopGroup! + + override func setUp() { + XCTAssertNil(self.clientGroup) + self.clientGroup = getDefaultEventLoopGroup(numberOfThreads: 3) + } + + override func tearDown() { + XCTAssertNotNil(self.clientGroup) + XCTAssertNoThrow(try self.clientGroup.syncShutdownGracefully()) + self.clientGroup = nil + } + + func testCorrectEventLoopGroup() { + let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown()) + } + if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup) + return + } + XCTAssertTrue(httpClient.eventLoopGroup is MultiThreadedEventLoopGroup) + } + + func testDNSFailError() { + guard isTestingNIOTS() else { return } + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + } + + do { + _ = try httpClient.get(url: "http://dnsfail/").wait() + XCTFail("This should have failed") + } catch let error as NWDNSError { + XCTAssertEqual(error.errorType, DNSServiceErrorType(kDNSServiceErr_NoSuchRecord)) + } catch { + XCTFail("Error should have been NWDSNError not \(type(of:error))") + } + } + + func testTLSFailError() { + guard isTestingNIOTS() else { return } + let httpBin = HTTPBin(ssl: true) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + XCTAssertNoThrow(try httpBin.shutdown()) + } + + do { + _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() + XCTFail("This should have failed") + } catch let error as NWTLSError { + XCTAssertEqual(error.status, errSSLHandshakeFail) + } catch { + XCTFail("Error should have been NWTLSError not \(type(of:error))") + } + } + + func testConnectionFailError() { + guard isTestingNIOTS() else { return } + let httpBin = HTTPBin(ssl: true) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + } + let port = httpBin.port + XCTAssertNoThrow(try httpBin.shutdown()) + + do { + _ = try httpClient.get(url: "https://localhost:\(port)/get").wait() + XCTFail("This should have failed") + } catch let error as NWPOSIXError { + XCTAssertEqual(error.errorCode, .ECONNREFUSED) + } catch { + XCTFail("Error should have been NWPOSIXError not \(type(of:error))") + } + } + + func testTLSVersionError() { + guard isTestingNIOTS() else { return } + let httpBin = HTTPBin(ssl: true) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(tlsConfiguration: TLSConfiguration.forClient(minimumTLSVersion: .tlsv11, maximumTLSVersion: .tlsv1, certificateVerification: .none)) + ) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + XCTAssertNoThrow(try httpBin.shutdown()) + } + + do { + _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() + XCTFail("This should have failed") + } catch let error as NWTLSError { + XCTAssertEqual(error.status, errSSLHandshakeFail) + } catch { + XCTFail("Error should have been NWTLSError not \(type(of:error))") + } + } +} + +#endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 713551451..bdd967055 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -97,8 +97,7 @@ extension HTTPClientTests { ("testPoolClosesIdleConnections", testPoolClosesIdleConnections), ("testRacePoolIdleConnectionsAndGet", testRacePoolIdleConnectionsAndGet), ("testAvoidLeakingTLSHandshakeCompletionPromise", testAvoidLeakingTLSHandshakeCompletionPromise), - ("testAsyncShutdown", testAsyncShutdown), - ("testCorrectEventLoopGroup", testCorrectEventLoopGroup) + ("testAsyncShutdown", testAsyncShutdown) ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 1a923e90e..d91592b79 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -550,7 +550,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/noresponse").wait(), "Should fail") { error in - guard case let error = error as? NIOSSLError, error == .uncleanShutdown else { + guard case let sslError = error as? NIOSSLError, sslError == .uncleanShutdown else { return XCTFail("Should fail with NIOSSLError.uncleanShutdown") } } @@ -567,7 +567,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/noresponse").wait(), "Should fail") { error in - guard case let error = error as? NIOSSLError, error == .uncleanShutdown else { + guard case let sslError = error as? NIOSSLError, sslError == .uncleanShutdown else { return XCTFail("Should fail with NIOSSLError.uncleanShutdown") } } @@ -584,7 +584,7 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/wrongcontentlength").wait(), "Should fail") { error in - guard case let error = error as? NIOSSLError, error == .uncleanShutdown else { + guard case let sslError = error as? NIOSSLError, sslError == .uncleanShutdown else { return XCTFail("Should fail with NIOSSLError.uncleanShutdown") } } @@ -1024,21 +1024,19 @@ class HTTPClientTests: XCTestCase { XCTFail("Shouldn't succeed") continue case .failure(let error): - #if canImport(Network) if isTestingNIOTS() { - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { - guard let clientError = error as? NWError, case NWError.tls(let status) = clientError else { - XCTFail("Unexpected error: \(error)") - continue - } - XCTAssertEqual(status, errSSLHandshakeFail) + #if canImport(Network) + guard let clientError = error as? NWTLSError else { + XCTFail("Unexpected error: \(error)") + continue + } + XCTAssertEqual(clientError.status, errSSLHandshakeFail) + #endif + } else { + guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { + XCTFail("Unexpected error: \(error)") + continue } - continue - } - #endif - guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { - XCTFail("Unexpected error: \(error)") - continue } } } @@ -1681,15 +1679,13 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)").wait()) { error in - #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), isTestingNIOTS() { - guard let nwError = error as? NWError, case NWError.posix(let posixErrorCode) = nwError, posixErrorCode == .ECONNREFUSED else { + if isTestingNIOTS() { + guard let ioError = error as? IOError, ioError.errnoCode == ECONNREFUSED else { XCTFail("Unexpected error: \(error)") return } return } - #endif guard error is NIOConnectionError else { XCTFail("Unexpected error: \(error)") return @@ -1708,19 +1704,4 @@ class HTTPClientTests: XCTestCase { } XCTAssertNoThrow(try promise.futureResult.wait()) } - - - func testCorrectEventLoopGroup() { - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - defer { - XCTAssertNoThrow(try httpClient.syncShutdown()) - } - #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { - XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup) - return - } - #endif - XCTAssertTrue(httpClient.eventLoopGroup is MultiThreadedEventLoopGroup) - } } From 8d43633895e3efde0fbdf9b12c4dda42c1f319f6 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 4 Apr 2020 07:56:34 +0100 Subject: [PATCH 19/31] Use NIOTSConnectionBootstrap(validatingGroup:) --- Package.swift | 4 ++-- Sources/AsyncHTTPClient/Utils.swift | 24 ++++++++++++------- .../HTTPClientTests.swift | 4 +++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Package.swift b/Package.swift index d3924b8ff..7a79e52fa 100644 --- a/Package.swift +++ b/Package.swift @@ -21,10 +21,10 @@ let package = Package( .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.13.1"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.16.0"), .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.7.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"), - .package(url: "https://github.com/adam-fowler/swift-nio-transport-services.git", .branch("public-niots-eventloop")), + .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.5.1"), ], targets: [ .target( diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 1569c21ca..d9a64f20d 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -53,8 +53,7 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate { } extension ClientBootstrap { - fileprivate static func makeBootstrap( - on eventLoop: EventLoop, + fileprivate func makeClientTCPBootstrap( host: String, requiresTLS: Bool, configuration: HTTPClient.Configuration @@ -63,7 +62,7 @@ extension ClientBootstrap { let sslContext = try NIOSSLContext(configuration: tlsConfiguration) let hostname = (!requiresTLS || host.isIPAddress) ? nil : host let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname) - return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider) + return NIOClientTCPBootstrap(self, tls: tlsProvider) } } @@ -79,10 +78,11 @@ extension NIOClientTCPBootstrap { let bootstrap: NIOClientTCPBootstrap #if canImport(Network) // if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop { - let tsBootstrap = NIOTSConnectionBootstrap(group: eventLoop).channelOption(NIOTSChannelOptions.waitForActivity, value: false) - let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), + var tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { + tsBootstrap = tsBootstrap.channelOption(NIOTSChannelOptions.waitForActivity, value: false) + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() // if we have a proxy and require TLS then use NIOSSL tls support if configuration.proxy != nil, requiresTLS { let sslContext = try NIOSSLContext(configuration: tlsConfiguration) @@ -94,13 +94,19 @@ extension NIOClientTCPBootstrap { let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider) } + } else if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) { + bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration) } else { - bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) + preconditionFailure("Cannot create bootstrap for the supplied EventLoop") } #else - bootstrap = try ClientBootstrap.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) + if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) { + bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration) + } else { + preconditionFailure("Cannot create bootstrap for the supplied EventLoop") + } #endif - + // don't enable TLS if we have a proxy, this will be enabled later on if requiresTLS, configuration.proxy == nil { return bootstrap.enableTLS() diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index d91592b79..d5dd9fa01 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -1679,13 +1679,15 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)").wait()) { error in + #if canImport(Network) if isTestingNIOTS() { - guard let ioError = error as? IOError, ioError.errnoCode == ECONNREFUSED else { + guard let ioError = error as? NWPOSIXError, ioError.errorCode == .ECONNREFUSED else { XCTFail("Unexpected error: \(error)") return } return } + #endif guard error is NIOConnectionError else { XCTFail("Unexpected error: \(error)") return From 299e14c176c9c178409c75113407ca3dcdb4996b Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 4 Apr 2020 08:27:34 +0100 Subject: [PATCH 20/31] Re-enabled testStressGetClose() --- Tests/AsyncHTTPClientTests/HTTPClientTests.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index d5dd9fa01..3e9390060 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -1111,10 +1111,6 @@ class HTTPClientTests: XCTestCase { } func testStressGetClose() throws { - guard !isTestingNIOTS() else { - XCTFail("Disabled test as it crashes"); - return - } let httpBin = HTTPBin(ssl: false) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) From 69b0e3850d974a64162e229524230e6035bcf70a Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 4 Apr 2020 08:56:49 +0100 Subject: [PATCH 21/31] Changed NWDNSError into an enum --- .../NIOTransportServices/NWErrorHandler.swift | 99 +++++++++++++++---- .../HTTPClientNIOTSTests.swift | 5 +- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index 553a60573..a0e65d7b6 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -19,26 +19,87 @@ import NIO import NIOHTTP1 import NIOTransportServices -public struct NWDNSError: Error { - - /// DNS error type. Error type list is defined in - public let errorType: DNSServiceErrorType - - /// actual reason, in human readable form - private let reason: String - - /// Initialise a NWDNSError - /// - Parameters: - /// - errorType: DNS error type - /// - reason: String describing reason for error - public init(_ errorType: DNSServiceErrorType, reason: String) { - self.errorType = errorType - self.reason = reason - } +/// Enum containing all the DNS errors +public enum NWDNSError: Error { + case noError + case unknown /* 0xFFFE FFFF */ + case noSuchName + case noMemory + case badParam + case badReference + case badState + case badFlags + case unsupported + case notInitialized + case alreadyRegistered + case nameConflict + case invalid + case firewall + case incompatible /* client library incompatible with daemon */ + case badInterfaceIndex + case refused + case noSuchRecord + case noAuth + case noSuchKey + case NATTraversal + case doubleNAT + case badTime /* Codes up to here existed in Tiger */ + case badSig + case badKey + case transient + case serviceNotRunning /* Background daemon not running */ + case NATPortMappingUnsupported /* NAT doesn't support PCP, NAT-PMP or UPnP */ + case NATPortMappingDisabled /* NAT supports PCP, NAT-PMP or UPnP, but it's disabled by the administrator */ + case noRouter /* No router currently configured (probably no network connectivity) */ + case pollingMode + case timeout + case defunctConnection /* Connection to daemon returned a SO_ISDEFUNCT error result */ + case other(DNSServiceErrorType) } -extension NWDNSError: CustomStringConvertible { - public var description: String { return reason } +extension NWDNSError { + /// Initialize NWDNSError from DNSServiceErrorType + /// - Parameter error: error type + public init(from error: DNSServiceErrorType) { + let errorInt = Int(error) + switch (errorInt) { + case kDNSServiceErr_NoError: self = .noError + case kDNSServiceErr_Unknown: self = .unknown + case kDNSServiceErr_NoSuchName: self = .noSuchName + case kDNSServiceErr_NoMemory: self = .noMemory + case kDNSServiceErr_BadParam: self = .badParam + case kDNSServiceErr_BadReference: self = .badReference + case kDNSServiceErr_BadState: self = .badState + case kDNSServiceErr_BadFlags: self = .badFlags + case kDNSServiceErr_Unsupported: self = .unsupported + case kDNSServiceErr_NotInitialized: self = .notInitialized + case kDNSServiceErr_AlreadyRegistered: self = .alreadyRegistered + case kDNSServiceErr_NameConflict: self = .nameConflict + case kDNSServiceErr_Invalid: self = .invalid + case kDNSServiceErr_Firewall: self = .firewall + case kDNSServiceErr_Incompatible: self = .incompatible + case kDNSServiceErr_BadInterfaceIndex: self = .badInterfaceIndex + case kDNSServiceErr_Refused: self = .refused + case kDNSServiceErr_NoSuchRecord: self = .noSuchRecord + case kDNSServiceErr_NoAuth: self = .noAuth + case kDNSServiceErr_NoSuchKey: self = .noSuchKey + case kDNSServiceErr_NATTraversal: self = .NATTraversal + case kDNSServiceErr_DoubleNAT: self = .doubleNAT + case kDNSServiceErr_BadTime: self = .badTime + case kDNSServiceErr_BadSig: self = .badSig + case kDNSServiceErr_BadKey: self = .badKey + case kDNSServiceErr_Transient: self = .transient + case kDNSServiceErr_ServiceNotRunning: self = .serviceNotRunning + case kDNSServiceErr_NATPortMappingUnsupported: self = .NATPortMappingUnsupported + case kDNSServiceErr_NATPortMappingDisabled: self = .NATPortMappingDisabled + case kDNSServiceErr_NoRouter: self = .noRouter + case kDNSServiceErr_PollingMode: self = .pollingMode + case kDNSServiceErr_Timeout: self = .timeout + case kDNSServiceErr_DefunctConnection: self = .defunctConnection + default: + self = .other(error) + } + } } public struct NWPOSIXError: Error { @@ -98,7 +159,7 @@ class NWErrorHandler: ChannelInboundHandler { if let error = error as? NWError { switch error { case .dns(let errorType): - return NWDNSError(errorType, reason: error.localizedDescription) + return NWDNSError(from: errorType) case .tls(let status): return NWTLSError(status, reason: error.localizedDescription) case .posix(let errorCode): diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index 4e21ddcc1..8677c94dc 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -57,10 +57,9 @@ class HTTPClientNIOTSTests: XCTestCase { do { _ = try httpClient.get(url: "http://dnsfail/").wait() XCTFail("This should have failed") - } catch let error as NWDNSError { - XCTAssertEqual(error.errorType, DNSServiceErrorType(kDNSServiceErr_NoSuchRecord)) + } catch NWDNSError.noSuchRecord { } catch { - XCTFail("Error should have been NWDSNError not \(type(of:error))") + XCTFail("Error should have been NWDSNError.noSuchRecord not \(error)") } } From 0eef5f0e20b0983c18f5dc1f90886da51a3d3a28 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 6 Apr 2020 10:16:20 +0100 Subject: [PATCH 22/31] TLSConfiguration convert to NWProtocolTLS.Options changes Added macOS 10.14 fallbacks for min/max TLS protocol Enabled applicationProtocols code, now uses `String.withCString` --- .../TLSConfiguration.swift | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift index 4d4bf6c40..2b0edd252 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift @@ -35,6 +35,22 @@ internal extension TLSVersion { } } +internal extension TLSVersion { + /// return as SSL protocol + var sslProtocol: SSLProtocol { + switch self { + case .tlsv1: + return .tlsProtocol1 + case .tlsv11: + return .tlsProtocol11 + case .tlsv12: + return .tlsProtocol12 + case .tlsv13: + return .tlsProtocol13 + } + } +} + @available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) internal extension TLSConfiguration { @@ -51,6 +67,8 @@ internal extension TLSConfiguration { // minimum TLS protocol if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, self.minimumTLSVersion.nwTLSProtocolVersion) + } else { + sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, self.minimumTLSVersion.sslProtocol) } // maximum TLS protocol @@ -58,21 +76,16 @@ internal extension TLSConfiguration { if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { sec_protocol_options_set_max_tls_protocol_version(options.securityProtocolOptions, maximumTLSVersion.nwTLSProtocolVersion) } else { - precondition(self.maximumTLSVersion != nil, "TLSConfiguration.maximumTLSVersion is not supported") + sec_protocol_options_set_tls_max_version(options.securityProtocolOptions, maximumTLSVersion.sslProtocol) } } // application protocols - if self.applicationProtocols.count > 0 { - preconditionFailure("TLSConfiguration.applicationProtocols is not supported") - } - /*for applicationProtocol in self.applicationProtocols { - applicationProtocol.utf8.withContiguousStorageIfAvailable { buffer in - guard let opaquePointer = OpaquePointer(buffer.baseAddress) else { return } - let int8Pointer = UnsafePointer(opaquePointer) - sec_protocol_options_add_tls_application_protocol(options.securityProtocolOptions, int8Pointer) + for applicationProtocol in self.applicationProtocols { + applicationProtocol.withCString { buffer in + sec_protocol_options_add_tls_application_protocol(options.securityProtocolOptions, buffer) } - }*/ + } // the certificate chain if self.certificateChain.count > 0 { From f7a3a67f3eca3c68860f62f9251b798f56886991 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 6 Apr 2020 10:39:23 +0100 Subject: [PATCH 23/31] NIOClientTCPBootstrap.makeBootstrap() changes Removed setting option `waitForActivity` to false If running with a proxy don't setup NIOTSClientTLSProvider While running NIOTS ignore SSL unclean shutdown tests --- Sources/AsyncHTTPClient/Utils.swift | 27 ++++++------- .../HTTPClientNIOTSTests.swift | 9 ++--- .../HTTPClientTests.swift | 38 +++++++++++++++---- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index d9a64f20d..4deb8e311 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -58,11 +58,16 @@ extension ClientBootstrap { requiresTLS: Bool, configuration: HTTPClient.Configuration ) throws -> NIOClientTCPBootstrap { - let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() - let sslContext = try NIOSSLContext(configuration: tlsConfiguration) - let hostname = (!requiresTLS || host.isIPAddress) ? nil : host - let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname) - return NIOClientTCPBootstrap(self, tls: tlsProvider) + // if there is a proxy don't create TLS provider as it will be added at a later point + if configuration.proxy != nil { + return NIOClientTCPBootstrap(self, tls: NIOInsecureNoTLS()) + } else { + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + let sslContext = try NIOSSLContext(configuration: tlsConfiguration) + let hostname = (!requiresTLS || host.isIPAddress) ? nil : host + let tlsProvider = try NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname) + return NIOClientTCPBootstrap(self, tls: tlsProvider) + } } } @@ -79,15 +84,11 @@ extension NIOClientTCPBootstrap { #if canImport(Network) // if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - var tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { - - tsBootstrap = tsBootstrap.channelOption(NIOTSChannelOptions.waitForActivity, value: false) + let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() - // if we have a proxy and require TLS then use NIOSSL tls support - if configuration.proxy != nil, requiresTLS { - let sslContext = try NIOSSLContext(configuration: tlsConfiguration) - let hostname = (!requiresTLS || host.isIPAddress) ? nil : host - bootstrap = try NIOClientTCPBootstrap(tsBootstrap, tls: NIOSSLClientTLSProvider(context: sslContext, serverHostname: hostname)) + // if there is a proxy don't create TLS provider as it will be added at a later point + if configuration.proxy != nil { + bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: NIOInsecureNoTLS()) } else { // create NIOClientTCPBootstrap with NIOTS TLS provider let parameters = tlsConfiguration.getNWProtocolTLSOptions() diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index 8677c94dc..2a94c1d8a 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -57,9 +57,9 @@ class HTTPClientNIOTSTests: XCTestCase { do { _ = try httpClient.get(url: "http://dnsfail/").wait() XCTFail("This should have failed") - } catch NWDNSError.noSuchRecord { + } catch ChannelError.connectTimeout { } catch { - XCTFail("Error should have been NWDSNError.noSuchRecord not \(error)") + XCTFail("Error should have been ChannelError.connectTimeout not \(error)") } } @@ -95,10 +95,9 @@ class HTTPClientNIOTSTests: XCTestCase { do { _ = try httpClient.get(url: "https://localhost:\(port)/get").wait() XCTFail("This should have failed") - } catch let error as NWPOSIXError { - XCTAssertEqual(error.errorCode, .ECONNREFUSED) + } catch ChannelError.connectTimeout { } catch { - XCTFail("Error should have been NWPOSIXError not \(type(of:error))") + XCTFail("Error should have been ChannelError.connectTimeout not \(type(of:error))") } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 3e9390060..c0ce7b772 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -471,6 +471,9 @@ class HTTPClientTests: XCTestCase { } func testNoContentLengthForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) @@ -488,6 +491,9 @@ class HTTPClientTests: XCTestCase { } func testNoContentLengthWithIgnoreErrorForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, ignoreUncleanSSLShutdown: true)) @@ -506,6 +512,9 @@ class HTTPClientTests: XCTestCase { } func testCorrectContentLengthForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) @@ -524,6 +533,9 @@ class HTTPClientTests: XCTestCase { } func testNoContentForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) @@ -540,6 +552,9 @@ class HTTPClientTests: XCTestCase { } func testNoResponseForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) @@ -557,6 +572,9 @@ class HTTPClientTests: XCTestCase { } func testNoResponseWithIgnoreErrorForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, ignoreUncleanSSLShutdown: true)) @@ -574,6 +592,9 @@ class HTTPClientTests: XCTestCase { } func testWrongContentLengthForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none)) @@ -591,6 +612,9 @@ class HTTPClientTests: XCTestCase { } func testWrongContentLengthWithIgnoreErrorForSSLUncleanShutdown() throws { + // NIOTS deals with ssl unclean shutdown internally + guard !isTestingNIOTS() else { return } + let httpBin = HttpBinForSSLUncleanShutdown() let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: HTTPClient.Configuration(certificateVerification: .none, ignoreUncleanSSLShutdown: true)) @@ -1675,18 +1699,16 @@ class HTTPClientTests: XCTestCase { } XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)").wait()) { error in - #if canImport(Network) if isTestingNIOTS() { - guard let ioError = error as? NWPOSIXError, ioError.errorCode == .ECONNREFUSED else { + guard case ChannelError.connectTimeout = error else { + XCTFail("Unexpected error: \(error)") + return + } + } else { + guard error is NIOConnectionError else { XCTFail("Unexpected error: \(error)") return } - return - } - #endif - guard error is NIOConnectionError else { - XCTFail("Unexpected error: \(error)") - return } } } From 131f96a478f8e2d0cd896fce23aef9d9a0e9c324 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 7 Apr 2020 18:14:26 +0100 Subject: [PATCH 24/31] Clean up suggestions --- .../NIOTransportServices/NWErrorHandler.swift | 2 +- .../NIOTransportServices/TLSConfiguration.swift | 12 +++++------- Sources/AsyncHTTPClient/Utils.swift | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index a0e65d7b6..8a977174c 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -2,7 +2,7 @@ // // This source file is part of the AsyncHTTPClient open source project // -// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors +// Copyright (c) 2020 Apple Inc. and the AsyncHTTPClient project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift index 2b0edd252..45d475dde 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift @@ -2,7 +2,7 @@ // // This source file is part of the AsyncHTTPClient open source project // -// Copyright (c) 2018-2020 Apple Inc. and the AsyncHTTPClient project authors +// Copyright (c) 2020 Apple Inc. and the AsyncHTTPClient project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -94,16 +94,17 @@ internal extension TLSConfiguration { // cipher suites if self.cipherSuites.count > 0 { - //preconditionFailure("TLSConfiguration.cipherSuites is not supported") + // TODO: Requires NIOSSL to provide list of cipher values before we can continue + // https://github.com/apple/swift-nio-ssl/issues/207 } // key log callback - if let _ = self.keyLogCallback { + if self.keyLogCallback != nil { preconditionFailure("TLSConfiguration.keyLogCallback is not supported") } // private key - if let _ = self.privateKey { + if self.privateKey != nil { preconditionFailure("TLSConfiguration.privateKey is not supported") } @@ -120,9 +121,6 @@ internal extension TLSConfiguration { case .none: // add verify block to control certificate verification sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in - //let trust = sec_trust_copy_ref(sec_trust).takeRetainedValue() - //var error: CFError? - //if SecTrustEvaluateWithError(trust, &error) { sec_protocol_verify_complete(true) }, TLSConfiguration.tlsDispatchQueue) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 4deb8e311..e27e331a2 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -109,7 +109,7 @@ extension NIOClientTCPBootstrap { #endif // don't enable TLS if we have a proxy, this will be enabled later on - if requiresTLS, configuration.proxy == nil { + if requiresTLS && configuration.proxy == nil { return bootstrap.enableTLS() } return bootstrap From 72c384be77785a999ca1352aa256bf123d3a9625 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Tue, 14 Apr 2020 14:56:00 +0100 Subject: [PATCH 25/31] Run stubs of NIOTS tests on Linux --- .../HTTPClientNIOTSTests+XCTest.swift | 34 +++++++++++++++++++ .../HTTPClientNIOTSTests.swift | 28 +++++---------- .../HTTPClientTests+XCTest.swift | 2 +- Tests/LinuxMain.swift | 1 + 4 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests+XCTest.swift diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests+XCTest.swift new file mode 100644 index 000000000..c0d86085f --- /dev/null +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests+XCTest.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the AsyncHTTPClient open source project +// +// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +// +// HTTPClientNIOTSTests+XCTest.swift +// +import XCTest + +/// +/// NOTE: This file was generated by generate_linux_tests.rb +/// +/// Do NOT edit this file directly as it will be regenerated automatically when needed. +/// + +extension HTTPClientNIOTSTests { + static var allTests: [(String, (HTTPClientNIOTSTests) -> () throws -> Void)] { + return [ + ("testCorrectEventLoopGroup", testCorrectEventLoopGroup), + ("testTLSFailError", testTLSFailError), + ("testConnectionFailError", testConnectionFailError), + ("testTLSVersionError", testTLSVersionError), + ] + } +} diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index 2a94c1d8a..dd3c7250f 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -12,10 +12,10 @@ // //===----------------------------------------------------------------------===// -#if canImport(Network) - @testable import AsyncHTTPClient +#if canImport(Network) import Network +#endif import NIO import NIOSSL import NIOTransportServices @@ -40,31 +40,18 @@ class HTTPClientNIOTSTests: XCTestCase { defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } + #if canImport(Network) if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup) return } + #endif XCTAssertTrue(httpClient.eventLoopGroup is MultiThreadedEventLoopGroup) } - func testDNSFailError() { - guard isTestingNIOTS() else { return } - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) - defer { - XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) - } - - do { - _ = try httpClient.get(url: "http://dnsfail/").wait() - XCTFail("This should have failed") - } catch ChannelError.connectTimeout { - } catch { - XCTFail("Error should have been ChannelError.connectTimeout not \(error)") - } - } - func testTLSFailError() { guard isTestingNIOTS() else { return } + #if canImport(Network) let httpBin = HTTPBin(ssl: true) let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) defer { @@ -80,6 +67,7 @@ class HTTPClientNIOTSTests: XCTestCase { } catch { XCTFail("Error should have been NWTLSError not \(type(of:error))") } + #endif } func testConnectionFailError() { @@ -103,6 +91,7 @@ class HTTPClientNIOTSTests: XCTestCase { func testTLSVersionError() { guard isTestingNIOTS() else { return } + #if canImport(Network) let httpBin = HTTPBin(ssl: true) let httpClient = HTTPClient( eventLoopGroupProvider: .shared(self.clientGroup), @@ -121,7 +110,6 @@ class HTTPClientNIOTSTests: XCTestCase { } catch { XCTFail("Error should have been NWTLSError not \(type(of:error))") } + #endif } } - -#endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index bdd967055..8937682a0 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -97,7 +97,7 @@ extension HTTPClientTests { ("testPoolClosesIdleConnections", testPoolClosesIdleConnections), ("testRacePoolIdleConnectionsAndGet", testRacePoolIdleConnectionsAndGet), ("testAvoidLeakingTLSHandshakeCompletionPromise", testAvoidLeakingTLSHandshakeCompletionPromise), - ("testAsyncShutdown", testAsyncShutdown) + ("testAsyncShutdown", testAsyncShutdown), ] } } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 93e594a06..de6695f92 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -28,6 +28,7 @@ import XCTest XCTMain([ testCase(HTTPClientCookieTests.allTests), testCase(HTTPClientInternalTests.allTests), + testCase(HTTPClientNIOTSTests.allTests), testCase(HTTPClientTests.allTests), testCase(RequestValidationTests.allTests), ]) From 9d4e9efcad06fa1fbfd4d652a2b16f569898cf68 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 17 Apr 2020 15:22:04 +0100 Subject: [PATCH 26/31] Formatting changes --- Sources/AsyncHTTPClient/ConnectionPool.swift | 26 +- Sources/AsyncHTTPClient/HTTPClient.swift | 16 +- .../NIOTransportServices/NWErrorHandler.swift | 296 +++++++++--------- .../TLSConfiguration.swift | 216 ++++++------- Sources/AsyncHTTPClient/Utils.swift | 51 ++- .../HTTPClientNIOTSTests.swift | 78 ++--- .../HTTPClientTestUtils.swift | 8 +- .../HTTPClientTests.swift | 18 +- 8 files changed, 353 insertions(+), 356 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 885c6ab34..3bcec79e2 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -16,8 +16,8 @@ import Foundation import NIO import NIOConcurrencyHelpers import NIOHTTP1 -import NIOTransportServices import NIOTLS +import NIOTransportServices /// A connection pool that manages and creates new connections to hosts respecting the specified preferences /// @@ -376,9 +376,9 @@ final class ConnectionPool { self.activityPrecondition(expected: [.opened]) let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy) let requiresTLS = self.key.scheme == .https - let bootstrap : NIOClientTCPBootstrap + let bootstrap: NIOClientTCPBootstrap do { - bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: eventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: self.configuration) + bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: eventLoop, host: self.key.host, port: self.key.port, requiresTLS: requiresTLS, configuration: self.configuration) } catch { return eventLoop.makeFailedFuture(error) } @@ -396,18 +396,18 @@ final class ConnectionPool { let requiresSSLHandler = self.configuration.proxy != nil && self.key.scheme == .https channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { - channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) + channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) }.flatMap { #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { - return channel.pipeline.addHandler(NWErrorHandler(), position: .first) - } + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { + return channel.pipeline.addHandler(NWErrorHandler(), position: .first) + } #endif return eventLoop.makeSucceededFuture(()) }.map { - let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool) - connection.isLeased = true - return connection + let connection = Connection(key: self.key, channel: channel, parentPool: self.parentPool) + connection.isLeased = true + return connection } }.map { connection in self.configureCloseCallback(of: connection) @@ -415,9 +415,9 @@ final class ConnectionPool { }.flatMapError { error in var error = error #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { - error = NWErrorHandler.translateError(error) - } + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { + error = NWErrorHandler.translateError(error) + } #endif // This promise may not have been completed if we reach this // so we fail it to avoid any leak diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index 55deeb7c1..6c6dfa21a 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -18,8 +18,8 @@ import NIOConcurrencyHelpers import NIOHTTP1 import NIOHTTPCompression import NIOSSL -import NIOTransportServices import NIOTLS +import NIOTransportServices /// HTTPClient class provides API for request execution. /// @@ -67,13 +67,13 @@ public class HTTPClient { self.eventLoopGroup = group case .createNew: #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { - self.eventLoopGroup = NIOTSEventLoopGroup() - } else { - self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - } + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + self.eventLoopGroup = NIOTSEventLoopGroup() + } else { + self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + } #else - self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) #endif } self.configuration = configuration @@ -694,7 +694,7 @@ extension ChannelPipeline { let context = try NIOSSLContext(configuration: tlsConfiguration) handlers = [ try NIOSSLClientHandler(context: context, serverHostname: key.host.isIPAddress ? nil : key.host), - TLSEventsHandler(completionPromise: handshakePromise) + TLSEventsHandler(completionPromise: handshakePromise), ] } else { handlers = [TLSEventsHandler(completionPromise: handshakePromise)] diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index 8a977174c..5574d11d5 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -14,163 +14,159 @@ #if canImport(Network) -import Network -import NIO -import NIOHTTP1 -import NIOTransportServices - -/// Enum containing all the DNS errors -public enum NWDNSError: Error { - case noError - case unknown /* 0xFFFE FFFF */ - case noSuchName - case noMemory - case badParam - case badReference - case badState - case badFlags - case unsupported - case notInitialized - case alreadyRegistered - case nameConflict - case invalid - case firewall - case incompatible /* client library incompatible with daemon */ - case badInterfaceIndex - case refused - case noSuchRecord - case noAuth - case noSuchKey - case NATTraversal - case doubleNAT - case badTime /* Codes up to here existed in Tiger */ - case badSig - case badKey - case transient - case serviceNotRunning /* Background daemon not running */ - case NATPortMappingUnsupported /* NAT doesn't support PCP, NAT-PMP or UPnP */ - case NATPortMappingDisabled /* NAT supports PCP, NAT-PMP or UPnP, but it's disabled by the administrator */ - case noRouter /* No router currently configured (probably no network connectivity) */ - case pollingMode - case timeout - case defunctConnection /* Connection to daemon returned a SO_ISDEFUNCT error result */ - case other(DNSServiceErrorType) -} - -extension NWDNSError { - /// Initialize NWDNSError from DNSServiceErrorType - /// - Parameter error: error type - public init(from error: DNSServiceErrorType) { - let errorInt = Int(error) - switch (errorInt) { - case kDNSServiceErr_NoError: self = .noError - case kDNSServiceErr_Unknown: self = .unknown - case kDNSServiceErr_NoSuchName: self = .noSuchName - case kDNSServiceErr_NoMemory: self = .noMemory - case kDNSServiceErr_BadParam: self = .badParam - case kDNSServiceErr_BadReference: self = .badReference - case kDNSServiceErr_BadState: self = .badState - case kDNSServiceErr_BadFlags: self = .badFlags - case kDNSServiceErr_Unsupported: self = .unsupported - case kDNSServiceErr_NotInitialized: self = .notInitialized - case kDNSServiceErr_AlreadyRegistered: self = .alreadyRegistered - case kDNSServiceErr_NameConflict: self = .nameConflict - case kDNSServiceErr_Invalid: self = .invalid - case kDNSServiceErr_Firewall: self = .firewall - case kDNSServiceErr_Incompatible: self = .incompatible - case kDNSServiceErr_BadInterfaceIndex: self = .badInterfaceIndex - case kDNSServiceErr_Refused: self = .refused - case kDNSServiceErr_NoSuchRecord: self = .noSuchRecord - case kDNSServiceErr_NoAuth: self = .noAuth - case kDNSServiceErr_NoSuchKey: self = .noSuchKey - case kDNSServiceErr_NATTraversal: self = .NATTraversal - case kDNSServiceErr_DoubleNAT: self = .doubleNAT - case kDNSServiceErr_BadTime: self = .badTime - case kDNSServiceErr_BadSig: self = .badSig - case kDNSServiceErr_BadKey: self = .badKey - case kDNSServiceErr_Transient: self = .transient - case kDNSServiceErr_ServiceNotRunning: self = .serviceNotRunning - case kDNSServiceErr_NATPortMappingUnsupported: self = .NATPortMappingUnsupported - case kDNSServiceErr_NATPortMappingDisabled: self = .NATPortMappingDisabled - case kDNSServiceErr_NoRouter: self = .noRouter - case kDNSServiceErr_PollingMode: self = .pollingMode - case kDNSServiceErr_Timeout: self = .timeout - case kDNSServiceErr_DefunctConnection: self = .defunctConnection - default: - self = .other(error) + import Network + import NIO + import NIOHTTP1 + import NIOTransportServices + + /// Enum containing all the DNS errors + public enum NWDNSError: Error { + case noError + case unknown /* 0xFFFE FFFF */ + case noSuchName + case noMemory + case badParam + case badReference + case badState + case badFlags + case unsupported + case notInitialized + case alreadyRegistered + case nameConflict + case invalid + case firewall + case incompatible /* client library incompatible with daemon */ + case badInterfaceIndex + case refused + case noSuchRecord + case noAuth + case noSuchKey + case NATTraversal + case doubleNAT + case badTime /* Codes up to here existed in Tiger */ + case badSig + case badKey + case transient + case serviceNotRunning /* Background daemon not running */ + case NATPortMappingUnsupported /* NAT doesn't support PCP, NAT-PMP or UPnP */ + case NATPortMappingDisabled /* NAT supports PCP, NAT-PMP or UPnP, but it's disabled by the administrator */ + case noRouter /* No router currently configured (probably no network connectivity) */ + case pollingMode + case timeout + case defunctConnection /* Connection to daemon returned a SO_ISDEFUNCT error result */ + case other(DNSServiceErrorType) + } + + extension NWDNSError { + /// Initialize NWDNSError from DNSServiceErrorType + /// - Parameter error: error type + public init(from error: DNSServiceErrorType) { + let errorInt = Int(error) + switch errorInt { + case kDNSServiceErr_NoError: self = .noError + case kDNSServiceErr_Unknown: self = .unknown + case kDNSServiceErr_NoSuchName: self = .noSuchName + case kDNSServiceErr_NoMemory: self = .noMemory + case kDNSServiceErr_BadParam: self = .badParam + case kDNSServiceErr_BadReference: self = .badReference + case kDNSServiceErr_BadState: self = .badState + case kDNSServiceErr_BadFlags: self = .badFlags + case kDNSServiceErr_Unsupported: self = .unsupported + case kDNSServiceErr_NotInitialized: self = .notInitialized + case kDNSServiceErr_AlreadyRegistered: self = .alreadyRegistered + case kDNSServiceErr_NameConflict: self = .nameConflict + case kDNSServiceErr_Invalid: self = .invalid + case kDNSServiceErr_Firewall: self = .firewall + case kDNSServiceErr_Incompatible: self = .incompatible + case kDNSServiceErr_BadInterfaceIndex: self = .badInterfaceIndex + case kDNSServiceErr_Refused: self = .refused + case kDNSServiceErr_NoSuchRecord: self = .noSuchRecord + case kDNSServiceErr_NoAuth: self = .noAuth + case kDNSServiceErr_NoSuchKey: self = .noSuchKey + case kDNSServiceErr_NATTraversal: self = .NATTraversal + case kDNSServiceErr_DoubleNAT: self = .doubleNAT + case kDNSServiceErr_BadTime: self = .badTime + case kDNSServiceErr_BadSig: self = .badSig + case kDNSServiceErr_BadKey: self = .badKey + case kDNSServiceErr_Transient: self = .transient + case kDNSServiceErr_ServiceNotRunning: self = .serviceNotRunning + case kDNSServiceErr_NATPortMappingUnsupported: self = .NATPortMappingUnsupported + case kDNSServiceErr_NATPortMappingDisabled: self = .NATPortMappingDisabled + case kDNSServiceErr_NoRouter: self = .noRouter + case kDNSServiceErr_PollingMode: self = .pollingMode + case kDNSServiceErr_Timeout: self = .timeout + case kDNSServiceErr_DefunctConnection: self = .defunctConnection + default: + self = .other(error) + } } } -} - -public struct NWPOSIXError: Error { - - /// POSIX error code (enum) - public let errorCode: POSIXErrorCode - - /// actual reason, in human readable form - private let reason: String - - /// Initialise a NWPOSIXError - /// - Parameters: - /// - errorType: posix error type - /// - reason: String describing reason for error - public init(_ errorCode: POSIXErrorCode, reason: String) { - self.errorCode = errorCode - self.reason = reason + + public struct NWPOSIXError: Error { + /// POSIX error code (enum) + public let errorCode: POSIXErrorCode + + /// actual reason, in human readable form + private let reason: String + + /// Initialise a NWPOSIXError + /// - Parameters: + /// - errorType: posix error type + /// - reason: String describing reason for error + public init(_ errorCode: POSIXErrorCode, reason: String) { + self.errorCode = errorCode + self.reason = reason + } + } + + extension NWPOSIXError: CustomStringConvertible { + public var description: String { return self.reason } } -} - -extension NWPOSIXError: CustomStringConvertible { - public var description: String { return reason } -} - -public struct NWTLSError: Error { - - /// TLS error status. List of TLS errors can be found in - public let status: OSStatus - - /// actual reason, in human readable form - private let reason: String - - /// initialise a NWTLSError - /// - Parameters: - /// - status: TLS status - /// - reason: String describing reason for error - public init(_ status: OSStatus, reason: String) { - self.status = status - self.reason = reason + + public struct NWTLSError: Error { + /// TLS error status. List of TLS errors can be found in + public let status: OSStatus + + /// actual reason, in human readable form + private let reason: String + + /// initialise a NWTLSError + /// - Parameters: + /// - status: TLS status + /// - reason: String describing reason for error + public init(_ status: OSStatus, reason: String) { + self.status = status + self.reason = reason + } } -} - -extension NWTLSError: CustomStringConvertible { - public var description: String { return reason } -} - -@available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) -class NWErrorHandler: ChannelInboundHandler { - typealias InboundIn = HTTPClientResponsePart - - func errorCaught(context: ChannelHandlerContext, error: Error) { - context.fireErrorCaught(NWErrorHandler.translateError(error)) + + extension NWTLSError: CustomStringConvertible { + public var description: String { return self.reason } } - - static func translateError(_ error: Error) -> Error { - - if let error = error as? NWError { - switch error { - case .dns(let errorType): - return NWDNSError(from: errorType) - case .tls(let status): - return NWTLSError(status, reason: error.localizedDescription) - case .posix(let errorCode): - return NWPOSIXError(errorCode, reason: error.localizedDescription) - default: - return error + + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) + class NWErrorHandler: ChannelInboundHandler { + typealias InboundIn = HTTPClientResponsePart + + func errorCaught(context: ChannelHandlerContext, error: Error) { + context.fireErrorCaught(NWErrorHandler.translateError(error)) + } + + static func translateError(_ error: Error) -> Error { + if let error = error as? NWError { + switch error { + case .dns(let errorType): + return NWDNSError(from: errorType) + case .tls(let status): + return NWTLSError(status, reason: error.localizedDescription) + case .posix(let errorCode): + return NWPOSIXError(errorCode, reason: error.localizedDescription) + default: + return error + } } + return error } - return error } -} - #endif diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift index 45d475dde..e1003dd93 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/TLSConfiguration.swift @@ -14,125 +14,127 @@ #if canImport(Network) -import Foundation -import Network -import NIOSSL -import NIOTransportServices - -internal extension TLSVersion { - /// return Network framework TLS protocol version - var nwTLSProtocolVersion: tls_protocol_version_t { - switch self { - case .tlsv1: - return .TLSv10 - case .tlsv11: - return .TLSv11 - case .tlsv12: - return .TLSv12 - case .tlsv13: - return .TLSv13 + import Foundation + import Network + import NIOSSL + import NIOTransportServices + + extension TLSVersion { + /// return Network framework TLS protocol version + var nwTLSProtocolVersion: tls_protocol_version_t { + switch self { + case .tlsv1: + return .TLSv10 + case .tlsv11: + return .TLSv11 + case .tlsv12: + return .TLSv12 + case .tlsv13: + return .TLSv13 + } } } -} - -internal extension TLSVersion { - /// return as SSL protocol - var sslProtocol: SSLProtocol { - switch self { - case .tlsv1: - return .tlsProtocol1 - case .tlsv11: - return .tlsProtocol11 - case .tlsv12: - return .tlsProtocol12 - case .tlsv13: - return .tlsProtocol13 + + extension TLSVersion { + /// return as SSL protocol + var sslProtocol: SSLProtocol { + switch self { + case .tlsv1: + return .tlsProtocol1 + case .tlsv11: + return .tlsProtocol11 + case .tlsv12: + return .tlsProtocol12 + case .tlsv13: + return .tlsProtocol13 + } } } -} - -@available (macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) -internal extension TLSConfiguration { - - /// Dispatch queue used by Network framework TLS to control certificate verification - static var tlsDispatchQueue = DispatchQueue(label: "TLSDispatch") - - /// create NWProtocolTLS.Options for use with NIOTransportServices from the NIOSSL TLSConfiguration - /// - /// - Parameter queue: Dispatch queue to run `sec_protocol_options_set_verify_block` on. - /// - Returns: Equivalent NWProtocolTLS Options - func getNWProtocolTLSOptions() -> NWProtocolTLS.Options { - let options = NWProtocolTLS.Options() - - // minimum TLS protocol - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { - sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, self.minimumTLSVersion.nwTLSProtocolVersion) - } else { - sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, self.minimumTLSVersion.sslProtocol) - } - - // maximum TLS protocol - if let maximumTLSVersion = self.maximumTLSVersion { + + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) + extension TLSConfiguration { + /// Dispatch queue used by Network framework TLS to control certificate verification + static var tlsDispatchQueue = DispatchQueue(label: "TLSDispatch") + + /// create NWProtocolTLS.Options for use with NIOTransportServices from the NIOSSL TLSConfiguration + /// + /// - Parameter queue: Dispatch queue to run `sec_protocol_options_set_verify_block` on. + /// - Returns: Equivalent NWProtocolTLS Options + func getNWProtocolTLSOptions() -> NWProtocolTLS.Options { + let options = NWProtocolTLS.Options() + + // minimum TLS protocol if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { - sec_protocol_options_set_max_tls_protocol_version(options.securityProtocolOptions, maximumTLSVersion.nwTLSProtocolVersion) + sec_protocol_options_set_min_tls_protocol_version(options.securityProtocolOptions, self.minimumTLSVersion.nwTLSProtocolVersion) } else { - sec_protocol_options_set_tls_max_version(options.securityProtocolOptions, maximumTLSVersion.sslProtocol) + sec_protocol_options_set_tls_min_version(options.securityProtocolOptions, self.minimumTLSVersion.sslProtocol) } - } - // application protocols - for applicationProtocol in self.applicationProtocols { - applicationProtocol.withCString { buffer in - sec_protocol_options_add_tls_application_protocol(options.securityProtocolOptions, buffer) + // maximum TLS protocol + if let maximumTLSVersion = self.maximumTLSVersion { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) { + sec_protocol_options_set_max_tls_protocol_version(options.securityProtocolOptions, maximumTLSVersion.nwTLSProtocolVersion) + } else { + sec_protocol_options_set_tls_max_version(options.securityProtocolOptions, maximumTLSVersion.sslProtocol) + } } - } - // the certificate chain - if self.certificateChain.count > 0 { - preconditionFailure("TLSConfiguration.certificateChain is not supported") - } - - // cipher suites - if self.cipherSuites.count > 0 { - // TODO: Requires NIOSSL to provide list of cipher values before we can continue - // https://github.com/apple/swift-nio-ssl/issues/207 - } - - // key log callback - if self.keyLogCallback != nil { - preconditionFailure("TLSConfiguration.keyLogCallback is not supported") - } - - // private key - if self.privateKey != nil { - preconditionFailure("TLSConfiguration.privateKey is not supported") - } - - // renegotiation support key is unsupported - - // trust roots - if let trustRoots = self.trustRoots { - guard case .default = trustRoots else { - preconditionFailure("TLSConfiguration.trustRoots != .default is not supported") + // application protocols + for applicationProtocol in self.applicationProtocols { + applicationProtocol.withCString { buffer in + sec_protocol_options_add_tls_application_protocol(options.securityProtocolOptions, buffer) + } } - } - - switch self.certificateVerification { - case .none: - // add verify block to control certificate verification - sec_protocol_options_set_verify_block(options.securityProtocolOptions, { (sec_protocol_metadata, sec_trust, sec_protocol_verify_complete) in - sec_protocol_verify_complete(true) - }, TLSConfiguration.tlsDispatchQueue) - - case .noHostnameVerification: - precondition(self.certificateVerification != .noHostnameVerification, "TLSConfiguration.certificateVerification = .noHostnameVerification is not supported") - - case .fullVerification: - break - } - return options + // the certificate chain + if self.certificateChain.count > 0 { + preconditionFailure("TLSConfiguration.certificateChain is not supported") + } + + // cipher suites + if self.cipherSuites.count > 0 { + // TODO: Requires NIOSSL to provide list of cipher values before we can continue + // https://github.com/apple/swift-nio-ssl/issues/207 + } + + // key log callback + if self.keyLogCallback != nil { + preconditionFailure("TLSConfiguration.keyLogCallback is not supported") + } + + // private key + if self.privateKey != nil { + preconditionFailure("TLSConfiguration.privateKey is not supported") + } + + // renegotiation support key is unsupported + + // trust roots + if let trustRoots = self.trustRoots { + guard case .default = trustRoots else { + preconditionFailure("TLSConfiguration.trustRoots != .default is not supported") + } + } + + switch self.certificateVerification { + case .none: + // add verify block to control certificate verification + sec_protocol_options_set_verify_block( + options.securityProtocolOptions, + { _, _, sec_protocol_verify_complete in + sec_protocol_verify_complete(true) + }, TLSConfiguration.tlsDispatchQueue + ) + + case .noHostnameVerification: + precondition(self.certificateVerification != .noHostnameVerification, "TLSConfiguration.certificateVerification = .noHostnameVerification is not supported") + + case .fullVerification: + break + } + + return options + } } -} #endif diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index e27e331a2..e837fa86c 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -14,7 +14,7 @@ import Foundation #if canImport(Network) -import Network + import Network #endif import NIO import NIOHTTP1 @@ -72,7 +72,6 @@ extension ClientBootstrap { } extension NIOClientTCPBootstrap { - /// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function. fileprivate static func makeBootstrap( on eventLoop: EventLoop, @@ -82,34 +81,34 @@ extension NIOClientTCPBootstrap { ) throws -> NIOClientTCPBootstrap { let bootstrap: NIOClientTCPBootstrap #if canImport(Network) - // if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { - let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() - // if there is a proxy don't create TLS provider as it will be added at a later point - if configuration.proxy != nil { - bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: NIOInsecureNoTLS()) + // if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), + let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + // if there is a proxy don't create TLS provider as it will be added at a later point + if configuration.proxy != nil { + bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: NIOInsecureNoTLS()) + } else { + // create NIOClientTCPBootstrap with NIOTS TLS provider + let parameters = tlsConfiguration.getNWProtocolTLSOptions() + let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) + bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider) + } + } else if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) { + bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration) } else { - // create NIOClientTCPBootstrap with NIOTS TLS provider - let parameters = tlsConfiguration.getNWProtocolTLSOptions() - let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) - bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider) + preconditionFailure("Cannot create bootstrap for the supplied EventLoop") } - } else if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) { - bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration) - } else { - preconditionFailure("Cannot create bootstrap for the supplied EventLoop") - } #else - if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) { - bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration) - } else { - preconditionFailure("Cannot create bootstrap for the supplied EventLoop") - } + if let clientBootstrap = ClientBootstrap(validatingGroup: eventLoop) { + bootstrap = try clientBootstrap.makeClientTCPBootstrap(host: host, requiresTLS: requiresTLS, configuration: configuration) + } else { + preconditionFailure("Cannot create bootstrap for the supplied EventLoop") + } #endif - + // don't enable TLS if we have a proxy, this will be enabled later on - if requiresTLS && configuration.proxy == nil { + if requiresTLS, configuration.proxy == nil { return bootstrap.enableTLS() } return bootstrap @@ -122,7 +121,7 @@ extension NIOClientTCPBootstrap { requiresTLS: Bool, configuration: HTTPClient.Configuration ) throws -> NIOClientTCPBootstrap { - return try makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) + return try self.makeBootstrap(on: eventLoop, host: host, requiresTLS: requiresTLS, configuration: configuration) .channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1) .channelInitializer { channel in let channelAddedFuture: EventLoopFuture diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index dd3c7250f..6ca03e971 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -14,7 +14,7 @@ @testable import AsyncHTTPClient #if canImport(Network) -import Network + import Network #endif import NIO import NIOSSL @@ -41,10 +41,10 @@ class HTTPClientNIOTSTests: XCTestCase { XCTAssertNoThrow(try httpClient.syncShutdown()) } #if canImport(Network) - if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { - XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup) - return - } + if #available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + XCTAssertTrue(httpClient.eventLoopGroup is NIOTSEventLoopGroup) + return + } #endif XCTAssertTrue(httpClient.eventLoopGroup is MultiThreadedEventLoopGroup) } @@ -52,24 +52,24 @@ class HTTPClientNIOTSTests: XCTestCase { func testTLSFailError() { guard isTestingNIOTS() else { return } #if canImport(Network) - let httpBin = HTTPBin(ssl: true) - let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) - defer { - XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) - XCTAssertNoThrow(try httpBin.shutdown()) - } + let httpBin = HTTPBin(ssl: true) + let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup)) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + XCTAssertNoThrow(try httpBin.shutdown()) + } - do { - _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() - XCTFail("This should have failed") - } catch let error as NWTLSError { - XCTAssertEqual(error.status, errSSLHandshakeFail) - } catch { - XCTFail("Error should have been NWTLSError not \(type(of:error))") - } + do { + _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() + XCTFail("This should have failed") + } catch let error as NWTLSError { + XCTAssertEqual(error.status, errSSLHandshakeFail) + } catch { + XCTFail("Error should have been NWTLSError not \(type(of: error))") + } #endif } - + func testConnectionFailError() { guard isTestingNIOTS() else { return } let httpBin = HTTPBin(ssl: true) @@ -85,31 +85,31 @@ class HTTPClientNIOTSTests: XCTestCase { XCTFail("This should have failed") } catch ChannelError.connectTimeout { } catch { - XCTFail("Error should have been ChannelError.connectTimeout not \(type(of:error))") + XCTFail("Error should have been ChannelError.connectTimeout not \(type(of: error))") } } - + func testTLSVersionError() { guard isTestingNIOTS() else { return } #if canImport(Network) - let httpBin = HTTPBin(ssl: true) - let httpClient = HTTPClient( - eventLoopGroupProvider: .shared(self.clientGroup), - configuration: .init(tlsConfiguration: TLSConfiguration.forClient(minimumTLSVersion: .tlsv11, maximumTLSVersion: .tlsv1, certificateVerification: .none)) - ) - defer { - XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) - XCTAssertNoThrow(try httpBin.shutdown()) - } + let httpBin = HTTPBin(ssl: true) + let httpClient = HTTPClient( + eventLoopGroupProvider: .shared(self.clientGroup), + configuration: .init(tlsConfiguration: TLSConfiguration.forClient(minimumTLSVersion: .tlsv11, maximumTLSVersion: .tlsv1, certificateVerification: .none)) + ) + defer { + XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true)) + XCTAssertNoThrow(try httpBin.shutdown()) + } - do { - _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() - XCTFail("This should have failed") - } catch let error as NWTLSError { - XCTAssertEqual(error.status, errSSLHandshakeFail) - } catch { - XCTFail("Error should have been NWTLSError not \(type(of:error))") - } + do { + _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() + XCTFail("This should have failed") + } catch let error as NWTLSError { + XCTAssertEqual(error.status, errSSLHandshakeFail) + } catch { + XCTFail("Error should have been NWTLSError not \(type(of: error))") + } #endif } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index 962a9c5d6..f9ae98f0b 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -28,10 +28,10 @@ func isTestingNIOTS() -> Bool { func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup { #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - isTestingNIOTS() { - return NIOTSEventLoopGroup(loopCount: numberOfThreads, defaultQoS: .default) - } + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), + isTestingNIOTS() { + return NIOTSEventLoopGroup(loopCount: numberOfThreads, defaultQoS: .default) + } #endif return MultiThreadedEventLoopGroup(numberOfThreads: numberOfThreads) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index c0ce7b772..6a0b07900 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -14,7 +14,7 @@ @testable import AsyncHTTPClient #if canImport(Network) -import Network + import Network #endif import NIO import NIOConcurrencyHelpers @@ -1050,11 +1050,11 @@ class HTTPClientTests: XCTestCase { case .failure(let error): if isTestingNIOTS() { #if canImport(Network) - guard let clientError = error as? NWTLSError else { - XCTFail("Unexpected error: \(error)") - continue - } - XCTAssertEqual(clientError.status, errSSLHandshakeFail) + guard let clientError = error as? NWTLSError else { + XCTFail("Unexpected error: \(error)") + continue + } + XCTAssertEqual(clientError.status, errSSLHandshakeFail) #endif } else { guard let clientError = error as? NIOSSLError, case NIOSSLError.handshakeFailed = clientError else { @@ -1701,9 +1701,9 @@ class HTTPClientTests: XCTestCase { XCTAssertThrowsError(try httpClient.get(url: "http://localhost:\(port)").wait()) { error in if isTestingNIOTS() { guard case ChannelError.connectTimeout = error else { - XCTFail("Unexpected error: \(error)") - return - } + XCTFail("Unexpected error: \(error)") + return + } } else { guard error is NIOConnectionError else { XCTFail("Unexpected error: \(error)") From 05c0fe1b96d8fc1adc58d95098a8f7c6aa126187 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 17 Apr 2020 19:00:33 +0100 Subject: [PATCH 27/31] Re-formatted to make easier to read Also don't need to create a tlsConfiguration if there is a proxy so moved its initialisation down a bit --- Sources/AsyncHTTPClient/Utils.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index e837fa86c..95d85c958 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -82,14 +82,13 @@ extension NIOClientTCPBootstrap { let bootstrap: NIOClientTCPBootstrap #if canImport(Network) // if eventLoop is compatible with NIOTransportServices create a NIOTSConnectionBootstrap - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), - let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { - let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), let tsBootstrap = NIOTSConnectionBootstrap(validatingGroup: eventLoop) { // if there is a proxy don't create TLS provider as it will be added at a later point if configuration.proxy != nil { bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: NIOInsecureNoTLS()) } else { // create NIOClientTCPBootstrap with NIOTS TLS provider + let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient() let parameters = tlsConfiguration.getNWProtocolTLSOptions() let tlsProvider = NIOTSClientTLSProvider(tlsOptions: parameters) bootstrap = NIOClientTCPBootstrap(tsBootstrap, tls: tlsProvider) From 262a6fea43587783d1407c72d7bbc743ed0c11bf Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 17 Apr 2020 19:05:56 +0100 Subject: [PATCH 28/31] Moved NWTLSError, NWPosixError inside HTTPClient --- Sources/AsyncHTTPClient/ConnectionPool.swift | 4 +- .../NIOTransportServices/NWErrorHandler.swift | 184 +++++------------- .../HTTPClientNIOTSTests.swift | 4 +- .../HTTPClientTests.swift | 2 +- 4 files changed, 53 insertions(+), 141 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 3bcec79e2..aae2f9748 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -400,7 +400,7 @@ final class ConnectionPool { }.flatMap { #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { - return channel.pipeline.addHandler(NWErrorHandler(), position: .first) + return channel.pipeline.addHandler(HTTPClient.NWErrorHandler(), position: .first) } #endif return eventLoop.makeSucceededFuture(()) @@ -416,7 +416,7 @@ final class ConnectionPool { var error = error #if canImport(Network) if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), bootstrap.underlyingBootstrap is NIOTSConnectionBootstrap { - error = NWErrorHandler.translateError(error) + error = HTTPClient.NWErrorHandler.translateError(error) } #endif // This promise may not have been completed if we reach this diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index 5574d11d5..5eb64f2db 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -19,154 +19,66 @@ import NIOHTTP1 import NIOTransportServices - /// Enum containing all the DNS errors - public enum NWDNSError: Error { - case noError - case unknown /* 0xFFFE FFFF */ - case noSuchName - case noMemory - case badParam - case badReference - case badState - case badFlags - case unsupported - case notInitialized - case alreadyRegistered - case nameConflict - case invalid - case firewall - case incompatible /* client library incompatible with daemon */ - case badInterfaceIndex - case refused - case noSuchRecord - case noAuth - case noSuchKey - case NATTraversal - case doubleNAT - case badTime /* Codes up to here existed in Tiger */ - case badSig - case badKey - case transient - case serviceNotRunning /* Background daemon not running */ - case NATPortMappingUnsupported /* NAT doesn't support PCP, NAT-PMP or UPnP */ - case NATPortMappingDisabled /* NAT supports PCP, NAT-PMP or UPnP, but it's disabled by the administrator */ - case noRouter /* No router currently configured (probably no network connectivity) */ - case pollingMode - case timeout - case defunctConnection /* Connection to daemon returned a SO_ISDEFUNCT error result */ - case other(DNSServiceErrorType) - } - - extension NWDNSError { - /// Initialize NWDNSError from DNSServiceErrorType - /// - Parameter error: error type - public init(from error: DNSServiceErrorType) { - let errorInt = Int(error) - switch errorInt { - case kDNSServiceErr_NoError: self = .noError - case kDNSServiceErr_Unknown: self = .unknown - case kDNSServiceErr_NoSuchName: self = .noSuchName - case kDNSServiceErr_NoMemory: self = .noMemory - case kDNSServiceErr_BadParam: self = .badParam - case kDNSServiceErr_BadReference: self = .badReference - case kDNSServiceErr_BadState: self = .badState - case kDNSServiceErr_BadFlags: self = .badFlags - case kDNSServiceErr_Unsupported: self = .unsupported - case kDNSServiceErr_NotInitialized: self = .notInitialized - case kDNSServiceErr_AlreadyRegistered: self = .alreadyRegistered - case kDNSServiceErr_NameConflict: self = .nameConflict - case kDNSServiceErr_Invalid: self = .invalid - case kDNSServiceErr_Firewall: self = .firewall - case kDNSServiceErr_Incompatible: self = .incompatible - case kDNSServiceErr_BadInterfaceIndex: self = .badInterfaceIndex - case kDNSServiceErr_Refused: self = .refused - case kDNSServiceErr_NoSuchRecord: self = .noSuchRecord - case kDNSServiceErr_NoAuth: self = .noAuth - case kDNSServiceErr_NoSuchKey: self = .noSuchKey - case kDNSServiceErr_NATTraversal: self = .NATTraversal - case kDNSServiceErr_DoubleNAT: self = .doubleNAT - case kDNSServiceErr_BadTime: self = .badTime - case kDNSServiceErr_BadSig: self = .badSig - case kDNSServiceErr_BadKey: self = .badKey - case kDNSServiceErr_Transient: self = .transient - case kDNSServiceErr_ServiceNotRunning: self = .serviceNotRunning - case kDNSServiceErr_NATPortMappingUnsupported: self = .NATPortMappingUnsupported - case kDNSServiceErr_NATPortMappingDisabled: self = .NATPortMappingDisabled - case kDNSServiceErr_NoRouter: self = .noRouter - case kDNSServiceErr_PollingMode: self = .pollingMode - case kDNSServiceErr_Timeout: self = .timeout - case kDNSServiceErr_DefunctConnection: self = .defunctConnection - default: - self = .other(error) + extension HTTPClient { + public struct NWPOSIXError: Error, CustomStringConvertible { + /// POSIX error code (enum) + public let errorCode: POSIXErrorCode + + /// actual reason, in human readable form + private let reason: String + + /// Initialise a NWPOSIXError + /// - Parameters: + /// - errorType: posix error type + /// - reason: String describing reason for error + public init(_ errorCode: POSIXErrorCode, reason: String) { + self.errorCode = errorCode + self.reason = reason } + + public var description: String { return self.reason } } - } - - public struct NWPOSIXError: Error { - /// POSIX error code (enum) - public let errorCode: POSIXErrorCode - - /// actual reason, in human readable form - private let reason: String - - /// Initialise a NWPOSIXError - /// - Parameters: - /// - errorType: posix error type - /// - reason: String describing reason for error - public init(_ errorCode: POSIXErrorCode, reason: String) { - self.errorCode = errorCode - self.reason = reason - } - } - extension NWPOSIXError: CustomStringConvertible { - public var description: String { return self.reason } - } + public struct NWTLSError: Error, CustomStringConvertible { + /// TLS error status. List of TLS errors can be found in + public let status: OSStatus - public struct NWTLSError: Error { - /// TLS error status. List of TLS errors can be found in - public let status: OSStatus + /// actual reason, in human readable form + private let reason: String - /// actual reason, in human readable form - private let reason: String + /// initialise a NWTLSError + /// - Parameters: + /// - status: TLS status + /// - reason: String describing reason for error + public init(_ status: OSStatus, reason: String) { + self.status = status + self.reason = reason + } - /// initialise a NWTLSError - /// - Parameters: - /// - status: TLS status - /// - reason: String describing reason for error - public init(_ status: OSStatus, reason: String) { - self.status = status - self.reason = reason + public var description: String { return self.reason } } - } - - extension NWTLSError: CustomStringConvertible { - public var description: String { return self.reason } - } - @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) - class NWErrorHandler: ChannelInboundHandler { - typealias InboundIn = HTTPClientResponsePart + @available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) + class NWErrorHandler: ChannelInboundHandler { + typealias InboundIn = HTTPClientResponsePart - func errorCaught(context: ChannelHandlerContext, error: Error) { - context.fireErrorCaught(NWErrorHandler.translateError(error)) - } + func errorCaught(context: ChannelHandlerContext, error: Error) { + context.fireErrorCaught(NWErrorHandler.translateError(error)) + } - static func translateError(_ error: Error) -> Error { - if let error = error as? NWError { - switch error { - case .dns(let errorType): - return NWDNSError(from: errorType) - case .tls(let status): - return NWTLSError(status, reason: error.localizedDescription) - case .posix(let errorCode): - return NWPOSIXError(errorCode, reason: error.localizedDescription) - default: - return error + static func translateError(_ error: Error) -> Error { + if let error = error as? NWError { + switch error { + case .tls(let status): + return NWTLSError(status, reason: error.localizedDescription) + case .posix(let errorCode): + return NWPOSIXError(errorCode, reason: error.localizedDescription) + default: + return error + } } + return error } - return error } } - #endif diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index 6ca03e971..776a8e686 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -62,7 +62,7 @@ class HTTPClientNIOTSTests: XCTestCase { do { _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() XCTFail("This should have failed") - } catch let error as NWTLSError { + } catch let error as HTTPClient.NWTLSError { XCTAssertEqual(error.status, errSSLHandshakeFail) } catch { XCTFail("Error should have been NWTLSError not \(type(of: error))") @@ -105,7 +105,7 @@ class HTTPClientNIOTSTests: XCTestCase { do { _ = try httpClient.get(url: "https://localhost:\(httpBin.port)/get").wait() XCTFail("This should have failed") - } catch let error as NWTLSError { + } catch let error as HTTPClient.NWTLSError { XCTAssertEqual(error.status, errSSLHandshakeFail) } catch { XCTFail("Error should have been NWTLSError not \(type(of: error))") diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 6a0b07900..63c9d1397 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -1050,7 +1050,7 @@ class HTTPClientTests: XCTestCase { case .failure(let error): if isTestingNIOTS() { #if canImport(Network) - guard let clientError = error as? NWTLSError else { + guard let clientError = error as? HTTPClient.NWTLSError else { XCTFail("Unexpected error: \(error)") continue } From 038ef0b5be6de989ff880cf6d70cc61a973e867e Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 17 Apr 2020 19:17:06 +0100 Subject: [PATCH 29/31] Flipped Testing NIOTS switch to be DISABLE_TS_TESTS --- Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index f9ae98f0b..d615e3f9c 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -23,7 +23,7 @@ import NIOTransportServices /// Are we testing NIO Transport services func isTestingNIOTS() -> Bool { - return ProcessInfo.processInfo.environment["ENABLE_TS_TESTS"] == "true" + return ProcessInfo.processInfo.environment["DISABLE_TS_TESTS"] != "true" } func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup { From 8d929584b11c6a83716b026c27a24fcab3bdad39 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 17 Apr 2020 22:47:41 +0100 Subject: [PATCH 30/31] swift format fix up --- .../AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift index 5eb64f2db..1f9dceb88 100644 --- a/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift +++ b/Sources/AsyncHTTPClient/NIOTransportServices/NWErrorHandler.swift @@ -35,7 +35,7 @@ self.errorCode = errorCode self.reason = reason } - + public var description: String { return self.reason } } From 30d943cddd9c961fccec9c55d5b3d12555527a43 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sat, 18 Apr 2020 09:59:06 +0100 Subject: [PATCH 31/31] isTestingNIOTS() should return false if you cannot import Network --- Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift index d615e3f9c..25fafefa0 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTestUtils.swift @@ -23,7 +23,11 @@ import NIOTransportServices /// Are we testing NIO Transport services func isTestingNIOTS() -> Bool { - return ProcessInfo.processInfo.environment["DISABLE_TS_TESTS"] != "true" + #if canImport(Network) + return ProcessInfo.processInfo.environment["DISABLE_TS_TESTS"] != "true" + #else + return false + #endif } func getDefaultEventLoopGroup(numberOfThreads: Int) -> EventLoopGroup {