Browse Source

Add hello-world example (#2017)

Motivation:

Examples are great, we should add more.

Modifications:

- Add a 'Hello World' example

Result:

More examples
George Barnett 1 year ago
parent
commit
39f1681930

+ 20 - 1
Package@swift-6.swift

@@ -152,6 +152,7 @@ extension Target.Dependency {
   static var grpcInProcessTransport: Self { .target(name: "GRPCInProcessTransport") }
   static var grpcInterceptors: Self { .target(name: "GRPCInterceptors") }
   static var grpcHTTP2Core: Self { .target(name: "GRPCHTTP2Core") }
+  static var grpcHTTP2Transport: Self { .target(name: "GRPCHTTP2Transport") }
   static var grpcHTTP2TransportNIOPosix: Self { .target(name: "GRPCHTTP2TransportNIOPosix") }
   static var grpcHTTP2TransportNIOTransportServices: Self { .target(name: "GRPCHTTP2TransportNIOTransportServices") }
   static var grpcHealth: Self { .target(name: "GRPCHealth") }
@@ -706,7 +707,7 @@ extension Target {
       ].appending(
         .nioSSL, if: includeNIOSSL
       ),
-      path: "Sources/Examples/v2/Echo",
+      path: "Sources/Examples/v2/echo",
       swiftSettings: [
         .swiftLanguageMode(.v6),
         .enableUpcomingFeature("ExistentialAny"),
@@ -758,6 +759,23 @@ extension Target {
     )
   }
 
+  static var helloWorld_v2: Target {
+    .executableTarget(
+      name: "hello-world",
+      dependencies: [
+        .grpcProtobuf,
+        .grpcHTTP2Transport,
+        .argumentParser,
+      ],
+      path: "Sources/Examples/v2/hello-world",
+      swiftSettings: [
+        .swiftLanguageMode(.v6),
+        .enableUpcomingFeature("ExistentialAny"),
+        .enableUpcomingFeature("InternalImportsByDefault")
+      ]
+    )
+  }
+
   static var routeGuideModel: Target {
     .target(
       name: "RouteGuideModel",
@@ -1069,6 +1087,7 @@ let package = Package(
 
     // v2 examples
     .echo_v2,
+    .helloWorld_v2,
   ]
 )
 

+ 12 - 3
Protos/generate.sh

@@ -82,7 +82,7 @@ function generate_echo_v1_example {
 
 function generate_echo_v2_example {
   local proto="$here/examples/echo/echo.proto"
-  local output="$root/Sources/Examples/v2/Echo/Generated"
+  local output="$root/Sources/Examples/v2/echo/Generated"
 
   generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal"
   generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "_V2=true"
@@ -96,7 +96,7 @@ function generate_routeguide_example {
   generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Public"
 }
 
-function generate_helloworld_example {
+function generate_helloworld_v1_example {
   local proto="$here/upstream/grpc/examples/helloworld.proto"
   local output="$root/Sources/Examples/v1/HelloWorld/Model"
 
@@ -104,6 +104,14 @@ function generate_helloworld_example {
   generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Public"
 }
 
+function generate_helloworld_v2_example {
+  local proto="$here/upstream/grpc/examples/helloworld.proto"
+  local output="$root/Sources/Examples/v2/hello-world/Generated"
+
+  generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal"
+  generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "_V2=true"
+}
+
 function generate_reflection_service {
   local proto_v1="$here/upstream/grpc/reflection/v1/reflection.proto"
   local output_v1="$root/Sources/GRPCReflectionService/v1"
@@ -254,7 +262,8 @@ function generate_health_service {
 generate_echo_v1_example
 generate_echo_v2_example
 generate_routeguide_example
-generate_helloworld_example
+generate_helloworld_v1_example
+generate_helloworld_v2_example
 generate_reflection_data_example
 
 # Reflection service and tests

+ 0 - 0
Sources/Examples/v2/Echo/Echo.swift → Sources/Examples/v2/echo/Echo.swift


+ 0 - 0
Sources/Examples/v2/Echo/Generated/echo.grpc.swift → Sources/Examples/v2/echo/Generated/echo.grpc.swift


+ 0 - 0
Sources/Examples/v2/Echo/Generated/echo.pb.swift → Sources/Examples/v2/echo/Generated/echo.pb.swift


+ 0 - 0
Sources/Examples/v2/Echo/Subcommands/ClientArguments.swift → Sources/Examples/v2/echo/Subcommands/ClientArguments.swift


+ 0 - 0
Sources/Examples/v2/Echo/Subcommands/Collect.swift → Sources/Examples/v2/echo/Subcommands/Collect.swift


+ 0 - 0
Sources/Examples/v2/Echo/Subcommands/Expand.swift → Sources/Examples/v2/echo/Subcommands/Expand.swift


+ 0 - 0
Sources/Examples/v2/Echo/Subcommands/Get.swift → Sources/Examples/v2/echo/Subcommands/Get.swift


+ 0 - 0
Sources/Examples/v2/Echo/Subcommands/Serve.swift → Sources/Examples/v2/echo/Subcommands/Serve.swift


+ 0 - 0
Sources/Examples/v2/Echo/Subcommands/Update.swift → Sources/Examples/v2/echo/Subcommands/Update.swift


+ 181 - 0
Sources/Examples/v2/hello-world/Generated/helloworld.grpc.swift

@@ -0,0 +1,181 @@
+/// Copyright 2015 gRPC authors.
+///
+/// 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.
+
+// DO NOT EDIT.
+// swift-format-ignore-file
+//
+// Generated by the gRPC Swift generator plugin for the protocol buffer compiler.
+// Source: helloworld.proto
+//
+// For information on using the generated types, please see the documentation:
+//   https://github.com/grpc/grpc-swift
+
+internal import GRPCCore
+internal import GRPCProtobuf
+
+internal enum Helloworld_Greeter {
+    internal static let descriptor = GRPCCore.ServiceDescriptor.helloworld_Greeter
+    internal enum Method {
+        internal enum SayHello {
+            internal typealias Input = Helloworld_HelloRequest
+            internal typealias Output = Helloworld_HelloReply
+            internal static let descriptor = GRPCCore.MethodDescriptor(
+                service: Helloworld_Greeter.descriptor.fullyQualifiedService,
+                method: "SayHello"
+            )
+        }
+        internal static let descriptors: [GRPCCore.MethodDescriptor] = [
+            SayHello.descriptor
+        ]
+    }
+    @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+    internal typealias StreamingServiceProtocol = Helloworld_GreeterStreamingServiceProtocol
+    @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+    internal typealias ServiceProtocol = Helloworld_GreeterServiceProtocol
+    @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+    internal typealias ClientProtocol = Helloworld_GreeterClientProtocol
+    @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+    internal typealias Client = Helloworld_GreeterClient
+}
+
+extension GRPCCore.ServiceDescriptor {
+    internal static let helloworld_Greeter = Self(
+        package: "helloworld",
+        service: "Greeter"
+    )
+}
+
+/// The greeting service definition.
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+internal protocol Helloworld_GreeterStreamingServiceProtocol: GRPCCore.RegistrableRPCService {
+    /// Sends a greeting
+    func sayHello(request: GRPCCore.ServerRequest.Stream<Helloworld_HelloRequest>) async throws -> GRPCCore.ServerResponse.Stream<Helloworld_HelloReply>
+}
+
+/// Conformance to `GRPCCore.RegistrableRPCService`.
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+extension Helloworld_Greeter.StreamingServiceProtocol {
+    @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+    internal func registerMethods(with router: inout GRPCCore.RPCRouter) {
+        router.registerHandler(
+            forMethod: Helloworld_Greeter.Method.SayHello.descriptor,
+            deserializer: GRPCProtobuf.ProtobufDeserializer<Helloworld_HelloRequest>(),
+            serializer: GRPCProtobuf.ProtobufSerializer<Helloworld_HelloReply>(),
+            handler: { request in
+                try await self.sayHello(request: request)
+            }
+        )
+    }
+}
+
+/// The greeting service definition.
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+internal protocol Helloworld_GreeterServiceProtocol: Helloworld_Greeter.StreamingServiceProtocol {
+    /// Sends a greeting
+    func sayHello(request: GRPCCore.ServerRequest.Single<Helloworld_HelloRequest>) async throws -> GRPCCore.ServerResponse.Single<Helloworld_HelloReply>
+}
+
+/// Partial conformance to `Helloworld_GreeterStreamingServiceProtocol`.
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+extension Helloworld_Greeter.ServiceProtocol {
+    internal func sayHello(request: GRPCCore.ServerRequest.Stream<Helloworld_HelloRequest>) async throws -> GRPCCore.ServerResponse.Stream<Helloworld_HelloReply> {
+        let response = try await self.sayHello(request: GRPCCore.ServerRequest.Single(stream: request))
+        return GRPCCore.ServerResponse.Stream(single: response)
+    }
+}
+
+/// The greeting service definition.
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+internal protocol Helloworld_GreeterClientProtocol: Sendable {
+    /// Sends a greeting
+    func sayHello<R>(
+        request: GRPCCore.ClientRequest.Single<Helloworld_HelloRequest>,
+        serializer: some GRPCCore.MessageSerializer<Helloworld_HelloRequest>,
+        deserializer: some GRPCCore.MessageDeserializer<Helloworld_HelloReply>,
+        options: GRPCCore.CallOptions,
+        _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Helloworld_HelloReply>) async throws -> R
+    ) async throws -> R where R: Sendable
+}
+
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+extension Helloworld_Greeter.ClientProtocol {
+    internal func sayHello<R>(
+        request: GRPCCore.ClientRequest.Single<Helloworld_HelloRequest>,
+        options: GRPCCore.CallOptions = .defaults,
+        _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Helloworld_HelloReply>) async throws -> R = {
+            try $0.message
+        }
+    ) async throws -> R where R: Sendable {
+        try await self.sayHello(
+            request: request,
+            serializer: GRPCProtobuf.ProtobufSerializer<Helloworld_HelloRequest>(),
+            deserializer: GRPCProtobuf.ProtobufDeserializer<Helloworld_HelloReply>(),
+            options: options,
+            body
+        )
+    }
+}
+
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+extension Helloworld_Greeter.ClientProtocol {
+    /// Sends a greeting
+    internal func sayHello<Result>(
+        _ message: Helloworld_HelloRequest,
+        metadata: GRPCCore.Metadata = [:],
+        options: GRPCCore.CallOptions = .defaults,
+        onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single<Helloworld_HelloReply>) async throws -> Result = {
+            try $0.message
+        }
+    ) async throws -> Result where Result: Sendable {
+        let request = GRPCCore.ClientRequest.Single<Helloworld_HelloRequest>(
+            message: message,
+            metadata: metadata
+        )
+        return try await self.sayHello(
+            request: request,
+            options: options,
+            handleResponse
+        )
+    }
+}
+
+/// The greeting service definition.
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+internal struct Helloworld_GreeterClient: Helloworld_Greeter.ClientProtocol {
+    private let client: GRPCCore.GRPCClient
+    
+    internal init(wrapping client: GRPCCore.GRPCClient) {
+        self.client = client
+    }
+    
+    /// Sends a greeting
+    internal func sayHello<R>(
+        request: GRPCCore.ClientRequest.Single<Helloworld_HelloRequest>,
+        serializer: some GRPCCore.MessageSerializer<Helloworld_HelloRequest>,
+        deserializer: some GRPCCore.MessageDeserializer<Helloworld_HelloReply>,
+        options: GRPCCore.CallOptions = .defaults,
+        _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single<Helloworld_HelloReply>) async throws -> R = {
+            try $0.message
+        }
+    ) async throws -> R where R: Sendable {
+        try await self.client.unary(
+            request: request,
+            descriptor: Helloworld_Greeter.Method.SayHello.descriptor,
+            serializer: serializer,
+            deserializer: deserializer,
+            options: options,
+            handler: body
+        )
+    }
+}

+ 129 - 0
Sources/Examples/v2/hello-world/Generated/helloworld.pb.swift

@@ -0,0 +1,129 @@
+// DO NOT EDIT.
+// swift-format-ignore-file
+//
+// Generated by the Swift generator plugin for the protocol buffer compiler.
+// Source: helloworld.proto
+//
+// For information on using the generated types, please see the documentation:
+//   https://github.com/apple/swift-protobuf/
+
+/// Copyright 2015 gRPC authors.
+///
+/// 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 SwiftProtobuf
+
+// If the compiler emits an error on this type, it is because this file
+// was generated by a version of the `protoc` Swift plug-in that is
+// incompatible with the version of SwiftProtobuf to which you are linking.
+// Please ensure that you are building against the same version of the API
+// that was used to generate this file.
+fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
+  struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
+  typealias Version = _2
+}
+
+/// The request message containing the user's name.
+struct Helloworld_HelloRequest: Sendable {
+  // SwiftProtobuf.Message conformance is added in an extension below. See the
+  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
+  // methods supported on all messages.
+
+  var name: String = String()
+
+  var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  init() {}
+}
+
+/// The response message containing the greetings
+struct Helloworld_HelloReply: Sendable {
+  // SwiftProtobuf.Message conformance is added in an extension below. See the
+  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
+  // methods supported on all messages.
+
+  var message: String = String()
+
+  var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  init() {}
+}
+
+// MARK: - Code below here is support for the SwiftProtobuf runtime.
+
+fileprivate let _protobuf_package = "helloworld"
+
+extension Helloworld_HelloRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
+  static let protoMessageName: String = _protobuf_package + ".HelloRequest"
+  static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "name"),
+  ]
+
+  mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      // The use of inline closures is to circumvent an issue where the compiler
+      // allocates stack space for every case branch when no optimizations are
+      // enabled. https://github.com/apple/swift-protobuf/issues/1034
+      switch fieldNumber {
+      case 1: try { try decoder.decodeSingularStringField(value: &self.name) }()
+      default: break
+      }
+    }
+  }
+
+  func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
+    if !self.name.isEmpty {
+      try visitor.visitSingularStringField(value: self.name, fieldNumber: 1)
+    }
+    try unknownFields.traverse(visitor: &visitor)
+  }
+
+  static func ==(lhs: Helloworld_HelloRequest, rhs: Helloworld_HelloRequest) -> Bool {
+    if lhs.name != rhs.name {return false}
+    if lhs.unknownFields != rhs.unknownFields {return false}
+    return true
+  }
+}
+
+extension Helloworld_HelloReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
+  static let protoMessageName: String = _protobuf_package + ".HelloReply"
+  static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "message"),
+  ]
+
+  mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      // The use of inline closures is to circumvent an issue where the compiler
+      // allocates stack space for every case branch when no optimizations are
+      // enabled. https://github.com/apple/swift-protobuf/issues/1034
+      switch fieldNumber {
+      case 1: try { try decoder.decodeSingularStringField(value: &self.message) }()
+      default: break
+      }
+    }
+  }
+
+  func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
+    if !self.message.isEmpty {
+      try visitor.visitSingularStringField(value: self.message, fieldNumber: 1)
+    }
+    try unknownFields.traverse(visitor: &visitor)
+  }
+
+  static func ==(lhs: Helloworld_HelloReply, rhs: Helloworld_HelloReply) -> Bool {
+    if lhs.message != rhs.message {return false}
+    if lhs.unknownFields != rhs.unknownFields {return false}
+    return true
+  }
+}

+ 27 - 0
Sources/Examples/v2/hello-world/HelloWorld.swift

@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024, 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.
+ */
+
+internal import ArgumentParser
+
+@main
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+struct HelloWorld: AsyncParsableCommand {
+  static let configuration = CommandConfiguration(
+    commandName: "hello-world",
+    abstract: "A multi-tool to run a greeter server and execute RPCs against it.",
+    subcommands: [Serve.self, Greet.self]
+  )
+}

+ 49 - 0
Sources/Examples/v2/hello-world/Subcommands/Greet.swift

@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024, 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.
+ */
+
+internal import ArgumentParser
+internal import GRPCHTTP2Transport
+internal import GRPCProtobuf
+
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+struct Greet: AsyncParsableCommand {
+  static let configuration = CommandConfiguration(abstract: "Sends a request to the greeter server")
+
+  @Option(help: "The port to listen on")
+  var port: Int = 31415
+
+  @Option(help: "The person to greet")
+  var name: String = ""
+
+  func run() async throws {
+    try await withThrowingDiscardingTaskGroup { group in
+      let http2 = try HTTP2ClientTransport.Posix(target: .ipv4(host: "127.0.0.1", port: self.port))
+      let client = GRPCClient(transport: http2)
+
+      group.addTask {
+        try await client.run()
+      }
+
+      defer {
+        client.close()
+      }
+
+      let greeter = Helloworld_GreeterClient(wrapping: client)
+      let reply = try await greeter.sayHello(.with { $0.name = self.name })
+      print(reply.message)
+    }
+  }
+}

+ 54 - 0
Sources/Examples/v2/hello-world/Subcommands/Serve.swift

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024, 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.
+ */
+
+internal import ArgumentParser
+internal import GRPCHTTP2Transport
+internal import GRPCProtobuf
+
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+struct Serve: AsyncParsableCommand {
+  static let configuration = CommandConfiguration(abstract: "Starts a greeter server.")
+
+  @Option(help: "The port to listen on")
+  var port: Int = 31415
+
+  func run() async throws {
+    let http2 = HTTP2ServerTransport.Posix(
+      address: .ipv4(host: "127.0.0.1", port: self.port),
+      config: .defaults(transportSecurity: .plaintext)
+    )
+
+    let server = GRPCServer(transport: http2, services: [Greeter()])
+
+    try await withThrowingDiscardingTaskGroup { group in
+      group.addTask { try await server.run() }
+      let address = try await http2.listeningAddress
+      print("Greeter listening on \(address)")
+    }
+  }
+}
+
+@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
+struct Greeter: Helloworld_GreeterServiceProtocol {
+  func sayHello(
+    request: ServerRequest.Single<Helloworld_HelloRequest>
+  ) async throws -> ServerResponse.Single<Helloworld_HelloReply> {
+    var reply = Helloworld_HelloReply()
+    let recipient = request.message.name.isEmpty ? "stranger" : request.message.name
+    reply.message = "Hello, \(recipient)"
+    return ServerResponse.Single(message: reply)
+  }
+}