Skip to content

Commit

Permalink
Created README for the Reflection Service (#1694)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stefanadranca authored Nov 16, 2023
1 parent 187b609 commit 7ea3260
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 1 deletion.
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -530,6 +548,7 @@ let package = Package(
.routeGuideClient,
.routeGuideServer,
.packetCapture,
.reflectionServer,

// v2
.grpcCore,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CgplY2hvLnByb3RvEgRlY2hvIiEKC0VjaG9SZXF1ZXN0EhIKBHRleHQYASABKAlSBHRleHQiIgoMRWNob1Jlc3BvbnNlEhIKBHRleHQYASABKAlSBHRleHQy2AEKBEVjaG8SLgoDR2V0EhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgASMwoGRXhwYW5kEhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgAwARI0CgdDb2xsZWN0EhEuZWNoby5FY2hvUmVxdWVzdBoSLmVjaG8uRWNob1Jlc3BvbnNlIgAoARI1CgZVcGRhdGUSES5lY2hvLkVjaG9SZXF1ZXN0GhIuZWNoby5FY2hvUmVzcG9uc2UiACgBMAFK/QoKBhIEDgAoAQrCBAoBDBIDDgASMrcEIENvcHlyaWdodCAoYykgMjAxNSwgR29vZ2xlIEluYy4KCiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgIkxpY2Vuc2UiKTsKIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS4KIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgICBodHRwOi8vd3d3LmFwYWNoZS5vcmcvbGljZW5zZXMvTElDRU5TRS0yLjAKCiBVbmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcgb3IgYWdyZWVkIHRvIGluIHdyaXRpbmcsIHNvZnR3YXJlCiBkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLAogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuCiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kCiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KCggKAQISAxAADQoKCgIGABIEEgAeAQoKCgMGAAESAxIIDAo4CgQGAAIAEgMUAjAaKyBJbW1lZGlhdGVseSByZXR1cm5zIGFuIGVjaG8gb2YgYSByZXF1ZXN0LgoKDAoFBgACAAESAxQGCQoMCgUGAAIAAhIDFAoVCgwKBQYAAgADEgMUICwKWQoEBgACARIDFwI6GkwgU3BsaXRzIGEgcmVxdWVzdCBpbnRvIHdvcmRzIGFuZCByZXR1cm5zIGVhY2ggd29yZCBpbiBhIHN0cmVhbSBvZiBtZXNzYWdlcy4KCgwKBQYAAgEBEgMXBgwKDAoFBgACAQISAxcNGAoMCgUGAAIBBhIDFyMpCgwKBQYAAgEDEgMXKjYKYgoEBgACAhIDGgI7GlUgQ29sbGVjdHMgYSBzdHJlYW0gb2YgbWVzc2FnZXMgYW5kIHJldHVybnMgdGhlbSBjb25jYXRlbmF0ZWQgd2hlbiB0aGUgY2FsbGVyIGNsb3Nlcy4KCgwKBQYAAgIBEgMaBg0KDAoFBgACAgUSAxoOFAoMCgUGAAICAhIDGhUgCgwKBQYAAgIDEgMaKzcKTQoEBgACAxIDHQJBGkAgU3RyZWFtcyBiYWNrIG1lc3NhZ2VzIGFzIHRoZXkgYXJlIHJlY2VpdmVkIGluIGFuIGlucHV0IHN0cmVhbS4KCgwKBQYAAgMBEgMdBgwKDAoFBgACAwUSAx0NEwoMCgUGAAIDAhIDHRQfCgwKBQYAAgMGEgMdKjAKDAoFBgACAwMSAx0xPQoKCgIEABIEIAAjAQoKCgMEAAESAyAIEwoyCgQEAAIAEgMiAhIaJSBUaGUgdGV4dCBvZiBhIG1lc3NhZ2UgdG8gYmUgZWNob2VkLgoKDAoFBAACAAUSAyICCAoMCgUEAAIAARIDIgkNCgwKBQQAAgADEgMiEBEKCgoCBAESBCUAKAEKCgoDBAEBEgMlCBQKLAoEBAECABIDJwISGh8gVGhlIHRleHQgb2YgYW4gZWNobyByZXNwb25zZS4KCgwKBQQBAgAFEgMnAggKDAoFBAECAAESAycJDQoMCgUEAQIAAxIDJxARYgZwcm90bzM=
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ChBoZWxsb3dvcmxkLnByb3RvEgpoZWxsb3dvcmxkIiIKDEhlbGxvUmVxdWVzdBISCgRuYW1lGAEgASgJUgRuYW1lIiYKCkhlbGxvUmVwbHkSGAoHbWVzc2FnZRgBIAEoCVIHbWVzc2FnZTJJCgdHcmVldGVyEj4KCFNheUhlbGxvEhguaGVsbG93b3JsZC5IZWxsb1JlcXVlc3QaFi5oZWxsb3dvcmxkLkhlbGxvUmVwbHkiAEI2Chtpby5ncnBjLmV4YW1wbGVzLmhlbGxvd29ybGRCD0hlbGxvV29ybGRQcm90b1ABogIDSExXSrMICgYSBA4AJQEKvwQKAQwSAw4AEjK0BCBDb3B5cmlnaHQgMjAxNSBnUlBDIGF1dGhvcnMuCgogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlICJMaWNlbnNlIik7CiB5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCiBZb3UgbWF5IG9idGFpbiBhIGNvcHkgb2YgdGhlIExpY2Vuc2UgYXQKCiAgICAgaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wCgogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gIkFTIElTIiBCQVNJUywKIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLgogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZAogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuCgoICgEIEgMQACIKCQoCCAoSAxAAIgoICgEIEgMRADQKCQoCCAESAxEANAoICgEIEgMSADAKCQoCCAgSAxIAMAoICgEIEgMTACEKCQoCCCQSAxMAIQoICgECEgMVABMKLgoCBgASBBgAGwEaIiBUaGUgZ3JlZXRpbmcgc2VydmljZSBkZWZpbml0aW9uLgoKCgoDBgABEgMYCA8KIAoEBgACABIDGgI1GhMgU2VuZHMgYSBncmVldGluZy4KCgwKBQYAAgABEgMaBg4KDAoFBgACAAISAxoQHAoMCgUGAAIAAxIDGicxCj0KAgQAEgQeACABGjEgVGhlIHJlcXVlc3QgbWVzc2FnZSBjb250YWluaW5nIHRoZSB1c2VyJ3MgbmFtZS4KCgoKAwQAARIDHggUCgsKBAQAAgASAx8CEgoMCgUEAAIABRIDHwIICgwKBQQAAgABEgMfCQ0KDAoFBAACAAMSAx8QEQo8CgIEARIEIwAlARowIFRoZSByZXNwb25zZSBtZXNzYWdlIGNvbnRhaW5pbmcgdGhlIGdyZWV0aW5ncy4KCgoKAwQBARIDIwgSCgsKBAQBAgASAyQCFQoMCgUEAQIABRIDJAIICgwKBQQBAgABEgMkCRAKDAoFBAECAAMSAyQTFGIGcHJvdG8z
1 change: 1 addition & 0 deletions Sources/Examples/ReflectionService/GreeterProvider.swift
57 changes: 57 additions & 0 deletions Sources/Examples/ReflectionService/ReflectionServer.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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
25 changes: 24 additions & 1 deletion Sources/GRPCReflectionService/Server/ReflectionService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
)
Expand Down

0 comments on commit 7ea3260

Please sign in to comment.