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 Examples/v1/ReflectionService/ReflectionServer.swift and it supports the "Greeter", "Echo", and "Reflection" services.
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 and the gRPC command line tool to:
gRPC Swift supports both v1 and v1alpha of the reflection service.
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.
The server from this example uses the GreeterProvider and the EchoProvider,
besides the ReflectionService.
The associated proto files are located at Examples/v1/HelloWorld/Model/helloworld.proto, and
Examples/v1/Echo/Model/echo.proto respectively.
In order to generate the reflection data for the helloworld.proto, you can run the following command:
$ protoc Examples/v1/HelloWorld/Model/helloworld.proto \
--proto_path=Examples/v1/HelloWorld/Model \
--grpc-swift_opt=Client=false,Server=false,ReflectionData=true \
--grpc-swift_out=Examples/v1/ReflectionService/Generated
Let's break the command down:
protoc is the path
to the .proto file to generate reflection data
for: Examples/v1/HelloWorld/Model/helloworld.proto.proto_path flag is the path to search for imports: Examples/v1/HelloWorld/Model.Client=false,Server=false,ReflectionData=true.grpc-swift_out flag is used to set the path of the directory
where the generated file will be located: Examples/v1/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 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.
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 you are using you might need to use the .v1alpha instead.
Beginning with gRPCurl v1.8.8 it uses the v1 reflection. Earlier versions use v1alpha
reflection.
// Getting the URLs of the files containing the reflection data.
guard
let greeterURL = Bundle.module.url(
forResource: "helloworld",
withExtension: "grpc.reflection",
subdirectory: "Generated"
),
let echoURL = Bundle.module.url(
forResource: "echo",
withExtension: "grpc.reflection",
subdirectory: "Generated"
)
else {
print("The resource could not be loaded.")
throw ExitCode.failure
}
let reflectionService = try ReflectionService(
reflectionDataFileURLs: [greeterURL, echoURL],
version: .v1
)
Reflection data can also be generated via the SPM plugin by including "reflectionData": true in grpc-swift-config.json. This will generate the same reflection data as running protoc above. The generated reflection files are added to your module Bundle and can be accessed at runtime. More about spm-plugin can be found here.
{
"invocations": [
{
"protoFiles": [
"helloworld.proto"
],
"visibility": "public",
"server": true,
"reflectionData": true
}
]
}
To instantiate the ReflectionService you can search for files with the extension reflection in your module Bundle.
let reflectionDataFilePaths = Bundle.module.paths(
forResourcesOfType: "reflection",
inDirectory: nil
)
let reflectionService = try ReflectionService(
reflectionDataFilePaths: reflectionDataFilePaths,
version: .v1Alpha
)
In our example the server isn't configured with TLS and listens on localhost port 1234. The following code configures and starts the server:
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:
$ swift run ReflectionServer
Please follow the instructions from the gRPCurl README 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:
$ grpcurl -plaintext localhost:1234 list
echo.Echo
helloworld.Greeter
To see what methods are available for a service:
$ 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:
$ 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:
$ 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: