44//! subsequent requests for notarization.
55
66use http_body_util:: { BodyExt as _, Either , Empty , Full } ;
7- use hyper:: { body:: Bytes , client:: conn:: http1:: Parts , Request , StatusCode } ;
7+ use hyper:: {
8+ body:: { Bytes , Incoming } ,
9+ client:: conn:: http1:: Parts ,
10+ Request , Response , StatusCode ,
11+ } ;
812use hyper_util:: rt:: TokioIo ;
913use notary_server:: { ClientType , NotarizationSessionRequest , NotarizationSessionResponse } ;
1014use std:: {
@@ -16,6 +20,7 @@ use std::{
1620use tokio:: {
1721 io:: { AsyncRead , AsyncWrite , ReadBuf } ,
1822 net:: TcpStream ,
23+ time:: { sleep, timeout, Duration } ,
1924} ;
2025use tokio_rustls:: {
2126 client:: TlsStream ,
@@ -129,6 +134,14 @@ pub struct NotaryClient {
129134 /// in notary server.
130135 #[ builder( setter( into, strip_option) , default ) ]
131136 api_key : Option < String > ,
137+ /// The duration of notarization request timeout in seconds.
138+ #[ builder( default = "60" ) ]
139+ request_timeout : usize ,
140+ /// The number of seconds to wait between notarization request retries.
141+ ///
142+ /// By default uses the value suggested by the server.
143+ #[ builder( default = "None" ) ]
144+ request_retry_override : Option < u64 > ,
132145}
133146
134147impl NotaryClientBuilder {
@@ -355,15 +368,56 @@ impl NotaryClient {
355368
356369 debug ! ( "Sending notarization request: {:?}" , notarization_request) ;
357370
358- let notarization_response = notary_request_sender
359- . send_request ( notarization_request)
360- . await
361- . map_err ( |err| {
362- error ! ( "Failed to send http request for notarization" ) ;
363- ClientError :: new ( ErrorKind :: Http , Some ( Box :: new ( err) ) )
364- } ) ?;
371+ let notarize_with_retry_fut = async {
372+ loop {
373+ let notarization_response = notary_request_sender
374+ . send_request ( notarization_request. clone ( ) )
375+ . await
376+ . map_err ( |err| {
377+ error ! ( "Failed to send http request for notarization" ) ;
378+ ClientError :: new ( ErrorKind :: Http , Some ( Box :: new ( err) ) )
379+ } ) ?;
380+
381+ if notarization_response. status ( ) == StatusCode :: SWITCHING_PROTOCOLS {
382+ return Ok :: < Response < Incoming > , ClientError > ( notarization_response) ;
383+ } else if notarization_response. status ( ) == StatusCode :: SERVICE_UNAVAILABLE {
384+ let retry_after = self
385+ . request_retry_override
386+ . unwrap_or ( parse_retry_after ( & notarization_response) ?) ;
387+
388+ debug ! ( "Retrying notarization request in {:?}" , retry_after) ;
389+
390+ sleep ( Duration :: from_secs ( retry_after) ) . await ;
391+ } else {
392+ return Err ( ClientError :: new (
393+ ErrorKind :: Internal ,
394+ Some (
395+ format ! (
396+ "Server sent unexpected status code {:?}" ,
397+ notarization_response. status( )
398+ )
399+ . into ( ) ,
400+ ) ,
401+ ) ) ;
402+ }
403+ }
404+ } ;
405+
406+ let notarization_response = timeout (
407+ Duration :: from_secs ( self . request_timeout as u64 ) ,
408+ notarize_with_retry_fut,
409+ )
410+ . await
411+ . map_err ( |_| {
412+ ClientError :: new (
413+ ErrorKind :: Internal ,
414+ Some (
415+ "Timed out while waiting for server to accept notarization request" . into ( ) ,
416+ ) ,
417+ )
418+ } ) ??;
365419
366- debug ! ( "Sent notarization request" ) ;
420+ debug ! ( "Notarization request was accepted by the server " ) ;
367421
368422 if notarization_response. status ( ) != StatusCode :: SWITCHING_PROTOCOLS {
369423 return Err ( ClientError :: new (
@@ -388,6 +442,17 @@ impl NotaryClient {
388442
389443 Ok ( ( notary_socket. into_inner ( ) , session_id) )
390444 }
445+
446+ /// Sets notarization request timeout duration in seconds.
447+ pub fn request_timeout ( & mut self , timeout : usize ) {
448+ self . request_timeout = timeout;
449+ }
450+
451+ /// Sets the number of seconds to wait between notarization request
452+ /// retries.
453+ pub fn request_retry_override ( & mut self , seconds : u64 ) {
454+ self . request_retry_override = Some ( seconds) ;
455+ }
391456}
392457
393458/// Default root store using mozilla certs.
@@ -403,3 +468,34 @@ fn default_root_store() -> RootCertStore {
403468
404469 root_store
405470}
471+
472+ // Attempts to parse the value of the "Retry-After" header from the given
473+ // `response`.
474+ fn parse_retry_after ( response : & Response < Incoming > ) -> Result < u64 , ClientError > {
475+ let seconds = match response. headers ( ) . get ( "Retry-After" ) {
476+ Some ( value) => {
477+ let value_str = value. to_str ( ) . map_err ( |err| {
478+ ClientError :: new (
479+ ErrorKind :: Internal ,
480+ Some ( format ! ( "Invalid Retry-After header: {}" , err) . into ( ) ) ,
481+ )
482+ } ) ?;
483+
484+ let seconds: u64 = value_str. parse ( ) . map_err ( |err| {
485+ ClientError :: new (
486+ ErrorKind :: Internal ,
487+ Some ( format ! ( "Could not parse Retry-After header as number: {}" , err) . into ( ) ) ,
488+ )
489+ } ) ?;
490+ seconds
491+ }
492+ None => {
493+ return Err ( ClientError :: new (
494+ ErrorKind :: Internal ,
495+ Some ( "The expected Retry-After header was not found in server response" . into ( ) ) ,
496+ ) ) ;
497+ }
498+ } ;
499+
500+ Ok ( seconds)
501+ }
0 commit comments