Tonic客户端/通道通用Trait实现及多场景连接优化咨询
Rust Tonic gRPC客户端多场景存储与实现优化
问题1:存储不同类型的Client/Channel方案
方案1:Trait对象(动态分发)
所有通道类型都实现了tonic::service::GrpcService trait,我们可以将其装箱为统一的 trait 对象,以此兼容不同场景的类型差异。核心是利用Rust的动态分发特性,将具体类型抽象为通用的 trait 约束:
use tonic::{body::BoxBody, service::GrpcService, Status, Response}; use hyper::Request; // 定义统一的动态通道类型 type DynGrpcChannel = Box<dyn GrpcService<BoxBody, Response = Response<BoxBody>, Error = Status> + Send + Sync>; // 根据配置创建并返回统一类型的通道 fn create_channel(args: &Args) -> Result<DynGrpcChannel, Box<dyn std::error::Error>> { match args.mode { Mode::Http => { let channel = Client::builder().http2_only(true).build_http(); Ok(Box::new(channel)) } Mode::Https => { let tls_config = create_insecure_tls_config(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_or_http() .enable_http2() .build(); let channel = Client::builder().build(connector); Ok(Box::new(channel)) } Mode::Auth => { let tls_config = create_insecure_tls_config(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_or_http() .enable_http2() .build(); let inner = Client::builder().build(connector); let channel = InterceptedService::new(inner, auth_interceptor(args)); Ok(Box::new(channel)) } Mode::SocksProxy => { let proxy_connector = create_socks_connector(args); let tls_config = create_insecure_tls_config(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_or_http() .enable_http2() .wrap_connector(proxy_connector); let inner = Client::builder().build(connector); let channel = InterceptedService::new(inner, auth_interceptor(args)); Ok(Box::new(channel)) } } }
如果需要存储客户端而非通道,可以自定义业务相关的 trait,为自动生成的gRPC客户端实现该 trait,再用 trait 对象统一存储:
use tonic::Status; use hello_world::HelloRequest; // 自定义客户端 trait,只暴露业务需要的方法 trait HelloServiceClientExt { async fn say_hello(&mut self, req: HelloRequest) -> Result<hello_world::HelloResponse, Status>; } // 为自动生成的HelloServiceClient实现自定义trait impl<S> HelloServiceClientExt for HelloServiceClient<S> where S: GrpcService<BoxBody> + Send + Sync + 'static, S::Response: Into<Response<BoxBody>>, S::Error: Into<Status>, { async fn say_hello(&mut self, req: HelloRequest) -> Result<hello_world::HelloResponse, Status> { self.say_hello(req).await.map(|res| res.into_inner()) } } // 统一客户端类型 type DynHelloClient = Box<dyn HelloServiceClientExt + Send>;
方案2:枚举包裹所有变体
定义枚举包含所有可能的通道/客户端类型,手动为枚举实现GrpcService或自定义 trait,每个变体转发对应的方法调用。这种方式是静态分发,性能略优于 trait 对象,但需要维护所有变体的实现逻辑:
use tonic::{service::GrpcService, body::BoxBody, Response, Status}; use hyper::Client as HyperClient; use hyper_rustls::HttpsConnector; use socks::SocksConnector; use tower::util::InterceptedService; use futures::Future; use std::pin::Pin; enum Channel { Http(HyperClient<hyper::client::HttpConnector, BoxBody>), Https(HyperClient<HttpsConnector<hyper::client::HttpConnector>, BoxBody>), Auth(InterceptedService<HyperClient<HttpsConnector<hyper::client::HttpConnector>, BoxBody>, fn(Request<()>) -> Result<Request<()>, Status>>), SocksProxy(InterceptedService<HyperClient<HttpsConnector<SocksConnector<hyper::client::HttpConnector>>, BoxBody>, fn(Request<()>) -> Result<Request<()>, Status>>), } impl GrpcService<BoxBody> for Channel { type Response = Response<BoxBody>; type Error = Status; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; fn call(&mut self, req: hyper::Request<BoxBody>) -> Self::Future { match self { Channel::Http(inner) => Box::pin(inner.call(req).map_err(Into::into)), Channel::Https(inner) => Box::pin(inner.call(req).map_err(Into::into)), Channel::Auth(inner) => Box::pin(inner.call(req).map_err(Into::into)), Channel::SocksProxy(inner) => Box::pin(inner.call(req).map_err(Into::into)), } } }
问题2:简化多场景客户端实现的建议
1. 提取重复逻辑为工具函数
将TLS配置、拦截器、代理连接器等重复代码抽成独立函数,避免代码冗余:
use rustls::{ClientConfig, RootCertStore}; use rustls::client::NoCertificateVerification; use std::sync::Arc; // 生成跳过证书验证的TLS配置 fn create_insecure_tls_config() -> ClientConfig { let roots = RootCertStore::empty(); let mut tls = ClientConfig::builder() .with_safe_defaults() .with_root_certificates(roots) .with_no_client_auth(); tls.dangerous().set_certificate_verifier(Arc::new(NoCertificateVerification {})); tls } // 生成认证拦截器 fn auth_interceptor(args: &Args) -> impl Fn(Request<()>) -> Result<Request<()>, Status> + Clone { let username = args.username.clone(); let password = args.password.clone(); move |mut req: Request<()>| { req.metadata_mut().insert("username", username.parse().unwrap()); req.metadata_mut().insert("password", password.parse().unwrap()); req.metadata_mut().insert("secure", "false".parse().unwrap()); Ok(req) } } // 生成Socks代理连接器 fn create_socks_connector(args: &Args) -> SocksConnector<hyper::client::HttpConnector> { let mut lower_connector = hyper::client::HttpConnector::new(); lower_connector.enforce_http(false); SocksConnector { proxy_addr: args.proxy.parse::<Uri>().unwrap(), auth: None, connector: lower_connector, } }
2. 构建器模式封装创建流程
自定义构建器类,通过链式调用配置HTTP/HTTPS、认证、代理等选项,最后生成统一类型的客户端:
struct GrpcClientBuilder { addr: String, use_https: bool, insecure_tls: bool, auth: Option<(String, String)>, proxy: Option<String>, } impl GrpcClientBuilder { fn new(addr: &str) -> Self { Self { addr: addr.to_string(), use_https: false, insecure_tls: false, auth: None, proxy: None, } } fn use_https(mut self, insecure: bool) -> Self { self.use_https = true; self.insecure_tls = insecure; self } fn with_auth(mut self, username: &str, password: &str) -> Self { self.auth = Some((username.to_string(), password.to_string())); self } fn with_socks_proxy(mut self, proxy_addr: &str) -> Self { self.proxy = Some(proxy_addr.to_string()); self } async fn build(self) -> Result<DynHelloClient, Box<dyn std::error::Error>> { let channel = match (self.use_https, self.proxy, self.auth) { (false, None, None) => { let channel = Client::builder().http2_only(true).build_http(); Box::new(channel) as DynGrpcChannel } (true, None, None) => { let tls_config = create_insecure_tls_config(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_or_http() .enable_http2() .build(); let channel = Client::builder().build(connector); Box::new(channel) } (true, None, Some((username, password))) => { let tls_config = create_insecure_tls_config(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_or_http() .enable_http2() .build(); let inner = Client::builder().build(connector); let interceptor = move |mut req: Request<()>| { req.metadata_mut().insert("username", username.parse().unwrap()); req.metadata_mut().insert("password", password.parse().unwrap()); req.metadata_mut().insert("secure", "false".parse().unwrap()); Ok(req) }; let channel = InterceptedService::new(inner, interceptor); Box::new(channel) } // 处理其他组合场景... _ => unimplemented!(), }; let uri = Uri::from_maybe_shared( if self.use_https { format!("https://{}", self.addr) } else { format!("http://{}", self.addr) } )?; let client = HelloServiceClient::with_origin(channel, uri); Ok(Box::new(client)) } }
3. 利用tower层组合功能
借助tonic依赖的tower库,将认证、代理等功能封装为独立的Layer,通过ServiceBuilder组合到通道上,实现功能的模块化复用:
use tower::{ServiceBuilder, layer::Layer}; use futures::future::Ready; // 自定义认证层 struct AuthLayer { username: String, password: String, } impl<S> Layer<S> for AuthLayer { type Service = InterceptedService<S, Self>; fn layer(&self, inner: S) -> Self::Service { InterceptedService::new(inner, self.clone()) } } impl Clone for AuthLayer { fn clone(&self) -> Self { Self { username: self.username.clone(), password: self.password.clone(), } } } impl tower::Service<Request<()>> for AuthLayer { type Response = Request<()>; type Error = Status; type Future = Ready<Result<Self::Response, Self::Error>>; fn poll_ready(&mut self, _cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> { std::task::Poll::Ready(Ok(())) } fn call(&mut self, mut req: Request<()>) -> Self::Future { req.metadata_mut().insert("username", self.username.parse().unwrap()); req.metadata_mut().insert("password", self.password.parse().unwrap()); req.metadata_mut().insert("secure", "false".parse().unwrap()); futures::future::ready(Ok(req)) } } // 使用ServiceBuilder组合层 let channel = ServiceBuilder::new() .layer(AuthLayer { username: args.username.clone(), password: args.password.clone() }) .service(inner_channel);
内容的提问来源于stack exchange,提问作者jsantander
相关产品推荐
相关产品推荐

