使用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
相关产品推荐
相关产品推荐

