如何在Tower中间件与Tonic中正确返回错误?
解决Tower中间件+Tonic返回错误的问题
你的问题核心是错误类型不匹配:你的中间件MyMiddleware的Error类型是S::Error(底层服务的错误类型),但你直接返回的Status::internal("foo")是tonic::Status类型,两者无法直接兼容,导致编译失败。
方案一:明确底层服务的错误类型为tonic::Status
如果你的底层Tonic服务的Service实现中,Error类型就是tonic::Status,可以直接修改中间件的泛型约束,将S::Error固定为tonic::Status:
#[derive(Debug, Clone)] struct MyMiddleware<S> { inner: S, } type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>; impl<S> Service<hyper::Request<BoxBody>> for MyMiddleware<S> where // 明确指定S的Error类型为tonic::Status S: Service<hyper::Request<BoxBody>, Response = hyper::Response<BoxBody>, Error = tonic::Status> + Clone + Send + 'static, S::Future: Send + 'static, { type Response = S::Response; type Error = tonic::Status; // 直接指定Error类型为tonic::Status type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { self.inner.poll_ready(cx) } fn call(&mut self, req: hyper::Request<BoxBody>) -> Self::Future { let clone = self.inner.clone(); let mut inner = std::mem::replace(&mut self.inner, clone); Box::pin(async move { if condition { let response = inner.call(req).await?; Ok(response) } else { // 现在类型匹配,可以直接返回Status错误 Err(Status::internal("foo")) } }) } }
方案二:实现错误类型转换
如果不想固定S::Error的类型,可以让S::Error实现From<tonic::Status> trait(或者手动转换),这样就能将Status转换成S::Error:
// 在call方法的错误分支中使用into()转换 Err(Status::internal("foo").into())
需要确保你的底层服务的错误类型支持从tonic::Status转换,比如如果S::Error是自定义错误,需要实现:
impl From<tonic::Status> for YourCustomError { fn from(status: tonic::Status) -> Self { // 自定义转换逻辑 YourCustomError::Grpc(status) } }
补充说明
Tonic依赖Tower处理请求,返回的tonic::Status会被自动转换成符合gRPC规范的Hyper响应。如果直接返回Hyper响应(比如Ok(hyper::Response::builder().status(500).body(...))),虽然能运行,但不符合gRPC的错误格式规范,不推荐使用。
内容的提问来源于stack exchange,提问作者Vana
相关产品推荐
相关产品推荐

