tonic/client/grpc.rs
1/*
2 *
3 * Copyright 2025 gRPC authors.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 *
23 */
24
25use crate::codec::EncodeBody;
26use crate::codec::{CompressionEncoding, EnabledCompressionEncodings};
27use crate::metadata::GRPC_CONTENT_TYPE;
28use crate::{
29 Code, Request, Response, Status,
30 body::Body,
31 client::GrpcService,
32 codec::{Codec, Decoder, Streaming},
33 request::SanitizeHeaders,
34};
35use http::{
36 header::{CONTENT_TYPE, HeaderValue, TE},
37 uri::{PathAndQuery, Uri},
38};
39use http_body::Body as HttpBody;
40use std::{fmt, future, pin::pin};
41use tokio_stream::{Stream, StreamExt};
42
43/// A gRPC client dispatcher.
44///
45/// This will wrap some inner [`GrpcService`] and will encode/decode
46/// messages via the provided codec.
47///
48/// Each request method takes a [`Request`], a [`PathAndQuery`], and a
49/// [`Codec`]. The request contains the message to send via the
50/// [`Codec::encoder`]. The path determines the fully qualified path
51/// that will be append to the outgoing uri. The path must follow
52/// the conventions explained in the [gRPC protocol definition] under `Path →`. An
53/// example of this path could look like `/greeter.Greeter/SayHello`.
54///
55/// [gRPC protocol definition]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
56pub struct Grpc<T> {
57 inner: T,
58 config: GrpcConfig,
59}
60
61struct GrpcConfig {
62 origin: Uri,
63 /// Which compression encodings does the client accept?
64 accept_compression_encodings: EnabledCompressionEncodings,
65 /// The compression encoding that will be applied to requests.
66 send_compression_encodings: Option<CompressionEncoding>,
67 /// Limits the maximum size of a decoded message.
68 max_decoding_message_size: Option<usize>,
69 /// Limits the maximum size of an encoded message.
70 max_encoding_message_size: Option<usize>,
71}
72
73impl<T> Grpc<T> {
74 /// Creates a new gRPC client with the provided [`GrpcService`].
75 pub fn new(inner: T) -> Self {
76 Self::with_origin(inner, Uri::default())
77 }
78
79 /// Creates a new gRPC client with the provided [`GrpcService`] and `Uri`.
80 ///
81 /// The provided Uri will use only the scheme and authority parts as the
82 /// path_and_query portion will be set for each method.
83 pub fn with_origin(inner: T, origin: Uri) -> Self {
84 Self {
85 inner,
86 config: GrpcConfig {
87 origin,
88 send_compression_encodings: None,
89 accept_compression_encodings: EnabledCompressionEncodings::default(),
90 max_decoding_message_size: None,
91 max_encoding_message_size: None,
92 },
93 }
94 }
95
96 /// Compress requests with the provided encoding.
97 ///
98 /// Requires the server to accept the specified encoding, otherwise it might return an error.
99 ///
100 /// # Example
101 ///
102 /// The most common way of using this is through a client generated by tonic-build:
103 ///
104 /// ```rust
105 /// use tonic::transport::Channel;
106 /// # enum CompressionEncoding { Gzip }
107 /// # struct TestClient<T>(T);
108 /// # impl<T> TestClient<T> {
109 /// # fn new(channel: T) -> Self { Self(channel) }
110 /// # fn send_compressed(self, _: CompressionEncoding) -> Self { self }
111 /// # }
112 ///
113 /// # async {
114 /// let channel = Channel::builder("127.0.0.1:3000".parse().unwrap())
115 /// .connect()
116 /// .await
117 /// .unwrap();
118 ///
119 /// let client = TestClient::new(channel).send_compressed(CompressionEncoding::Gzip);
120 /// # };
121 /// ```
122 pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
123 self.config.send_compression_encodings = Some(encoding);
124 self
125 }
126
127 /// Enable accepting compressed responses.
128 ///
129 /// Requires the server to also support sending compressed responses.
130 ///
131 /// # Example
132 ///
133 /// The most common way of using this is through a client generated by tonic-build:
134 ///
135 /// ```rust
136 /// use tonic::transport::Channel;
137 /// # enum CompressionEncoding { Gzip }
138 /// # struct TestClient<T>(T);
139 /// # impl<T> TestClient<T> {
140 /// # fn new(channel: T) -> Self { Self(channel) }
141 /// # fn accept_compressed(self, _: CompressionEncoding) -> Self { self }
142 /// # }
143 ///
144 /// # async {
145 /// let channel = Channel::builder("127.0.0.1:3000".parse().unwrap())
146 /// .connect()
147 /// .await
148 /// .unwrap();
149 ///
150 /// let client = TestClient::new(channel).accept_compressed(CompressionEncoding::Gzip);
151 /// # };
152 /// ```
153 pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
154 self.config.accept_compression_encodings.enable(encoding);
155 self
156 }
157
158 /// Limits the maximum size of a decoded message.
159 ///
160 /// # Example
161 ///
162 /// The most common way of using this is through a client generated by tonic-build:
163 ///
164 /// ```rust
165 /// use tonic::transport::Channel;
166 /// # struct TestClient<T>(T);
167 /// # impl<T> TestClient<T> {
168 /// # fn new(channel: T) -> Self { Self(channel) }
169 /// # fn max_decoding_message_size(self, _: usize) -> Self { self }
170 /// # }
171 ///
172 /// # async {
173 /// let channel = Channel::builder("127.0.0.1:3000".parse().unwrap())
174 /// .connect()
175 /// .await
176 /// .unwrap();
177 ///
178 /// // Set the limit to 2MB, Defaults to 4MB.
179 /// let limit = 2 * 1024 * 1024;
180 /// let client = TestClient::new(channel).max_decoding_message_size(limit);
181 /// # };
182 /// ```
183 pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
184 self.config.max_decoding_message_size = Some(limit);
185 self
186 }
187
188 /// Limits the maximum size of an encoded message.
189 ///
190 /// # Example
191 ///
192 /// The most common way of using this is through a client generated by tonic-build:
193 ///
194 /// ```rust
195 /// use tonic::transport::Channel;
196 /// # struct TestClient<T>(T);
197 /// # impl<T> TestClient<T> {
198 /// # fn new(channel: T) -> Self { Self(channel) }
199 /// # fn max_encoding_message_size(self, _: usize) -> Self { self }
200 /// # }
201 ///
202 /// # async {
203 /// let channel = Channel::builder("127.0.0.1:3000".parse().unwrap())
204 /// .connect()
205 /// .await
206 /// .unwrap();
207 ///
208 /// // Set the limit to 2MB, Defaults to `usize::MAX`.
209 /// let limit = 2 * 1024 * 1024;
210 /// let client = TestClient::new(channel).max_encoding_message_size(limit);
211 /// # };
212 /// ```
213 pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
214 self.config.max_encoding_message_size = Some(limit);
215 self
216 }
217
218 /// Check if the inner [`GrpcService`] is able to accept a new request.
219 ///
220 /// This will call [`GrpcService::poll_ready`] until it returns ready or
221 /// an error. If this returns ready the inner [`GrpcService`] is ready to
222 /// accept one more request.
223 pub async fn ready(&mut self) -> Result<(), T::Error>
224 where
225 T: GrpcService<Body>,
226 {
227 future::poll_fn(|cx| self.inner.poll_ready(cx)).await
228 }
229
230 /// Send a single unary gRPC request.
231 pub async fn unary<M1, M2, C>(
232 &mut self,
233 request: Request<M1>,
234 path: PathAndQuery,
235 codec: C,
236 ) -> Result<Response<M2>, Status>
237 where
238 T: GrpcService<Body>,
239 T::ResponseBody: HttpBody + Send + 'static,
240 <T::ResponseBody as HttpBody>::Error: Into<crate::BoxError>,
241 C: Codec<Encode = M1, Decode = M2>,
242 M1: Send + Sync + 'static,
243 M2: Send + Sync + 'static,
244 {
245 let request = request.map(|m| tokio_stream::once(m));
246 self.client_streaming(request, path, codec).await
247 }
248
249 /// Send a client side streaming gRPC request.
250 pub async fn client_streaming<S, M1, M2, C>(
251 &mut self,
252 request: Request<S>,
253 path: PathAndQuery,
254 codec: C,
255 ) -> Result<Response<M2>, Status>
256 where
257 T: GrpcService<Body>,
258 T::ResponseBody: HttpBody + Send + 'static,
259 <T::ResponseBody as HttpBody>::Error: Into<crate::BoxError>,
260 S: Stream<Item = M1> + Send + 'static,
261 C: Codec<Encode = M1, Decode = M2>,
262 M1: Send + Sync + 'static,
263 M2: Send + Sync + 'static,
264 {
265 let (mut parts, body, extensions) =
266 self.streaming(request, path, codec).await?.into_parts();
267
268 let mut body = pin!(body);
269
270 let message = body
271 .try_next()
272 .await
273 .map_err(|mut status| {
274 status.metadata_mut().merge(parts.clone());
275 status
276 })?
277 .ok_or_else(|| Status::internal("Missing response message."))?;
278
279 if let Some(trailers) = body.trailers().await? {
280 parts.merge(trailers);
281 }
282
283 Ok(Response::from_parts(parts, message, extensions))
284 }
285
286 /// Send a server side streaming gRPC request.
287 pub async fn server_streaming<M1, M2, C>(
288 &mut self,
289 request: Request<M1>,
290 path: PathAndQuery,
291 codec: C,
292 ) -> Result<Response<Streaming<M2>>, Status>
293 where
294 T: GrpcService<Body>,
295 T::ResponseBody: HttpBody + Send + 'static,
296 <T::ResponseBody as HttpBody>::Error: Into<crate::BoxError>,
297 C: Codec<Encode = M1, Decode = M2>,
298 M1: Send + Sync + 'static,
299 M2: Send + Sync + 'static,
300 {
301 let request = request.map(|m| tokio_stream::once(m));
302 self.streaming(request, path, codec).await
303 }
304
305 /// Send a bi-directional streaming gRPC request.
306 pub async fn streaming<S, M1, M2, C>(
307 &mut self,
308 mut request: Request<S>,
309 path: PathAndQuery,
310 mut codec: C,
311 ) -> Result<Response<Streaming<M2>>, Status>
312 where
313 T: GrpcService<Body>,
314 T::ResponseBody: HttpBody + Send + 'static,
315 <T::ResponseBody as HttpBody>::Error: Into<crate::BoxError>,
316 S: Stream<Item = M1> + Send + 'static,
317 C: Codec<Encode = M1, Decode = M2>,
318 M1: Send + Sync + 'static,
319 M2: Send + Sync + 'static,
320 {
321 let cancellation_token = request.remove_cancellation_handle().map(|c| c.into_token());
322
323 let request = request
324 .map(|s| {
325 EncodeBody::new_client_with_cancellation(
326 codec.encoder(),
327 s.map(Ok),
328 self.config.send_compression_encodings,
329 self.config.max_encoding_message_size,
330 cancellation_token,
331 )
332 })
333 .map(Body::new);
334
335 let request = self.config.prepare_request(request, path);
336
337 let response = self
338 .inner
339 .call(request)
340 .await
341 .map_err(Status::from_error_generic)?;
342
343 let decoder = codec.decoder();
344
345 self.create_response(decoder, response)
346 }
347
348 // Keeping this code in a separate function from Self::streaming lets functions that return the
349 // same output share the generated binary code
350 fn create_response<M2>(
351 &self,
352 decoder: impl Decoder<Item = M2, Error = Status> + Send + 'static,
353 response: http::Response<T::ResponseBody>,
354 ) -> Result<Response<Streaming<M2>>, Status>
355 where
356 T: GrpcService<Body>,
357 T::ResponseBody: HttpBody + Send + 'static,
358 <T::ResponseBody as HttpBody>::Error: Into<crate::BoxError>,
359 {
360 let encoding = CompressionEncoding::from_encoding_header(
361 response.headers(),
362 self.config.accept_compression_encodings,
363 )?;
364
365 let status_code = response.status();
366 let trailers_only_status = Status::from_header_map(response.headers());
367
368 // We do not need to check for trailers if the `grpc-status` header is present
369 // with a valid code.
370 let expect_additional_trailers = if let Some(status) = trailers_only_status {
371 if status.code() != Code::Ok {
372 return Err(status);
373 }
374
375 false
376 } else {
377 true
378 };
379
380 let response = response.map(|body| {
381 if expect_additional_trailers {
382 Streaming::new_response(
383 decoder,
384 body,
385 status_code,
386 encoding,
387 self.config.max_decoding_message_size,
388 )
389 } else {
390 Streaming::new_empty(decoder, body)
391 }
392 });
393
394 Ok(Response::from_http(response))
395 }
396}
397
398impl GrpcConfig {
399 fn prepare_request(&self, request: Request<Body>, path: PathAndQuery) -> http::Request<Body> {
400 let mut parts = self.origin.clone().into_parts();
401
402 match &parts.path_and_query {
403 Some(pnq) if pnq != "/" => {
404 parts.path_and_query = Some(
405 format!("{}{}", pnq.path(), path)
406 .parse()
407 .expect("must form valid path_and_query"),
408 )
409 }
410 _ => {
411 parts.path_and_query = Some(path);
412 }
413 }
414
415 let uri = Uri::from_parts(parts).expect("path_and_query only is valid Uri");
416
417 let mut request = request.into_http(
418 uri,
419 http::Method::POST,
420 http::Version::HTTP_2,
421 SanitizeHeaders::Yes,
422 );
423
424 // Add the gRPC related HTTP headers
425 request
426 .headers_mut()
427 .insert(TE, HeaderValue::from_static("trailers"));
428
429 // Set the content type
430 request
431 .headers_mut()
432 .insert(CONTENT_TYPE, GRPC_CONTENT_TYPE);
433
434 #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))]
435 if let Some(encoding) = self.send_compression_encodings {
436 request.headers_mut().insert(
437 crate::codec::compression::ENCODING_HEADER,
438 encoding.into_header_value(),
439 );
440 }
441
442 if let Some(header_value) = self
443 .accept_compression_encodings
444 .into_accept_encoding_header_value()
445 {
446 request.headers_mut().insert(
447 crate::codec::compression::ACCEPT_ENCODING_HEADER,
448 header_value,
449 );
450 }
451
452 request
453 }
454}
455
456impl<T: Clone> Clone for Grpc<T> {
457 fn clone(&self) -> Self {
458 Self {
459 inner: self.inner.clone(),
460 config: GrpcConfig {
461 origin: self.config.origin.clone(),
462 send_compression_encodings: self.config.send_compression_encodings,
463 accept_compression_encodings: self.config.accept_compression_encodings,
464 max_encoding_message_size: self.config.max_encoding_message_size,
465 max_decoding_message_size: self.config.max_decoding_message_size,
466 },
467 }
468 }
469}
470
471impl<T: fmt::Debug> fmt::Debug for Grpc<T> {
472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 f.debug_struct("Grpc")
474 .field("inner", &self.inner)
475 .field("origin", &self.config.origin)
476 .field(
477 "compression_encoding",
478 &self.config.send_compression_encodings,
479 )
480 .field(
481 "accept_compression_encodings",
482 &self.config.accept_compression_encodings,
483 )
484 .field(
485 "max_decoding_message_size",
486 &self.config.max_decoding_message_size,
487 )
488 .field(
489 "max_encoding_message_size",
490 &self.config.max_encoding_message_size,
491 )
492 .finish()
493 }
494}