CombineGRPC
CombineGRPC 是一个库,为 Combine 框架 提供了 Swift gRPC 的集成。
CombineGRPC 提供了两种功能风味,call
和 handle
。使用 call
来进行客户端上的 gRPC 调用,并使用 handle
来处理服务器端的请求。库为所有 RPC 样式提供了 call
和 handle
的版本。以下是每个的输入和输出类型。
RPC 样式 | 输入和输出类型 |
---|---|
单一 | 请求 -> AnyPublisher<响应, RPCError> |
服务器端流 | 请求 -> AnyPublisher<响应, RPCError> |
客户端流 | AnyPublisher<请求, Error> -> AnyPublisher<响应, RPCError> |
双向流 | AnyPublisher<请求, Error> -> AnyPublisher<响应, RPCError> |
当您进行单一调用时,您提供了一个请求消息,并收到一个响应发布者。响应发布者将发布单个响应,或者在失败时用 RPCError
错误。同样,如果您正在处理单一 RPC 调用,您提供一个处理程序,它接受一个请求参数并返回一个 AnyPublisher<Response, RPCError>
。
您可以根据同样的直观方式理解其他 RPC 样式的类型。唯一的区别是,流式 RPC 的发布者可能会发布零个或多个消息,而不是像单一响应发布者期望的单个响应消息。
快速浏览
让我们看看一个快速示例。请考虑以下简单的 echo 服务 protobuf 定义。该服务定义了一个双向 RPC。您向它发送一个消息流,它会将消息回显给您。
syntax = "proto3";
service EchoService {
rpc SayItBack (stream EchoRequest) returns (stream EchoResponse);
}
message EchoRequest {
string message = 1;
}
message EchoResponse {
string message = 1;
}
服务器端
要实现服务器,您需要提供一个处理程序函数,该函数接收一个输入流 AnyPublisher<EchoRequest, Error>
并返回一个输出流 AnyPublisher<EchoResponse, RPCError>
.
import Foundation
import Combine
import CombineGRPC
import GRPC
import NIO
class EchoServiceProvider: EchoProvider {
// Simple bidirectional RPC that echoes back each request message
func sayItBack(context: StreamingResponseCallContext<EchoResponse>) -> EventLoopFuture<(StreamEvent<EchoRequest>) -> Void> {
CombineGRPC.handle(context) { requests in
requests
.map { req in
EchoResponse.with { $0.message = req.message }
}
.setFailureType(to: RPCError.self)
.eraseToAnyPublisher()
}
}
}
启动服务器。这与Swift gRPC中的过程相同。
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try! eventLoopGroup.syncShutdownGracefully()
}
// Start the gRPC server and wait until it shuts down.
_ = try Server
.insecure(group: eventLoopGroup)
.withServiceProviders([EchoServiceProvider()])
.bind(host: "localhost", port: 8080)
.flatMap { $0.onClose }
.wait()
客户端
现在让我们设置客户端。同样,这和使用Swift gRPC的过程相同。
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let channel = ClientConnection
.insecure(group: eventLoopGroup)
.connect(host: "localhost", port: 8080)
let echoClient = EchoServiceClient(channel: channel)
要调用服务,创建一个 GRPCExecutor
并使用其 call
方法。您提供请求流 AnyPublisher<EchoRequest, Error>
,然后您将获得从服务器返回的响应流 AnyPublisher<EchoResponse, RPCError>
.
let requests = repeatElement(EchoRequest.with { $0.message = "hello"}, count: 10)
let requestStream: AnyPublisher<EchoRequest, Error> =
Publishers.Sequence(sequence: requests).eraseToAnyPublisher()
let grpc = GRPCExecutor()
grpc.call(echoClient.sayItBack)(requestStream)
.filter { $0.message == "hello" }
.count()
.sink(receiveValue: { count in
assert(count == 10)
})
就这样!您已经在服务器和客户端之间设置了双向流。生成的 EchoServiceClient
的 sayItBack
方法由Swift gRPC生成。请注意,调用是curried的。您可以使用偏应用预先选择RPC调用
let sayItBack = grpc.call(echoClient.sayItBack)
sayItBack(requestStream).map { response in
// ...
}
配置RPC调用
GRPCExecutor
允许您配置RPC调用的 CallOptions
。您可以为 GRPCExecutor
的初始化程序提供一个流 AnyPublisher<CallOptions, Never>
,并且当进行调用时将使用最新 CallOptions
的值。
let timeoutOptions = CallOptions(timeout: try! .seconds(5))
let grpc = GRPCExecutor(callOptions: Just(timeoutOptions).eraseToAnyPublisher())
重试策略
您还可以通过指定 RetryPolicy
来配置 GRPCExecutor
以自动重试失败的调用。在下面的示例中,我们重试状态失败为 .unauthenticated
的调用。我们使用 CallOptions
添加Bearer令牌到身份验证头中,然后重试调用。
// Default CallOptions with no authentication
let callOptions = CurrentValueSubject<CallOptions, Never>(CallOptions())
let grpc = GRPCExecutor(
callOptions: callOptions.eraseToAnyPublisher(),
retry: .failedCall(
upTo: 1,
when: { error in
error.status.code == .unauthenticated
},
delayUntilNext: { retryCount, error in // Useful for implementing exponential backoff
// Retry the call with authentication
callOptions.send(CallOptions(customMetadata: HTTPHeaders([("authorization", "Bearer xxx")])))
return Just(()).eraseToAnyPublisher()
},
didGiveUp: {
print("Authenticated call failed.")
}
)
)
grpc.call(client.authenticatedRpc)(request)
.map { response in
// ...
}
您可以考虑类似的操作,当ID令牌过期时,无缝地重试调用。后端服务返回状态 .unauthenticated
,您使用刷新令牌获取新的ID令牌,然后重试调用。
更多示例
查看 CombineGRPC 测试 以获取所有不同 RPC 调用和处理实现示例。匹配的 protobuf 可在此找到:此处。
物流
从 Protobuf 生成 Swift 代码
要从 .proto 文件生成 Swift 代码,您需要首先安装 protoc 协议缓冲区编译器。
brew install protobuf
接下来,下载 Swift 和 grpc-swift protoc 插件。解压缩下载的文件,并将 bin 目录中的二进制文件移动到您的 $PATH
中的某个位置。
现在您可以生成从 protobuf 接口定义文件创建的 Swift 代码。
让我们为 Swift 生成消息类型、gRPC 服务器和 gRPC 客户端。
protoc example_service.proto --swift_out=Generated/
protoc example_service.proto --grpc-swift_out=Generated/
您将看到 protoc 为我们创建了两个源文件。
ls Generated/
example_service.grpc.swift
example_service.pb.swift
将 CombineGRPC 添加到您的项目
您可以使用 Swift 包管理器或 CocoaPods 轻松将 CombineGRPC 添加到项目中。
Swift 包管理器
将包依赖项添加到您的 Package.swift
。
dependencies: [
.package(url: "https://github.com/vyshane/grpc-swift-combine.git", from: "1.0.8"),
],
CocoaPods
将以下行添加到您的 Podfile
pod 'CombineGRPC', '~> 1.0.6'
兼容性
由于此库集成了Combine,它仅在支持Combine的平台上演示。这目前意味着以下最低版本
平台 | 最低支持版本 |
---|---|
macOS | 10.15 (Catalina) |
iOS & iPadOS | 13 |
tvOS | 13 |
watchOS | 6 |
功能状态
RPC客户端调用
- 单一
- 客户端流
- 服务器端流
- 双向流
- 自动客户端调用重试的重试策略
服务器端处理程序
- 单一
- 客户端流
- 服务器端流
- 双向流
端到端测试
- 单一
- 客户端流
- 服务器端流
- 双向流
贡献
为测试中使用的protobuf生成Swift源代码
make protobuf
然后您可以在Xcode中打开Package.swift
,构建和运行测试。