如何在Kotlin gRPC客户端拦截器中等待响应并包装异常
在Kotlin gRPC客户端实现带客户端堆栈的自定义异常拦截器
当gRPC客户端调用服务返回非成功状态时,默认抛出的StatusException不会包含客户端的调用堆栈,导致难以定位请求失败的源头。可以通过客户端拦截器对所有gRPC调用添加try/catch包裹,捕获原始异常后抛出包含完整客户端堆栈的自定义GrpcClientException。
1. 定义自定义异常
首先创建自定义异常类,用于包装gRPC原始异常并保留客户端堆栈:
class GrpcClientException(message: String, cause: Throwable) : RuntimeException(message, cause)
2. 实现gRPC客户端拦截器
实现ClientInterceptor接口,包装所有类型的gRPC调用,在响应阶段捕获异常并转换为自定义异常:
import io.grpc.* import kotlinx.coroutines.flow.catch class GrpcExceptionWrapperInterceptor : ClientInterceptor { override fun <ReqT : Any, RespT : Any> interceptCall( method: MethodDescriptor<ReqT, RespT>, callOptions: CallOptions, next: Channel ): ClientCall<ReqT, RespT> { val delegateCall = next.newCall(method, callOptions) return object : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(delegateCall) { override fun start(responseListener: ClientCall.Listener<RespT>, headers: Metadata) { val wrappedListener = object : ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) { // 处理Unary/ServerStreaming调用的最终状态 override fun onClose(status: Status, trailers: Metadata) { if (!status.isOk) { val statusException = status.asRuntimeException(trailers) throw GrpcClientException("gRPC调用失败: ${status.description}", statusException) } super.onClose(status, trailers) } // 处理响应消息时的异常(可选) override fun onMessage(message: RespT) { try { super.onMessage(message) } catch (e: Exception) { throw GrpcClientException("处理gRPC响应消息失败", e) } } } // 捕获调用启动阶段的异常 try { super.start(wrappedListener, headers) } catch (e: Exception) { throw GrpcClientException("启动gRPC调用失败", e) } } } } // 针对Kotlin协程Flow风格的gRPC调用(grpc-kotlin-stub) fun <T> wrapStreamingFlow(flow: kotlinx.coroutines.flow.Flow<T>): kotlinx.coroutines.flow.Flow<T> { return flow.catch { e -> throw when (e) { is StatusException -> GrpcClientException("gRPC流式调用失败", e) else -> GrpcClientException("处理gRPC流式响应失败", e) } } } }
拦截器逻辑说明
- 通过
ForwardingClientCall包装原始调用,避免直接修改底层逻辑 - 在
onClose中检查gRPC返回的Status,非成功状态时将StatusException包装为GrpcClientException,保留客户端调用堆栈 - 针对协程流式调用,提供
wrapStreamingFlow方法捕获Flow中的异常,确保流式场景也能覆盖
3. 注册拦截器到gRPC客户端
普通Stub(阻塞/异步)
val channel = ManagedChannelBuilder.forAddress("localhost", 50051) .intercept(GrpcExceptionWrapperInterceptor()) .usePlaintext() // 生产环境建议使用TLS .build() // 阻塞Stub val blockingStub = MyServiceGrpc.newBlockingStub(channel) // 异步Stub val asyncStub = MyServiceGrpc.newStub(channel)
协程Stub(grpc-kotlin)
val channel = ManagedChannelBuilder.forAddress("localhost", 50051) .intercept(GrpcExceptionWrapperInterceptor()) .usePlaintext() .build() val coroutineStub = MyServiceCoroutineStub(channel) // 流式调用时使用wrapStreamingFlow包装Flow val responseFlow = GrpcExceptionWrapperInterceptor().wrapStreamingFlow( coroutineStub.streamingMethod(request) )
4. 效果验证
当gRPC服务返回错误时,抛出的GrpcClientException会包含客户端的调用堆栈,示例堆栈如下:
com.example.GrpcClientException: gRPC调用失败: Invalid argument at com.example.GrpcExceptionWrapperInterceptor$interceptCall$1$wrappedListener$1.onClose(GrpcExceptionWrapperInterceptor.kt:23) at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:562) ... at com.example.MyClient.invokeService(MyClient.kt:42) // 客户端调用代码位置,直接定位问题源头 Caused by: io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Invalid argument at io.grpc.Status.asRuntimeException(Status.java:535) ...
内容的提问来源于stack exchange,提问作者nbilal
相关产品推荐
相关产品推荐

