Rust Tonic + Tower:如何仅为特定服务应用中间件?
Tonic中为特定gRPC服务单独应用Tower中间件的解决方案
问题描述
使用Tower开发中间件后,通过layer方法可成功将其应用于Tonic服务,但该方式会将中间件作用于所有服务。尝试通过.add_service(AuthMiddleware::new(grpc_project_service))为特定服务单独应用中间件时,gRPC返回12错误码(未实现),且中间件与目标服务均未被调用(仅全局layer方式有效),需实现选择性应用中间件的方案。
中间件代码
use std::task::{Context, Poll}; use tower::{Layer, Service}; #[derive(Debug, Clone, Default)] pub struct AuthMiddlewareLayer; impl<S> Layer<S> for AuthMiddlewareLayer { type Service = AuthMiddleware<S>; fn layer(&self, inner: S) -> Self::Service { AuthMiddleware::new(inner) } } #[derive(Debug, Clone)] pub struct AuthMiddleware<S> { inner: S, } impl<S> AuthMiddleware<S> { pub fn new(inner: S) -> Self { AuthMiddleware { inner } } } impl<S, Req> Service<Req> for AuthMiddleware<S> where S: Service<Req>, { type Response = S::Response; type Error = S::Error; type Future = S::Future; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { println!("AuthMiddleware poll_ready called"); self.inner.poll_ready(cx) } fn call(&mut self, req: Req) -> Self::Future { print!("Im here!!!"); self.inner.call(req) } }
问题代码(main.rs)
#[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { ... let project_api = ProjectApi { project_service }; let grpc_project_service = grpc_project::ProjectServiceServer::new(project_api); let auth_api = AuthApi { auth_service }; let grpc_auth_service = grpc_auth::AuthServiceServer::new(auth_api); ... let reflection_service = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET) .build() .unwrap(); Server::builder() // 有效,但会应用于所有服务 // .layer(AuthMiddlewareLayer::default()) .add_service(grpc_auth_service) // 无效,gRPC返回12错误码(未实现),中间件和服务均未被调用 .add_service(AuthMiddleware::new(grpc_project_service)) .add_service(reflection_service) .serve(grpc_addr) .await .tap_err(|e| error!("Cannot start grpc server, error: {}", e))?; Ok(()) } impl NamedService for AuthMiddleware<ProjectServiceServer<ProjectApi>> { const NAME: &'static str = "ProjectService"; }
解决方案
核心问题是直接包裹服务实例时,中间件未满足Tonic服务所需的类型约束。官方推荐通过服务自身的with_layer方法为单个服务单独应用中间件,无需全局配置:
修改main.rs中的服务添加逻辑:
Server::builder() .add_service(grpc_auth_service) // 仅为grpc_project_service应用中间件 .add_service(grpc_project_service.with_layer(AuthMiddlewareLayer)) .add_service(reflection_service) .serve(grpc_addr) .await .tap_err(|e| error!("Cannot start grpc server, error: {}", e))?;
原理说明
Tonic的Server::add_service要求服务同时实现tonic::server::NamedService和适配Tonic请求类型的tower::Service。直接用AuthMiddleware::new()包裹服务时,中间件的泛型Req未限定为Tonic的请求类型(tonic::Request<T>),导致Server无法识别其为合法gRPC服务,从而返回"未实现"错误。而with_layer方法会自动处理类型适配,确保中间件正确作用于目标服务。
版本信息
tonic = "0.11.0" tower = "0.4.13" hyper = "1.2.0"
内容的提问来源于stack exchange,提问作者Teimuraz
相关产品推荐
相关产品推荐

