-
Notifications
You must be signed in to change notification settings - Fork 420
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds an example showcasing how to set/read request and response metadata. --------- Co-authored-by: George Barnett <gbarnett@apple.com>
- Loading branch information
Showing
14 changed files
with
539 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// 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: "echo-metadata", | ||
platforms: [.macOS("15.0")], | ||
dependencies: [ | ||
.package(url: "https://github.com/grpc/grpc-swift.git", exact: "2.0.0-rc.1"), | ||
.package(url: "https://github.com/grpc/grpc-swift-protobuf.git", exact: "1.0.0-rc.1"), | ||
.package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", exact: "1.0.0-rc.1"), | ||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"), | ||
], | ||
targets: [ | ||
.executableTarget( | ||
name: "echo-metadata", | ||
dependencies: [ | ||
.product(name: "GRPCCore", package: "grpc-swift"), | ||
.product(name: "GRPCNIOTransportHTTP2", package: "grpc-swift-nio-transport"), | ||
.product(name: "GRPCProtobuf", package: "grpc-swift-protobuf"), | ||
.product(name: "ArgumentParser", package: "swift-argument-parser"), | ||
], | ||
plugins: [ | ||
.plugin(name: "GRPCProtobufGenerator", package: "grpc-swift-protobuf") | ||
] | ||
) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Echo-Metadata | ||
|
||
This example demonstrates how to interact with `Metadata` on RPCs: how to set and read it on unary | ||
and streaming requests, as well as how to set and read both initial and trailing metadata on unary | ||
and streaming responses. This is done using a simple 'echo' server and client and the SwiftNIO | ||
based HTTP/2 transport. | ||
|
||
## Overview | ||
|
||
An `echo-metadata` command line tool that uses generated stubs for an 'echo-metadata' service | ||
which allows you to start a server and to make requests against it. | ||
|
||
You can use any of the client's subcommands (`get`, `collect`, `expand` and `update`) to send the | ||
provided `message` as both the request's message, and as the value for the `echo-message` key in | ||
the request's metadata. | ||
|
||
The server will then echo back the message and the metadata's `echo-message` key-value pair sent | ||
by the client. The request's metadata will be echoed both in the initial and the trailing metadata. | ||
|
||
The tool uses the [SwiftNIO](https://github.com/grpc/grpc-swift-nio-transport) HTTP/2 transport. | ||
|
||
## Prerequisites | ||
|
||
You must have the Protocol Buffers compiler (`protoc`) installed. You can find | ||
the instructions for doing this in the [gRPC Swift Protobuf documentation][0]. | ||
The `swift` commands below are all prefixed with `PROTOC_PATH=$(which protoc)`, | ||
this is to let the build system know where `protoc` is located so that it can | ||
generate stubs for you. You can read more about it in the [gRPC Swift Protobuf | ||
documentation][1]. | ||
|
||
## Usage | ||
|
||
Build and run the server using the CLI: | ||
|
||
```console | ||
$ PROTOC_PATH=$(which protoc) swift run echo-metadata serve | ||
Echo-Metadata listening on [ipv4]127.0.0.1:1234 | ||
``` | ||
|
||
Use the CLI to run the client and make a `get` (unary) request: | ||
|
||
```console | ||
$ PROTOC_PATH=$(which protoc) swift run echo-metadata get --message "hello" | ||
get → metadata: [("echo-message", "hello")] | ||
get → message: hello | ||
get ← initial metadata: [("echo-message", "hello")] | ||
get ← message: hello | ||
get ← trailing metadata: [("echo-message", "hello")] | ||
``` | ||
|
||
Get help with the CLI by running: | ||
|
||
```console | ||
$ PROTOC_PATH=$(which protoc) swift run echo-metadata --help | ||
``` | ||
|
||
[0]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation/grpcprotobuf/installing-protoc | ||
[1]: https://swiftpackageindex.com/grpc/grpc-swift-protobuf/documentation/grpcprotobuf/generating-stubs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright 2025, 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 GRPCNIOTransportHTTP2 | ||
|
||
struct ClientArguments: ParsableArguments { | ||
@Option(help: "The server's listening port") | ||
var port: Int = 1234 | ||
|
||
@Option( | ||
help: | ||
"Message to send to the server. It will also be sent in the request's metadata as the value for `echo-message`." | ||
) | ||
var message: String | ||
} | ||
|
||
extension ClientArguments { | ||
var target: any ResolvableTarget { | ||
return .ipv4(host: "127.0.0.1", port: self.port) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright 2025, 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 GRPCCore | ||
|
||
@main | ||
struct EchoMetadata: AsyncParsableCommand { | ||
static let configuration = CommandConfiguration( | ||
commandName: "echo-metadata", | ||
abstract: "A multi-tool to run an echo-metadata server and execute RPCs against it.", | ||
subcommands: [Serve.self, Get.self, Collect.self, Update.self, Expand.self] | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
* Copyright 2025, 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.ServiceProtocol { | ||
func get( | ||
request: ServerRequest<Echo_EchoRequest>, | ||
context: ServerContext | ||
) async throws -> ServerResponse<Echo_EchoResponse> { | ||
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") })) | ||
return ServerResponse( | ||
message: .with { $0.text = request.message.text }, | ||
metadata: responseMetadata, | ||
trailingMetadata: responseMetadata | ||
) | ||
} | ||
|
||
func collect( | ||
request: StreamingServerRequest<Echo_EchoRequest>, | ||
context: ServerContext | ||
) async throws -> ServerResponse<Echo_EchoResponse> { | ||
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") })) | ||
let messages = try await request.messages.reduce(into: []) { $0.append($1.text) } | ||
let joined = messages.joined(separator: " ") | ||
|
||
return ServerResponse( | ||
message: .with { $0.text = joined }, | ||
metadata: responseMetadata, | ||
trailingMetadata: responseMetadata | ||
) | ||
} | ||
|
||
func expand( | ||
request: ServerRequest<Echo_EchoRequest>, | ||
context: ServerContext | ||
) async throws -> StreamingServerResponse<Echo_EchoResponse> { | ||
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") })) | ||
let parts = request.message.text.split(separator: " ") | ||
let messages = parts.map { part in Echo_EchoResponse.with { $0.text = String(part) } } | ||
|
||
return StreamingServerResponse(metadata: responseMetadata) { writer in | ||
try await writer.write(contentsOf: messages) | ||
return responseMetadata | ||
} | ||
} | ||
|
||
func update( | ||
request: StreamingServerRequest<Echo_EchoRequest>, | ||
context: ServerContext | ||
) async throws -> StreamingServerResponse<Echo_EchoResponse> { | ||
let responseMetadata = Metadata(request.metadata.filter({ $0.key.starts(with: "echo-") })) | ||
return StreamingServerResponse(metadata: responseMetadata) { writer in | ||
for try await message in request.messages { | ||
try await writer.write(.with { $0.text = message.text }) | ||
} | ||
return responseMetadata | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../../../dev/protos/examples/echo/ |
7 changes: 7 additions & 0 deletions
7
Examples/echo-metadata/Sources/Protos/grpc-swift-proto-generator-config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"generate": { | ||
"clients": true, | ||
"servers": true, | ||
"messages": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* Copyright 2025, 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 GRPCCore | ||
import GRPCNIOTransportHTTP2 | ||
|
||
struct Collect: AsyncParsableCommand { | ||
static let configuration = CommandConfiguration( | ||
abstract: "Makes a client streaming RPC to the echo-metadata server." | ||
) | ||
|
||
@OptionGroup | ||
var arguments: ClientArguments | ||
|
||
func run() async throws { | ||
try await withGRPCClient( | ||
transport: .http2NIOPosix( | ||
target: self.arguments.target, | ||
transportSecurity: .plaintext | ||
) | ||
) { client in | ||
let echo = Echo_Echo.Client(wrapping: client) | ||
let requestMetadata: Metadata = ["echo-message": "\(arguments.message)"] | ||
|
||
print("collect → metadata: \(requestMetadata)") | ||
try await echo.collect(metadata: requestMetadata) { writer in | ||
for part in self.arguments.message.split(separator: " ") { | ||
print("collect → \(part)") | ||
try await writer.write(.with { $0.text = String(part) }) | ||
} | ||
} onResponse: { response in | ||
let initialMetadata = Metadata(response.metadata.filter({ $0.key.starts(with: "echo-") })) | ||
print("collect ← initial metadata: \(initialMetadata)") | ||
|
||
print("collect ← message: \(try response.message.text)") | ||
|
||
let trailingMetadata = Metadata( | ||
response.trailingMetadata.filter({ $0.key.starts(with: "echo-") }) | ||
) | ||
print("collect ← trailing metadata: \(trailingMetadata)") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
* Copyright 2025, 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 GRPCCore | ||
import GRPCNIOTransportHTTP2 | ||
|
||
struct Expand: AsyncParsableCommand { | ||
static let configuration = CommandConfiguration( | ||
abstract: "Makes a server streaming RPC to the echo-metadata server." | ||
) | ||
|
||
@OptionGroup | ||
var arguments: ClientArguments | ||
|
||
func run() async throws { | ||
try await withGRPCClient( | ||
transport: .http2NIOPosix( | ||
target: self.arguments.target, | ||
transportSecurity: .plaintext | ||
) | ||
) { client in | ||
let echo = Echo_Echo.Client(wrapping: client) | ||
let requestMetadata: Metadata = ["echo-message": "\(arguments.message)"] | ||
let message = Echo_EchoRequest.with { $0.text = self.arguments.message } | ||
|
||
print("expand → metadata: \(requestMetadata)") | ||
print("expand → message: \(message.text)") | ||
|
||
try await echo.expand(message, metadata: requestMetadata) { response in | ||
let responseContents = try response.accepted.get() | ||
|
||
let initialMetadata = Metadata( | ||
responseContents.metadata.filter({ $0.key.starts(with: "echo-") }) | ||
) | ||
print("expand ← initial metadata: \(initialMetadata)") | ||
for try await part in responseContents.bodyParts { | ||
switch part { | ||
case .message(let message): | ||
print("expand ← message: \(message.text)") | ||
|
||
case .trailingMetadata(let trailingMetadata): | ||
let trailingMetadata = Metadata( | ||
trailingMetadata.filter({ $0.key.starts(with: "echo-") }) | ||
) | ||
print("expand ← trailing metadata: \(trailingMetadata)") | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.