Skip to content

Commit 011d744

Browse files
committed
Merge branch 'main' into update_expires_date_parsing
2 parents 221a77b + 5f0a456 commit 011d744

14 files changed

+878
-1486
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2019-2020 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import Logging
17+
import NIO
18+
import NIOConcurrencyHelpers
19+
import NIOHTTP1
20+
import NIOHTTPCompression
21+
import NIOTLS
22+
import NIOTransportServices
23+
24+
/// A `Connection` represents a `Channel` in the context of the connection pool
25+
///
26+
/// In the `ConnectionPool`, each `Channel` belongs to a given `HTTP1ConnectionProvider`
27+
/// and has a certain "lease state" (see the `inUse` property).
28+
/// The role of `Connection` is to model this by storing a `Channel` alongside its associated properties
29+
/// so that they can be passed around together and correct provider can be identified when connection is released.
30+
class Connection {
31+
/// The provider this `Connection` belongs to.
32+
///
33+
/// This enables calling methods like `release()` directly on a `Connection` instead of
34+
/// calling `provider.release(connection)`. This gives a more object oriented feel to the API
35+
/// and can avoid having to keep explicit references to the pool at call site.
36+
private let provider: HTTP1ConnectionProvider
37+
38+
/// The `Channel` of this `Connection`
39+
///
40+
/// - Warning: Requests that lease connections from the `ConnectionPool` are responsible
41+
/// for removing the specific handlers they added to the `Channel` pipeline before releasing it to the pool.
42+
let channel: Channel
43+
44+
init(channel: Channel, provider: HTTP1ConnectionProvider) {
45+
self.channel = channel
46+
self.provider = provider
47+
}
48+
}
49+
50+
extension Connection {
51+
/// Release this `Connection` to its associated `HTTP1ConnectionProvider`.
52+
///
53+
/// - Warning: This only releases the connection and doesn't take care of cleaning handlers in the `Channel` pipeline.
54+
func release(closing: Bool, logger: Logger) {
55+
self.channel.eventLoop.assertInEventLoop()
56+
self.provider.release(connection: self, closing: closing, logger: logger)
57+
}
58+
59+
/// Called when channel exceeds idle time in pool.
60+
func timeout(logger: Logger) {
61+
self.channel.eventLoop.assertInEventLoop()
62+
self.provider.timeout(connection: self, logger: logger)
63+
}
64+
65+
/// Called when channel goes inactive while in the pool.
66+
func remoteClosed(logger: Logger) {
67+
self.channel.eventLoop.assertInEventLoop()
68+
self.provider.remoteClosed(connection: self, logger: logger)
69+
}
70+
71+
/// Called from `HTTP1ConnectionProvider.close` when client is shutting down.
72+
func close() -> EventLoopFuture<Void> {
73+
return self.channel.close()
74+
}
75+
}
76+
77+
/// Methods of Connection which are used in ConnectionsState extracted as protocol
78+
/// to facilitate test of ConnectionsState.
79+
protocol PoolManageableConnection: AnyObject {
80+
func cancel() -> EventLoopFuture<Void>
81+
var eventLoop: EventLoop { get }
82+
var isActiveEstimation: Bool { get }
83+
}
84+
85+
/// Implementation of methods used by ConnectionsState and its tests to manage Connection
86+
extension Connection: PoolManageableConnection {
87+
/// Convenience property indicating whether the underlying `Channel` is active or not.
88+
var isActiveEstimation: Bool {
89+
return self.channel.isActive
90+
}
91+
92+
var eventLoop: EventLoop {
93+
return self.channel.eventLoop
94+
}
95+
96+
func cancel() -> EventLoopFuture<Void> {
97+
return self.channel.triggerUserOutboundEvent(TaskCancelEvent())
98+
}
99+
}
100+
101+
extension Connection {
102+
/// Sets idle timeout handler and channel inactivity listener.
103+
func setIdleTimeout(timeout: TimeAmount?, logger: Logger) {
104+
_ = self.channel.pipeline.addHandler(IdleStateHandler(writeTimeout: timeout), position: .first).flatMap { _ in
105+
self.channel.pipeline.addHandler(IdlePoolConnectionHandler(connection: self, logger: logger))
106+
}
107+
}
108+
109+
/// Removes idle timeout handler and channel inactivity listener
110+
func cancelIdleTimeout() -> EventLoopFuture<Void> {
111+
return self.removeHandler(IdleStateHandler.self).flatMap { _ in
112+
self.removeHandler(IdlePoolConnectionHandler.self)
113+
}
114+
}
115+
}
116+
117+
class IdlePoolConnectionHandler: ChannelInboundHandler, RemovableChannelHandler {
118+
typealias InboundIn = NIOAny
119+
120+
let connection: Connection
121+
var eventSent: Bool
122+
let logger: Logger
123+
124+
init(connection: Connection, logger: Logger) {
125+
self.connection = connection
126+
self.eventSent = false
127+
self.logger = logger
128+
}
129+
130+
// this is needed to detect when remote end closes connection while connection is in the pool idling
131+
func channelInactive(context: ChannelHandlerContext) {
132+
if !self.eventSent {
133+
self.eventSent = true
134+
self.connection.remoteClosed(logger: self.logger)
135+
}
136+
}
137+
138+
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
139+
if let idleEvent = event as? IdleStateHandler.IdleStateEvent, idleEvent == .write {
140+
if !self.eventSent {
141+
self.eventSent = true
142+
self.connection.timeout(logger: self.logger)
143+
}
144+
} else {
145+
context.fireUserInboundEventTriggered(event)
146+
}
147+
}
148+
}
149+
150+
extension Connection: CustomStringConvertible {
151+
var description: String {
152+
return "\(self.channel)"
153+
}
154+
}
155+
156+
struct ConnectionKey<ConnectionType>: Hashable where ConnectionType: PoolManageableConnection {
157+
let connection: ConnectionType
158+
159+
init(_ connection: ConnectionType) {
160+
self.connection = connection
161+
}
162+
163+
static func == (lhs: ConnectionKey<ConnectionType>, rhs: ConnectionKey<ConnectionType>) -> Bool {
164+
return ObjectIdentifier(lhs.connection) == ObjectIdentifier(rhs.connection)
165+
}
166+
167+
func hash(into hasher: inout Hasher) {
168+
hasher.combine(ObjectIdentifier(self.connection))
169+
}
170+
171+
func cancel() -> EventLoopFuture<Void> {
172+
return self.connection.cancel()
173+
}
174+
}

0 commit comments

Comments
 (0)