CombineGRPC 1.0.8

CombineGRPC 1.0.8

谢维山 维护。



 
依赖项
gRPC-Swift= 1.6.0
CombineExt= 1.5.1
 

CombineGRPC

CombineGRPC 是一个库,为 Combine 框架 提供了 Swift gRPC 的集成。

CombineGRPC 提供了两种功能风味,callhandle。使用 call 来进行客户端上的 gRPC 调用,并使用 handle 来处理服务器端的请求。库为所有 RPC 样式提供了 callhandle 的版本。以下是每个的输入和输出类型。

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)
  })

就这样!您已经在服务器和客户端之间设置了双向流。生成的 EchoServiceClientsayItBack 方法由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,构建和运行测试。