Browse Source

Rename GRPCClient to GRPCClientConnection (#418)

George Barnett 6 years ago
parent
commit
4b8295018f

+ 10 - 10
Sources/Examples/EchoNIO/Generated/echo.grpc.swift

@@ -35,19 +35,19 @@ internal protocol Echo_EchoService_NIO {
   func update(callOptions: CallOptions?, handler: @escaping (Echo_EchoResponse) -> Void) -> BidirectionalStreamingClientCall<Echo_EchoRequest, Echo_EchoResponse>
 }
 
-internal final class Echo_EchoService_NIOClient: GRPCServiceClient, Echo_EchoService_NIO {
-  internal let client: GRPCClient
+internal final class Echo_EchoService_NIOClient: GRPCClient, Echo_EchoService_NIO {
+  internal let connection: GRPCClientConnection
   internal let service = "echo.Echo"
   internal var defaultCallOptions: CallOptions
 
   /// Creates a client for the echo.Echo service.
   ///
   /// - Parameters:
-  ///   - client: `GRPCClient` with a connection to the service host.
+  ///   - connection: `GRPCClientConnection` to the service host.
   ///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them. Defaults to `client.defaultCallOptions`.
-  internal init(client: GRPCClient, defaultCallOptions: CallOptions? = nil) {
-    self.client = client
-    self.defaultCallOptions = defaultCallOptions ?? client.defaultCallOptions
+  internal init(connection: GRPCClientConnection, defaultCallOptions: CallOptions? = nil) {
+    self.connection = connection
+    self.defaultCallOptions = defaultCallOptions ?? connection.defaultCallOptions
   }
 
   /// Asynchronous unary call to Get.
@@ -57,7 +57,7 @@ internal final class Echo_EchoService_NIOClient: GRPCServiceClient, Echo_EchoSer
   ///   - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
   /// - Returns: A `UnaryClientCall` with futures for the metadata, status and response.
   internal func get(_ request: Echo_EchoRequest, callOptions: CallOptions? = nil) -> UnaryClientCall<Echo_EchoRequest, Echo_EchoResponse> {
-    return UnaryClientCall(client: client, path: path(forMethod: "Get"), request: request, callOptions: callOptions ?? self.defaultCallOptions)
+    return UnaryClientCall(connection: self.connection, path: self.path(forMethod: "Get"), request: request, callOptions: callOptions ?? self.defaultCallOptions)
   }
 
   /// Asynchronous server-streaming call to Expand.
@@ -68,7 +68,7 @@ internal final class Echo_EchoService_NIOClient: GRPCServiceClient, Echo_EchoSer
   ///   - handler: A closure called when each response is received from the server.
   /// - Returns: A `ServerStreamingClientCall` with futures for the metadata and status.
   internal func expand(_ request: Echo_EchoRequest, callOptions: CallOptions? = nil, handler: @escaping (Echo_EchoResponse) -> Void) -> ServerStreamingClientCall<Echo_EchoRequest, Echo_EchoResponse> {
-    return ServerStreamingClientCall(client: client, path: path(forMethod: "Expand"), request: request, callOptions: callOptions ?? self.defaultCallOptions, handler: handler)
+    return ServerStreamingClientCall(connection: self.connection, path: self.path(forMethod: "Expand"), request: request, callOptions: callOptions ?? self.defaultCallOptions, handler: handler)
   }
 
   /// Asynchronous client-streaming call to Collect.
@@ -80,7 +80,7 @@ internal final class Echo_EchoService_NIOClient: GRPCServiceClient, Echo_EchoSer
   ///   - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
   /// - Returns: A `ClientStreamingClientCall` with futures for the metadata, status and response.
   internal func collect(callOptions: CallOptions? = nil) -> ClientStreamingClientCall<Echo_EchoRequest, Echo_EchoResponse> {
-    return ClientStreamingClientCall(client: client, path: path(forMethod: "Collect"), callOptions: callOptions ?? self.defaultCallOptions)
+    return ClientStreamingClientCall(connection: self.connection, path: self.path(forMethod: "Collect"), callOptions: callOptions ?? self.defaultCallOptions)
   }
 
   /// Asynchronous bidirectional-streaming call to Update.
@@ -93,7 +93,7 @@ internal final class Echo_EchoService_NIOClient: GRPCServiceClient, Echo_EchoSer
   ///   - handler: A closure called when each response is received from the server.
   /// - Returns: A `ClientStreamingClientCall` with futures for the metadata and status.
   internal func update(callOptions: CallOptions? = nil, handler: @escaping (Echo_EchoResponse) -> Void) -> BidirectionalStreamingClientCall<Echo_EchoRequest, Echo_EchoResponse> {
-    return BidirectionalStreamingClientCall(client: client, path: path(forMethod: "Update"), callOptions: callOptions ?? self.defaultCallOptions, handler: handler)
+    return BidirectionalStreamingClientCall(connection: self.connection, path: self.path(forMethod: "Update"), callOptions: callOptions ?? self.defaultCallOptions, handler: handler)
   }
 
 }

+ 3 - 3
Sources/Examples/EchoNIO/main.swift

@@ -32,7 +32,7 @@ let messageOption = Option("message",
                            default: "Testing 1 2 3",
                            description: "message to send")
 
-func makeClientTLS(enabled: Bool) throws -> GRPCClient.TLSMode {
+func makeClientTLS(enabled: Bool) throws -> GRPCClientConnection.TLSMode {
   guard enabled else {
     return .none
   }
@@ -73,8 +73,8 @@ func makeServerTLSConfiguration() throws -> TLSConfiguration {
 func makeEchoClient(address: String, port: Int, ssl: Bool) -> Echo_EchoService_NIOClient? {
   let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
   do {
-    return try GRPCClient.start(host: address, port: port, eventLoopGroup: eventLoopGroup, tls: try makeClientTLS(enabled: ssl))
-      .map { client in Echo_EchoService_NIOClient(client: client) }
+    return try GRPCClientConnection.start(host: address, port: port, eventLoopGroup: eventLoopGroup, tls: try makeClientTLS(enabled: ssl))
+      .map { Echo_EchoService_NIOClient(connection: $0) }
       .wait()
   } catch {
     print("Unable to create an EchoClient: \(error)")

+ 16 - 16
Sources/SwiftGRPCNIO/ClientCalls/BaseClientCall.swift

@@ -29,8 +29,8 @@ import SwiftProtobuf
 ///
 /// This class also provides much of the framework user facing functionality via conformance to `ClientCall`.
 open class BaseClientCall<RequestMessage: Message, ResponseMessage: Message> {
-  /// The underlying `GRPCClient` providing the HTTP/2 channel and multiplexer.
-  internal let client: GRPCClient
+  /// The underlying `GRPCClientConnection` providing the HTTP/2 channel and multiplexer.
+  internal let connection: GRPCClientConnection
 
   /// Promise for an HTTP/2 stream to execute the call on.
   internal let streamPromise: EventLoopPromise<Channel>
@@ -48,21 +48,21 @@ open class BaseClientCall<RequestMessage: Message, ResponseMessage: Message> {
   /// - a timeout is scheduled if one is set in the `callOptions`.
   ///
   /// - Parameters:
-  ///   - client: client containing the HTTP/2 channel and multiplexer to use for this call.
+  ///   - connection: connection containing the HTTP/2 channel and multiplexer to use for this call.
   ///   - path: path for this RPC method.
   ///   - callOptions: options to use when configuring this call.
   ///   - responseObserver: observer for received messages.
   init(
-    client: GRPCClient,
+    connection: GRPCClientConnection,
     path: String,
     callOptions: CallOptions,
     responseObserver: ResponseObserver<ResponseMessage>
   ) {
-    self.client = client
-    self.streamPromise = client.channel.eventLoop.makePromise()
+    self.connection = connection
+    self.streamPromise = connection.channel.eventLoop.makePromise()
     self.clientChannelHandler = GRPCClientChannelHandler(
-      initialMetadataPromise: client.channel.eventLoop.makePromise(),
-      statusPromise: client.channel.eventLoop.makePromise(),
+      initialMetadataPromise: connection.channel.eventLoop.makePromise(),
+      statusPromise: connection.channel.eventLoop.makePromise(),
       responseObserver: responseObserver)
 
     self.createStreamChannel()
@@ -90,7 +90,7 @@ extension BaseClientCall: ClientCall {
   }
 
   public func cancel() {
-    self.client.channel.eventLoop.execute {
+    self.connection.channel.eventLoop.execute {
       self.subchannel.whenSuccess { channel in
         channel.close(mode: .all, promise: nil)
       }
@@ -103,9 +103,9 @@ extension BaseClientCall {
   ///
   /// - Important: This should only ever be called once.
   private func createStreamChannel() {
-    self.client.channel.eventLoop.execute {
-      self.client.multiplexer.createStreamChannel(promise: self.streamPromise) { (subchannel, streamID) -> EventLoopFuture<Void> in
-        subchannel.pipeline.addHandlers(HTTP2ToHTTP1ClientCodec(streamID: streamID, httpProtocol: self.client.httpProtocol),
+    self.connection.channel.eventLoop.execute {
+      self.connection.multiplexer.createStreamChannel(promise: self.streamPromise) { (subchannel, streamID) -> EventLoopFuture<Void> in
+        subchannel.pipeline.addHandlers(HTTP2ToHTTP1ClientCodec(streamID: streamID, httpProtocol: self.connection.httpProtocol),
                                         HTTP1ToRawGRPCClientCodec(),
                                         GRPCClientCodec<RequestMessage, ResponseMessage>(),
                                         self.clientChannelHandler)
@@ -133,7 +133,7 @@ extension BaseClientCall {
   /// - Parameter requestHead: The request head to send.
   /// - Returns: A future which will be succeeded once the request head has been sent.
   internal func sendHead(_ requestHead: HTTPRequestHead) -> EventLoopFuture<Void> {
-    let promise = client.channel.eventLoop.makePromise(of: Void.self)
+    let promise = connection.channel.eventLoop.makePromise(of: Void.self)
     self.sendHead(requestHead, promise: promise)
     return promise.futureResult
   }
@@ -155,7 +155,7 @@ extension BaseClientCall {
   /// - Note: This is prefixed to allow for classes conforming to `StreamingRequestClientCall` to use the non-underbarred name.
   /// - Returns: A future which will be fullfilled when the message reaches the network.
   internal func _sendMessage(_ message: RequestMessage) -> EventLoopFuture<Void> {
-    let promise = client.channel.eventLoop.makePromise(of: Void.self)
+    let promise = connection.channel.eventLoop.makePromise(of: Void.self)
     self._sendMessage(message, promise: promise)
     return promise.futureResult
   }
@@ -177,7 +177,7 @@ extension BaseClientCall {
   /// - Important: This should only ever be called once.
   ///- Returns: A future which will be succeeded once the end has been sent.
   internal func _sendEnd() -> EventLoopFuture<Void> {
-    let promise = client.channel.eventLoop.makePromise(of: Void.self)
+    let promise = connection.channel.eventLoop.makePromise(of: Void.self)
     self._sendEnd(promise: promise)
     return promise.futureResult
   }
@@ -188,7 +188,7 @@ extension BaseClientCall {
   private func setTimeout(_ timeout: GRPCTimeout) {
     if timeout == .infinite { return }
 
-    self.client.channel.eventLoop.scheduleTask(in: timeout.asNIOTimeAmount) { [weak self] in
+    self.connection.channel.eventLoop.scheduleTask(in: timeout.asNIOTimeAmount) { [weak self] in
       self?.clientChannelHandler.observeError(.client(.deadlineExceeded(timeout)))
     }
   }

+ 4 - 4
Sources/SwiftGRPCNIO/ClientCalls/BidirectionalStreamingClientCall.swift

@@ -29,11 +29,11 @@ import NIO
 public class BidirectionalStreamingClientCall<RequestMessage: Message, ResponseMessage: Message>: BaseClientCall<RequestMessage, ResponseMessage>, StreamingRequestClientCall {
   private var messageQueue: EventLoopFuture<Void>
 
-  public init(client: GRPCClient, path: String, callOptions: CallOptions, handler: @escaping (ResponseMessage) -> Void) {
-    self.messageQueue = client.channel.eventLoop.makeSucceededFuture(())
-    super.init(client: client, path: path, callOptions: callOptions, responseObserver: .callback(handler))
+  public init(connection: GRPCClientConnection, path: String, callOptions: CallOptions, handler: @escaping (ResponseMessage) -> Void) {
+    self.messageQueue = connection.channel.eventLoop.makeSucceededFuture(())
+    super.init(connection: connection, path: path, callOptions: callOptions, responseObserver: .callback(handler))
 
-    let requestHead = self.makeRequestHead(path: path, host: client.host, callOptions: callOptions)
+    let requestHead = self.makeRequestHead(path: path, host: connection.host, callOptions: callOptions)
     self.messageQueue = self.messageQueue.flatMap { self.sendHead(requestHead) }
   }
 

+ 5 - 5
Sources/SwiftGRPCNIO/ClientCalls/ClientStreamingClientCall.swift

@@ -31,18 +31,18 @@ public class ClientStreamingClientCall<RequestMessage: Message, ResponseMessage:
   public let response: EventLoopFuture<ResponseMessage>
   private var messageQueue: EventLoopFuture<Void>
 
-  public init(client: GRPCClient, path: String, callOptions: CallOptions) {
-    let responsePromise: EventLoopPromise<ResponseMessage> = client.channel.eventLoop.makePromise()
+  public init(connection: GRPCClientConnection, path: String, callOptions: CallOptions) {
+    let responsePromise: EventLoopPromise<ResponseMessage> = connection.channel.eventLoop.makePromise()
     self.response = responsePromise.futureResult
-    self.messageQueue = client.channel.eventLoop.makeSucceededFuture(())
+    self.messageQueue = connection.channel.eventLoop.makeSucceededFuture(())
 
     super.init(
-      client: client,
+      connection: connection,
       path: path,
       callOptions: callOptions,
       responseObserver: .succeedPromise(responsePromise))
 
-    let requestHead = self.makeRequestHead(path: path, host: client.host, callOptions: callOptions)
+    let requestHead = self.makeRequestHead(path: path, host: connection.host, callOptions: callOptions)
     self.messageQueue = self.messageQueue.flatMap { self.sendHead(requestHead) }
   }
 

+ 3 - 3
Sources/SwiftGRPCNIO/ClientCalls/ServerStreamingClientCall.swift

@@ -24,10 +24,10 @@ import NIO
 /// - `status`: the status of the gRPC call after it has ended,
 /// - `trailingMetadata`: any metadata returned from the server alongside the `status`.
 public class ServerStreamingClientCall<RequestMessage: Message, ResponseMessage: Message>: BaseClientCall<RequestMessage, ResponseMessage> {
-  public init(client: GRPCClient, path: String, request: RequestMessage, callOptions: CallOptions, handler: @escaping (ResponseMessage) -> Void) {
-    super.init(client: client, path: path, callOptions: callOptions, responseObserver: .callback(handler))
+  public init(connection: GRPCClientConnection, path: String, request: RequestMessage, callOptions: CallOptions, handler: @escaping (ResponseMessage) -> Void) {
+    super.init(connection: connection, path: path, callOptions: callOptions, responseObserver: .callback(handler))
 
-    let requestHead = self.makeRequestHead(path: path, host: client.host, callOptions: callOptions)
+    let requestHead = self.makeRequestHead(path: path, host: connection.host, callOptions: callOptions)
     self.sendHead(requestHead)
       .flatMap { self._sendMessage(request) }
       .whenSuccess { self._sendEnd(promise: nil) }

+ 4 - 4
Sources/SwiftGRPCNIO/ClientCalls/UnaryClientCall.swift

@@ -27,17 +27,17 @@ import NIO
 public class UnaryClientCall<RequestMessage: Message, ResponseMessage: Message>: BaseClientCall<RequestMessage, ResponseMessage>, UnaryResponseClientCall {
   public let response: EventLoopFuture<ResponseMessage>
 
-  public init(client: GRPCClient, path: String, request: RequestMessage, callOptions: CallOptions) {
-    let responsePromise: EventLoopPromise<ResponseMessage> = client.channel.eventLoop.makePromise()
+  public init(connection: GRPCClientConnection, path: String, request: RequestMessage, callOptions: CallOptions) {
+    let responsePromise: EventLoopPromise<ResponseMessage> = connection.channel.eventLoop.makePromise()
     self.response = responsePromise.futureResult
 
     super.init(
-      client: client,
+      connection: connection,
       path: path,
       callOptions: callOptions,
       responseObserver: .succeedPromise(responsePromise))
 
-    let requestHead = self.makeRequestHead(path: path, host: client.host, callOptions: callOptions)
+    let requestHead = self.makeRequestHead(path: path, host: connection.host, callOptions: callOptions)
     self.sendHead(requestHead)
       .flatMap { self._sendMessage(request) }
       .whenSuccess { self._sendEnd(promise: nil) }

+ 3 - 118
Sources/SwiftGRPCNIO/GRPCClient.swift

@@ -14,91 +14,11 @@
  * limitations under the License.
  */
 import Foundation
-import NIO
-import NIOHTTP2
-import NIOSSL
-
-/// Underlying channel and HTTP/2 stream multiplexer.
-///
-/// Different service clients implementing `GRPCServiceClient` may share an instance of this class.
-open class GRPCClient {
-  public static func start(
-    host: String,
-    port: Int,
-    eventLoopGroup: EventLoopGroup,
-    tls tlsMode: TLSMode = .none
-  ) throws -> EventLoopFuture<GRPCClient> {
-    // We need to capture the multiplexer from the channel initializer to store it after connection.
-    let multiplexerPromise: EventLoopPromise<HTTP2StreamMultiplexer> = eventLoopGroup.next().makePromise()
-
-    let bootstrap = ClientBootstrap(group: eventLoopGroup)
-      // Enable SO_REUSEADDR.
-      .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
-      .channelInitializer { channel in
-        let multiplexer = configureTLS(mode: tlsMode, channel: channel, host: host).flatMap {
-          channel.configureHTTP2Pipeline(mode: .client)
-        }
-
-        multiplexer.cascade(to: multiplexerPromise)
-        return multiplexer.map { _ in }
-      }
-
-    return bootstrap.connect(host: host, port: port)
-      .and(multiplexerPromise.futureResult)
-      .map { channel, multiplexer in GRPCClient(channel: channel, multiplexer: multiplexer, host: host, httpProtocol: tlsMode.httpProtocol) }
-  }
-
-  /// Configure an SSL handler on the channel, if one is required.
-  ///
-  /// - Parameters:
-  ///   - mode: TLS mode to use when creating the new handler.
-  ///   - channel: The channel on which to add the SSL handler.
-  ///   - host: The hostname of the server we're connecting to.
-  /// - Returns: A future which will be succeeded when the pipeline has been configured.
-  private static func configureTLS(mode tls: TLSMode, channel: Channel, host: String) -> EventLoopFuture<Void> {
-    let handlerAddedPromise: EventLoopPromise<Void> = channel.eventLoop.makePromise()
-
-    do {
-      guard let sslContext = try tls.makeSSLContext() else {
-        handlerAddedPromise.succeed(())
-        return handlerAddedPromise.futureResult
-      }
-      channel.pipeline.addHandler(try NIOSSLClientHandler(context: sslContext, serverHostname: host)).cascade(to: handlerAddedPromise)
-    } catch {
-      handlerAddedPromise.fail(error)
-    }
-
-    return handlerAddedPromise.futureResult
-  }
-
-  public let channel: Channel
-  public let multiplexer: HTTP2StreamMultiplexer
-  public let host: String
-  public var defaultCallOptions: CallOptions
-  public let httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol
-
-  init(channel: Channel, multiplexer: HTTP2StreamMultiplexer, host: String, httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol, defaultCallOptions: CallOptions = CallOptions()) {
-    self.channel = channel
-    self.multiplexer = multiplexer
-    self.host = host
-    self.defaultCallOptions = defaultCallOptions
-    self.httpProtocol = httpProtocol
-  }
-
-  /// Fired when the client shuts down.
-  public var onClose: EventLoopFuture<Void> {
-    return channel.closeFuture
-  }
-
-  public func close() -> EventLoopFuture<Void> {
-    return channel.close(mode: .all)
-  }
-}
 
 /// A GRPC client for a given service.
-public protocol GRPCServiceClient {
-  /// The client providing the underlying HTTP/2 channel for this client.
-  var client: GRPCClient { get }
+public protocol GRPCClient {
+  /// The connection providing the underlying HTTP/2 channel for this client.
+  var connection: GRPCClientConnection { get }
 
   /// Name of the service this client is for (e.g. "echo.Echo").
   var service: String { get }
@@ -116,41 +36,6 @@ public protocol GRPCServiceClient {
 }
 
 extension GRPCClient {
-  public enum TLSMode {
-    case none
-    case anonymous
-    case custom(NIOSSLContext)
-
-    /// Returns an SSL context for the TLS mode.
-    ///
-    /// - Returns: An SSL context for the TLS mode, or `nil` if TLS is not being used.
-    public func makeSSLContext() throws -> NIOSSLContext? {
-      switch self {
-      case .none:
-        return nil
-
-      case .anonymous:
-        return try NIOSSLContext(configuration: .forClient())
-
-      case .custom(let context):
-        return context
-      }
-    }
-
-    /// Rethrns the HTTP protocol for the TLS mode.
-    public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
-      switch self {
-      case .none:
-        return .http
-
-      case .anonymous, .custom:
-        return .https
-      }
-    }
-  }
-}
-
-extension GRPCServiceClient {
   public func path(forMethod method: String) -> String {
     return "/\(service)/\(method)"
   }

+ 132 - 0
Sources/SwiftGRPCNIO/GRPCClientConnection.swift

@@ -0,0 +1,132 @@
+/*
+ * Copyright 2019, 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 Foundation
+import NIO
+import NIOHTTP2
+import NIOSSL
+
+/// Underlying channel and HTTP/2 stream multiplexer.
+///
+/// Different service clients implementing `GRPCClient` may share an instance of this class.
+open class GRPCClientConnection {
+  public static func start(
+    host: String,
+    port: Int,
+    eventLoopGroup: EventLoopGroup,
+    tls tlsMode: TLSMode = .none
+  ) throws -> EventLoopFuture<GRPCClientConnection> {
+    // We need to capture the multiplexer from the channel initializer to store it after connection.
+    let multiplexerPromise: EventLoopPromise<HTTP2StreamMultiplexer> = eventLoopGroup.next().makePromise()
+
+    let bootstrap = ClientBootstrap(group: eventLoopGroup)
+      // Enable SO_REUSEADDR.
+      .channelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
+      .channelInitializer { channel in
+        let multiplexer = configureTLS(mode: tlsMode, channel: channel, host: host).flatMap {
+          channel.configureHTTP2Pipeline(mode: .client)
+        }
+
+        multiplexer.cascade(to: multiplexerPromise)
+        return multiplexer.map { _ in }
+      }
+
+    return bootstrap.connect(host: host, port: port)
+      .and(multiplexerPromise.futureResult)
+      .map { channel, multiplexer in GRPCClientConnection(channel: channel, multiplexer: multiplexer, host: host, httpProtocol: tlsMode.httpProtocol) }
+  }
+
+  /// Configure an SSL handler on the channel, if one is required.
+  ///
+  /// - Parameters:
+  ///   - mode: TLS mode to use when creating the new handler.
+  ///   - channel: The channel on which to add the SSL handler.
+  ///   - host: The hostname of the server we're connecting to.
+  /// - Returns: A future which will be succeeded when the pipeline has been configured.
+  private static func configureTLS(mode tls: TLSMode, channel: Channel, host: String) -> EventLoopFuture<Void> {
+    let handlerAddedPromise: EventLoopPromise<Void> = channel.eventLoop.makePromise()
+
+    do {
+      guard let sslContext = try tls.makeSSLContext() else {
+        handlerAddedPromise.succeed(())
+        return handlerAddedPromise.futureResult
+      }
+      channel.pipeline.addHandler(try NIOSSLClientHandler(context: sslContext, serverHostname: host)).cascade(to: handlerAddedPromise)
+    } catch {
+      handlerAddedPromise.fail(error)
+    }
+
+    return handlerAddedPromise.futureResult
+  }
+
+  public let channel: Channel
+  public let multiplexer: HTTP2StreamMultiplexer
+  public let host: String
+  public var defaultCallOptions: CallOptions
+  public let httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol
+
+  init(channel: Channel, multiplexer: HTTP2StreamMultiplexer, host: String, httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol, defaultCallOptions: CallOptions = CallOptions()) {
+    self.channel = channel
+    self.multiplexer = multiplexer
+    self.host = host
+    self.defaultCallOptions = defaultCallOptions
+    self.httpProtocol = httpProtocol
+  }
+
+  /// Fired when the client shuts down.
+  public var onClose: EventLoopFuture<Void> {
+    return channel.closeFuture
+  }
+
+  public func close() -> EventLoopFuture<Void> {
+    return channel.close(mode: .all)
+  }
+}
+
+
+extension GRPCClientConnection {
+  public enum TLSMode {
+    case none
+    case anonymous
+    case custom(NIOSSLContext)
+
+    /// Returns an SSL context for the TLS mode.
+    ///
+    /// - Returns: An SSL context for the TLS mode, or `nil` if TLS is not being used.
+    public func makeSSLContext() throws -> NIOSSLContext? {
+      switch self {
+      case .none:
+        return nil
+
+      case .anonymous:
+        return try NIOSSLContext(configuration: .forClient())
+
+      case .custom(let context):
+        return context
+      }
+    }
+
+    /// Rethrns the HTTP protocol for the TLS mode.
+    public var httpProtocol: HTTP2ToHTTP1ClientCodec.HTTPProtocol {
+      switch self {
+      case .none:
+        return .http
+
+      case .anonymous, .custom:
+        return .https
+      }
+    }
+  }
+}

+ 10 - 10
Sources/protoc-gen-swiftgrpc/Generator-Client.swift

@@ -446,21 +446,21 @@ extension Generator {
   }
 
   private func printNIOServiceClientImplementation() {
-    println("\(access) final class \(serviceClassName)Client: GRPCServiceClient, \(serviceClassName) {")
+    println("\(access) final class \(serviceClassName)Client: GRPCClient, \(serviceClassName) {")
     indent()
-    println("\(access) let client: GRPCClient")
+    println("\(access) let connection: GRPCClientConnection")
     println("\(access) let service = \"\(servicePath)\"")
     println("\(access) var defaultCallOptions: CallOptions")
     println()
     println("/// Creates a client for the \(servicePath) service.")
     println("///")
     printParameters()
-    println("///   - client: `GRPCClient` with a connection to the service host.")
+    println("///   - connection: `GRPCClientConnection` to the service host.")
     println("///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them. Defaults to `client.defaultCallOptions`.")
-    println("\(access) init(client: GRPCClient, defaultCallOptions: CallOptions? = nil) {")
+    println("\(access) init(connection: GRPCClientConnection, defaultCallOptions: CallOptions? = nil) {")
     indent()
-    println("self.client = client")
-    println("self.defaultCallOptions = defaultCallOptions ?? client.defaultCallOptions")
+    println("self.connection = connection")
+    println("self.defaultCallOptions = defaultCallOptions ?? connection.defaultCallOptions")
     outdent()
     println("}")
     println()
@@ -477,7 +477,7 @@ extension Generator {
         println("/// - Returns: A `UnaryClientCall` with futures for the metadata, status and response.")
         println("\(access) func \(methodFunctionName)(_ request: \(methodInputName), callOptions: CallOptions? = nil) -> UnaryClientCall<\(methodInputName), \(methodOutputName)> {")
         indent()
-        println("return UnaryClientCall(client: client, path: path(forMethod: \"\(method.name)\"), request: request, callOptions: callOptions ?? self.defaultCallOptions)")
+        println("return UnaryClientCall(connection: self.connection, path: self.path(forMethod: \"\(method.name)\"), request: request, callOptions: callOptions ?? self.defaultCallOptions)")
         outdent()
         println("}")
 
@@ -491,7 +491,7 @@ extension Generator {
         println("/// - Returns: A `ServerStreamingClientCall` with futures for the metadata and status.")
         println("\(access) func \(methodFunctionName)(_ request: \(methodInputName), callOptions: CallOptions? = nil, handler: @escaping (\(methodOutputName)) -> Void) -> ServerStreamingClientCall<\(methodInputName), \(methodOutputName)> {")
         indent()
-        println("return ServerStreamingClientCall(client: client, path: path(forMethod: \"\(method.name)\"), request: request, callOptions: callOptions ?? self.defaultCallOptions, handler: handler)")
+        println("return ServerStreamingClientCall(connection: self.connection, path: self.path(forMethod: \"\(method.name)\"), request: request, callOptions: callOptions ?? self.defaultCallOptions, handler: handler)")
         outdent()
         println("}")
 
@@ -505,7 +505,7 @@ extension Generator {
         println("/// - Returns: A `ClientStreamingClientCall` with futures for the metadata, status and response.")
         println("\(access) func \(methodFunctionName)(callOptions: CallOptions? = nil) -> ClientStreamingClientCall<\(methodInputName), \(methodOutputName)> {")
         indent()
-        println("return ClientStreamingClientCall(client: client, path: path(forMethod: \"\(method.name)\"), callOptions: callOptions ?? self.defaultCallOptions)")
+        println("return ClientStreamingClientCall(connection: self.connection, path: self.path(forMethod: \"\(method.name)\"), callOptions: callOptions ?? self.defaultCallOptions)")
         outdent()
         println("}")
 
@@ -520,7 +520,7 @@ extension Generator {
         println("/// - Returns: A `ClientStreamingClientCall` with futures for the metadata and status.")
         println("\(access) func \(methodFunctionName)(callOptions: CallOptions? = nil, handler: @escaping (\(methodOutputName)) -> Void) -> BidirectionalStreamingClientCall<\(methodInputName), \(methodOutputName)> {")
         indent()
-        println("return BidirectionalStreamingClientCall(client: client, path: path(forMethod: \"\(method.name)\"), callOptions: callOptions ?? self.defaultCallOptions, handler: handler)")
+        println("return BidirectionalStreamingClientCall(connection: self.connection, path: self.path(forMethod: \"\(method.name)\"), callOptions: callOptions ?? self.defaultCallOptions, handler: handler)")
         outdent()
         println("}")
       }

+ 5 - 5
Tests/SwiftGRPCNIOTests/NIOBasicEchoTestCase.swift

@@ -76,7 +76,7 @@ extension TransportSecurity {
     }
   }
 
-  func makeClientTLS() throws -> GRPCClient.TLSMode {
+  func makeClientTLS() throws -> GRPCClientConnection.TLSMode {
     return try makeClientTLSConfiguration().map { .custom(try NIOSSLContext(configuration: $0)) } ?? .none
   }
 
@@ -121,8 +121,8 @@ class NIOEchoTestCaseBase: XCTestCase {
     ).wait()
   }
 
-  func makeClient() throws -> GRPCClient {
-    return try GRPCClient.start(
+  func makeClientConnection() throws -> GRPCClientConnection {
+    return try GRPCClientConnection.start(
       host: "localhost",
       port: 5050,
       eventLoopGroup: self.clientEventLoopGroup,
@@ -135,7 +135,7 @@ class NIOEchoTestCaseBase: XCTestCase {
   }
 
   func makeEchoClient() throws -> Echo_EchoService_NIOClient {
-    return Echo_EchoService_NIOClient(client: try self.makeClient())
+    return Echo_EchoService_NIOClient(connection: try self.makeClientConnection())
   }
 
   override func setUp() {
@@ -145,7 +145,7 @@ class NIOEchoTestCaseBase: XCTestCase {
   }
 
   override func tearDown() {
-    XCTAssertNoThrow(try self.client.client.close().wait())
+    XCTAssertNoThrow(try self.client.connection.close().wait())
     XCTAssertNoThrow(try self.clientEventLoopGroup.syncShutdownGracefully())
     self.client = nil