Bläddra i källkod

Add HelloWorld example (#590)

Motivation:

To help users get started quickly with gRPC Swift it would be useful to
have a "hello world" example. grpc.io has such examples for
a number of languages: https://grpc.io/docs/quickstart/ but not for Swift.

Another motivating factor for this change was feedback from the gRPC on
the SSWG draft proposal suggesting that it would be useful to frame the
proposal from a canonical example, either "HelloWorld" or "RouteGuide".

Modifications:

- Add a "HelloWorld" example
- Create a "quick-start" guide modelled on those on grpc.io

Result:

- No functional changes
- Another example and tutorial
George Barnett 6 år sedan
förälder
incheckning
65415e3c2a

+ 45 - 17
Makefile

@@ -5,6 +5,10 @@ SWIFT_BUILD_PATH:=./.build
 SWIFT_BUILD_CONFIGURATION:=debug
 SWIFT_FLAGS:=--build-path=${SWIFT_BUILD_PATH} --configuration=${SWIFT_BUILD_CONFIGURATION}
 
+# protoc plugins.
+PROTOC_GEN_SWIFT=${SWIFT_BUILD_PATH}/${SWIFT_BUILD_CONFIGURATION}/protoc-gen-swift
+PROTOC_GEN_GRPC_SWIFT=${SWIFT_BUILD_PATH}/${SWIFT_BUILD_CONFIGURATION}/protoc-gen-grpc-swift
+
 SWIFT_BUILD:=${SWIFT} build ${SWIFT_FLAGS}
 SWIFT_TEST:=${SWIFT} test ${SWIFT_FLAGS}
 SWIFT_PACKAGE:=${SWIFT} package ${SWIFT_FLAGS}
@@ -17,12 +21,13 @@ XCODEPROJ:=GRPC.xcodeproj
 all:
 	${SWIFT_BUILD}
 
-plugins: protoc-gen-swift protoc-gen-grpc-swift
+.PHONY:
+plugins: ${PROTOC_GEN_SWIFT} ${PROTOC_GEN_GRPC_SWIFT}
 
-protoc-gen-swift:
+${PROTOC_GEN_SWIFT}:
 	${SWIFT_BUILD} --product protoc-gen-swift
 
-protoc-gen-grpc-swift:
+${PROTOC_GEN_GRPC_SWIFT}:
 	${SWIFT_BUILD} --product protoc-gen-grpc-swift
 
 interop-test-runner:
@@ -33,6 +38,7 @@ interop-backoff-test-runner:
 
 ### Xcodeproj and LinuxMain
 
+.PHONY:
 project: ${XCODEPROJ}
 
 ${XCODEPROJ}:
@@ -46,40 +52,62 @@ generate-linuxmain:
 
 ### Protobuf Generation ########################################################
 
-# Generates protobufs and gRPC client and server for the Echo example
-generate-echo: plugins
-	protoc Sources/Examples/Echo/Model/echo.proto \
-		--proto_path=Sources/Examples/Echo/Model \
-		--plugin=${SWIFT_BUILD_PATH}/${SWIFT_BUILD_CONFIGURATION}/protoc-gen-swift \
-		--plugin=${SWIFT_BUILD_PATH}/${SWIFT_BUILD_CONFIGURATION}/protoc-gen-grpc-swift \
+%.pb.swift: %.proto ${PROTOC_GEN_SWIFT}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_SWIFT} \
 		--swift_opt=Visibility=Public \
-		--swift_out=Sources/Examples/Echo/Model/Generated \
+		--swift_out=$(dir $<)
+
+%.grpc.swift: %.proto ${PROTOC_GEN_GRPC_SWIFT}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_GRPC_SWIFT} \
 		--grpc-swift_opt=Visibility=Public \
-		--grpc-swift_out=Sources/Examples/Echo/Model/Generated
+		--grpc-swift_out=$(dir $<)
+
+ECHO_PROTO=Sources/Examples/Echo/Model/echo.proto
+ECHO_PB=$(ECHO_PROTO:.proto=.pb.swift)
+ECHO_GRPC=$(ECHO_PROTO:.proto=.grpc.swift)
+
+# Generates protobufs and gRPC client and server for the Echo example
+.PHONY:
+generate-echo: ${ECHO_PB} ${ECHO_GRPC}
+
+HELLOWORLD_PROTO=Sources/Examples/HelloWorld/Model/helloworld.proto
+HELLOWORLD_PB=$(HELLOWORLD_PROTO:.proto=.pb.swift)
+HELLOWORLD_GRPC=$(HELLOWORLD_PROTO:.proto=.grpc.swift)
+
+# Generates protobufs and gRPC client and server for the Hello World example
+.PHONY:
+generate-helloworld: ${HELLOWORLD_PB} ${HELLOWORLD_GRPC}
 
 ### Testing ####################################################################
 
 # Normal test suite.
+.PHONY:
 test:
 	${SWIFT_TEST}
 
 # Checks that linuxmain has been updated: requires macOS.
+.PHONY:
 test-generate-linuxmain: generate-linuxmain
 	@git diff --exit-code Tests/LinuxMain.swift Tests/*/XCTestManifests.swift > /dev/null || \
 		{ echo "Generated tests are out-of-date; run 'swift test --generate-linuxmain' to update them!"; exit 1; }
 
 # Generates code for the Echo server and client and tests them against 'golden' data.
-test-plugin: plugins
-	protoc Sources/Examples/Echo/Model/echo.proto \
-		--proto_path=Sources/Examples/Echo/Model \
-		--plugin=${SWIFT_BUILD_PATH}/${SWIFT_BUILD_CONFIGURATION}/protoc-gen-swift \
-		--plugin=${SWIFT_BUILD_PATH}/${SWIFT_BUILD_CONFIGURATION}/protoc-gen-grpc-swift \
+.PHONY:
+test-plugin: ${ECHO_PROTO} ${PROTOC_GEN_GRPC_SWIFT} ${ECHO_GRPC}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_GRPC_SWIFT} \
 		--grpc-swift_opt=Visibility=Public \
 		--grpc-swift_out=/tmp
-	diff -u /tmp/echo.grpc.swift Sources/Examples/Echo/Model/Generated/echo.grpc.swift
+	diff -u /tmp/echo.grpc.swift ${ECHO_GRPC}
 
 ### Misc. ######################################################################
 
+.PHONY:
 clean:
 	-rm -rf Packages
 	-rm -rf ${SWIFT_BUILD_PATH}

+ 33 - 0
Package.swift

@@ -168,5 +168,38 @@ let package = Package(
       ],
       path: "Sources/Examples/Echo/Model"
     ),
+
+    // Model for the HelloWorld example
+    .target(
+      name: "HelloWorldModel",
+      dependencies: [
+        "GRPC",
+        "NIO",
+        "NIOHTTP1",
+        "SwiftProtobuf"
+      ],
+      path: "Sources/Examples/HelloWorld/Model"
+    ),
+
+    // Client for the HelloWorld example
+    .target(
+      name: "HelloWorldClient",
+      dependencies: [
+        "GRPC",
+        "HelloWorldModel",
+      ],
+      path: "Sources/Examples/HelloWorld/Client"
+    ),
+
+    // Server for the HelloWorld example
+    .target(
+      name: "HelloWorldServer",
+      dependencies: [
+        "GRPC",
+        "NIO",
+        "HelloWorldModel",
+      ],
+      path: "Sources/Examples/HelloWorld/Server"
+    ),
   ]
 )

+ 1 - 1
README.md

@@ -38,7 +38,7 @@ necessary targets:
 
 ```swift
 dependencies: [
-  .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0-alpha.5")
+  .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.0.0-alpha.6")
 ]
 ```
 

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


+ 1 - 1
Sources/Examples/Echo/Model/Generated/echo.pb.swift → Sources/Examples/Echo/Model/echo.pb.swift

@@ -3,7 +3,7 @@
 // Generated by the Swift generator plugin for the protocol buffer compiler.
 // Source: echo.proto
 //
-// For information on using the generated types, please see the documentation:
+// For information on using the generated types, please see the documenation:
 //   https://github.com/apple/swift-protobuf/
 
 // Copyright (c) 2015, Google Inc.

+ 86 - 0
Sources/Examples/HelloWorld/Client/main.swift

@@ -0,0 +1,86 @@
+/*
+ * 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 GRPC
+import HelloWorldModel
+import NIO
+import Logging
+
+// Quieten the logs.
+LoggingSystem.bootstrap {
+  var handler = StreamLogHandler.standardOutput(label: $0)
+  handler.logLevel = .critical
+  return handler
+}
+
+func greet(name: String?, client greeter: Helloworld_GreeterServiceClient) {
+  // Form the request with the name, if one was provided.
+  let request = Helloworld_HelloRequest.with {
+    $0.name = name ?? ""
+  }
+
+  // Make the RPC call to the server.
+  let sayHello = greeter.sayHello(request)
+
+  // wait() on the response to stop the program from exiting before the response is received.
+  do {
+    let response = try sayHello.response.wait()
+    print("Greeter received: \(response.message)")
+  } catch {
+    print("Greeter failed: \(error)")
+  }
+}
+
+func main(args: [String]) {
+  // arg0 (dropped) is the program name. We expect arg1 to be the port, and arg2 (optional) to be
+  // the name sent in the request.
+  let arg1 = args.dropFirst(1).first
+  let arg2 = args.dropFirst(2).first
+
+  switch (arg1.flatMap(Int.init), arg2) {
+  case (.none, _):
+    print("Usage: PORT [NAME]")
+    exit(1)
+
+  case let (.some(port), name):
+    // Setup an `EventLoopGroup` for the connection to run on.
+    //
+    // See: https://github.com/apple/swift-nio#eventloops-and-eventloopgroups
+    let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+
+    // Make sure the group is shutdown when we're done with it.
+    defer {
+      try! group.syncShutdownGracefully()
+    }
+
+    // Provide some basic configuration for the connection, in this case we connect to an endpoint on
+    // localhost at the given port.
+    let configuration = ClientConnection.Configuration(
+      target: .hostAndPort("localhost", port),
+      eventLoopGroup: group
+    )
+
+    // Create a connection using the configuration.
+    let connection = ClientConnection(configuration: configuration)
+
+    // Provide the connection to the generated client.
+    let greeter = Helloworld_GreeterServiceClient(connection: connection)
+
+    // Do the greeting.
+    greet(name: name, client: greeter)
+  }
+}
+
+main(args: CommandLine.arguments)

+ 87 - 0
Sources/Examples/HelloWorld/Model/helloworld.grpc.swift

@@ -0,0 +1,87 @@
+//
+// DO NOT EDIT.
+//
+// Generated by the protocol buffer compiler.
+// Source: helloworld.proto
+//
+
+//
+// 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 Foundation
+import GRPC
+import NIO
+import NIOHTTP1
+import SwiftProtobuf
+
+
+/// Usage: instantiate Helloworld_GreeterServiceClient, then call methods of this protocol to make API calls.
+public protocol Helloworld_GreeterService {
+  func sayHello(_ request: Helloworld_HelloRequest, callOptions: CallOptions?) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply>
+}
+
+public final class Helloworld_GreeterServiceClient: GRPCServiceClient, Helloworld_GreeterService {
+  public let connection: ClientConnection
+  public var serviceName: String { return "helloworld.Greeter" }
+  public var defaultCallOptions: CallOptions
+
+  /// Creates a client for the helloworld.Greeter service.
+  ///
+  /// - Parameters:
+  ///   - connection: `ClientConnection` to the service host.
+  ///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them.
+  public init(connection: ClientConnection, defaultCallOptions: CallOptions = CallOptions()) {
+    self.connection = connection
+    self.defaultCallOptions = defaultCallOptions
+  }
+
+  /// Asynchronous unary call to SayHello.
+  ///
+  /// - Parameters:
+  ///   - request: Request to send to SayHello.
+  ///   - callOptions: Call options; `self.defaultCallOptions` is used if `nil`.
+  /// - Returns: A `UnaryCall` with futures for the metadata, status and response.
+  public func sayHello(_ request: Helloworld_HelloRequest, callOptions: CallOptions? = nil) -> UnaryCall<Helloworld_HelloRequest, Helloworld_HelloReply> {
+    return self.makeUnaryCall(path: self.path(forMethod: "SayHello"),
+                              request: request,
+                              callOptions: callOptions ?? self.defaultCallOptions)
+  }
+
+}
+
+/// To build a server, implement a class that conforms to this protocol.
+public protocol Helloworld_GreeterProvider: CallHandlerProvider {
+  func sayHello(request: Helloworld_HelloRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Helloworld_HelloReply>
+}
+
+extension Helloworld_GreeterProvider {
+  public var serviceName: String { return "helloworld.Greeter" }
+
+  /// Determines, calls and returns the appropriate request handler, depending on the request's method.
+  /// Returns nil for methods not handled by this service.
+  public func handleMethod(_ methodName: String, callHandlerContext: CallHandlerContext) -> GRPCCallHandler? {
+    switch methodName {
+    case "SayHello":
+      return UnaryCallHandler(callHandlerContext: callHandlerContext) { context in
+        return { request in
+          self.sayHello(request: request, context: context)
+        }
+      }
+
+    default: return nil
+    }
+  }
+}
+

+ 122 - 0
Sources/Examples/HelloWorld/Model/helloworld.pb.swift

@@ -0,0 +1,122 @@
+// DO NOT EDIT.
+//
+// Generated by the Swift generator plugin for the protocol buffer compiler.
+// Source: helloworld.proto
+//
+// For information on using the generated types, please see the documenation:
+//   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 your 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.
+public struct Helloworld_HelloRequest {
+  // 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.
+
+  public var name: String = String()
+
+  public var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  public init() {}
+}
+
+/// The response message containing the greetings.
+public struct Helloworld_HelloReply {
+  // 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.
+
+  public var message: String = String()
+
+  public var unknownFields = SwiftProtobuf.UnknownStorage()
+
+  public 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 {
+  public static let protoMessageName: String = _protobuf_package + ".HelloRequest"
+  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "name"),
+  ]
+
+  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      switch fieldNumber {
+      case 1: try decoder.decodeSingularStringField(value: &self.name)
+      default: break
+      }
+    }
+  }
+
+  public 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)
+  }
+
+  public 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 {
+  public static let protoMessageName: String = _protobuf_package + ".HelloReply"
+  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
+    1: .same(proto: "message"),
+  ]
+
+  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
+    while let fieldNumber = try decoder.nextFieldNumber() {
+      switch fieldNumber {
+      case 1: try decoder.decodeSingularStringField(value: &self.message)
+      default: break
+      }
+    }
+  }
+
+  public 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)
+  }
+
+  public 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
+  }
+}

+ 38 - 0
Sources/Examples/HelloWorld/Model/helloworld.proto

@@ -0,0 +1,38 @@
+// 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.
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting.
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings.
+message HelloReply {
+  string message = 1;
+}

+ 31 - 0
Sources/Examples/HelloWorld/Server/GreeterProvider.swift

@@ -0,0 +1,31 @@
+/*
+ * 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 GRPC
+import HelloWorldModel
+import NIO
+
+class GreeterProvider: Helloworld_GreeterProvider {
+  func sayHello(
+    request: Helloworld_HelloRequest,
+    context: StatusOnlyCallContext
+  ) -> EventLoopFuture<Helloworld_HelloReply> {
+    let recipient = request.name.isEmpty ? "stranger" : request.name
+    let response = Helloworld_HelloReply.with {
+      $0.message = "Hello \(recipient)!"
+    }
+    return context.eventLoop.makeSucceededFuture(response)
+  }
+}

+ 51 - 0
Sources/Examples/HelloWorld/Server/main.swift

@@ -0,0 +1,51 @@
+/*
+ * 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 GRPC
+import HelloWorldModel
+import NIO
+import Logging
+
+// Quieten the logs.
+LoggingSystem.bootstrap {
+  var handler = StreamLogHandler.standardOutput(label: $0)
+  handler.logLevel = .critical
+  return handler
+}
+
+let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
+defer {
+  try! group.syncShutdownGracefully()
+}
+
+// Create some configuration for the server:
+let configuration = Server.Configuration(
+  target: .hostAndPort("localhost", 0),
+  eventLoopGroup: group,
+  serviceProviders: [GreeterProvider()]
+)
+
+// Start the server and print its address once it has started.
+let server = Server.start(configuration: configuration)
+server.map {
+  $0.channel.localAddress
+}.whenSuccess { address in
+  print("server started on port \(address!.port!)")
+}
+
+// Wait on the server's `onClose` future to stop the program from exiting.
+_ = try server.flatMap {
+  $0.onClose
+}.wait()

+ 223 - 0
docs/quick-start.md

@@ -0,0 +1,223 @@
+# gRPC Swift Quick Start
+
+## Before you begin
+
+### Prerequisites
+
+#### Swift Version
+
+gRPC requires Swift 5.0 or higher.
+
+#### Install Protocol Buffers v3
+
+Install the protoc compiler that is used to generate gRPC service code. The
+simplest way to do this is to download pre-compiled binaries for your
+platform (`protoc-<version>-<platform>.zip`) from here:
+[https://github.com/google/protobuf/releases][protobuf-releases].
+
+* Unzip this file.
+* Update the environment variable `PATH` to include the path to the `protoc`
+  binary file.
+
+### Download the example
+
+You'll need a local copy of the example code to work through this quickstart.
+Download the example code from our GitHub repository (the following command
+clones the entire repository, but you just need the examples for this quickstart
+and other tutorials):
+
+```sh
+$ # Clone the repository at the latest release to get the example code:
+$ git clone -b 1.0.0-alpha.6 https://github.com/grpc/grpc-swift
+$ # Navigate to the repository
+$ cd grpc-swift/
+```
+
+## Run a gRPC application
+
+From the `grpc-swift` directory:
+
+1. Compile and run the server
+
+   ```sh
+   $ swift run HelloWorldServer
+   server started on port 52200
+   $ # Note: the port may be different on your machine.
+   ```
+
+2. In another terminal, compile and run the client
+
+   ```sh
+   $ swift run HelloWorldClient 52200
+   Greeter received: Hello stranger!
+   ```
+
+Congratulations! You've just run a client-server application with gRPC.
+
+## Update a gRPC service
+
+Now let's look at how to update the application with an extra method on the
+server for the client to call. Our gRPC service is defined using protocol
+buffers; you can find out lots more about how to define a service in a `.proto`
+file in [What is gRPC?][grpc-guides]. For now all you need to know is that both
+the server and the client "stub" have a `SayHello` RPC method that takes a
+`HelloRequest` parameter from the client and returns a `HelloReply` from the
+server, and that this method is defined like this:
+
+```proto
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting.
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings.
+message HelloReply {
+  string message = 1;
+}
+```
+
+Let's update this so that the `Greeter` service has two methods. Edit
+`Sources/Examples/HelloWorld/Model/helloworld.proto` and update it with a new
+`SayHelloAgain` method, with the same request and response types:
+
+```proto
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting.
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+  // Sends another greeting.
+  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings.
+message HelloReply {
+  string message = 1;
+}
+```
+
+(Don't forget to save the file!)
+
+### Update and run the application
+
+We need to regenerate
+`Sources/Examples/HelloWorld/Model/helloworld.grpc.swift`, which
+contains our generated gRPC client and server classes.
+
+From the `grpc-swift` directory run
+
+```sh
+make generate-helloworld
+```
+
+This also regenerates classes for populating, serializing, and retrieving our
+request and response types.
+
+However, we still need to implement and call the new method in the human-written
+parts of our example application.
+
+#### Update the server
+
+In the same directory, open
+`Sources/Examples/HelloWorld/Server/GreeterProvider.swift`. Implement the new
+method like this:
+
+```swift
+class GreeterProvider: Helloworld_GreeterProvider {
+  func sayHello(
+    request: Helloworld_HelloRequest,
+    context: StatusOnlyCallContext
+  ) -> EventLoopFuture<Helloworld_HelloReply> {
+    let recipient = request.name.isEmpty ? "stranger" : request.name
+    let response = Helloworld_HelloReply.with {
+      $0.message = "Hello \(recipient)!"
+    }
+    return context.eventLoop.makeSucceededFuture(response)
+  }
+
+  func sayHelloAgain(
+    request: Helloworld_HelloRequest,
+    context: StatusOnlyCallContext
+  ) -> EventLoopFuture<Helloworld_HelloReply> {
+    let recipient = request.name.isEmpty ? "stranger" : request.name
+    let response = Helloworld_HelloReply.with {
+      $0.message = "Hello again \(recipient)!"
+    }
+    return context.eventLoop.makeSucceededFuture(response)
+  }
+}
+```
+
+#### Update the client
+
+In the same directory, open
+`Sources/Examples/HelloWorld/Client/main.swift`. Call the new method like this:
+
+```swift
+func greet(name: String?, client greeter: Helloworld_GreeterServiceClient) {
+  // Form the request with the name, if one was provided.
+  let request = Helloworld_HelloRequest.with {
+    $0.name = name ?? ""
+  }
+
+  // Make the RPC call to the server.
+  let sayHello = greeter.sayHello(request)
+
+  // wait() on the response to stop the program from exiting before the response is received.
+  do {
+    let response = try sayHello.response.wait()
+    print("Greeter received: \(response.message)")
+  } catch {
+    print("Greeter failed: \(error)")
+    return
+  }
+
+  let sayHelloAgain = greeter.sayHelloAgain(request)
+  do {
+    let response = try sayHelloAgain.response.wait()
+    print("Greeter received: \(response.message)")
+  } catch {
+    print("Greeter failed: \(error)")
+    return
+  }
+}
+```
+
+#### Run!
+
+Just like we did before, from the top level `grpc-swift` directory:
+
+1. Compile and run the server
+
+   ```sh
+   $ swift run HelloWorldServer
+   server started on port 52416
+   $ # Note: the port may be different on your machine.
+   ```
+
+2. In another terminal, compile and run the client
+
+   ```sh
+   $ swift run HelloWorldClient 52416
+   Greeter received: Hello stranger!
+   Greeter received: Hello again stranger!
+   ```
+
+### What's next
+
+- Read a full explanation of how gRPC works in [What is gRPC?][grpc-guides] and
+  [gRPC Concepts][grpc-concepts]
+
+[grpc-guides]: https://grpc.io/docs/guides/
+[grpc-concepts]: https://grpc.io/docs/guides/concepts/
+[protobuf-releases]: https://github.com/google/protobuf/releases