/src/grpc-swift/Sources/GRPC/GRPCChannel/GRPCChannelBuilder.swift
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright 2020, gRPC Authors All rights reserved. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | import Dispatch |
17 | | import Logging |
18 | | import NIOCore |
19 | | |
20 | | extension ClientConnection { |
21 | | /// Returns an insecure ``ClientConnection`` builder which is *not configured with TLS*. |
22 | 0 | public static func insecure(group: EventLoopGroup) -> ClientConnection.Builder { |
23 | 0 | return Builder(group: group) |
24 | 0 | } |
25 | | |
26 | | /// Returns a ``ClientConnection`` builder configured with a TLS backend appropriate for the |
27 | | /// given `EventLoopGroup`. |
28 | | /// |
29 | | /// gRPC Swift offers two TLS 'backends'. The 'NIOSSL' backend is available on Darwin and Linux |
30 | | /// platforms and delegates to SwiftNIO SSL. On recent Darwin platforms (macOS 10.14+, iOS 12+, |
31 | | /// tvOS 12+, and watchOS 6+) the 'Network.framework' backend is available. The two backends have |
32 | | /// a number of incompatible configuration options and users are responsible for selecting the |
33 | | /// appropriate APIs. The TLS configuration options on the builder document which backends they |
34 | | /// support. |
35 | | /// |
36 | | /// TLS backends must also be used with an appropriate `EventLoopGroup` implementation. The |
37 | | /// 'NIOSSL' backend may be used either a `MultiThreadedEventLoopGroup` or a |
38 | | /// `NIOTSEventLoopGroup`. The 'Network.framework' backend may only be used with a |
39 | | /// `NIOTSEventLoopGroup`. |
40 | | /// |
41 | | /// This function returns a builder using the `NIOSSL` backend if a `MultiThreadedEventLoopGroup` |
42 | | /// is supplied and a 'Network.framework' backend if a `NIOTSEventLoopGroup` is used. |
43 | | public static func usingPlatformAppropriateTLS( |
44 | | for group: EventLoopGroup |
45 | 0 | ) -> ClientConnection.Builder.Secure { |
46 | 0 | let networkPreference = NetworkPreference.userDefined(.matchingEventLoopGroup(group)) |
47 | 0 | return Builder.Secure( |
48 | 0 | group: group, |
49 | 0 | tlsConfiguration: .makeClientDefault(for: networkPreference) |
50 | 0 | ) |
51 | 0 | } |
52 | | |
53 | | /// Returns a ``ClientConnection`` builder configured with the TLS backend appropriate for the |
54 | | /// provided configuration and `EventLoopGroup`. |
55 | | /// |
56 | | /// - Important: The caller is responsible for ensuring the provided `configuration` may be used |
57 | | /// the the `group`. |
58 | | public static func usingTLS( |
59 | | with configuration: GRPCTLSConfiguration, |
60 | | on group: EventLoopGroup |
61 | 0 | ) -> ClientConnection.Builder.Secure { |
62 | 0 | return Builder.Secure(group: group, tlsConfiguration: configuration) |
63 | 0 | } |
64 | | } |
65 | | |
66 | | extension ClientConnection { |
67 | | public class Builder { |
68 | | private var configuration: ClientConnection.Configuration |
69 | 0 | private var maybeTLS: GRPCTLSConfiguration? { return nil } |
70 | | |
71 | 0 | private var connectionBackoff = ConnectionBackoff() |
72 | 0 | private var connectionBackoffIsEnabled = true |
73 | | |
74 | 0 | fileprivate init(group: EventLoopGroup) { |
75 | 0 | // This is okay: the configuration is only consumed on a call to `connect` which sets the host |
76 | 0 | // and port. |
77 | 0 | self.configuration = .default(target: .hostAndPort("", .max), eventLoopGroup: group) |
78 | 0 | } |
79 | | |
80 | 0 | public func connect(host: String, port: Int) -> ClientConnection { |
81 | 0 | // Finish setting up the configuration. |
82 | 0 | self.configuration.target = .hostAndPort(host, port) |
83 | 0 | self.configuration.connectionBackoff = |
84 | 0 | self.connectionBackoffIsEnabled ? self.connectionBackoff : nil |
85 | 0 | self.configuration.tlsConfiguration = self.maybeTLS |
86 | 0 | return ClientConnection(configuration: self.configuration) |
87 | 0 | } |
88 | | |
89 | 0 | public func withConnectedSocket(_ socket: NIOBSDSocket.Handle) -> ClientConnection { |
90 | 0 | precondition( |
91 | 0 | !PlatformSupport.isTransportServicesEventLoopGroup(self.configuration.eventLoopGroup), |
92 | 0 | "'\(#function)' requires 'group' to not be a 'NIOTransportServices.NIOTSEventLoopGroup' or 'NIOTransportServices.QoSEventLoop' (but was '\(type(of: self.configuration.eventLoopGroup))'" |
93 | 0 | ) |
94 | 0 | self.configuration.target = .connectedSocket(socket) |
95 | 0 | self.configuration.connectionBackoff = |
96 | 0 | self.connectionBackoffIsEnabled ? self.connectionBackoff : nil |
97 | 0 | self.configuration.tlsConfiguration = self.maybeTLS |
98 | 0 | return ClientConnection(configuration: self.configuration) |
99 | 0 | } |
100 | | } |
101 | | } |
102 | | |
103 | | extension ClientConnection.Builder { |
104 | | public class Secure: ClientConnection.Builder { |
105 | | internal var tls: GRPCTLSConfiguration |
106 | 0 | override internal var maybeTLS: GRPCTLSConfiguration? { |
107 | 0 | return self.tls |
108 | 0 | } |
109 | | |
110 | 0 | internal init(group: EventLoopGroup, tlsConfiguration: GRPCTLSConfiguration) { |
111 | 0 | group.preconditionCompatible(with: tlsConfiguration) |
112 | 0 | self.tls = tlsConfiguration |
113 | 0 | super.init(group: group) |
114 | 0 | } |
115 | | |
116 | | /// Connect to `host` on port 443. |
117 | 0 | public func connect(host: String) -> ClientConnection { |
118 | 0 | return self.connect(host: host, port: 443) |
119 | 0 | } |
120 | | } |
121 | | } |
122 | | |
123 | | extension ClientConnection.Builder { |
124 | | /// Sets the initial connection backoff. That is, the initial time to wait before re-attempting to |
125 | | /// establish a connection. Jitter will *not* be applied to the initial backoff. Defaults to |
126 | | /// 1 second if not set. |
127 | | @discardableResult |
128 | 0 | public func withConnectionBackoff(initial amount: TimeAmount) -> Self { |
129 | 0 | self.connectionBackoff.initialBackoff = .seconds(from: amount) |
130 | 0 | return self |
131 | 0 | } |
132 | | |
133 | | /// Set the maximum connection backoff. That is, the maximum amount of time to wait before |
134 | | /// re-attempting to establish a connection. Note that this time amount represents the maximum |
135 | | /// backoff *before* jitter is applied. Defaults to 120 seconds if not set. |
136 | | @discardableResult |
137 | 0 | public func withConnectionBackoff(maximum amount: TimeAmount) -> Self { |
138 | 0 | self.connectionBackoff.maximumBackoff = .seconds(from: amount) |
139 | 0 | return self |
140 | 0 | } |
141 | | |
142 | | /// Backoff is 'jittered' to randomise the amount of time to wait before re-attempting to |
143 | | /// establish a connection. The jittered backoff will be no more than `jitter ⨯ unjitteredBackoff` |
144 | | /// from `unjitteredBackoff`. Defaults to 0.2 if not set. |
145 | | /// |
146 | | /// - Precondition: `0 <= jitter <= 1` |
147 | | @discardableResult |
148 | 0 | public func withConnectionBackoff(jitter: Double) -> Self { |
149 | 0 | self.connectionBackoff.jitter = jitter |
150 | 0 | return self |
151 | 0 | } |
152 | | |
153 | | /// The multiplier for scaling the unjittered backoff between attempts to establish a connection. |
154 | | /// Defaults to 1.6 if not set. |
155 | | @discardableResult |
156 | 0 | public func withConnectionBackoff(multiplier: Double) -> Self { |
157 | 0 | self.connectionBackoff.multiplier = multiplier |
158 | 0 | return self |
159 | 0 | } |
160 | | |
161 | | /// The minimum timeout to use when attempting to establishing a connection. The connection |
162 | | /// timeout for each attempt is the larger of the jittered backoff and the minimum connection |
163 | | /// timeout. Defaults to 20 seconds if not set. |
164 | | @discardableResult |
165 | 0 | public func withConnectionTimeout(minimum amount: TimeAmount) -> Self { |
166 | 0 | self.connectionBackoff.minimumConnectionTimeout = .seconds(from: amount) |
167 | 0 | return self |
168 | 0 | } |
169 | | |
170 | | /// Sets the initial and maximum backoff to given amount. Disables jitter and sets the backoff |
171 | | /// multiplier to 1.0. |
172 | | @discardableResult |
173 | 0 | public func withConnectionBackoff(fixed amount: TimeAmount) -> Self { |
174 | 0 | let seconds = Double.seconds(from: amount) |
175 | 0 | self.connectionBackoff.initialBackoff = seconds |
176 | 0 | self.connectionBackoff.maximumBackoff = seconds |
177 | 0 | self.connectionBackoff.multiplier = 1.0 |
178 | 0 | self.connectionBackoff.jitter = 0.0 |
179 | 0 | return self |
180 | 0 | } |
181 | | |
182 | | /// Sets the limit on the number of times to attempt to re-establish a connection. Defaults |
183 | | /// to `.unlimited` if not set. |
184 | | @discardableResult |
185 | 0 | public func withConnectionBackoff(retries: ConnectionBackoff.Retries) -> Self { |
186 | 0 | self.connectionBackoff.retries = retries |
187 | 0 | return self |
188 | 0 | } |
189 | | |
190 | | /// Sets whether the connection should be re-established automatically if it is dropped. Defaults |
191 | | /// to `true` if not set. |
192 | | @discardableResult |
193 | 0 | public func withConnectionReestablishment(enabled: Bool) -> Self { |
194 | 0 | self.connectionBackoffIsEnabled = enabled |
195 | 0 | return self |
196 | 0 | } |
197 | | |
198 | | /// Sets a custom configuration for keepalive |
199 | | /// The defaults for client and server are determined by the gRPC keepalive |
200 | | /// [documentation] (https://github.com/grpc/grpc/blob/master/doc/keepalive.md). |
201 | | @discardableResult |
202 | 0 | public func withKeepalive(_ keepalive: ClientConnectionKeepalive) -> Self { |
203 | 0 | self.configuration.connectionKeepalive = keepalive |
204 | 0 | return self |
205 | 0 | } |
206 | | |
207 | | /// The amount of time to wait before closing the connection. The idle timeout will start only |
208 | | /// if there are no RPCs in progress and will be cancelled as soon as any RPCs start. If a |
209 | | /// connection becomes idle, starting a new RPC will automatically create a new connection. |
210 | | /// Defaults to 30 minutes if not set. |
211 | | @discardableResult |
212 | 0 | public func withConnectionIdleTimeout(_ timeout: TimeAmount) -> Self { |
213 | 0 | self.configuration.connectionIdleTimeout = timeout |
214 | 0 | return self |
215 | 0 | } |
216 | | |
217 | | /// The behavior used to determine when an RPC should start. That is, whether it should wait for |
218 | | /// an active connection or fail quickly if no connection is currently available. Calls will |
219 | | /// use `.waitsForConnectivity` by default. |
220 | | @discardableResult |
221 | 0 | public func withCallStartBehavior(_ behavior: CallStartBehavior) -> Self { |
222 | 0 | self.configuration.callStartBehavior = behavior |
223 | 0 | return self |
224 | 0 | } |
225 | | } |
226 | | |
227 | | extension ClientConnection.Builder { |
228 | | /// Sets the client error delegate. |
229 | | @discardableResult |
230 | 0 | public func withErrorDelegate(_ delegate: ClientErrorDelegate?) -> Self { |
231 | 0 | self.configuration.errorDelegate = delegate |
232 | 0 | return self |
233 | 0 | } |
234 | | } |
235 | | |
236 | | extension ClientConnection.Builder { |
237 | | /// Sets the client connectivity state delegate and the `DispatchQueue` on which the delegate |
238 | | /// should be called. If no `queue` is provided then gRPC will create a `DispatchQueue` on which |
239 | | /// to run the delegate. |
240 | | @discardableResult |
241 | | public func withConnectivityStateDelegate( |
242 | | _ delegate: ConnectivityStateDelegate?, |
243 | | executingOn queue: DispatchQueue? = nil |
244 | 0 | ) -> Self { |
245 | 0 | self.configuration.connectivityStateDelegate = delegate |
246 | 0 | self.configuration.connectivityStateDelegateQueue = queue |
247 | 0 | return self |
248 | 0 | } |
249 | | } |
250 | | |
251 | | // MARK: - Common TLS options |
252 | | |
253 | | extension ClientConnection.Builder.Secure { |
254 | | /// Sets a server hostname override to be used for the TLS Server Name Indication (SNI) extension. |
255 | | /// The hostname from `connect(host:port)` is for TLS SNI if this value is not set and hostname |
256 | | /// verification is enabled. |
257 | | /// |
258 | | /// - Note: May be used with the 'NIOSSL' and 'Network.framework' TLS backend. |
259 | | /// - Note: `serverHostnameOverride` may not be `nil` when using the 'Network.framework' backend. |
260 | | @discardableResult |
261 | 0 | public func withTLS(serverHostnameOverride: String?) -> Self { |
262 | 0 | self.tls.hostnameOverride = serverHostnameOverride |
263 | 0 | return self |
264 | 0 | } |
265 | | } |
266 | | |
267 | | extension ClientConnection.Builder { |
268 | | /// Sets the HTTP/2 flow control target window size. Defaults to 8MB if not explicitly set. |
269 | | /// Values are clamped between 1 and 2^31-1 inclusive. |
270 | | @discardableResult |
271 | 0 | public func withHTTPTargetWindowSize(_ httpTargetWindowSize: Int) -> Self { |
272 | 0 | self.configuration.httpTargetWindowSize = httpTargetWindowSize |
273 | 0 | return self |
274 | 0 | } |
275 | | |
276 | | /// Sets the maximum size of an HTTP/2 frame in bytes which the client is willing to receive from |
277 | | /// the server. Defaults to 16384. Value are clamped between 2^14 and 2^24-1 octets inclusive |
278 | | /// (the minimum and maximum permitted values per RFC 7540 § 4.2). |
279 | | /// |
280 | | /// Raising this value may lower CPU usage for large message at the cost of increasing head of |
281 | | /// line blocking for small messages. |
282 | | @discardableResult |
283 | 0 | public func withHTTPMaxFrameSize(_ httpMaxFrameSize: Int) -> Self { |
284 | 0 | self.configuration.httpMaxFrameSize = httpMaxFrameSize |
285 | 0 | return self |
286 | 0 | } |
287 | | } |
288 | | |
289 | | extension ClientConnection.Builder { |
290 | | /// Sets the maximum message size the client is permitted to receive in bytes. |
291 | | /// |
292 | | /// - Precondition: `limit` must not be negative. |
293 | | @discardableResult |
294 | 0 | public func withMaximumReceiveMessageLength(_ limit: Int) -> Self { |
295 | 0 | self.configuration.maximumReceiveMessageLength = limit |
296 | 0 | return self |
297 | 0 | } |
298 | | } |
299 | | |
300 | | extension ClientConnection.Builder { |
301 | | /// Sets a logger to be used for background activity such as connection state changes. Defaults |
302 | | /// to a no-op logger if not explicitly set. |
303 | | /// |
304 | | /// Note that individual RPCs will use the logger from `CallOptions`, not the logger specified |
305 | | /// here. |
306 | | @discardableResult |
307 | 0 | public func withBackgroundActivityLogger(_ logger: Logger) -> Self { |
308 | 0 | self.configuration.backgroundActivityLogger = logger |
309 | 0 | return self |
310 | 0 | } |
311 | | } |
312 | | |
313 | | extension ClientConnection.Builder { |
314 | | /// A channel initializer which will be run after gRPC has initialized each channel. This may be |
315 | | /// used to add additional handlers to the pipeline and is intended for debugging. |
316 | | /// |
317 | | /// - Warning: The initializer closure may be invoked *multiple times*. |
318 | | @discardableResult |
319 | | @preconcurrency |
320 | | public func withDebugChannelInitializer( |
321 | | _ debugChannelInitializer: @Sendable @escaping (Channel) -> EventLoopFuture<Void> |
322 | 0 | ) -> Self { |
323 | 0 | self.configuration.debugChannelInitializer = debugChannelInitializer |
324 | 0 | return self |
325 | 0 | } |
326 | | } |
327 | | |
328 | | extension Double { |
329 | 0 | fileprivate static func seconds(from amount: TimeAmount) -> Double { |
330 | 0 | return Double(amount.nanoseconds) / 1_000_000_000 |
331 | 0 | } |
332 | | } |