volo_grpc/transport/
client.rs1use std::{io, marker::PhantomData};
2
3use bytes::Bytes;
4use http::{
5 HeaderValue,
6 header::{CONTENT_TYPE, TE},
7};
8use http_body::Frame;
9use http_body_util::StreamBody;
10use hyper_util::rt::{TokioExecutor, TokioTimer};
11use motore::Service;
12use tower::{Service as TowerService, util::ServiceExt};
13use volo::net::Address;
14
15use super::connect::Connector;
16use crate::{
17 Code, Request, Response, Status,
18 body::boxed,
19 client::Http2Config,
20 codec::{
21 compression::{ACCEPT_ENCODING_HEADER, ENCODING_HEADER},
22 decode::Kind,
23 },
24 context::{ClientContext, Config},
25};
26
27#[allow(clippy::type_complexity)]
30pub struct ClientTransport<U> {
31 http_client: hyper_util::client::legacy::Client<
32 Connector,
33 StreamBody<crate::BoxStream<'static, Result<Frame<Bytes>, crate::Status>>>,
34 >,
35 _marker: PhantomData<fn(U)>,
36}
37
38impl<U> Clone for ClientTransport<U> {
39 fn clone(&self) -> Self {
40 Self {
41 http_client: self.http_client.clone(),
42 _marker: self._marker,
43 }
44 }
45}
46
47impl<U> ClientTransport<U> {
48 pub fn new(http2_config: &Http2Config, rpc_config: &Config) -> Self {
51 let config = volo::net::dial::Config::new(
52 rpc_config.connect_timeout,
53 rpc_config.read_timeout,
54 rpc_config.write_timeout,
55 );
56 let http_client = hyper_util::client::legacy::Client::builder(TokioExecutor::new())
57 .timer(TokioTimer::new())
58 .http2_only(true)
59 .http2_initial_stream_window_size(http2_config.init_stream_window_size)
60 .http2_initial_connection_window_size(http2_config.init_connection_window_size)
61 .http2_max_frame_size(http2_config.max_frame_size)
62 .http2_adaptive_window(http2_config.adaptive_window)
63 .http2_keep_alive_interval(http2_config.http2_keepalive_interval)
64 .http2_keep_alive_timeout(http2_config.http2_keepalive_timeout)
65 .http2_keep_alive_while_idle(http2_config.http2_keepalive_while_idle)
66 .http2_max_concurrent_reset_streams(http2_config.max_concurrent_reset_streams)
67 .http2_max_send_buf_size(http2_config.max_send_buf_size)
68 .build(Connector::new(Some(config)));
69
70 ClientTransport {
71 http_client,
72 _marker: PhantomData,
73 }
74 }
75
76 #[cfg(feature = "__tls")]
77 #[cfg_attr(docsrs, doc(cfg(any(feature = "rustls", feature = "native-tls"))))]
78 pub fn new_with_tls(
79 http2_config: &Http2Config,
80 rpc_config: &Config,
81 tls_config: volo::net::tls::ClientTlsConfig,
82 ) -> Self {
83 let config = volo::net::dial::Config::new(
84 rpc_config.connect_timeout,
85 rpc_config.read_timeout,
86 rpc_config.write_timeout,
87 );
88 let http_client = hyper_util::client::legacy::Client::builder(TokioExecutor::new())
89 .timer(TokioTimer::new())
90 .http2_only(true)
91 .http2_initial_stream_window_size(http2_config.init_stream_window_size)
92 .http2_initial_connection_window_size(http2_config.init_connection_window_size)
93 .http2_max_frame_size(http2_config.max_frame_size)
94 .http2_adaptive_window(http2_config.adaptive_window)
95 .http2_keep_alive_interval(http2_config.http2_keepalive_interval)
96 .http2_keep_alive_timeout(http2_config.http2_keepalive_timeout)
97 .http2_keep_alive_while_idle(http2_config.http2_keepalive_while_idle)
98 .http2_max_concurrent_reset_streams(http2_config.max_concurrent_reset_streams)
99 .http2_max_send_buf_size(http2_config.max_send_buf_size)
100 .build(Connector::new_with_tls(Some(config), tls_config));
101
102 ClientTransport {
103 http_client,
104 _marker: PhantomData,
105 }
106 }
107}
108
109impl<T, U> Service<ClientContext, Request<T>> for ClientTransport<U>
110where
111 T: crate::message::SendEntryMessage + Send + 'static,
112 U: crate::message::RecvEntryMessage + 'static,
113{
114 type Response = Response<U>;
115
116 type Error = Status;
117
118 #[cfg_attr(not(feature = "compress"), allow(unused_variables))]
119 async fn call(
120 &self,
121 cx: &mut ClientContext,
122 volo_req: Request<T>,
123 ) -> Result<Self::Response, Self::Error> {
124 let mut http_client = self.http_client.clone();
125 let target = cx.rpc_info.callee().address().ok_or_else(|| {
128 io::Error::new(std::io::ErrorKind::InvalidData, "address is required")
129 })?;
130
131 let (metadata, extensions, message) = volo_req.into_parts();
132 let path = cx.rpc_info.method();
133 let rpc_config = cx.rpc_info.config();
134 let accept_compressions = &rpc_config.accept_compressions;
135
136 let send_compression = rpc_config
138 .send_compressions
139 .as_ref()
140 .map(|config| config[0]);
141
142 let body = http_body_util::StreamBody::new(message.into_body(send_compression));
143
144 let mut req = http::Request::builder()
145 .version(http::Version::HTTP_2)
146 .method(http::Method::POST)
147 .uri(build_uri(target.clone(), path))
148 .extension(extensions)
149 .body(body)
150 .map_err(|err| Status::from_error(err.into()))?;
151 *req.headers_mut() = metadata.into_headers();
152 req.headers_mut()
153 .insert(TE, HeaderValue::from_static("trailers"));
154 req.headers_mut()
155 .insert(CONTENT_TYPE, HeaderValue::from_static("application/grpc"));
156
157 if let Some(send_compression) = send_compression {
159 req.headers_mut()
160 .insert(ENCODING_HEADER, send_compression.into_header_value());
161 }
162 if let Some(accept_compressions) = accept_compressions {
163 if !accept_compressions.is_empty() {
164 if let Some(header_value) =
165 accept_compressions[0].into_accept_encoding_header_value(accept_compressions)
166 {
167 req.headers_mut()
168 .insert(ACCEPT_ENCODING_HEADER, header_value);
169 }
170 }
171 }
172 cx.stats.record_make_transport_start_at();
173
174 let resp = http_client
175 .ready()
176 .await
177 .map_err(|err| Status::from_error(err.into()))?
178 .call(req)
179 .await
180 .map_err(|err| Status::from_error(err.into()))?;
181
182 cx.stats.record_make_transport_end_at();
183
184 let status_code = resp.status();
185 let headers = resp.headers();
186
187 if let Some(status) = Status::from_header_map(headers) {
188 if status.code() != Code::Ok {
189 return Err(status);
190 }
191 }
192 let path = cx.rpc_info.method();
193 let rpc_config = cx.rpc_info.config();
194
195 #[cfg(not(feature = "compress"))]
196 let accept_compression = None;
197 #[cfg(feature = "compress")]
198 let accept_compression =
199 crate::codec::compression::CompressionEncoding::from_encoding_header(
200 headers,
201 &rpc_config.accept_compressions,
202 )?;
203
204 let (parts, body) = resp.into_parts();
205
206 let body = U::from_body(
207 Some(path),
208 boxed(body),
209 Kind::Response(status_code),
210 accept_compression,
211 )?;
212 let resp = hyper::Response::from_parts(parts, body);
213 Ok(Response::from_http(resp))
214 }
215}
216
217fn build_uri(addr: Address, path: &str) -> hyper::Uri {
218 match addr {
219 Address::Ip(ip) => hyper::Uri::builder()
220 .scheme(http::uri::Scheme::HTTP)
221 .authority(ip.to_string())
222 .path_and_query(path)
223 .build()
224 .expect("fail to build ip uri"),
225 #[cfg(target_family = "unix")]
226 Address::Unix(unix) => hyper::Uri::builder()
227 .scheme("http+unix")
228 .authority(hex::encode(
229 unix.as_pathname()
230 .expect("target address is an invalid unix socket")
231 .to_string_lossy()
232 .as_bytes(),
233 ))
234 .path_and_query(path)
235 .build()
236 .expect("fail to build unix uri"),
237 #[allow(unreachable_patterns)]
238 _ => unimplemented!("unsupported type of address"),
239 }
240}
241
242#[cfg(test)]
243mod tests {
244
245 #[test]
246 fn test_build_uri_ip() {
247 let addr = "127.0.0.1:8000".parse::<std::net::SocketAddr>().unwrap();
248 let path = "/path?query=1";
249 let uri = "http://127.0.0.1:8000/path?query=1"
250 .parse::<hyper::Uri>()
251 .unwrap();
252 assert_eq!(super::build_uri(volo::net::Address::from(addr), path), uri);
253 }
254
255 #[cfg(target_family = "unix")]
256 #[test]
257 fn test_build_uri_unix() {
258 let addr = "/tmp/rpc.sock".parse::<std::path::PathBuf>().unwrap();
259 let path = "/path?query=1";
260 let uri = "http+unix://2f746d702f7270632e736f636b/path?query=1"
261 .parse::<hyper::Uri>()
262 .unwrap();
263 assert_eq!(
264 super::build_uri(
265 volo::net::Address::from(
266 std::os::unix::net::SocketAddr::from_pathname(addr).unwrap()
267 ),
268 path
269 ),
270 uri
271 );
272 }
273
274 fn is_unpin<T: Unpin>() {}
275
276 #[test]
277 fn test_is_unpin() {
278 is_unpin::<super::ClientTransport<()>>();
279 }
280}