From 01ff24ec67f97d4fc2eeccac65e408670414ac7d Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 30 Apr 2021 13:17:09 +0200 Subject: [PATCH 01/13] Support request specific TLS configuration --- .../BestEffortHashableTLSConfiguration.swift | 18 ++++++++++++++++++ Sources/AsyncHTTPClient/ConnectionPool.swift | 5 +++++ Sources/AsyncHTTPClient/HTTPHandler.swift | 7 +++++-- Sources/AsyncHTTPClient/Utils.swift | 3 ++- 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift diff --git a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift new file mode 100644 index 000000000..1129b2bcf --- /dev/null +++ b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift @@ -0,0 +1,18 @@ +import NIOSSL + +/// Wrapper around `TLSConfiguration` from NIOSSL to provide a best effort implementation of `Hashable` +struct BestEffortHashableTLSConfiguration: Hashable { + let base: TLSConfiguration + + init(wrapping base: TLSConfiguration) { + self.base = base + } + + func hash(into hasher: inout Hasher) { + base.bestEffortHash(into: &hasher) + } + + static func == (lhs: BestEffortHashableTLSConfiguration, rhs: BestEffortHashableTLSConfiguration) -> Bool { + lhs.base.bestEffortEquals(rhs.base) + } +} diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index e7c68aba4..3da53fddb 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -20,6 +20,7 @@ import NIOHTTP1 import NIOHTTPCompression import NIOTLS import NIOTransportServices +import NIOSSL /// A connection pool that manages and creates new connections to hosts respecting the specified preferences /// @@ -139,12 +140,16 @@ final class ConnectionPool { self.port = request.port self.host = request.host self.unixPath = request.socketPath + if let tls = request.tlsConfiguration { + self.tlsConfiguration = BestEffortHashableTLSConfiguration(wrapping: tls) + } } var scheme: Scheme var host: String var port: Int var unixPath: String + var tlsConfiguration: BestEffortHashableTLSConfiguration? enum Scheme: Hashable { case http diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index c15b64a1e..f217bb9e5 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -186,6 +186,8 @@ extension HTTPClient { public var headers: HTTPHeaders /// Request body, defaults to no body. public var body: Body? + /// Request-specific TLS configuration, defaults to no request-specific TLS configuration. + public var tlsConfiguration: TLSConfiguration? struct RedirectState { var count: Int @@ -208,7 +210,7 @@ extension HTTPClient { /// - `emptyScheme` if URL does not contain HTTP scheme. /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. - public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration? = nil) throws { guard let url = URL(string: url) else { throw HTTPClientError.invalidURL } @@ -228,7 +230,7 @@ extension HTTPClient { /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. /// - `missingSocketPath` if URL does not contains a socketPath as an encoded host. - public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration? = nil) throws { guard let scheme = url.scheme?.lowercased() else { throw HTTPClientError.emptyScheme } @@ -244,6 +246,7 @@ extension HTTPClient { self.scheme = scheme self.headers = headers self.body = body + self.tlsConfiguration = tlsConfiguration } /// Whether request will be executed using secure socket. diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 30fbd8107..9bdbbedb1 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -165,7 +165,8 @@ extension NIOClientTCPBootstrap { let requiresSSLHandler = configuration.proxy != nil && key.scheme.requiresTLS let handshakePromise = channel.eventLoop.makePromise(of: Void.self) - channel.pipeline.addSSLHandlerIfNeeded(for: key, tlsConfiguration: configuration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) + let tlsConfiguration = key.tlsConfiguration?.base ?? configuration.tlsConfiguration + channel.pipeline.addSSLHandlerIfNeeded(for: key, tlsConfiguration: tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) From 2b11cd66ff9c6517f920a26165ec4a3606f6e9d9 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 30 Apr 2021 13:24:34 +0200 Subject: [PATCH 02/13] remove unnecessary import --- Sources/AsyncHTTPClient/ConnectionPool.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index 3da53fddb..b90a9943f 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -20,7 +20,6 @@ import NIOHTTP1 import NIOHTTPCompression import NIOTLS import NIOTransportServices -import NIOSSL /// A connection pool that manages and creates new connections to hosts respecting the specified preferences /// From 2beb65fb80995a846375bf7240bccbc15a44ae01 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 30 Apr 2021 18:02:34 +0200 Subject: [PATCH 03/13] Apply request tls to config --- Sources/AsyncHTTPClient/Utils.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 9bdbbedb1..ffd38ac18 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -145,9 +145,14 @@ extension NIOClientTCPBootstrap { let key = destination let requiresTLS = key.scheme.requiresTLS + // Override specific key configuration. + var keyConfiguration = configuration + if let tlsConfiguration = destination.tlsConfiguration { + keyConfiguration.tlsConfiguration = tlsConfiguration.base + } let bootstrap: NIOClientTCPBootstrap do { - bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: channelEventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: configuration) + bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: channelEventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: keyConfiguration) } catch { return channelEventLoop.makeFailedFuture(error) } @@ -155,18 +160,17 @@ extension NIOClientTCPBootstrap { let channel: EventLoopFuture switch key.scheme { case .http, .https: - let address = HTTPClient.resolveAddress(host: key.host, port: key.port, proxy: configuration.proxy) + let address = HTTPClient.resolveAddress(host: key.host, port: key.port, proxy: keyConfiguration.proxy) channel = bootstrap.connect(host: address.host, port: address.port) case .unix, .http_unix, .https_unix: channel = bootstrap.connect(unixDomainSocketPath: key.unixPath) } return channel.flatMap { channel in - let requiresSSLHandler = configuration.proxy != nil && key.scheme.requiresTLS + let requiresSSLHandler = keyConfiguration.proxy != nil && key.scheme.requiresTLS let handshakePromise = channel.eventLoop.makePromise(of: Void.self) - let tlsConfiguration = key.tlsConfiguration?.base ?? configuration.tlsConfiguration - channel.pipeline.addSSLHandlerIfNeeded(for: key, tlsConfiguration: tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) + channel.pipeline.addSSLHandlerIfNeeded(for: key, tlsConfiguration: keyConfiguration.tlsConfiguration, addSSLClient: requiresSSLHandler, handshakePromise: handshakePromise) return handshakePromise.futureResult.flatMap { channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes) From 0a2e9399ecbe24a39d0a200d99d100624e372b77 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 5 May 2021 17:20:23 +0200 Subject: [PATCH 04/13] add test --- Sources/AsyncHTTPClient/HTTPHandler.swift | 4 +++- Sources/AsyncHTTPClient/Utils.swift | 4 ++-- .../HTTPClientTests+XCTest.swift | 1 + .../AsyncHTTPClientTests/HTTPClientTests.swift | 18 ++++++++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 985df8613..b12360a33 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -205,6 +205,7 @@ extension HTTPClient { /// - method: HTTP method. /// - headers: Custom HTTP headers. /// - body: Request body. + /// - tlsConfiguration: Request TLS configuration /// - throws: /// - `invalidURL` if URL cannot be parsed. /// - `emptyScheme` if URL does not contain HTTP scheme. @@ -215,7 +216,7 @@ extension HTTPClient { throw HTTPClientError.invalidURL } - try self.init(url: url, method: method, headers: headers, body: body) + try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: tlsConfiguration) } /// Create an HTTP `Request`. @@ -225,6 +226,7 @@ extension HTTPClient { /// - method: HTTP method. /// - headers: Custom HTTP headers. /// - body: Request body. + /// - tlsConfiguration: Request TLS configuration /// - throws: /// - `emptyScheme` if URL does not contain HTTP scheme. /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 6f371cd09..27775c29f 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -150,9 +150,9 @@ extension NIOClientTCPBootstrap { let key = destination let requiresTLS = key.scheme.requiresTLS - // Override specific key configuration. + // Override optional connection pool configuration. var keyConfiguration = configuration - if let tlsConfiguration = destination.tlsConfiguration { + if let tlsConfiguration = key.tlsConfiguration { keyConfiguration.tlsConfiguration = tlsConfiguration.base } let bootstrap: NIOClientTCPBootstrap diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift index 4344fd72e..7e9e2b5d6 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests+XCTest.swift @@ -133,6 +133,7 @@ extension HTTPClientTests { ("testFileDownloadChunked", testFileDownloadChunked), ("testCloseWhileBackpressureIsExertedIsFine", testCloseWhileBackpressureIsExertedIsFine), ("testErrorAfterCloseWhileBackpressureExerted", testErrorAfterCloseWhileBackpressureExerted), + ("testRequestSpecificTLS", testRequestSpecificTLS), ] } } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 34e95924e..921e470b7 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -2916,4 +2916,22 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(error as? ExpectedError, .expected) } } + + func testRequestSpecificTLS() throws { + let configuration = HTTPClient.Configuration(tlsConfiguration: nil, + timeout: .init(), + ignoreUncleanSSLShutdown: false, + decompression: .disabled) + let localHTTPBin = HTTPBin(ssl: true) + let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), + configuration: configuration) + defer { + XCTAssertNoThrow(try localClient.syncShutdown()) + XCTAssertNoThrow(try localHTTPBin.shutdown()) + } + + let request = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(certificateVerification: .none)) + let response = try localClient.execute(request: request).wait() + XCTAssertEqual(.ok, response.status) + } } From 72b1930d04422e83b910f03234b5ea0827c25a77 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Wed, 5 May 2021 19:13:10 +0200 Subject: [PATCH 05/13] use main branch of swift-nio-ssl (for now) --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index a14c1db8b..b1b27ff0b 100644 --- a/Package.swift +++ b/Package.swift @@ -22,7 +22,8 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.27.0"), - .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.12.0"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", .branch("main")), +// .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.12.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.5.1"), .package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"), From e2e6817f3a62b72a2091fcf1ceffba8a83bc2cb0 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 09:36:21 +0200 Subject: [PATCH 06/13] add assertions for connection numbers --- .../HTTPClientTests.swift | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 921e470b7..75da4a4a5 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -2925,13 +2925,43 @@ class HTTPClientTests: XCTestCase { let localHTTPBin = HTTPBin(ssl: true) let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: configuration) + let decoder = JSONDecoder() + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - let request = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(certificateVerification: .none)) - let response = try localClient.execute(request: request).wait() - XCTAssertEqual(.ok, response.status) + // First two requests use identical TLS configurations. + let firstRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(certificateVerification: .none)) + let firstResponse = try localClient.execute(request: firstRequest).wait() + guard let firstBody = firstResponse.body else { + XCTFail("No request body found") + return + } + let firstConnectionNumber = try decoder.decode(RequestInfo.self, from: firstBody).connectionNumber + + let secondRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(certificateVerification: .none)) + let secondResponse = try localClient.execute(request: secondRequest).wait() + guard let secondBody = secondResponse.body else { + XCTFail("No request body found") + return + } + let secondConnectionNumber = try decoder.decode(RequestInfo.self, from: secondBody).connectionNumber + + // Uses a differrent TLS config. + let thirdRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(maximumTLSVersion: .tlsv1, certificateVerification: .none)) + let thirdResponse = try localClient.execute(request: thirdRequest).wait() + guard let thirdBody = thirdResponse.body else { + XCTFail("No request body found") + return + } + let thirdConnectionNumber = try decoder.decode(RequestInfo.self, from: thirdBody).connectionNumber + + XCTAssertEqual(firstResponse.status, .ok) + XCTAssertEqual(secondResponse.status, .ok) + XCTAssertEqual(thirdResponse.status, .ok) + XCTAssertEqual(firstConnectionNumber, secondConnectionNumber, "Identical TLS configurations did not use the same connection") + XCTAssertNotEqual(thirdConnectionNumber, firstConnectionNumber, "Different TLS configurations did not use different connections.") } } From b9e4a34c32ea219072b86daf02b4c4cb9432a53b Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 13:11:06 +0200 Subject: [PATCH 07/13] add missing return statement --- .../AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift index 1129b2bcf..4df01da07 100644 --- a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift @@ -13,6 +13,6 @@ struct BestEffortHashableTLSConfiguration: Hashable { } static func == (lhs: BestEffortHashableTLSConfiguration, rhs: BestEffortHashableTLSConfiguration) -> Bool { - lhs.base.bestEffortEquals(rhs.base) + return lhs.base.bestEffortEquals(rhs.base) } } From cb28d0e25906fd839f6b62a590f357c21d851023 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 14:27:33 +0200 Subject: [PATCH 08/13] fix api breakage --- Sources/AsyncHTTPClient/HTTPHandler.swift | 43 ++++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index b12360a33..3bca438c1 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -197,6 +197,23 @@ extension HTTPClient { var redirectState: RedirectState? let kind: Kind + /// Create HTTP request. + /// + /// - parameters: + /// - url: Remote `URL`. + /// - version: HTTP version. + /// - method: HTTP method. + /// - headers: Custom HTTP headers. + /// - body: Request body. + /// - throws: + /// - `invalidURL` if URL cannot be parsed. + /// - `emptyScheme` if URL does not contain HTTP scheme. + /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. + /// - `emptyHost` if URL does not contains a host. + public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: nil) + } + /// Create HTTP request. /// /// - parameters: @@ -211,14 +228,30 @@ extension HTTPClient { /// - `emptyScheme` if URL does not contain HTTP scheme. /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. - public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration? = nil) throws { + public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration?) throws { guard let url = URL(string: url) else { throw HTTPClientError.invalidURL } - + try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: tlsConfiguration) } + /// Create an HTTP `Request`. + /// + /// - parameters: + /// - url: Remote `URL`. + /// - method: HTTP method. + /// - headers: Custom HTTP headers. + /// - body: Request body. + /// - throws: + /// - `emptyScheme` if URL does not contain HTTP scheme. + /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. + /// - `emptyHost` if URL does not contains a host. + /// - `missingSocketPath` if URL does not contains a socketPath as an encoded host. + public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { + try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: nil) + } + /// Create an HTTP `Request`. /// /// - parameters: @@ -232,16 +265,16 @@ extension HTTPClient { /// - `unsupportedScheme` if URL does contains unsupported HTTP scheme. /// - `emptyHost` if URL does not contains a host. /// - `missingSocketPath` if URL does not contains a socketPath as an encoded host. - public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration? = nil) throws { + public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil, tlsConfiguration: TLSConfiguration?) throws { guard let scheme = url.scheme?.lowercased() else { throw HTTPClientError.emptyScheme } - + self.kind = try Kind(forScheme: scheme) self.host = try self.kind.hostFromURL(url) self.socketPath = try self.kind.socketPathFromURL(url) self.uri = self.kind.uriFromURL(url) - + self.redirectState = nil self.url = url self.method = method From 22c5f2587b6766d8ce7240ceb18d08958fee6635 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 14:28:12 +0200 Subject: [PATCH 09/13] run swiftformat --- .../BestEffortHashableTLSConfiguration.swift | 8 ++++---- Sources/AsyncHTTPClient/HTTPHandler.swift | 10 +++++----- Tests/AsyncHTTPClientTests/HTTPClientTests.swift | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift index 4df01da07..6a1034666 100644 --- a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift @@ -3,15 +3,15 @@ import NIOSSL /// Wrapper around `TLSConfiguration` from NIOSSL to provide a best effort implementation of `Hashable` struct BestEffortHashableTLSConfiguration: Hashable { let base: TLSConfiguration - + init(wrapping base: TLSConfiguration) { self.base = base } - + func hash(into hasher: inout Hasher) { - base.bestEffortHash(into: &hasher) + self.base.bestEffortHash(into: &hasher) } - + static func == (lhs: BestEffortHashableTLSConfiguration, rhs: BestEffortHashableTLSConfiguration) -> Bool { return lhs.base.bestEffortEquals(rhs.base) } diff --git a/Sources/AsyncHTTPClient/HTTPHandler.swift b/Sources/AsyncHTTPClient/HTTPHandler.swift index 3bca438c1..4850c51d8 100644 --- a/Sources/AsyncHTTPClient/HTTPHandler.swift +++ b/Sources/AsyncHTTPClient/HTTPHandler.swift @@ -213,7 +213,7 @@ extension HTTPClient { public init(url: String, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: nil) } - + /// Create HTTP request. /// /// - parameters: @@ -232,7 +232,7 @@ extension HTTPClient { guard let url = URL(string: url) else { throw HTTPClientError.invalidURL } - + try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: tlsConfiguration) } @@ -251,7 +251,7 @@ extension HTTPClient { public init(url: URL, method: HTTPMethod = .GET, headers: HTTPHeaders = HTTPHeaders(), body: Body? = nil) throws { try self.init(url: url, method: method, headers: headers, body: body, tlsConfiguration: nil) } - + /// Create an HTTP `Request`. /// /// - parameters: @@ -269,12 +269,12 @@ extension HTTPClient { guard let scheme = url.scheme?.lowercased() else { throw HTTPClientError.emptyScheme } - + self.kind = try Kind(forScheme: scheme) self.host = try self.kind.hostFromURL(url) self.socketPath = try self.kind.socketPathFromURL(url) self.uri = self.kind.uriFromURL(url) - + self.redirectState = nil self.url = url self.method = method diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index 75da4a4a5..ed8c1acc5 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -2916,7 +2916,7 @@ class HTTPClientTests: XCTestCase { XCTAssertEqual(error as? ExpectedError, .expected) } } - + func testRequestSpecificTLS() throws { let configuration = HTTPClient.Configuration(tlsConfiguration: nil, timeout: .init(), @@ -2926,12 +2926,12 @@ class HTTPClientTests: XCTestCase { let localClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup), configuration: configuration) let decoder = JSONDecoder() - + defer { XCTAssertNoThrow(try localClient.syncShutdown()) XCTAssertNoThrow(try localHTTPBin.shutdown()) } - + // First two requests use identical TLS configurations. let firstRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(certificateVerification: .none)) let firstResponse = try localClient.execute(request: firstRequest).wait() @@ -2940,7 +2940,7 @@ class HTTPClientTests: XCTestCase { return } let firstConnectionNumber = try decoder.decode(RequestInfo.self, from: firstBody).connectionNumber - + let secondRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(certificateVerification: .none)) let secondResponse = try localClient.execute(request: secondRequest).wait() guard let secondBody = secondResponse.body else { @@ -2948,7 +2948,7 @@ class HTTPClientTests: XCTestCase { return } let secondConnectionNumber = try decoder.decode(RequestInfo.self, from: secondBody).connectionNumber - + // Uses a differrent TLS config. let thirdRequest = try HTTPClient.Request(url: "https://localhost:\(localHTTPBin.port)/get", method: .GET, tlsConfiguration: .forClient(maximumTLSVersion: .tlsv1, certificateVerification: .none)) let thirdResponse = try localClient.execute(request: thirdRequest).wait() @@ -2957,7 +2957,7 @@ class HTTPClientTests: XCTestCase { return } let thirdConnectionNumber = try decoder.decode(RequestInfo.self, from: thirdBody).connectionNumber - + XCTAssertEqual(firstResponse.status, .ok) XCTAssertEqual(secondResponse.status, .ok) XCTAssertEqual(thirdResponse.status, .ok) From b89c92be5987ff36f07552d1f6899948446b64cb Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 14:29:56 +0200 Subject: [PATCH 10/13] add missing header --- .../BestEffortHashableTLSConfiguration.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift index 6a1034666..8164bd051 100644 --- a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift @@ -1,3 +1,17 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + import NIOSSL /// Wrapper around `TLSConfiguration` from NIOSSL to provide a best effort implementation of `Hashable` From ed453201855314b6aa5ff55459543440610b3562 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 18:17:05 +0200 Subject: [PATCH 11/13] fix copyright year --- .../AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift index 8164bd051..58169f645 100644 --- a/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.swift +++ b/Sources/AsyncHTTPClient/BestEffortHashableTLSConfiguration.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) 2021 Apple Inc. and the AsyncHTTPClient project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information From dabaebb77ee08f559cc3e8c431c31ef51b652674 Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Thu, 6 May 2021 18:34:59 +0200 Subject: [PATCH 12/13] refactor key configuration --- Sources/AsyncHTTPClient/ConnectionPool.swift | 11 ++++++++++- Sources/AsyncHTTPClient/Utils.swift | 11 +++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Sources/AsyncHTTPClient/ConnectionPool.swift b/Sources/AsyncHTTPClient/ConnectionPool.swift index b90a9943f..63be3aa37 100644 --- a/Sources/AsyncHTTPClient/ConnectionPool.swift +++ b/Sources/AsyncHTTPClient/ConnectionPool.swift @@ -82,7 +82,7 @@ final class ConnectionPool { } else { let provider = HTTP1ConnectionProvider(key: key, eventLoop: taskEventLoop, - configuration: self.configuration, + configuration: key.config(overriding: self.configuration), pool: self, backgroundActivityLogger: self.backgroundActivityLogger) let enqueued = provider.enqueue() @@ -166,6 +166,15 @@ final class ConnectionPool { } } } + + /// Returns a key-specific `HTTPClient.Configuration` by overriding the properties of `base` + func config(overriding base: HTTPClient.Configuration) -> HTTPClient.Configuration { + var config = base + if let tlsConfiguration = self.tlsConfiguration { + config.tlsConfiguration = tlsConfiguration.base + } + return config + } } } diff --git a/Sources/AsyncHTTPClient/Utils.swift b/Sources/AsyncHTTPClient/Utils.swift index 27775c29f..24ec338ab 100644 --- a/Sources/AsyncHTTPClient/Utils.swift +++ b/Sources/AsyncHTTPClient/Utils.swift @@ -150,14 +150,9 @@ extension NIOClientTCPBootstrap { let key = destination let requiresTLS = key.scheme.requiresTLS - // Override optional connection pool configuration. - var keyConfiguration = configuration - if let tlsConfiguration = key.tlsConfiguration { - keyConfiguration.tlsConfiguration = tlsConfiguration.base - } let bootstrap: NIOClientTCPBootstrap do { - bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: channelEventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: keyConfiguration) + bootstrap = try NIOClientTCPBootstrap.makeHTTPClientBootstrapBase(on: channelEventLoop, host: key.host, port: key.port, requiresTLS: requiresTLS, configuration: configuration) } catch { return channelEventLoop.makeFailedFuture(error) } @@ -165,7 +160,7 @@ extension NIOClientTCPBootstrap { let channel: EventLoopFuture switch key.scheme { case .http, .https: - let address = HTTPClient.resolveAddress(host: key.host, port: key.port, proxy: keyConfiguration.proxy) + let address = HTTPClient.resolveAddress(host: key.host, port: key.port, proxy: configuration.proxy) channel = bootstrap.connect(host: address.host, port: address.port) case .unix, .http_unix, .https_unix: channel = bootstrap.connect(unixDomainSocketPath: key.unixPath) @@ -178,7 +173,7 @@ extension NIOClientTCPBootstrap { if requiresLateSSLHandler { let handshakePromise = channel.eventLoop.makePromise(of: Void.self) - channel.pipeline.syncAddLateSSLHandlerIfNeeded(for: key, tlsConfiguration: keyConfiguration.tlsConfiguration, handshakePromise: handshakePromise) + channel.pipeline.syncAddLateSSLHandlerIfNeeded(for: key, tlsConfiguration: configuration.tlsConfiguration, handshakePromise: handshakePromise) handshakeFuture = handshakePromise.futureResult } else if requiresTLS { do { From 21a76d191bad0d27535a8372553e2382fa04938c Mon Sep 17 00:00:00 2001 From: Mads Odgaard Date: Fri, 7 May 2021 13:52:36 +0200 Subject: [PATCH 13/13] update swift-nio-ssl version --- Package.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index b1b27ff0b..f2e606a93 100644 --- a/Package.swift +++ b/Package.swift @@ -22,8 +22,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.27.0"), - .package(url: "https://github.com/apple/swift-nio-ssl.git", .branch("main")), -// .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.12.0"), + .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.13.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.5.1"), .package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),