Browse Source

Created README for the Reflection Service (#1694)

Motivation:

To help users set up a Server that is implementing the Reflection Service.

Modifications:

Added a step by step tutorial on how to set up the example server and run GRPCurl
in order to test the Reflection Service.

Result:

Users will get information on how to set up the Reflection Service for a Server.
Stefana-Ioana Dranca 2 years ago
parent
commit
7ea3260e29

+ 24 - 0
Makefile

@@ -196,6 +196,30 @@ ${TEST_REFLECTION_V1ALPHA_PB}: ${REFLECTION_V1ALPHA_PROTO} ${PROTOC_GEN_SWIFT}
 .PHONY:
 generate-reflection-test-clients: ${TEST_REFLECTION_V1_PB} ${TEST_REFLECTION_V1_GRPC} ${TEST_REFLECTION_V1ALPHA_PB} ${TEST_REFLECTION_V1ALPHA_GRPC}
 
+HELLOWORLD_SERIALIZED_PROTO_GRPC=Sources/Examples/ReflectionService/Generated/helloworld.grpc.reflection.txt
+
+${HELLOWORLD_SERIALIZED_PROTO_GRPC}: ${HELLOWORLD_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_GRPC_SWIFT} \
+		--grpc-swift_opt=Client=false,Server=false,ReflectionData=true \
+		--grpc-swift_out=$(dir ${HELLOWORLD_SERIALIZED_PROTO_GRPC})
+    
+.PHONY:
+generate-helloworld-reflection-data: ${HELLOWORLD_SERIALIZED_PROTO_GRPC}
+
+ECHO_SERIALIZED_PROTO_GRPC=Sources/Examples/ReflectionService/Generated/echo.grpc.reflection.txt
+
+${ECHO_SERIALIZED_PROTO_GRPC}: ${ECHO_PROTO} ${PROTOC_GEN_GRPC_SWIFT}
+	protoc $< \
+		--proto_path=$(dir $<) \
+		--plugin=${PROTOC_GEN_GRPC_SWIFT} \
+		--grpc-swift_opt=Client=false,Server=false,ReflectionData=true \
+		--grpc-swift_out=$(dir ${ECHO_SERIALIZED_PROTO_GRPC})
+    
+.PHONY:
+generate-echo-reflection-data: ${ECHO_SERIALIZED_PROTO_GRPC}
+
 ### Testing ####################################################################
 
 # Normal test suite.

+ 19 - 0
Package.swift

@@ -459,6 +459,24 @@ extension Target {
       "v1Alpha/reflection-v1alpha.proto"
     ]
   )
+  
+  static let reflectionServer: Target = .executableTarget(
+    name: "ReflectionServer",
+    dependencies: [
+      .grpc,
+      .reflectionService,
+      .helloWorldModel,
+      .nioCore,
+      .nioPosix,
+      .argumentParser,
+      .echoModel,
+      .echoImplementation
+    ],
+    path: "Sources/Examples/ReflectionService",
+    resources: [
+      .copy("Generated")
+    ]
+  )
 }
 
 // MARK: - Products
@@ -530,6 +548,7 @@ let package = Package(
     .routeGuideClient,
     .routeGuideServer,
     .packetCapture,
+    .reflectionServer,
 
     // v2
     .grpcCore,

File diff suppressed because it is too large
+ 0 - 0
Sources/Examples/ReflectionService/Generated/echo.grpc.reflection.txt


+ 1 - 0
Sources/Examples/ReflectionService/Generated/helloworld.grpc.reflection.txt

@@ -0,0 +1 @@
+ChBoZWxsb3dvcmxkLnByb3RvEgpoZWxsb3dvcmxkIiIKDEhlbGxvUmVxdWVzdBISCgRuYW1lGAEgASgJUgRuYW1lIiYKCkhlbGxvUmVwbHkSGAoHbWVzc2FnZRgBIAEoCVIHbWVzc2FnZTJJCgdHcmVldGVyEj4KCFNheUhlbGxvEhguaGVsbG93b3JsZC5IZWxsb1JlcXVlc3QaFi5oZWxsb3dvcmxkLkhlbGxvUmVwbHkiAEI2Chtpby5ncnBjLmV4YW1wbGVzLmhlbGxvd29ybGRCD0hlbGxvV29ybGRQcm90b1ABogIDSExXSrMICgYSBA4AJQEKvwQKAQwSAw4AEjK0BCBDb3B5cmlnaHQgMjAxNSBnUlBDIGF1dGhvcnMuCgogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKCiAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCgogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgoICgEIEgMQACIKCQoCCAoSAxAAIgoICgEIEgMRADQKCQoCCAESAxEANAoICgEIEgMSADAKCQoCCAgSAxIAMAoICgEIEgMTACEKCQoCCCQSAxMAIQoICgECEgMVABMKLgoCBgASBBgAGwEaIiBUaGUgZ3JlZXRpbmcgc2VydmljZSBkZWZpbml0aW9uLgoKCgoDBgABEgMYCA8KIAoEBgACABIDGgI1GhMgU2VuZHMgYSBncmVldGluZy4KCgwKBQYAAgABEgMaBg4KDAoFBgACAAISAxoQHAoMCgUGAAIAAxIDGicxCj0KAgQAEgQeACABGjEgVGhlIHJlcXVlc3QgbWVzc2FnZSBjb250YWluaW5nIHRoZSB1c2VyJ3MgbmFtZS4KCgoKAwQAARIDHggUCgsKBAQAAgASAx8CEgoMCgUEAAIABRIDHwIICgwKBQQAAgABEgMfCQ0KDAoFBAACAAMSAx8QEQo8CgIEARIEIwAlARowIFRoZSByZXNwb25zZSBtZXNzYWdlIGNvbnRhaW5pbmcgdGhlIGdyZWV0aW5ncy4KCgoKAwQBARIDIwgSCgsKBAQBAgASAyQCFQoMCgUEAQIABRIDJAIICgwKBQQBAgABEgMkCRAKDAoFBAECAAMSAyQTFGIGcHJvdG8z

+ 1 - 0
Sources/Examples/ReflectionService/GreeterProvider.swift

@@ -0,0 +1 @@
+../HelloWorld/Server/GreeterProvider.swift

+ 57 - 0
Sources/Examples/ReflectionService/ReflectionServer.swift

@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023, 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 ArgumentParser
+import EchoImplementation
+import EchoModel
+import Foundation
+import GRPC
+import GRPCReflectionService
+import NIOPosix
+import SwiftProtobuf
+
+@available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *)
+@main
+struct ReflectionServer: AsyncParsableCommand {
+  func run() async throws {
+    // Getting the URLs of the files containing the reflection data.
+    guard
+      let greeterURL = Bundle.module.url(
+        forResource: "helloworld",
+        withExtension: "grpc.reflection.txt"
+      ),
+      let echoURL = Bundle.module.url(forResource: "echo", withExtension: "grpc.reflection.txt")
+    else {
+      print("The resource could not be loaded.")
+      throw ExitCode.failure
+    }
+
+    let reflectionService = try ReflectionService(
+      reflectionDataFileURLs: [greeterURL, echoURL],
+      version: .v1
+    )
+
+    // Start the server and print its address once it has started.
+    let server = try await Server.insecure(group: MultiThreadedEventLoopGroup.singleton)
+      .withServiceProviders([reflectionService, GreeterProvider(), EchoProvider()])
+      .bind(host: "localhost", port: 1234)
+      .get()
+
+    print("server started on port \(server.channel.localAddress!.port!)")
+    // Wait on the server's `onClose` future to stop the program from exiting.
+    try await server.onClose.get()
+  }
+}

+ 189 - 0
Sources/GRPCReflectionService/Documentation.docc/ReflectionServiceTutorial.md

@@ -0,0 +1,189 @@
+# Reflection service
+
+This tutorial goes through the steps of adding Reflection service to a 
+server, running it and testing it using gRPCurl. 
+
+ The server used in this example is implemented at 
+ [Sources/Examples/ReflectionService/ReflectionServer.swift][reflection-server]
+ and it supports the "Greeter", "Echo", and "Reflection" services. 
+
+
+## Overview
+
+The Reflection service provides information about the public RPCs served by a server. 
+It is specific to services defined using the Protocol Buffers IDL.
+By calling the Reflection service, clients can construct and send requests to services
+without needing to generate code and types for them. 
+
+You can also use CLI clients such as [gRPCurl][grpcurl-setup] and the [gRPC command line tool][grpc-cli] to: 
+- list services,
+- describe services and their methods,
+- describe symbols,
+- describe extensions,
+- construct and invoke RPCs.
+
+gRPC Swift supports both [v1][v1] and [v1alpha][v1alpha] of the reflection service.
+
+## Adding the Reflection service to a server
+
+You can use the Reflection service by adding it as a provider when constructing your server.
+
+To initialise the Reflection service we will use 
+``GRPCReflectionService/ReflectionService/init(reflectionDataFileURLs:version:)``.
+It receives the URLs of the files containing the reflection data of the proto files 
+describing the services of the server and the version of the reflection service.
+
+### Generating the reflection data
+
+The server from this example uses the `GreeterProvider` and the `EchoProvider`,
+besides the `ReflectionService`.
+
+The associated proto files are located at `Sources/Examples/HelloWorld/Model/helloworld.proto`, and 
+`Sources/Examples/Echo/Model/echo.proto` respectively.
+
+In order to generate the reflection data for the `helloworld.proto`, you can run the following command:
+
+```sh
+$ protoc Sources/Examples/HelloWorld/Model/helloworld.proto \
+    --proto_path=Sources/Examples/HelloWorld/Model \
+    --grpc-swift_opt=Client=false,Server=false,ReflectionData=true \
+    --grpc-swift_out=Sources/Examples/ReflectionService/Generated
+```
+
+Let's break the command down:
+- The first argument passed to `protoc` is the path 
+  to the `.proto` file to generate reflection data 
+  for: [`Sources/Examples/HelloWorld/Model/helloworld.proto`][helloworld-proto].
+- The `proto_path` flag is the path to search for imports: `Sources/Examples/HelloWorld/Model`.
+- The 'grpc-swift_opt' flag allows us to list options for the Swift generator.
+  To generate only the reflection data set: `Client=false,Server=false,ReflectionData=true`.
+- The `grpc-swift_out` flag is used to set the path of the directory
+  where the generated file will be located: `Sources/Examples/ReflectionService/Generated`.
+
+This command assumes that the `protoc-gen-grpc-swift` plugin is in your `$PATH` environment variable.
+You can learn how to get the plugin from this section of the `grpc-swift` README: 
+https://github.com/grpc/grpc-swift#getting-the-protoc-plugins.
+
+The command for generating the reflection data for the `Echo` service is similar.
+
+You can use Swift Package Manager [resources][swiftpm-resources] to add the generated reflection data to your target. 
+In our example the reflection data is written into the "Generated" directory within the target 
+so we include the `.copy("Generated")` rule in our target's resource list.
+
+### Instantiating the Reflection service 
+
+To instantiate the `ReflectionService` you need to pass the URLs of the files containing 
+the generated reflection data and the version to use, in our case `.v1`.
+
+Depending on the version of [gRPCurl][grpcurl] you are using you might need to use the `.v1alpha` instead.
+Beginning with [gRPCurl v1.8.8][grpcurl-v188] it uses the [v1][v1] reflection. Earlier versions use [v1alpha][v1alpha]
+reflection.
+
+```swift
+// Getting the URLs of the files containing the reflection data.
+guard
+  let greeterURL = Bundle.module.url(
+    forResource: "helloworld",
+    withExtension: "grpc.reflection.txt"
+  ),
+  let echoURL = Bundle.module.url(forResource: "echo", withExtension: "grpc.reflection.txt")
+else {
+  print("The resource could not be loaded.")
+  throw ExitCode.failure
+}
+let reflectionService = try ReflectionService(
+  reflectionDataFileURLs: [greeterURL, echoURL],
+  version: .v1
+)
+```
+
+### Running the server
+
+In our example the server isn't configured with TLS and listens on localhost port 1234.
+The following code configures and starts the server:
+
+```swift
+let server = try await Server.insecure(group: group)
+  .withServiceProviders([reflectionService, GreeterProvider(), EchoProvider()])
+  .bind(host: "localhost", port: self.port)
+  .get()
+
+```
+
+To run the server, from the root of the package run:
+
+```sh
+$ swift run ReflectionServer
+```
+
+## Calling the Reflection service with gRPCurl
+
+Please follow the instructions from the [gRPCurl README][grpcurl-setup] to set up gRPCurl.
+
+From a different terminal than the one used for running the server, we will call gRPCurl commands,
+following the format: `grpcurl [flags] [address] [list|describe] [symbol]`.
+
+We use the `-plaintext` flag, because the server isn't configured with TLS, and 
+the address is set to `localhost:1234`.
+
+
+To see the available services use `list`:
+
+```sh
+$ grpcurl -plaintext localhost:1234 list
+echo.Echo
+helloworld.Greeter
+```
+
+To see what methods are available for a service:
+
+```sh
+$ grpcurl -plaintext localhost:1234 list echo.Echo
+echo.Echo.Collect
+echo.Echo.Expand
+echo.Echo.Get
+echo.Echo.Update
+```
+
+You can also get descriptions of objects like services, methods, and messages. The following
+command fetches a description of the Echo service:
+
+```sh
+$ grpcurl -plaintext localhost:1234 describe echo.Echo
+echo.Echo is a service:
+service Echo {
+  // Collects a stream of messages and returns them concatenated when the caller closes.
+  rpc Collect ( stream .echo.EchoRequest ) returns ( .echo.EchoResponse );
+  // Splits a request into words and returns each word in a stream of messages.
+  rpc Expand ( .echo.EchoRequest ) returns ( stream .echo.EchoResponse );
+  // Immediately returns an echo of a request.
+  rpc Get ( .echo.EchoRequest ) returns ( .echo.EchoResponse );
+  // Streams back messages as they are received in an input stream.
+  rpc Update ( stream .echo.EchoRequest ) returns ( stream .echo.EchoResponse );
+}
+```
+
+You can send requests to the services with gRPCurl:
+
+```sh
+$ grpcurl -d '{ "text": "test" }' -plaintext localhost:1234 echo.Echo.Get
+{
+  "text": "Swift echo get: test"
+}
+```
+
+Note that when specifying a service, a method or a symbol, we have to use the fully qualified names:
+- service: \<package\>.\<service\>
+- method: \<package\>.\<service\>.\<method\>
+- type: \<package\>.\<type\>
+
+[grpcurl-setup]: https://github.com/fullstorydev/grpcurl#grpcurl
+[grpcurl]: https://github.com/fullstorydev/grpcurl
+[grpc-cli]: https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md
+[v1]: ../v1/reflection-v1.proto
+[v1alpha]: ../v1Alpha/reflection-v1alpha.proto
+[reflection-server]: ../../Examples/ReflectionService/ReflectionServer.swift
+[helloworld-proto]: ../../Examples/HelloWorld/Model/helloworld.proto
+[echo-proto]: ../../Examples/Echo/Model/echo.proto
+[grpcurl-v188]: https://github.com/fullstorydev/grpcurl/releases/tag/v1.8.8
+[swiftpm-resources]: https://github.com/apple/swift-package-manager/blob/main/Documentation/PackageDescription.md#resource

+ 24 - 1
Sources/GRPCReflectionService/Server/ReflectionService.swift

@@ -32,6 +32,29 @@ public final class ReflectionService: CallHandlerProvider, Sendable {
     }
   }
 
+  /// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
+  ///
+  /// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
+  /// setting the `ReflectionData` option  to `True`.
+  ///
+  /// - Parameter fileURLs: The URLs of the files containing serialized reflection data.
+  /// - Parameter version: The version of the reflection service to create.
+  ///
+  /// - Throws: When a file can't be read from disk or parsed.
+  public convenience init(reflectionDataFileURLs fileURLs: [URL], version: Version) throws {
+    let filePaths: [String]
+    #if os(Linux)
+    filePaths = fileURLs.map { $0.path }
+    #else
+    if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
+      filePaths = fileURLs.map { $0.path() }
+    } else {
+      filePaths = fileURLs.map { $0.path }
+    }
+    #endif
+    try self.init(reflectionDataFilePaths: filePaths, version: version)
+  }
+
   /// Creates a `ReflectionService` by loading serialized reflection data created by `protoc-gen-grpc-swift`.
   ///
   /// You can generate serialized reflection data using the `protoc-gen-grpc-swift` plugin for `protoc` by
@@ -42,7 +65,7 @@ public final class ReflectionService: CallHandlerProvider, Sendable {
   /// - Parameter version: The version of the reflection service to create.
   ///
   /// - Throws: When a file can't be read from disk or parsed.
-  public init(serializedFileDescriptorProtoFilePaths filePaths: [String], version: Version) throws {
+  public init(reflectionDataFilePaths filePaths: [String], version: Version) throws {
     let fileDescriptorProtos = try ReflectionService.readSerializedFileDescriptorProtos(
       atPaths: filePaths
     )

Some files were not shown because too many files changed in this diff