diff --git a/Examples/EchoXcode/Echo/AppDelegate.swift b/Examples/EchoXcode/Echo/AppDelegate.swift index 15c3abd3e..3de4977e7 100644 --- a/Examples/EchoXcode/Echo/AppDelegate.swift +++ b/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", + serviceProviders: [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, + serviceProviders: [echoProvider]) + secureServer.start() } } diff --git a/Sources/Examples/Echo/Generated/echo.grpc.swift b/Sources/Examples/Echo/Generated/echo.grpc.swift index 04b224488..1f6c30530 100644 --- a/Sources/Examples/Echo/Generated/echo.grpc.swift +++ b/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_EchoGetSession {} @@ -303,54 +336,3 @@ fileprivate final class Echo_EchoUpdateSessionBase: ServerSessionBidirectionalSt class Echo_EchoUpdateSessionTestStub: ServerSessionBidirectionalStreamingTestStub, 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 - } - } -} - diff --git a/Sources/Examples/Echo/main.swift b/Sources/Examples/Echo/main.swift index 513edbe1d..03bd01e53 100644 --- a/Sources/Examples/Echo/main.swift +++ b/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, + serviceProviders: [EchoProvider()]) echoServer?.start() } else { print("starting insecure server") - echoServer = Echo_EchoServer(address: address + ":" + port, - provider: echoProvider) + echoServer = ServiceServer(address: address + ":" + port, serviceProviders: [EchoProvider()]) echoServer?.start() } // This blocks to keep the main thread from finishing while the server runs, diff --git a/Sources/SwiftGRPC/Runtime/ServiceProvider.swift b/Sources/SwiftGRPC/Runtime/ServiceProvider.swift new file mode 100644 index 000000000..84bfeda31 --- /dev/null +++ b/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? +} diff --git a/Sources/SwiftGRPC/Runtime/ServiceServer.swift b/Sources/SwiftGRPC/Runtime/ServiceServer.swift index fd32d7d4f..6acc01e22 100644 --- a/Sources/SwiftGRPC/Runtime/ServiceServer.swift +++ b/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, serviceProviders: [ServiceProvider]) { gRPC.initialize() self.address = address server = Server(address: address) + servicesByName = Dictionary(uniqueKeysWithValues: serviceProviders.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, serviceProviders: [ServiceProvider]) { gRPC.initialize() self.address = address server = Server(address: address, key: keyString, certs: certificateString) + servicesByName = Dictionary(uniqueKeysWithValues: serviceProviders.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, serviceProviders: [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: serviceProviders.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: diff --git a/Sources/protoc-gen-swiftgrpc/Generator-Names.swift b/Sources/protoc-gen-swiftgrpc/Generator-Names.swift index 1756ba6cf..cbd76b5ef 100644 --- a/Sources/protoc-gen-swiftgrpc/Generator-Names.swift +++ b/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 + "\"" + } } diff --git a/Sources/protoc-gen-swiftgrpc/Generator-Server.swift b/Sources/protoc-gen-swiftgrpc/Generator-Server.swift index 3907be95a..943a32296 100644 --- a/Sources/protoc-gen-swiftgrpc/Generator-Server.swift +++ b/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() diff --git a/Tests/SwiftGRPCTests/BasicEchoTestCase.swift b/Tests/SwiftGRPCTests/BasicEchoTestCase.swift index 493747011..9aad05db0 100644 --- a/Tests/SwiftGRPCTests/BasicEchoTestCase.swift +++ b/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)!, + serviceProviders: [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, serviceProviders: [provider]) server.start() client = Echo_EchoServiceClient(address: address, secure: false) }