Skip to main content

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}