Skip to content

Commit

Permalink
Merge pull request #255 from MrMage/multiple-services
Browse files Browse the repository at this point in the history
For service servers, replace inheritance with composition
  • Loading branch information
timburks authored Jun 13, 2018
2 parents ac74772 + a6e8cbb commit 0d511b3
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 136 deletions.
30 changes: 15 additions & 15 deletions Examples/EchoXcode/Echo/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
86 changes: 34 additions & 52 deletions Sources/Examples/Echo/Generated/echo.grpc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down Expand Up @@ -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
}
}
}

14 changes: 6 additions & 8 deletions Sources/Examples/Echo/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
33 changes: 33 additions & 0 deletions Sources/SwiftGRPC/Runtime/ServiceProvider.swift
Original file line number Diff line number Diff line change
@@ -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?
}
29 changes: 16 additions & 13 deletions Sources/SwiftGRPC/Runtime/ServiceServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
16 changes: 8 additions & 8 deletions Sources/protoc-gen-swiftgrpc/Generator-Names.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand All @@ -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 + "\""
}
}
40 changes: 6 additions & 34 deletions Sources/protoc-gen-swiftgrpc/Generator-Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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()
Expand Down
Loading

0 comments on commit 0d511b3

Please sign in to comment.