@@ -14,11 +14,14 @@ use crate::error::Result;
14
14
use async_trait:: async_trait;
15
15
use editoast_derive:: EditoastError ;
16
16
pub use http_client:: { HttpClient , HttpClientBuilder } ;
17
+ use log:: info;
17
18
use reqwest:: Url ;
18
19
use serde:: { de:: DeserializeOwned , Serialize } ;
19
20
use serde_derive:: Deserialize ;
20
21
use thiserror:: Error ;
21
22
23
+ const MAX_RETRIES : u8 = 5 ;
24
+
22
25
#[ derive( Debug , Clone ) ]
23
26
pub enum CoreClient {
24
27
Direct ( HttpClient ) ,
@@ -50,11 +53,27 @@ impl CoreClient {
50
53
) -> Result < R :: Response > {
51
54
match self {
52
55
CoreClient :: Direct ( client) => {
53
- let mut request = client. request ( method, path) ;
54
- if let Some ( body) = body {
55
- request = request. json ( body) ;
56
- }
57
- let response = request. send ( ) . await . map_err ( Into :: < CoreError > :: into) ?;
56
+ let mut i_try = 0 ;
57
+ let response = loop {
58
+ let mut request = client. request ( method. clone ( ) , path) ;
59
+ if let Some ( body) = body {
60
+ request = request. json ( body) ;
61
+ }
62
+ match request. send ( ) . await . map_err ( Into :: < CoreError > :: into) {
63
+ // This error occurs quite often in the CI.
64
+ // It's linked to this issue https://github.com/hyperium/hyper/issues/2136.
65
+ // This is why we retry the request here
66
+ Err ( CoreError :: ConnectionClosedBeforeMessageCompleted )
67
+ if i_try < MAX_RETRIES =>
68
+ {
69
+ i_try += 1 ;
70
+ info ! ( "Core request '{}: {}': Connection closed before message completed. Retry [{}/{}]" , method, path, i_try, MAX_RETRIES ) ;
71
+ continue ;
72
+ }
73
+ response => break response?,
74
+ }
75
+ } ;
76
+
58
77
let url = response. url ( ) . to_string ( ) ;
59
78
let status = response. status ( ) ;
60
79
let bytes =
@@ -65,33 +84,31 @@ impl CoreClient {
65
84
msg : err. to_string ( ) ,
66
85
} ) ?;
67
86
if status. is_success ( ) {
68
- R :: from_bytes ( bytes. as_ref ( ) )
69
- } else {
70
- // We try to deserialize the response as the standard Core error format
71
- // If that fails we try to return a generic error containing the raw error
72
- let core_error =
73
- <Json < CoreErrorPayload > as CoreResponse >:: from_bytes ( bytes. as_ref ( ) )
74
- . map_err ( |err| {
75
- if let Ok ( utf8_raw_error) =
76
- String :: from_utf8 ( bytes. as_ref ( ) . to_vec ( ) )
77
- {
78
- CoreError :: GenericCoreError {
79
- status : status. as_str ( ) . to_owned ( ) ,
80
- url : url. clone ( ) ,
81
- raw_error : utf8_raw_error,
82
- }
83
- . into ( )
84
- } else {
85
- err
86
- }
87
- } ) ?;
88
- Err ( CoreError :: Forward {
89
- status : status. as_u16 ( ) ,
90
- core_error,
91
- url,
87
+ return R :: from_bytes ( bytes. as_ref ( ) ) ;
88
+ }
89
+ // We try to deserialize the response as the standard Core error format
90
+ // If that fails we try to return a generic error containing the raw error
91
+ let core_error = <Json < CoreErrorPayload > as CoreResponse >:: from_bytes (
92
+ bytes. as_ref ( ) ,
93
+ )
94
+ . map_err ( |err| {
95
+ if let Ok ( utf8_raw_error) = String :: from_utf8 ( bytes. as_ref ( ) . to_vec ( ) ) {
96
+ CoreError :: GenericCoreError {
97
+ status : status. as_str ( ) . to_owned ( ) ,
98
+ url : url. clone ( ) ,
99
+ raw_error : utf8_raw_error,
100
+ }
101
+ . into ( )
102
+ } else {
103
+ err
92
104
}
93
- . into ( ) )
105
+ } ) ?;
106
+ Err ( CoreError :: Forward {
107
+ status : status. as_u16 ( ) ,
108
+ core_error,
109
+ url,
94
110
}
111
+ . into ( ) )
95
112
}
96
113
#[ cfg( test) ]
97
114
CoreClient :: Mocked ( client) => client
@@ -264,13 +281,25 @@ enum CoreError {
264
281
url : String ,
265
282
raw_error : String ,
266
283
} ,
284
+ #[ error( "Core connection closed before message completed. Should retry." ) ]
285
+ #[ editoast_error( status = 500 ) ]
286
+ ConnectionClosedBeforeMessageCompleted ,
267
287
#[ cfg( test) ]
268
288
#[ error( "The mocked response had no body configured - check out StubResponseBuilder::body if this is unexpected" ) ]
269
289
NoResponseContent ,
270
290
}
271
291
272
292
impl From < reqwest:: Error > for CoreError {
273
293
fn from ( value : reqwest:: Error ) -> Self {
294
+ // Since we should retry the request it's useful to have its own kind of error.
295
+ if value
296
+ . to_string ( )
297
+ . contains ( "connection closed before message completed" )
298
+ {
299
+ return Self :: ConnectionClosedBeforeMessageCompleted ;
300
+ }
301
+
302
+ // Convert the reqwest error
274
303
Self :: GenericCoreError {
275
304
status : value
276
305
. status ( )
0 commit comments