1
- use crate :: metadata:: MetadataMap ;
1
+ use crate :: metadata:: { MetadataMap , MetadataValue } ;
2
2
#[ cfg( feature = "transport" ) ]
3
3
use crate :: transport:: Certificate ;
4
4
use futures_core:: Stream ;
5
5
use http:: Extensions ;
6
- use std:: net:: SocketAddr ;
7
6
#[ cfg( feature = "transport" ) ]
8
7
use std:: sync:: Arc ;
8
+ use std:: { net:: SocketAddr , time:: Duration } ;
9
9
10
10
/// A gRPC request and metadata from an RPC call.
11
11
#[ derive( Debug ) ]
@@ -221,6 +221,39 @@ impl<T> Request<T> {
221
221
pub ( crate ) fn get < I : Send + Sync + ' static > ( & self ) -> Option < & I > {
222
222
self . extensions . get :: < I > ( )
223
223
}
224
+
225
+ /// Set the max duration the request is allowed to take.
226
+ ///
227
+ /// Requires the server to support the `grpc-timeout` metadata, which Tonic does.
228
+ ///
229
+ /// The duration will be formatted according to [the spec] and use the most precise unit
230
+ /// possible.
231
+ ///
232
+ /// Example:
233
+ ///
234
+ /// ```rust
235
+ /// use std::time::Duration;
236
+ /// use tonic::Request;
237
+ ///
238
+ /// let mut request = Request::new(());
239
+ ///
240
+ /// request.set_timeout(Duration::from_secs(30));
241
+ ///
242
+ /// let value = request.metadata().get("grpc-timeout").unwrap();
243
+ ///
244
+ /// assert_eq!(
245
+ /// value,
246
+ /// // equivalent to 30 seconds
247
+ /// "30000000u"
248
+ /// );
249
+ /// ```
250
+ ///
251
+ /// [the spec]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
252
+ pub fn set_timeout ( & mut self , deadline : Duration ) {
253
+ let value = MetadataValue :: from_str ( & duration_to_grpc_timeout ( deadline) ) . unwrap ( ) ;
254
+ self . metadata_mut ( )
255
+ . insert ( crate :: metadata:: GRPC_TIMEOUT_HEADER , value) ;
256
+ }
224
257
}
225
258
226
259
impl < T > IntoRequest < T > for T {
@@ -265,6 +298,40 @@ mod sealed {
265
298
pub trait Sealed { }
266
299
}
267
300
301
+ fn duration_to_grpc_timeout ( duration : Duration ) -> String {
302
+ fn try_format < T : Into < u128 > > (
303
+ duration : Duration ,
304
+ unit : char ,
305
+ convert : impl FnOnce ( Duration ) -> T ,
306
+ ) -> Option < String > {
307
+ // The gRPC spec specifies that the timeout most be at most 8 digits. So this is the largest a
308
+ // value can be before we need to use a bigger unit.
309
+ let max_size: u128 = 99_999_999 ; // exactly 8 digits
310
+
311
+ let value = convert ( duration) . into ( ) ;
312
+ if value > max_size {
313
+ None
314
+ } else {
315
+ Some ( format ! ( "{}{}" , value, unit) )
316
+ }
317
+ }
318
+
319
+ // pick the most precise unit that is less than or equal to 8 digits as per the gRPC spec
320
+ try_format ( duration, 'n' , |d| d. as_nanos ( ) )
321
+ . or_else ( || try_format ( duration, 'u' , |d| d. as_micros ( ) ) )
322
+ . or_else ( || try_format ( duration, 'm' , |d| d. as_millis ( ) ) )
323
+ . or_else ( || try_format ( duration, 'S' , |d| d. as_secs ( ) ) )
324
+ . or_else ( || try_format ( duration, 'M' , |d| d. as_secs ( ) / 60 ) )
325
+ . or_else ( || {
326
+ try_format ( duration, 'H' , |d| {
327
+ let minutes = d. as_secs ( ) / 60 ;
328
+ minutes / 60
329
+ } )
330
+ } )
331
+ // duration has to be more than 11_415 years for this to happen
332
+ . expect ( "duration is unrealistically large" )
333
+ }
334
+
268
335
#[ cfg( test) ]
269
336
mod tests {
270
337
use super :: * ;
@@ -283,4 +350,25 @@ mod tests {
283
350
let http_request = r. into_http ( Uri :: default ( ) ) ;
284
351
assert ! ( http_request. headers( ) . is_empty( ) ) ;
285
352
}
353
+
354
+ #[ test]
355
+ fn duration_to_grpc_timeout_less_than_second ( ) {
356
+ let timeout = Duration :: from_millis ( 500 ) ;
357
+ let value = duration_to_grpc_timeout ( timeout) ;
358
+ assert_eq ! ( value, format!( "{}u" , timeout. as_micros( ) ) ) ;
359
+ }
360
+
361
+ #[ test]
362
+ fn duration_to_grpc_timeout_more_than_second ( ) {
363
+ let timeout = Duration :: from_secs ( 30 ) ;
364
+ let value = duration_to_grpc_timeout ( timeout) ;
365
+ assert_eq ! ( value, format!( "{}u" , timeout. as_micros( ) ) ) ;
366
+ }
367
+
368
+ #[ test]
369
+ fn duration_to_grpc_timeout_a_very_long_time ( ) {
370
+ let one_hour = Duration :: from_secs ( 60 * 60 ) ;
371
+ let value = duration_to_grpc_timeout ( one_hour) ;
372
+ assert_eq ! ( value, format!( "{}m" , one_hour. as_millis( ) ) ) ;
373
+ }
286
374
}
0 commit comments