@@ -31,6 +31,9 @@ pub enum StorageErr {
3131 #[ error( "HTTP status error {0}" ) ]
3232 HttpStatusErr ( String ) ,
3333
34+ #[ error( "HTTP fetch failed after {0} retries" ) ]
35+ FetchRetryMax ( u8 ) ,
36+
3437 #[ error( "Authority missing" ) ]
3538 AuthorityMissing ,
3639
@@ -45,8 +48,11 @@ pub struct UriHandler {
4548 uri : url:: Url ,
4649 uri_scheme : String ,
4750 max_size : Option < usize > ,
51+ retries : u8 ,
4852}
4953
54+ const DEFAULT_RETRY_NUMB : u8 = 1 ;
55+
5056impl UriHandler {
5157 fn supported_scheme ( scheme : & str ) -> bool {
5258 if risc0_zkvm:: is_dev_mode ( ) {
@@ -59,7 +65,11 @@ impl UriHandler {
5965 matches ! ( authority, "image" | "input" )
6066 }
6167
62- pub fn new ( uri_str : & str , max_size : Option < usize > ) -> Result < Self , StorageErr > {
68+ pub fn new (
69+ uri_str : & str ,
70+ max_size : Option < usize > ,
71+ retries : Option < u8 > ,
72+ ) -> Result < Self , StorageErr > {
6373 let uri = url:: Url :: parse ( uri_str) ?;
6474
6575 let scheme = uri. scheme ( ) . to_string ( ) ;
@@ -86,7 +96,12 @@ impl UriHandler {
8696 }
8797 }
8898
89- Ok ( Self { uri, uri_scheme : scheme, max_size } )
99+ Ok ( Self {
100+ uri,
101+ uri_scheme : scheme,
102+ max_size,
103+ retries : retries. unwrap_or ( DEFAULT_RETRY_NUMB ) ,
104+ } )
90105 }
91106
92107 pub fn exists ( & self ) -> bool {
@@ -103,15 +118,29 @@ impl UriHandler {
103118 match self . uri_scheme . as_ref ( ) {
104119 "bonsai" => Err ( StorageErr :: BonsaiFetch ) ,
105120 "http" | "https" => {
106- let res = reqwest:: get ( self . uri . to_string ( ) ) . await ?;
107- let status = res. status ( ) ;
108- if !status. is_success ( ) {
109- let body = res. text ( ) . await ?;
110- return Err ( StorageErr :: HttpStatusErr ( format ! (
111- "HTTP fetch err: {} - {}" ,
112- status, body
113- ) ) ) ;
114- }
121+ let mut retry = 0 ;
122+ let res = loop {
123+ // TODO: move these ?'s to captures + retries
124+ // currently only retry on http status code failures
125+ let res = reqwest:: get ( self . uri . to_string ( ) ) . await ?;
126+ let status = res. status ( ) ;
127+ if status. is_success ( ) {
128+ break res;
129+ } else {
130+ let body = res. text ( ) . await ?;
131+ tracing:: error!(
132+ "HTTP error fetching contents {retry}/{}: {status} - {body}" ,
133+ self . retries
134+ ) ;
135+ if retry == self . retries {
136+ return Err ( StorageErr :: FetchRetryMax ( self . retries ) ) ;
137+ }
138+ retry += 1 ;
139+ // TODO configurable...
140+ tokio:: time:: sleep ( tokio:: time:: Duration :: from_secs ( 1 ) ) . await ;
141+ continue ;
142+ }
143+ } ;
115144
116145 let mut buffer = vec ! [ ] ;
117146 if let Some ( content_length) = res. content_length ( ) {
@@ -163,20 +192,26 @@ impl UriHandler {
163192pub struct UriHandlerBuilder {
164193 uri_str : String ,
165194 max_size : Option < usize > ,
195+ retries : Option < u8 > ,
166196}
167197
168198impl UriHandlerBuilder {
169199 pub fn new ( uri_str : & str ) -> Self {
170- Self { uri_str : uri_str. into ( ) , max_size : None }
200+ Self { uri_str : uri_str. into ( ) , max_size : None , retries : None }
171201 }
172202
173203 pub fn set_max_size ( mut self , max_size : usize ) -> Self {
174204 self . max_size = Some ( max_size) ;
175205 self
176206 }
177207
208+ pub fn set_retries ( mut self , retries : u8 ) -> Self {
209+ self . retries = Some ( retries) ;
210+ self
211+ }
212+
178213 pub fn build ( self ) -> Result < UriHandler , StorageErr > {
179- UriHandler :: new ( & self . uri_str , self . max_size )
214+ UriHandler :: new ( & self . uri_str , self . max_size , self . retries )
180215 }
181216}
182217
@@ -189,6 +224,8 @@ impl Display for UriHandler {
189224#[ cfg( test) ]
190225mod tests {
191226 use super :: * ;
227+ use httpmock:: prelude:: * ;
228+ use tracing_test:: traced_test;
192229
193230 #[ test]
194231 fn bonsai_uri_parser ( ) {
@@ -230,22 +267,65 @@ mod tests {
230267
231268 #[ tokio:: test]
232269 async fn http_fetch ( ) {
233- let handler = UriHandlerBuilder :: new ( "https://risczero.com/" )
234- . set_max_size ( 1_000_000 )
235- . build ( )
236- . unwrap ( ) ;
270+ let server = MockServer :: start ( ) ;
271+ let resp_data = vec ! [ 0x41 , 0x41 , 0x41 , 0x41 ] ;
272+ let get_mock = server. mock ( |when, then| {
273+ when. method ( GET ) . path ( "/image" ) ;
274+ then. status ( 200 ) . body ( & resp_data) ;
275+ } ) ;
276+
277+ let url = format ! ( "http://{}/image" , server. address( ) ) ;
278+ let handler = UriHandlerBuilder :: new ( & url) . set_max_size ( 1_000_000 ) . build ( ) . unwrap ( ) ;
279+ assert ! ( !handler. exists( ) ) ;
280+
281+ let data = handler. fetch ( ) . await . unwrap ( ) ;
282+ assert_eq ! ( data, resp_data) ;
283+ get_mock. assert ( ) ;
284+ }
285+
286+ #[ traced_test]
287+ #[ tokio:: test]
288+ async fn http_fetch_retry ( ) {
289+ static mut REQ_COUNT : u32 = 0 ;
290+
291+ let server = MockServer :: start ( ) ;
292+ let get_mock = server. mock ( |when, then| {
293+ when. method ( GET ) . path ( "/image" ) . matches ( |_req : & HttpMockRequest | {
294+ let req = unsafe {
295+ let req = REQ_COUNT ;
296+ REQ_COUNT += 1 ;
297+ req
298+ } ;
299+ req >= 1
300+ } ) ;
301+ then. status ( 200 ) . body ( "TEST" ) ;
302+ } ) ;
303+
304+ let url = format ! ( "http://{}/image" , server. address( ) ) ;
305+ let handler =
306+ UriHandlerBuilder :: new ( & url) . set_max_size ( 1_000_000 ) . set_retries ( 1 ) . build ( ) . unwrap ( ) ;
237307 assert ! ( !handler. exists( ) ) ;
238308
239309 let _data = handler. fetch ( ) . await . unwrap ( ) ;
310+ get_mock. assert ( ) ;
311+ assert ! ( logs_contain( "HTTP error fetching contents 0/1" ) ) ;
240312 }
241313
242314 #[ tokio:: test]
243315 #[ should_panic( expected = "TooLarge" ) ]
244316 async fn max_size_limit ( ) {
245- let handler =
246- UriHandlerBuilder :: new ( "https://risczero.com/" ) . set_max_size ( 1 ) . build ( ) . unwrap ( ) ;
317+ let server = MockServer :: start ( ) ;
318+ let resp_data = vec ! [ 0x41 , 0x41 , 0x41 , 0x41 ] ;
319+ let get_mock = server. mock ( |when, then| {
320+ when. method ( GET ) . path ( "/image" ) ;
321+ then. status ( 200 ) . body ( & resp_data) ;
322+ } ) ;
323+
324+ let url = format ! ( "http://{}/image" , server. address( ) ) ;
325+ let handler = UriHandlerBuilder :: new ( & url) . set_max_size ( 1 ) . build ( ) . unwrap ( ) ;
247326 assert ! ( !handler. exists( ) ) ;
248327
249328 let _data = handler. fetch ( ) . await . unwrap ( ) ;
329+ get_mock. assert ( ) ;
250330 }
251331}
0 commit comments