Browse Source

For service servers, replace inheritance with composition.

This lets us spin up one server handling multiple gRPC services, where each service has its own `Provider` implementation. I.e. we can now serve multiple services from the same port.
Daniel Alm 7 years ago
parent
commit
98f7fa6316

+ 15 - 15
Examples/EchoXcode/Echo/AppDelegate.swift

@@ -20,25 +20,25 @@ class AppDelegate: NSObject, NSApplicationDelegate {
   @IBOutlet var window: NSWindow!
 
   var echoProvider: Echo_EchoProvider!
-  var insecureServer: Echo_EchoServer!
-  var secureServer: Echo_EchoServer!
+  var insecureServer: ServiceServer!
+  var secureServer: ServiceServer!
 
   func applicationDidFinishLaunching(_: Notification) {
     // instantiate our custom-written application handler
     echoProvider = EchoProvider()
 
-    // create and start a server for handling insecure requests
-    insecureServer = Echo_EchoServer(address: "localhost:8081",
-                                     provider: echoProvider)
-    insecureServer.start()
-
-    // create and start a server for handling secure requests
-    let certificateURL = Bundle.main.url(forResource: "ssl", withExtension: "crt")!
-    let keyURL = Bundle.main.url(forResource: "ssl", withExtension: "key")!
-    secureServer = Echo_EchoServer(address: "localhost:8443",
-                                   certificateURL: certificateURL,
-                                   keyURL: keyURL,
-                                   provider: echoProvider)
-    secureServer.start()
+	// create and start a server for handling insecure requests
+	insecureServer = ServiceServer(address: "localhost:8081",
+								   services: [echoProvider])
+	insecureServer.start()
+	
+	// create and start a server for handling secure requests
+	let certificateURL = Bundle.main.url(forResource: "ssl", withExtension: "crt")!
+	let keyURL = Bundle.main.url(forResource: "ssl", withExtension: "key")!
+	secureServer = ServiceServer(address: "localhost:8443",
+								 certificateURL: certificateURL,
+								 keyURL: keyURL,
+								 services: [echoProvider])
+	secureServer.start()
   }
 }

+ 34 - 52
Sources/Examples/Echo/Generated/echo.grpc.swift

@@ -213,13 +213,46 @@ class Echo_EchoServiceTestStub: ServiceClientTestStubBase, Echo_EchoService {
 /// To build a server, implement a class that conforms to this protocol.
 /// If one of the methods returning `ServerStatus?` returns nil,
 /// it is expected that you have already returned a status to the client by means of `session.close`.
-internal protocol Echo_EchoProvider {
+internal protocol Echo_EchoProvider: ServiceProvider {
   func get(request: Echo_EchoRequest, session: Echo_EchoGetSession) throws -> Echo_EchoResponse
   func expand(request: Echo_EchoRequest, session: Echo_EchoExpandSession) throws -> ServerStatus?
   func collect(session: Echo_EchoCollectSession) throws -> Echo_EchoResponse?
   func update(session: Echo_EchoUpdateSession) throws -> ServerStatus?
 }
 
+extension Echo_EchoProvider {
+  internal var serviceName: String { return "echo.Echo" }
+
+  /// Determines and calls the appropriate request handler, depending on the request's method.
+  /// Throws `HandleMethodError.unknownMethod` for methods not handled by this service.
+  internal func handleMethod(_ method: String, handler: Handler) throws -> ServerStatus? {
+    switch method {
+    case "/echo.Echo/Get":
+      return try Echo_EchoGetSessionBase(
+        handler: handler,
+        providerBlock: { try self.get(request: $0, session: $1 as! Echo_EchoGetSessionBase) })
+          .run()
+    case "/echo.Echo/Expand":
+      return try Echo_EchoExpandSessionBase(
+        handler: handler,
+        providerBlock: { try self.expand(request: $0, session: $1 as! Echo_EchoExpandSessionBase) })
+          .run()
+    case "/echo.Echo/Collect":
+      return try Echo_EchoCollectSessionBase(
+        handler: handler,
+        providerBlock: { try self.collect(session: $0 as! Echo_EchoCollectSessionBase) })
+          .run()
+    case "/echo.Echo/Update":
+      return try Echo_EchoUpdateSessionBase(
+        handler: handler,
+        providerBlock: { try self.update(session: $0 as! Echo_EchoUpdateSessionBase) })
+          .run()
+    default:
+      throw HandleMethodError.unknownMethod
+    }
+  }
+}
+
 internal protocol Echo_EchoGetSession: ServerSessionUnary {}
 
 fileprivate final class Echo_EchoGetSessionBase: ServerSessionUnaryBase<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoGetSession {}
@@ -303,54 +336,3 @@ fileprivate final class Echo_EchoUpdateSessionBase: ServerSessionBidirectionalSt
 
 class Echo_EchoUpdateSessionTestStub: ServerSessionBidirectionalStreamingTestStub<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoUpdateSession {}
 
-
-/// Main server for generated service
-internal final class Echo_EchoServer: ServiceServer {
-  private let provider: Echo_EchoProvider
-
-  internal init(address: String, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(address: address)
-  }
-
-  internal init?(address: String, certificateURL: URL, keyURL: URL, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(address: address, certificateURL: certificateURL, keyURL: keyURL)
-  }
-
-  internal init?(address: String, certificateString: String, keyString: String, provider: Echo_EchoProvider) {
-    self.provider = provider
-    super.init(address: address, certificateString: certificateString, keyString: keyString)
-  }
-
-  /// Determines and calls the appropriate request handler, depending on the request's method.
-  /// Throws `HandleMethodError.unknownMethod` for methods not handled by this service.
-  internal override func handleMethod(_ method: String, handler: Handler) throws -> ServerStatus? {
-    let provider = self.provider
-    switch method {
-    case "/echo.Echo/Get":
-      return try Echo_EchoGetSessionBase(
-        handler: handler,
-        providerBlock: { try provider.get(request: $0, session: $1 as! Echo_EchoGetSessionBase) })
-          .run()
-    case "/echo.Echo/Expand":
-      return try Echo_EchoExpandSessionBase(
-        handler: handler,
-        providerBlock: { try provider.expand(request: $0, session: $1 as! Echo_EchoExpandSessionBase) })
-          .run()
-    case "/echo.Echo/Collect":
-      return try Echo_EchoCollectSessionBase(
-        handler: handler,
-        providerBlock: { try provider.collect(session: $0 as! Echo_EchoCollectSessionBase) })
-          .run()
-    case "/echo.Echo/Update":
-      return try Echo_EchoUpdateSessionBase(
-        handler: handler,
-        providerBlock: { try provider.update(session: $0 as! Echo_EchoUpdateSessionBase) })
-          .run()
-    default:
-      throw HandleMethodError.unknownMethod
-    }
-  }
-}
-

+ 6 - 8
Sources/Examples/Echo/main.swift

@@ -61,21 +61,19 @@ Group {
              portOption,
              description: "Run an echo server.") { ssl, address, port in
     let sem = DispatchSemaphore(value: 0)
-    let echoProvider = EchoProvider()
-    var echoServer: Echo_EchoServer?
+    var echoServer: ServiceServer?
     if ssl {
       print("starting secure server")
       let certificateURL = URL(fileURLWithPath: "ssl.crt")
       let keyURL = URL(fileURLWithPath: "ssl.key")
-      echoServer = Echo_EchoServer(address: address + ":" + port,
-                                   certificateURL: certificateURL,
-                                   keyURL: keyURL,
-                                   provider: echoProvider)
+      echoServer = ServiceServer(address: address + ":" + port,
+                                 certificateURL: certificateURL,
+                                 keyURL: keyURL,
+                                 services: [EchoProvider()])
       echoServer?.start()
     } else {
       print("starting insecure server")
-      echoServer = Echo_EchoServer(address: address + ":" + port,
-                                   provider: echoProvider)
+      echoServer = ServiceServer(address: address + ":" + port, services: [EchoProvider()])
       echoServer?.start()
     }
     // This blocks to keep the main thread from finishing while the server runs,

+ 33 - 0
Sources/SwiftGRPC/Runtime/ServiceProvider.swift

@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018, 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 Foundation
+import SwiftProtobuf
+
+public enum HandleMethodError: Error {
+  case unknownMethod
+}
+
+// Helper protocol to let one `ServiceServer` serve multiple gRPC services.
+// Implementations for these methods are usually provided by the code generated by our protoc plugin.
+public protocol ServiceProvider {
+  // The name of the service (including package names) provided by this object, e.g. "echo.Echo".
+  var serviceName: String { get }
+  
+  /// Handle the given method. Needs to be overridden by actual implementations.
+  func handleMethod(_ method: String, handler: Handler) throws -> ServerStatus?
+}

+ 16 - 13
Sources/SwiftGRPC/Runtime/ServiceServer.swift

@@ -23,39 +23,36 @@ open class ServiceServer {
   public let server: Server
 
   public var shouldLogRequests = true
-
+  
+  fileprivate let servicesByName: [String: ServiceProvider]
+  
   /// Create a server that accepts insecure connections.
-  public init(address: String) {
+  public init(address: String, services: [ServiceProvider]) {
     gRPC.initialize()
     self.address = address
     server = Server(address: address)
+    servicesByName = Dictionary(uniqueKeysWithValues: services.map { ($0.serviceName, $0) })
   }
 
   /// Create a server that accepts secure connections.
-  public init(address: String, certificateString: String, keyString: String) {
+  public init(address: String, certificateString: String, keyString: String, services: [ServiceProvider]) {
     gRPC.initialize()
     self.address = address
     server = Server(address: address, key: keyString, certs: certificateString)
+    servicesByName = Dictionary(uniqueKeysWithValues: services.map { ($0.serviceName, $0) })
   }
 
   /// Create a server that accepts secure connections.
-  public init?(address: String, certificateURL: URL, keyURL: URL) {
+  public init?(address: String, certificateURL: URL, keyURL: URL, services: [ServiceProvider]) {
     guard let certificate = try? String(contentsOf: certificateURL, encoding: .utf8),
       let key = try? String(contentsOf: keyURL, encoding: .utf8)
       else { return nil }
     gRPC.initialize()
     self.address = address
     server = Server(address: address, key: key, certs: certificate)
+    servicesByName = Dictionary(uniqueKeysWithValues: services.map { ($0.serviceName, $0) })
   }
 
-  public enum HandleMethodError: Error {
-    case unknownMethod
-  }
-  
-  /// Handle the given method. Needs to be overridden by actual implementations.
-  /// Returns whether the method was actually handled.
-  open func handleMethod(_ method: String, handler: Handler) throws -> ServerStatus? { fatalError("needs to be overridden") }
-
   /// Start the server.
   public func start() {
     server.run { [weak self] handler in
@@ -76,7 +73,13 @@ open class ServiceServer {
       
       do {
         do {
-          if let responseStatus = try strongSelf.handleMethod(unwrappedMethod, handler: handler),
+          let methodComponents = unwrappedMethod.components(separatedBy: "/")
+          guard methodComponents.count >= 3 && methodComponents[0].isEmpty,
+            let providerForServiceName = strongSelf.servicesByName[methodComponents[1]] else {
+            throw HandleMethodError.unknownMethod
+          }
+          
+          if let responseStatus = try providerForServiceName.handleMethod(unwrappedMethod, handler: handler),
             !handler.completionQueue.hasBeenShutdown {
             // The handler wants us to send the status for them; do that.
             // But first, ensure that all outgoing messages have been enqueued, to avoid ending the stream prematurely:

+ 8 - 8
Sources/protoc-gen-swiftgrpc/Generator-Names.swift

@@ -51,10 +51,6 @@ extension Generator {
     return nameForPackageService(file, service) + "Provider"
   }
 
-  internal var serverName: String {
-    return nameForPackageService(file, service) + "Server"
-  }
-
   internal var callName: String {
     return nameForPackageServiceMethod(file, service, method) + "Call"
   }
@@ -75,12 +71,16 @@ extension Generator {
   internal var methodOutputName: String {
     return protoMessageName(method.outputType)
   }
-
-  internal var methodPath: String {
+  
+  internal var servicePath: String {
     if !file.package.isEmpty {
-      return "\"/" + file.package + "." + service.name + "/" + method.name + "\""
+      return file.package + "." + service.name
     } else {
-      return "\"/" + service.name + "/" + method.name + "\""
+      return service.name
     }
   }
+
+  internal var methodPath: String {
+    return "\"/" + servicePath + "/" + method.name + "\""
+  }
 }

+ 6 - 34
Sources/protoc-gen-swiftgrpc/Generator-Server.swift

@@ -35,15 +35,13 @@ extension Generator {
       }
       println()
     }
-    println()
-    printServerMainClass()
   }
 
   private func printServerProtocol() {
     println("/// To build a server, implement a class that conforms to this protocol.")
     println("/// If one of the methods returning `ServerStatus?` returns nil,")
     println("/// it is expected that you have already returned a status to the client by means of `session.close`.")
-    println("\(access) protocol \(providerName) {")
+    println("\(access) protocol \(providerName): ServiceProvider {")
     indent()
     for method in service.methods {
       self.method = method
@@ -61,40 +59,14 @@ extension Generator {
     outdent()
     println("}")
     println()
-  }
-
-  private func printServerMainClass() {
-    println("/// Main server for generated service")
-    println("\(access) final class \(serverName): ServiceServer {")
-    indent()
-    println("private let provider: \(providerName)")
-    println()
-    println("\(access) init(address: String, provider: \(providerName)) {")
+    println("extension \(providerName) {")
     indent()
-    println("self.provider = provider")
-    println("super.init(address: address)")
-    outdent()
-    println("}")
-    println()
-    println("\(access) init?(address: String, certificateURL: URL, keyURL: URL, provider: \(providerName)) {")
-    indent()
-    println("self.provider = provider")
-    println("super.init(address: address, certificateURL: certificateURL, keyURL: keyURL)")
-    outdent()
-    println("}")
-    println()
-    println("\(access) init?(address: String, certificateString: String, keyString: String, provider: \(providerName)) {")
-    indent()
-    println("self.provider = provider")
-    println("super.init(address: address, certificateString: certificateString, keyString: keyString)")
-    outdent()
-    println("}")
+    println("\(access) var serviceName: String { return \"\(servicePath)\" }")
     println()
     println("/// Determines and calls the appropriate request handler, depending on the request's method.")
     println("/// Throws `HandleMethodError.unknownMethod` for methods not handled by this service.")
-    println("\(access) override func handleMethod(_ method: String, handler: Handler) throws -> ServerStatus? {")
+    println("\(access) func handleMethod(_ method: String, handler: Handler) throws -> ServerStatus? {")
     indent()
-    println("let provider = self.provider")
     println("switch method {")
     for method in service.methods {
       self.method = method
@@ -105,7 +77,7 @@ extension Generator {
         println("return try \(methodSessionName)Base(")
         indent()
         println("handler: handler,")
-        println("providerBlock: { try provider.\(methodFunctionName)(request: $0, session: $1 as! \(methodSessionName)Base) })")
+        println("providerBlock: { try self.\(methodFunctionName)(request: $0, session: $1 as! \(methodSessionName)Base) })")
         indent()
         println(".run()")
         outdent()
@@ -114,7 +86,7 @@ extension Generator {
         println("return try \(methodSessionName)Base(")
         indent()
         println("handler: handler,")
-        println("providerBlock: { try provider.\(methodFunctionName)(session: $0 as! \(methodSessionName)Base) })")
+        println("providerBlock: { try self.\(methodFunctionName)(session: $0 as! \(methodSessionName)Base) })")
         indent()
         println(".run()")
         outdent()

+ 6 - 6
Tests/SwiftGRPCTests/BasicEchoTestCase.swift

@@ -34,7 +34,7 @@ class BasicEchoTestCase: XCTestCase {
   func makeProvider() -> Echo_EchoProvider { return EchoProvider() }
 
   var provider: Echo_EchoProvider!
-  var server: Echo_EchoServer!
+  var server: ServiceServer!
   var client: Echo_EchoServiceClient!
   
   var defaultTimeout: TimeInterval { return 1.0 }
@@ -48,15 +48,15 @@ class BasicEchoTestCase: XCTestCase {
     
     if secure {
       let certificateString = String(data: certificateForTests, encoding: .utf8)!
-      server = Echo_EchoServer(address: address,
-                               certificateString: certificateString,
-                               keyString: String(data: keyForTests, encoding: .utf8)!,
-                               provider: provider)
+      server = ServiceServer(address: address,
+                             certificateString: certificateString,
+                             keyString: String(data: keyForTests, encoding: .utf8)!,
+                             services: [provider])
       server.start()
       client = Echo_EchoServiceClient(address: address, certificates: certificateString, arguments: [.sslTargetNameOverride("example.com")])
       client.host = "example.com"
     } else {
-      server = Echo_EchoServer(address: address, provider: provider)
+      server = ServiceServer(address: address, services: [provider])
       server.start()
       client = Echo_EchoServiceClient(address: address, secure: false)
     }