Skip to content

Commit 9500459

Browse files
adam-fowlerYasumotoweissi
committed
NIO Transport Services
Choose NIOTSConnectionBootstrap or ClientBootstrap based on the EventLoop passed to the client. Co-authored-by: Joe Smith <[email protected]> Co-authored-by: Johannes Weiss <[email protected]>
1 parent 7211025 commit 9500459

File tree

6 files changed

+69
-26
lines changed

6 files changed

+69
-26
lines changed

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,20 @@ import PackageDescription
1717

1818
let package = Package(
1919
name: "async-http-client",
20+
platforms: [.iOS(.v12), .tvOS(.v12)],
2021
products: [
2122
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
2223
],
2324
dependencies: [
2425
.package(url: "https://github.com/apple/swift-nio.git", from: "2.13.1"),
2526
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.4.1"),
2627
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"),
28+
.package(url: "https://github.com/adam-fowler/swift-nio-transport-services.git", .branch("master")),
2729
],
2830
targets: [
2931
.target(
3032
name: "AsyncHTTPClient",
31-
dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression", "NIOFoundationCompat"]
33+
dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression", "NIOFoundationCompat", "NIOTransportServices"]
3234
),
3335
.testTarget(
3436
name: "AsyncHTTPClientTests",

Sources/AsyncHTTPClient/ConnectionPool.swift

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import Foundation
1616
import NIO
1717
import NIOConcurrencyHelpers
1818
import NIOHTTP1
19+
import NIOSSL
20+
import NIOTransportServices
1921
import NIOTLS
2022

2123
/// A connection pool that manages and creates new connections to hosts respecting the specified preferences
@@ -368,11 +370,58 @@ final class ConnectionPool {
368370
}
369371
}
370372

373+
private func makeNonTSBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap {
374+
let tlsConfiguration = configuration.tlsConfiguration ?? TLSConfiguration.forClient()
375+
let sslContext = try NIOSSLContext(configuration: tlsConfiguration)
376+
let tlsProvider = try NIOSSLClientTLSProvider<ClientBootstrap>(context: sslContext, serverHostname: key.host.isIPAddress ? nil : key.host)
377+
return NIOClientTCPBootstrap(ClientBootstrap(group: eventLoop), tls: tlsProvider)
378+
}
379+
380+
/// create a TCP Bootstrap based off what type of `EventLoop` has been passed to the function.
381+
private func makeBootstrap(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap {
382+
let bootstrap: NIOClientTCPBootstrap
383+
#if canImport(Network)
384+
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *), eventLoop is NIOTSEventLoop {
385+
let tlsProvider = NIOTSClientTLSProvider(tlsOptions: .init())
386+
bootstrap = NIOClientTCPBootstrap(NIOTSConnectionBootstrap(group: eventLoop), tls: tlsProvider)
387+
} else {
388+
bootstrap = try makeNonTSBootstrap(on: eventLoop)
389+
}
390+
#else
391+
bootstrap = try makeNonTSBootstrap(on: eventLoop)
392+
#endif
393+
394+
if key.scheme == .https {
395+
return bootstrap.enableTLS()
396+
}
397+
return bootstrap
398+
}
399+
400+
private func makeHTTPClientBootstrapBase(on eventLoop: EventLoop) throws -> NIOClientTCPBootstrap {
401+
return try makeBootstrap(on: eventLoop)
402+
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
403+
.channelInitializer { channel in
404+
let channelAddedFuture: EventLoopFuture<Void>
405+
switch self.configuration.proxy {
406+
case .none:
407+
channelAddedFuture = eventLoop.makeSucceededFuture(())
408+
case .some:
409+
channelAddedFuture = channel.pipeline.addProxyHandler(host: self.key.host, port: self.key.port, authorization: self.configuration.proxy?.authorization)
410+
}
411+
return channelAddedFuture
412+
}
413+
}
414+
371415
private func makeConnection(on eventLoop: EventLoop) -> EventLoopFuture<Connection> {
372416
self.activityPrecondition(expected: [.opened])
373417
let handshakePromise = eventLoop.makePromise(of: Void.self)
374-
let bootstrap = ClientBootstrap.makeHTTPClientBootstrapBase(group: eventLoop, host: self.key.host, port: self.key.port, configuration: self.configuration)
375418
let address = HTTPClient.resolveAddress(host: self.key.host, port: self.key.port, proxy: self.configuration.proxy)
419+
let bootstrap : NIOClientTCPBootstrap
420+
do {
421+
bootstrap = try makeHTTPClientBootstrapBase(on: eventLoop)
422+
} catch {
423+
return eventLoop.makeFailedFuture(error)
424+
}
376425

377426
let channel: EventLoopFuture<Channel>
378427
switch self.key.scheme {
@@ -383,7 +432,8 @@ final class ConnectionPool {
383432
}
384433

385434
return channel.flatMap { channel -> EventLoopFuture<ConnectionPool.Connection> in
386-
channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise)
435+
handshakePromise.succeed(())
436+
// channel.pipeline.addSSLHandlerIfNeeded(for: self.key, tlsConfiguration: self.configuration.tlsConfiguration, handshakePromise: handshakePromise)
387437
return handshakePromise.futureResult.flatMap {
388438
channel.pipeline.addHTTPClientHandlers(leftOverBytesStrategy: .forwardBytes)
389439
}.map {

Sources/AsyncHTTPClient/HTTPClient.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import NIOConcurrencyHelpers
1818
import NIOHTTP1
1919
import NIOHTTPCompression
2020
import NIOSSL
21+
import NIOTransportServices
2122
import NIOTLS
2223

2324
/// HTTPClient class provides API for request execution.
@@ -65,7 +66,15 @@ public class HTTPClient {
6566
case .shared(let group):
6667
self.eventLoopGroup = group
6768
case .createNew:
69+
#if canImport(Network)
70+
if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) {
71+
self.eventLoopGroup = NIOTSEventLoopGroup()
72+
} else {
73+
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
74+
}
75+
#else
6876
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
77+
#endif
6978
}
7079
self.configuration = configuration
7180
self.pool = ConnectionPool(configuration: configuration)

Sources/AsyncHTTPClient/Utils.swift

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,6 @@ public final class HTTPClientCopyingDelegate: HTTPClientResponseDelegate {
4646
}
4747
}
4848

49-
extension ClientBootstrap {
50-
static func makeHTTPClientBootstrapBase(group: EventLoopGroup, host: String, port: Int, configuration: HTTPClient.Configuration, channelInitializer: ((Channel) -> EventLoopFuture<Void>)? = nil) -> ClientBootstrap {
51-
return ClientBootstrap(group: group)
52-
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
53-
54-
.channelInitializer { channel in
55-
let channelAddedFuture: EventLoopFuture<Void>
56-
switch configuration.proxy {
57-
case .none:
58-
channelAddedFuture = group.next().makeSucceededFuture(())
59-
case .some:
60-
channelAddedFuture = channel.pipeline.addProxyHandler(host: host, port: port, authorization: configuration.proxy?.authorization)
61-
}
62-
return channelAddedFuture.flatMap { (_: Void) -> EventLoopFuture<Void> in
63-
channelInitializer?(channel) ?? group.next().makeSucceededFuture(())
64-
}
65-
}
66-
}
67-
}
68-
6949
extension CircularBuffer {
7050
@discardableResult
7151
mutating func swapWithFirstAndRemove(at index: Index) -> Element? {

Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ class HTTPClientInternalTests: XCTestCase {
217217
func didFinishRequest(task: HTTPClient.Task<Response>) throws {}
218218
}
219219

220-
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
220+
// cannot test with NIOTS as `maxMessagesPerRead` is not supported
221+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
222+
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup))
221223
let promise = httpClient.eventLoopGroup.next().makePromise(of: Channel.self)
222224
let httpBin = HTTPBin(channelPromise: promise)
223225

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ class HTTPClientTests: XCTestCase {
730730
}
731731

732732
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in
733-
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.redirectCycleDetected)
733+
XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectCycleDetected)
734734
}
735735
}
736736

@@ -745,7 +745,7 @@ class HTTPClientTests: XCTestCase {
745745
}
746746

747747
XCTAssertThrowsError(try httpClient.get(url: "https://localhost:\(httpBin.port)/redirect/infinite1").wait(), "Should fail with redirect limit") { error in
748-
XCTAssertEqual(error as! HTTPClientError, HTTPClientError.redirectLimitReached)
748+
XCTAssertEqual(error as? HTTPClientError, HTTPClientError.redirectLimitReached)
749749
}
750750
}
751751

0 commit comments

Comments
 (0)