| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- /*
- * Copyright 2020, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import Dispatch
- import Logging
- import NIOCore
- import NIOSSL
- #if canImport(Security)
- import Security
- #endif
- extension ClientConnection {
- /// Returns an insecure `ClientConnection` builder which is *not configured with TLS*.
- public static func insecure(group: EventLoopGroup) -> ClientConnection.Builder {
- return Builder(group: group)
- }
- /// Returns a `ClientConnection` builder configured with TLS.
- @available(
- *, deprecated,
- message: "Use one of 'usingPlatformAppropriateTLS(for:)', 'usingTLSBackedByNIOSSL(on:)' or 'usingTLSBackedByNetworkFramework(on:)' or 'usingTLS(on:with:)'"
- )
- public static func secure(group: EventLoopGroup) -> ClientConnection.Builder.Secure {
- return ClientConnection.usingTLSBackedByNIOSSL(on: group)
- }
- /// Returns a `ClientConnection` builder configured with a TLS backend appropriate for the
- /// given `EventLoopGroup`.
- ///
- /// gRPC Swift offers two TLS 'backends'. The 'NIOSSL' backend is available on Darwin and Linux
- /// platforms and delegates to SwiftNIO SSL. On recent Darwin platforms (macOS 10.14+, iOS 12+,
- /// tvOS 12+, and watchOS 6+) the 'Network.framework' backend is available. The two backends have
- /// a number of incompatible configuration options and users are responsible for selecting the
- /// appropriate APIs. The TLS configuration options on the builder document which backends they
- /// support.
- ///
- /// TLS backends must also be used with an appropriate `EventLoopGroup` implementation. The
- /// 'NIOSSL' backend may be used either a `MultiThreadedEventLoopGroup` or a
- /// `NIOTSEventLoopGroup`. The 'Network.framework' backend may only be used with a
- /// `NIOTSEventLoopGroup`.
- ///
- /// This function returns a builder using the `NIOSSL` backend if a `MultiThreadedEventLoopGroup`
- /// is supplied and a 'Network.framework' backend if a `NIOTSEventLoopGroup` is used.
- public static func usingPlatformAppropriateTLS(
- for group: EventLoopGroup
- ) -> ClientConnection.Builder.Secure {
- let networkPreference = NetworkPreference.userDefined(.matchingEventLoopGroup(group))
- return Builder.Secure(
- group: group,
- tlsConfiguration: .makeClientDefault(for: networkPreference)
- )
- }
- /// Returns a `ClientConnection` builder configured with the 'NIOSSL' TLS backend.
- ///
- /// This builder may use either a `MultiThreadedEventLoopGroup` or a `NIOTSEventLoopGroup` (or an
- /// `EventLoop` from either group).
- ///
- /// - Parameter group: The `EventLoopGroup` use for the connection.
- /// - Returns: A builder for a connection using the NIOSSL TLS backend.
- public static func usingTLSBackedByNIOSSL(
- on group: EventLoopGroup
- ) -> ClientConnection.Builder.Secure {
- return Builder.Secure(group: group, tlsConfiguration: .makeClientConfigurationBackedByNIOSSL())
- }
- #if canImport(Network)
- /// Returns a `ClientConnection` builder configured with the Network.framework TLS backend.
- ///
- /// This builder must use a `NIOTSEventLoopGroup` (or an `EventLoop` from a
- /// `NIOTSEventLoopGroup`).
- ///
- /// - Parameter group: The `EventLoopGroup` use for the connection.
- /// - Returns: A builder for a connection using the Network.framework TLS backend.
- @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
- public static func usingTLSBackedByNetworkFramework(
- on group: EventLoopGroup
- ) -> ClientConnection.Builder.Secure {
- precondition(
- PlatformSupport.isTransportServicesEventLoopGroup(group),
- "'\(#function)' requires 'group' to be a 'NIOTransportServices.NIOTSEventLoopGroup' or 'NIOTransportServices.QoSEventLoop' (but was '\(type(of: group))'"
- )
- return Builder.Secure(
- group: group,
- tlsConfiguration: .makeClientConfigurationBackedByNetworkFramework()
- )
- }
- #endif
- /// Returns a `ClientConnection` builder configured with the TLS backend appropriate for the
- /// provided configuration and `EventLoopGroup`.
- ///
- /// - Important: The caller is responsible for ensuring the provided `configuration` may be used
- /// the the `group`.
- public static func usingTLS(
- with configuration: GRPCTLSConfiguration,
- on group: EventLoopGroup
- ) -> ClientConnection.Builder.Secure {
- return Builder.Secure(group: group, tlsConfiguration: configuration)
- }
- }
- extension ClientConnection {
- public class Builder {
- private var configuration: ClientConnection.Configuration
- private var maybeTLS: GRPCTLSConfiguration? { return nil }
- private var connectionBackoff = ConnectionBackoff()
- private var connectionBackoffIsEnabled = true
- fileprivate init(group: EventLoopGroup) {
- // This is okay: the configuration is only consumed on a call to `connect` which sets the host
- // and port.
- self.configuration = .default(target: .hostAndPort("", .max), eventLoopGroup: group)
- }
- public func connect(host: String, port: Int) -> ClientConnection {
- // Finish setting up the configuration.
- self.configuration.target = .hostAndPort(host, port)
- self.configuration.connectionBackoff =
- self.connectionBackoffIsEnabled ? self.connectionBackoff : nil
- self.configuration.tlsConfiguration = self.maybeTLS
- return ClientConnection(configuration: self.configuration)
- }
- }
- }
- extension ClientConnection.Builder {
- public class Secure: ClientConnection.Builder {
- internal var tls: GRPCTLSConfiguration
- override internal var maybeTLS: GRPCTLSConfiguration? {
- return self.tls
- }
- internal init(group: EventLoopGroup, tlsConfiguration: GRPCTLSConfiguration) {
- group.preconditionCompatible(with: tlsConfiguration)
- self.tls = tlsConfiguration
- super.init(group: group)
- }
- /// Connect to `host` on port 443.
- public func connect(host: String) -> ClientConnection {
- return self.connect(host: host, port: 443)
- }
- }
- }
- extension ClientConnection.Builder {
- /// Sets the initial connection backoff. That is, the initial time to wait before re-attempting to
- /// establish a connection. Jitter will *not* be applied to the initial backoff. Defaults to
- /// 1 second if not set.
- @discardableResult
- public func withConnectionBackoff(initial amount: TimeAmount) -> Self {
- self.connectionBackoff.initialBackoff = .seconds(from: amount)
- return self
- }
- /// Set the maximum connection backoff. That is, the maximum amount of time to wait before
- /// re-attempting to establish a connection. Note that this time amount represents the maximum
- /// backoff *before* jitter is applied. Defaults to 120 seconds if not set.
- @discardableResult
- public func withConnectionBackoff(maximum amount: TimeAmount) -> Self {
- self.connectionBackoff.maximumBackoff = .seconds(from: amount)
- return self
- }
- /// Backoff is 'jittered' to randomise the amount of time to wait before re-attempting to
- /// establish a connection. The jittered backoff will be no more than `jitter ⨯ unjitteredBackoff`
- /// from `unjitteredBackoff`. Defaults to 0.2 if not set.
- ///
- /// - Precondition: `0 <= jitter <= 1`
- @discardableResult
- public func withConnectionBackoff(jitter: Double) -> Self {
- self.connectionBackoff.jitter = jitter
- return self
- }
- /// The multiplier for scaling the unjittered backoff between attempts to establish a connection.
- /// Defaults to 1.6 if not set.
- @discardableResult
- public func withConnectionBackoff(multiplier: Double) -> Self {
- self.connectionBackoff.multiplier = multiplier
- return self
- }
- /// The minimum timeout to use when attempting to establishing a connection. The connection
- /// timeout for each attempt is the larger of the jittered backoff and the minimum connection
- /// timeout. Defaults to 20 seconds if not set.
- @discardableResult
- public func withConnectionTimeout(minimum amount: TimeAmount) -> Self {
- self.connectionBackoff.minimumConnectionTimeout = .seconds(from: amount)
- return self
- }
- /// Sets the initial and maximum backoff to given amount. Disables jitter and sets the backoff
- /// multiplier to 1.0.
- @discardableResult
- public func withConnectionBackoff(fixed amount: TimeAmount) -> Self {
- let seconds = Double.seconds(from: amount)
- self.connectionBackoff.initialBackoff = seconds
- self.connectionBackoff.maximumBackoff = seconds
- self.connectionBackoff.multiplier = 1.0
- self.connectionBackoff.jitter = 0.0
- return self
- }
- /// Sets the limit on the number of times to attempt to re-establish a connection. Defaults
- /// to `.unlimited` if not set.
- @discardableResult
- public func withConnectionBackoff(retries: ConnectionBackoff.Retries) -> Self {
- self.connectionBackoff.retries = retries
- return self
- }
- /// Sets whether the connection should be re-established automatically if it is dropped. Defaults
- /// to `true` if not set.
- @discardableResult
- public func withConnectionReestablishment(enabled: Bool) -> Self {
- self.connectionBackoffIsEnabled = enabled
- return self
- }
- /// Sets a custom configuration for keepalive
- /// The defaults for client and server are determined by the gRPC keepalive
- /// [documentation] (https://github.com/grpc/grpc/blob/master/doc/keepalive.md).
- @discardableResult
- public func withKeepalive(_ keepalive: ClientConnectionKeepalive) -> Self {
- self.configuration.connectionKeepalive = keepalive
- return self
- }
- /// The amount of time to wait before closing the connection. The idle timeout will start only
- /// if there are no RPCs in progress and will be cancelled as soon as any RPCs start. If a
- /// connection becomes idle, starting a new RPC will automatically create a new connection.
- /// Defaults to 30 minutes if not set.
- @discardableResult
- public func withConnectionIdleTimeout(_ timeout: TimeAmount) -> Self {
- self.configuration.connectionIdleTimeout = timeout
- return self
- }
- /// The behavior used to determine when an RPC should start. That is, whether it should wait for
- /// an active connection or fail quickly if no connection is currently available. Calls will
- /// use `.waitsForConnectivity` by default.
- @discardableResult
- public func withCallStartBehavior(_ behavior: CallStartBehavior) -> Self {
- self.configuration.callStartBehavior = behavior
- return self
- }
- }
- extension ClientConnection.Builder {
- /// Sets the client error delegate.
- @discardableResult
- public func withErrorDelegate(_ delegate: ClientErrorDelegate?) -> Self {
- self.configuration.errorDelegate = delegate
- return self
- }
- }
- extension ClientConnection.Builder {
- /// Sets the client connectivity state delegate and the `DispatchQueue` on which the delegate
- /// should be called. If no `queue` is provided then gRPC will create a `DispatchQueue` on which
- /// to run the delegate.
- @discardableResult
- public func withConnectivityStateDelegate(
- _ delegate: ConnectivityStateDelegate?,
- executingOn queue: DispatchQueue? = nil
- ) -> Self {
- self.configuration.connectivityStateDelegate = delegate
- self.configuration.connectivityStateDelegateQueue = queue
- return self
- }
- }
- // MARK: - Common TLS options
- extension ClientConnection.Builder.Secure {
- /// Sets a server hostname override to be used for the TLS Server Name Indication (SNI) extension.
- /// The hostname from `connect(host:port)` is for TLS SNI if this value is not set and hostname
- /// verification is enabled.
- ///
- /// - Note: May be used with the 'NIOSSL' and 'Network.framework' TLS backend.
- /// - Note: `serverHostnameOverride` may not be `nil` when using the 'Network.framework' backend.
- @discardableResult
- public func withTLS(serverHostnameOverride: String?) -> Self {
- self.tls.hostnameOverride = serverHostnameOverride
- return self
- }
- }
- // MARK: - NIOSSL TLS backend options
- extension ClientConnection.Builder.Secure {
- /// Sets the sources of certificates to offer during negotiation. No certificates are offered
- /// during negotiation by default.
- ///
- /// - Note: May only be used with the 'NIOSSL' TLS backend.
- @discardableResult
- public func withTLS(certificateChain: [NIOSSLCertificate]) -> Self {
- self.tls.updateNIOCertificateChain(to: certificateChain)
- return self
- }
- /// Sets the private key associated with the leaf certificate.
- ///
- /// - Note: May only be used with the 'NIOSSL' TLS backend.
- @discardableResult
- public func withTLS(privateKey: NIOSSLPrivateKey) -> Self {
- self.tls.updateNIOPrivateKey(to: privateKey)
- return self
- }
- /// Sets the trust roots to use to validate certificates. This only needs to be provided if you
- /// intend to validate certificates. Defaults to the system provided trust store (`.default`) if
- /// not set.
- ///
- /// - Note: May only be used with the 'NIOSSL' TLS backend.
- @discardableResult
- public func withTLS(trustRoots: NIOSSLTrustRoots) -> Self {
- self.tls.updateNIOTrustRoots(to: trustRoots)
- return self
- }
- /// Whether to verify remote certificates. Defaults to `.fullVerification` if not otherwise
- /// configured.
- ///
- /// - Note: May only be used with the 'NIOSSL' TLS backend.
- @discardableResult
- public func withTLS(certificateVerification: CertificateVerification) -> Self {
- self.tls.updateNIOCertificateVerification(to: certificateVerification)
- return self
- }
- /// A custom verification callback that allows completely overriding the certificate verification logic.
- ///
- /// - Note: May only be used with the 'NIOSSL' TLS backend.
- @discardableResult
- public func withTLSCustomVerificationCallback(
- _ callback: @escaping NIOSSLCustomVerificationCallback
- ) -> Self {
- self.tls.updateNIOCustomVerificationCallback(to: callback)
- return self
- }
- }
- // MARK: - Network.framework TLS backend options
- #if canImport(Network)
- extension ClientConnection.Builder.Secure {
- /// Update the local identity.
- ///
- /// - Note: May only be used with the 'Network.framework' TLS backend.
- @discardableResult
- @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
- public func withTLS(localIdentity: SecIdentity) -> Self {
- self.tls.updateNetworkLocalIdentity(to: localIdentity)
- return self
- }
- /// Update the callback used to verify a trust object during a TLS handshake.
- ///
- /// - Note: May only be used with the 'Network.framework' TLS backend.
- @discardableResult
- @available(macOS 10.14, iOS 12.0, watchOS 6.0, tvOS 12.0, *)
- public func withTLSHandshakeVerificationCallback(
- on queue: DispatchQueue,
- verificationCallback callback: @escaping sec_protocol_verify_t
- ) -> Self {
- self.tls.updateNetworkVerifyCallbackWithQueue(callback: callback, queue: queue)
- return self
- }
- }
- #endif
- extension ClientConnection.Builder {
- /// Sets the HTTP/2 flow control target window size. Defaults to 8MB if not explicitly set.
- /// Values are clamped between 1 and 2^31-1 inclusive.
- @discardableResult
- public func withHTTPTargetWindowSize(_ httpTargetWindowSize: Int) -> Self {
- self.configuration.httpTargetWindowSize = httpTargetWindowSize
- return self
- }
- /// Sets the maximum size of an HTTP/2 frame in bytes which the client is willing to receive from
- /// the server. Defaults to 16384. Value are clamped between 2^14 and 2^24-1 octets inclusive
- /// (the minimum and maximum permitted values per RFC 7540 § 4.2).
- ///
- /// Raising this value may lower CPU usage for large message at the cost of increasing head of
- /// line blocking for small messages.
- @discardableResult
- public func withHTTPMaxFrameSize(_ httpMaxFrameSize: Int) -> Self {
- self.configuration.httpMaxFrameSize = httpMaxFrameSize
- return self
- }
- }
- extension ClientConnection.Builder {
- /// Sets the maximum message size the client is permitted to receive in bytes.
- ///
- /// - Precondition: `limit` must not be negative.
- @discardableResult
- public func withMaximumReceiveMessageLength(_ limit: Int) -> Self {
- self.configuration.maximumReceiveMessageLength = limit
- return self
- }
- }
- extension ClientConnection.Builder {
- /// Sets a logger to be used for background activity such as connection state changes. Defaults
- /// to a no-op logger if not explicitly set.
- ///
- /// Note that individual RPCs will use the logger from `CallOptions`, not the logger specified
- /// here.
- @discardableResult
- public func withBackgroundActivityLogger(_ logger: Logger) -> Self {
- self.configuration.backgroundActivityLogger = logger
- return self
- }
- }
- extension ClientConnection.Builder {
- /// A channel initializer which will be run after gRPC has initialized each channel. This may be
- /// used to add additional handlers to the pipeline and is intended for debugging.
- ///
- /// - Warning: The initializer closure may be invoked *multiple times*.
- @discardableResult
- public func withDebugChannelInitializer(
- _ debugChannelInitializer: @escaping (Channel) -> EventLoopFuture<Void>
- ) -> Self {
- self.configuration.debugChannelInitializer = debugChannelInitializer
- return self
- }
- }
- private extension Double {
- static func seconds(from amount: TimeAmount) -> Double {
- return Double(amount.nanoseconds) / 1_000_000_000
- }
- }
|