瀏覽代碼

Make 'NIOSSLContext' ahead of time (#1184)

Motivation:

A `NIOSSLContext` may be used for multiple connections. Moreover,
creating `NIOSSLContext`s is _expensive_ and we should avoid creating
them unnecessarily. At the moment we create one context per connection
for the client and the server.

Modifications:

- Make `NIOSSLContext`s when setting up the client (rather than when
  initializing a channel).
- Make `NIOSSLContext`s when preparing a server bootstrap

Result:

- We only create `NIOSSLContext` once per server/client connection
George Barnett 4 年之前
父節點
當前提交
17ae7a062f
共有 3 個文件被更改,包括 30 次插入23 次删除
  1. 4 4
      Sources/GRPC/ClientConnection.swift
  2. 15 6
      Sources/GRPC/ConnectionManagerChannelProvider.swift
  3. 11 13
      Sources/GRPC/Server.swift

+ 4 - 4
Sources/GRPC/ClientConnection.swift

@@ -421,7 +421,7 @@ extension ChannelPipeline.SynchronousOperations {
   internal func configureGRPCClient(
     channel: Channel,
     httpTargetWindowSize: Int,
-    tlsConfiguration: TLSConfiguration?,
+    sslContext: Result<NIOSSLContext, Error>?,
     tlsServerHostname: String?,
     connectionManager: ConnectionManager,
     connectionKeepalive: ClientConnectionKeepalive,
@@ -439,17 +439,17 @@ extension ChannelPipeline.SynchronousOperations {
     }
     #endif
 
-    if let tlsConfiguration = tlsConfiguration {
+    if let sslContext = try sslContext?.get() {
       let sslClientHandler: NIOSSLClientHandler
       if let customVerificationCallback = customVerificationCallback {
         sslClientHandler = try NIOSSLClientHandler(
-          context: try NIOSSLContext(configuration: tlsConfiguration),
+          context: sslContext,
           serverHostname: tlsServerHostname,
           customVerificationCallback: customVerificationCallback
         )
       } else {
         sslClientHandler = try NIOSSLClientHandler(
-          context: try NIOSSLContext(configuration: tlsConfiguration),
+          context: sslContext,
           serverHostname: tlsServerHostname
         )
       }

+ 15 - 6
Sources/GRPC/ConnectionManagerChannelProvider.swift

@@ -38,7 +38,7 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
   internal var connectionKeepalive: ClientConnectionKeepalive
   internal var connectionIdleTimeout: TimeAmount
 
-  internal var tlsConfiguration: Optional<TLSConfiguration>
+  internal var sslContext: Result<NIOSSLContext, Error>?
   internal var tlsHostnameOverride: Optional<String>
   internal var tlsCustomVerificationCallback: Optional<NIOSSLCustomVerificationCallback>
 
@@ -51,7 +51,7 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
     connectionTarget: ConnectionTarget,
     connectionKeepalive: ClientConnectionKeepalive,
     connectionIdleTimeout: TimeAmount,
-    tlsConfiguration: TLSConfiguration?,
+    sslContext: Result<NIOSSLContext, Error>?,
     tlsHostnameOverride: String?,
     tlsCustomVerificationCallback: NIOSSLCustomVerificationCallback?,
     httpTargetWindowSize: Int,
@@ -62,7 +62,7 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
     self.connectionKeepalive = connectionKeepalive
     self.connectionIdleTimeout = connectionIdleTimeout
 
-    self.tlsConfiguration = tlsConfiguration
+    self.sslContext = sslContext
     self.tlsHostnameOverride = tlsHostnameOverride
     self.tlsCustomVerificationCallback = tlsCustomVerificationCallback
 
@@ -73,11 +73,20 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
   }
 
   internal init(configuration: ClientConnection.Configuration) {
+    // Making a `NIOSSLContext` is expensive and we should only do it (at most) once per TLS
+    // configuration. We do it now and surface any error during channel creation (we're limited by
+    // our API in when we can throw any error).
+    let sslContext: Result<NIOSSLContext, Error>? = configuration.tls.map { tls in
+      return Result {
+        try NIOSSLContext(configuration: tls.configuration)
+      }
+    }
+
     self.init(
       connectionTarget: configuration.target,
       connectionKeepalive: configuration.connectionKeepalive,
       connectionIdleTimeout: configuration.connectionIdleTimeout,
-      tlsConfiguration: configuration.tls?.configuration,
+      sslContext: sslContext,
       tlsHostnameOverride: configuration.tls?.hostnameOverride,
       tlsCustomVerificationCallback: configuration.tls?.customVerificationCallback,
       httpTargetWindowSize: configuration.httpTargetWindowSize,
@@ -92,7 +101,7 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
   }
 
   private var hasTLS: Bool {
-    return self.tlsConfiguration != nil
+    return self.sslContext != nil
   }
 
   private func requiresZeroLengthWorkaround(eventLoop: EventLoop) -> Bool {
@@ -118,7 +127,7 @@ internal struct DefaultChannelProvider: ConnectionManagerChannelProvider {
           try sync.configureGRPCClient(
             channel: channel,
             httpTargetWindowSize: self.httpTargetWindowSize,
-            tlsConfiguration: self.tlsConfiguration,
+            sslContext: self.sslContext,
             tlsServerHostname: hostname,
             connectionManager: connectionManager,
             connectionKeepalive: self.connectionKeepalive,

+ 11 - 13
Sources/GRPC/Server.swift

@@ -88,6 +88,15 @@ public final class Server {
       _ = bootstrap.serverChannelOption(ChannelOptions.backlog, value: 256)
     }
 
+    // Making a `NIOSSLContext` is expensive, we should only do it once per TLS configuration so
+    // we'll do it now, before accepting connections. Unfortunately our API isn't throwing so we'll
+    // only surface any error when initializing a child channel.
+    let sslContext: Result<NIOSSLContext, Error>? = configuration.tls.map { tls in
+      return Result {
+        try NIOSSLContext(configuration: tls.configuration)
+      }
+    }
+
     return bootstrap
       // Enable `SO_REUSEADDR` to avoid "address already in use" error.
       .serverChannelOption(
@@ -103,8 +112,8 @@ public final class Server {
 
         do {
           let sync = channel.pipeline.syncOperations
-          if let tls = configuration.tls {
-            try sync.configureTLS(configuration: tls)
+          if let sslContext = try sslContext?.get() {
+            try sync.addHandler(NIOSSLServerHandler(context: sslContext))
           }
 
           // Configures the pipeline based on whether the connection uses TLS or not.
@@ -334,17 +343,6 @@ extension Server {
   }
 }
 
-extension ChannelPipeline.SynchronousOperations {
-  /// Configure an SSL handler on the channel.
-  ///
-  /// - Parameters:
-  ///   - configuration: The configuration to use when creating the handler.
-  fileprivate func configureTLS(configuration: Server.Configuration.TLS) throws {
-    let context = try NIOSSLContext(configuration: configuration.configuration)
-    try self.addHandler(NIOSSLServerHandler(context: context))
-  }
-}
-
 private extension ServerBootstrapProtocol {
   func bind(to target: BindTarget) -> EventLoopFuture<Channel> {
     switch target.wrapped {