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

使用Rust的Tonic框架如何剥离请求URL中的/api前缀?

解决方案

可以通过添加Tower中间件来剥离请求URL中的/api前缀,让Tonic能够正确处理带前缀的gRPC-Web请求。以下是两种实现方式:

方式一:使用tower_http的StripPrefix中间件(推荐)

这是最简单的方案,借助tower_http库提供的现成中间件完成前缀剥离:

1. 添加依赖

在Cargo.toml中加入tower_http依赖并启用strip-prefix特性:

[dependencies]
tonic = "0.10"
tonic-web = "0.10"
tokio = { version = "1.0", features = ["full"] }
tower-http = { version = "0.5", features = ["strip-prefix"] }
# 替换为你的proto生成包,比如myapi = { path = "./proto" }

2. 修改代码

将gRPC服务用StripPrefix包裹后,通过serve_with_service方法启动服务:

use tonic::transport::Server;
use tonic_web::GrpcWebLayer;
use tower_http::services::StripPrefix;
// 导入你的gRPC服务定义
use myapi::greeting_server::{Greeting, GreetingServer};
use myapi::{HelloRequest, HelloResponse};

#[tonic::async_trait]
impl Greeting for GreetingService {
    async fn hello(&self, request: tonic::Request<HelloRequest>) -> Result<tonic::Response<HelloResponse>, tonic::Status> {
        Ok(tonic::Response::new(HelloResponse {
            message: format!("Hello {}!", request.into_inner().name)
        }))
    }
}

struct GreetingService;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:8080".parse()?;

    // 创建gRPC服务实例
    let greeting_service = GreetingServer::new(GreetingService);

    // 用StripPrefix中间件剥离/api前缀
    let wrapped_service = StripPrefix::new("/api", greeting_service);

    Server::builder()
        .accept_http1(true)
        .layer(GrpcWebLayer::new())
        // 使用serve_with_service传递经过包装的服务
        .serve_with_service(addr, wrapped_service)
        .await?;

    Ok(())
}

方式二:自定义中间件

如果不想额外引入tower_http依赖,可以手动实现一个简单的前缀剥离中间件:

use tonic::transport::Server;
use tonic_web::GrpcWebLayer;
use tower::{Service, Layer};
use http::{Uri, Request};
use std::task::{Context, Poll};
// 导入你的gRPC服务定义
use myapi::greeting_server::{Greeting, GreetingServer};
use myapi::{HelloRequest, HelloResponse};

#[tonic::async_trait]
impl Greeting for GreetingService {
    async fn hello(&self, request: tonic::Request<HelloRequest>) -> Result<tonic::Response<HelloResponse>, tonic::Status> {
        Ok(tonic::Response::new(HelloResponse {
            message: format!("Hello {}!", request.into_inner().name)
        }))
    }
}

struct GreetingService;

// 自定义剥离前缀的中间件层
struct StripPrefixLayer(String);

impl StripPrefixLayer {
    fn new(prefix: &str) -> Self {
        Self(prefix.to_string())
    }
}

impl<S> Layer<S> for StripPrefixLayer {
    type Service = StripPrefixService<S>;

    fn layer(&self, inner: S) -> Self::Service {
        StripPrefixService { inner, prefix: self.prefix.clone() }
    }
}

// 自定义服务实现
struct StripPrefixService<S> {
    inner: S,
    prefix: String,
}

impl<S, ReqBody> Service<Request<ReqBody>> for StripPrefixService<S>
where
    S: Service<Request<ReqBody>>,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
        // 尝试剥离/api前缀
        if let Some(stripped_path) = req.uri().path().strip_prefix(&self.prefix) {
            // 构建新的URI
            if let Ok(new_uri) = Uri::builder()
                .scheme(req.uri().scheme().clone())
                .authority(req.uri().authority().clone())
                .path_and_query(stripped_path)
                .build()
            {
                *req.uri_mut() = new_uri;
            }
        }
        self.inner.call(req)
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:8080".parse()?;

    Server::builder()
        .accept_http1(true)
        .layer(GrpcWebLayer::new())
        // 添加自定义的前缀剥离中间件
        .layer(StripPrefixLayer::new("/api"))
        .add_service(GreetingServer::new(GreetingService))
        .serve(addr)
        .await?;

    Ok(())
}

两种方式都能让Tonic正确处理/api/myapi.v1.GreetingService/Hello这类请求,将其视为/myapi.v1.GreetingService/Hello来处理。

内容的提问来源于stack exchange,提问作者Stepan Romankov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 22:00:14