Skip to content

Commit

Permalink
Add reflection server example (#2149)
Browse files Browse the repository at this point in the history
Motivation:

We've added back the reflection server; we should have an example of how
to use it.

Modifications:

- Add a reflection server example
- Split the echo service into a separate file in the echo example so
that it can be symlinked from the reflection server

Result:

More examples
  • Loading branch information
glbrntt authored Dec 20, 2024
1 parent dee1f1a commit 5be11cd
Show file tree
Hide file tree
Showing 12 changed files with 1,378 additions and 38 deletions.
1 change: 1 addition & 0 deletions .licenseignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ LICENSE
**/*.swift
dev/protos/**/*.proto
Examples/hello-world/Protos/HelloWorld.proto
**/*.pb
55 changes: 55 additions & 0 deletions Examples/echo/Sources/Subcommands/EchoService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.
*/

import GRPCCore

struct EchoService: Echo_Echo.SimpleServiceProtocol {
func get(
request: Echo_EchoRequest,
context: ServerContext
) async throws -> Echo_EchoResponse {
return .with { $0.text = request.text }
}

func collect(
request: RPCAsyncSequence<Echo_EchoRequest, any Error>,
context: ServerContext
) async throws -> Echo_EchoResponse {
let messages = try await request.reduce(into: []) { $0.append($1.text) }
let joined = messages.joined(separator: " ")
return .with { $0.text = joined }
}

func expand(
request: Echo_EchoRequest,
response: RPCWriter<Echo_EchoResponse>,
context: ServerContext
) async throws {
let parts = request.text.split(separator: " ")
let messages = parts.map { part in Echo_EchoResponse.with { $0.text = String(part) } }
try await response.write(contentsOf: messages)
}

func update(
request: RPCAsyncSequence<Echo_EchoRequest, any Error>,
response: RPCWriter<Echo_EchoResponse>,
context: ServerContext
) async throws {
for try await message in request {
try await response.write(.with { $0.text = message.text })
}
}
}
38 changes: 0 additions & 38 deletions Examples/echo/Sources/Subcommands/Serve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,41 +41,3 @@ struct Serve: AsyncParsableCommand {
}
}
}

struct EchoService: Echo_Echo.SimpleServiceProtocol {
func get(
request: Echo_EchoRequest,
context: ServerContext
) async throws -> Echo_EchoResponse {
return .with { $0.text = request.text }
}

func collect(
request: RPCAsyncSequence<Echo_EchoRequest, any Error>,
context: ServerContext
) async throws -> Echo_EchoResponse {
let messages = try await request.reduce(into: []) { $0.append($1.text) }
let joined = messages.joined(separator: " ")
return .with { $0.text = joined }
}

func expand(
request: Echo_EchoRequest,
response: RPCWriter<Echo_EchoResponse>,
context: ServerContext
) async throws {
let parts = request.text.split(separator: " ")
let messages = parts.map { part in Echo_EchoResponse.with { $0.text = String(part) } }
try await response.write(contentsOf: messages)
}

func update(
request: RPCAsyncSequence<Echo_EchoRequest, any Error>,
response: RPCWriter<Echo_EchoResponse>,
context: ServerContext
) async throws {
for try await message in request {
try await response.write(.with { $0.text = message.text })
}
}
}
8 changes: 8 additions & 0 deletions Examples/reflection-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
45 changes: 45 additions & 0 deletions Examples/reflection-server/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// swift-tools-version:6.0
/*
* 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.
*/

import PackageDescription

let package = Package(
name: "reflection-server",
platforms: [.macOS(.v15)],
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", branch: "main"),
.package(url: "https://github.com/grpc/grpc-swift-protobuf.git", branch: "main"),
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", branch: "main"),
.package(url: "https://github.com/grpc/grpc-swift-extras.git", branch: "main"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),
],
targets: [
.executableTarget(
name: "reflection-server",
dependencies: [
.product(name: "GRPCCore", package: "grpc-swift"),
.product(name: "GRPCNIOTransportHTTP2", package: "grpc-swift-nio-transport"),
.product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"),
.product(name: "GRPCReflectionService", package: "grpc-swift-extras"),
.product(name: "ArgumentParser", package: "swift-argument-parser"),
],
resources: [
.copy("DescriptorSets")
]
)
]
)
61 changes: 61 additions & 0 deletions Examples/reflection-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Reflection Server

This example demonstrates the gRPC Reflection service which is described in more
detail in the [gRPC documentation](https://github.com/grpc/grpc/blob/6fa8043bf9befb070b846993b59a3348248e6566/doc/server-reflection.md).

## Overview

A 'reflection-server' command line tool that uses the reflection service implementation
from [grpc/grpc-swift-extras](https://github.com/grpc/grpc-swift-extras) and the
Echo service (see the 'echo' example).

The reflection service requires you to initialize it with a set of Protobuf file
descriptors for the services you're offering. You can use `protoc` to create a
descriptor set including dependencies and source information for each service.

The following command will generate a descriptor set at `path/to/output.pb` from
the `path/to/input.proto` file with source information and any imports used in
`input.proto`:

```console
protoc --descriptor_set_out=path/to/output.pb path/to/input.proto \
--include_source_info \
--include_imports
```

## Usage

Build and run the server using the CLI:

```console
$ swift run reflection-server
Reflection server listening on [ipv4]127.0.0.1:31415
```

You can use 'grpcurl' to query the reflection service. If you don't already have
it installed follow the instructions in the 'grpcurl' project's
[README](https://github.com/fullstorydev/grpcurl).

You can list all services with:

```console
$ grpcurl -plaintext 127.0.0.1:31415 list
echo.Echo
```

And describe the 'Get' method in the 'echo.Echo' service:

```console
$ grpcurl -plaintext 127.0.0.1:31415 describe echo.Echo.Get
echo.Echo.Get is a method:
// Immediately returns an echo of a request.
rpc Get ( .echo.EchoRequest ) returns ( .echo.EchoResponse );
```

You can also call the 'echo.Echo.Get' method:
```console
$ grpcurl -plaintext -d '{ "text": "Hello" }' 127.0.0.1:31415 echo.Echo.Get
{
"text": "Hello"
}
```
Binary file not shown.
1 change: 1 addition & 0 deletions Examples/reflection-server/Sources/Echo/EchoService.swift
Loading

0 comments on commit 5be11cd

Please sign in to comment.