You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 19:23:19