Skip to content

Commit 4aa41b2

Browse files
committed
Started work on h2
1 parent 0a6bef5 commit 4aa41b2

32 files changed

+7400
-504
lines changed

Diff for: Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ let package = Package(
2222
],
2323
dependencies: [
2424
.package(url: "https://github.com/apple/swift-nio.git", from: "2.27.0"),
25+
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.7.0"),
2526
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.13.0"),
2627
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.3.0"),
2728
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.5.1"),
@@ -30,7 +31,7 @@ let package = Package(
3031
targets: [
3132
.target(
3233
name: "AsyncHTTPClient",
33-
dependencies: ["NIO", "NIOHTTP1", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression",
34+
dependencies: ["NIO", "NIOHTTP1", "NIOHTTP2", "NIOSSL", "NIOConcurrencyHelpers", "NIOHTTPCompression",
3435
"NIOFoundationCompat", "NIOTransportServices", "Logging"]
3536
),
3637
.testTarget(

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Factory.swift

+52
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,58 @@ extension HTTPConnectionPool {
4646
}
4747

4848
extension HTTPConnectionPool.ConnectionFactory {
49+
50+
func makeConnection(for pool: HTTPConnectionPool, connectionID: HTTPConnectionPool.Connection.ID, eventLoop: EventLoop, logger: Logger) {
51+
var logger = logger
52+
logger[metadataKey: "ahc-connection"] = "\(connectionID)"
53+
54+
let future: EventLoopFuture<(Channel, HTTPVersion)>
55+
56+
if self.key.scheme.isProxyable, let proxy = self.clientConfiguration.proxy {
57+
future = self.makeHTTPProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)
58+
} else {
59+
future = self.makeChannel(eventLoop: eventLoop, logger: logger)
60+
}
61+
62+
future.whenComplete { result in
63+
do {
64+
switch result {
65+
case .success(let (channel, .http1_0)), .success(let (channel, .http1_1)):
66+
let connection = try HTTP1Connection(
67+
channel: channel,
68+
connectionID: connectionID,
69+
configuration: self.clientConfiguration,
70+
delegate: pool,
71+
logger: logger
72+
)
73+
pool.http1ConnectionCreated(connection)
74+
case .success(let (channel, .http2)):
75+
let http2Connection = try HTTP2Connection(
76+
channel: channel,
77+
connectionID: connectionID,
78+
delegate: pool,
79+
logger: logger
80+
)
81+
82+
http2Connection.readyToAcceptConnectionsFuture.whenComplete { result in
83+
switch result {
84+
case .success:
85+
pool.http2ConnectionCreated(http2Connection)
86+
case .failure(let error):
87+
pool.failedToCreateHTTPConnection(connectionID, error: error)
88+
}
89+
}
90+
case .failure(let error):
91+
throw error
92+
default:
93+
preconditionFailure("Unexpected new http version")
94+
}
95+
} catch {
96+
pool.failedToCreateHTTPConnection(connectionID, error: error)
97+
}
98+
}
99+
}
100+
49101
func makeBestChannel(connectionID: HTTPConnectionPool.Connection.ID, eventLoop: EventLoop, logger: Logger) -> EventLoopFuture<(Channel, HTTPVersion)> {
50102
if self.key.scheme.isProxyable, let proxy = self.clientConfiguration.proxy {
51103
return self.makeHTTPProxyChannel(proxy, connectionID: connectionID, eventLoop: eventLoop, logger: logger)

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool+Manager.swift

+152
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,159 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import Logging
16+
import NIO
1517
import NIOConcurrencyHelpers
18+
import NIOHTTP1
19+
20+
protocol HTTPConnectionPoolManagerDelegate: AnyObject {
21+
func httpConnectionPoolManagerDidShutdown(_: HTTPConnectionPool.Manager, unclean: Bool)
22+
}
23+
24+
extension HTTPConnectionPool {
25+
final class Manager {
26+
private typealias Key = ConnectionPool.Key
27+
28+
private var _pools: [Key: HTTPConnectionPool] = [:]
29+
private let lock = Lock()
30+
31+
private let sslContextCache = SSLContextCache()
32+
33+
enum State {
34+
case active
35+
case shuttingDown(unclean: Bool)
36+
case shutDown
37+
}
38+
39+
let eventLoopGroup: EventLoopGroup
40+
let configuration: HTTPClient.Configuration
41+
let connectionIDGenerator = Connection.ID.globalGenerator
42+
let logger: Logger
43+
44+
/// A delegate to inform about the pools managers shutdown
45+
///
46+
/// NOTE: Normally we create retain cycles in SwiftNIO code that we break on shutdown. However we wan't to inform
47+
/// users that they must call `shutdown` on their AsyncHTTPClient. The best way to make them aware is with
48+
/// a `preconditionFailure` in the HTTPClient's `deinit`. If we create a retain cycle here, the
49+
/// `HTTPClient`'s `deinit` can never be reached. Instead the `HTTPClient` would leak.
50+
///
51+
/// The delegate is not thread safe at all. This only works if the HTTPClient sets itself as a delegate in its own
52+
/// init.
53+
weak var delegate: HTTPConnectionPoolManagerDelegate?
54+
55+
private var state: State = .active
56+
57+
init(eventLoopGroup: EventLoopGroup,
58+
configuration: HTTPClient.Configuration,
59+
backgroundActivityLogger logger: Logger) {
60+
self.eventLoopGroup = eventLoopGroup
61+
self.configuration = configuration
62+
self.logger = logger
63+
}
64+
65+
deinit {
66+
guard case .shutDown = self.state else {
67+
preconditionFailure("Manager must be shutdown before deinit")
68+
}
69+
}
70+
71+
func execute(request: HTTP1RequestTask) {
72+
let key = Key(request.request)
73+
74+
let poolResult = self.lock.withLock { () -> Result<HTTPConnectionPool, HTTPClientError> in
75+
guard case .active = self.state else {
76+
return .failure(HTTPClientError.alreadyShutdown)
77+
}
78+
79+
if let pool = self._pools[key] {
80+
return .success(pool)
81+
}
82+
83+
let pool = HTTPConnectionPool(
84+
eventLoopGroup: self.eventLoopGroup,
85+
sslContextCache: self.sslContextCache,
86+
tlsConfiguration: request.request.tlsConfiguration,
87+
clientConfiguration: self.configuration,
88+
key: key,
89+
delegate: self,
90+
idGenerator: self.connectionIDGenerator,
91+
logger: self.logger
92+
)
93+
self._pools[key] = pool
94+
return .success(pool)
95+
}
96+
97+
switch poolResult {
98+
case .success(let pool):
99+
pool.execute(request: request)
100+
case .failure(let error):
101+
request.fail(error)
102+
}
103+
}
104+
105+
func shutdown() {
106+
let pools = self.lock.withLock { () -> [Key: HTTPConnectionPool] in
107+
guard case .active = self.state else {
108+
preconditionFailure("PoolManager already shutdown")
109+
}
110+
111+
// If there aren't any pools, we can mark the pool as shut down right away.
112+
if self._pools.isEmpty {
113+
self.state = .shutDown
114+
} else {
115+
self.state = .shuttingDown(unclean: false)
116+
}
117+
118+
return self._pools
119+
}
120+
121+
// if no pools are returned, the manager is already shutdown completely. Inform the
122+
// delegate. This is a very clean shutdown...
123+
if pools.isEmpty {
124+
self.delegate?.httpConnectionPoolManagerDidShutdown(self, unclean: false)
125+
return
126+
}
127+
128+
pools.values.forEach { pool in
129+
pool.shutdown()
130+
}
131+
}
132+
}
133+
}
134+
135+
extension HTTPConnectionPool.Manager: HTTPConnectionPoolDelegate {
136+
enum CloseAction {
137+
case close(unclean: Bool)
138+
case wait
139+
}
140+
141+
func connectionPoolDidShutdown(_ pool: HTTPConnectionPool, unclean: Bool) {
142+
let closeAction = self.lock.withLock { () -> CloseAction in
143+
guard case .shuttingDown(let soFarUnclean) = self.state else {
144+
preconditionFailure("Why are pools shutting down, if the manager did not give a signal")
145+
}
146+
147+
guard self._pools.removeValue(forKey: pool.key) === pool else {
148+
preconditionFailure("Expected that the pool was ")
149+
}
150+
151+
if self._pools.isEmpty {
152+
self.state = .shutDown
153+
return .close(unclean: soFarUnclean || unclean)
154+
} else {
155+
self.state = .shuttingDown(unclean: soFarUnclean || unclean)
156+
return .wait
157+
}
158+
}
159+
160+
switch closeAction {
161+
case .close(unclean: let unclean):
162+
self.delegate?.httpConnectionPoolManagerDidShutdown(self, unclean: unclean)
163+
case .wait:
164+
break
165+
}
166+
}
167+
}
16168

17169
extension HTTPConnectionPool.Connection.ID {
18170
static var globalGenerator = Generator()

0 commit comments

Comments
 (0)