1
1
package io .harness .cf .client .connector ;
2
2
3
3
import java .io .IOException ;
4
+ import java .text .ParseException ;
5
+ import java .text .SimpleDateFormat ;
6
+ import java .time .Duration ;
7
+ import java .time .Instant ;
8
+ import java .util .Date ;
9
+ import java .util .Locale ;
4
10
import java .util .concurrent .TimeUnit ;
5
- import lombok .extern .slf4j .Slf4j ;
6
11
import okhttp3 .*;
7
12
import org .jetbrains .annotations .NotNull ;
13
+ import org .slf4j .Logger ;
14
+ import org .slf4j .LoggerFactory ;
8
15
9
- @ Slf4j
10
16
public class NewRetryInterceptor implements Interceptor {
11
17
18
+ private static final Logger log = LoggerFactory .getLogger (NewRetryInterceptor .class );
19
+ private static final SimpleDateFormat imfDateFormat =
20
+ new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss zzz" , Locale .US );
12
21
private final long retryBackoffDelay ;
13
22
private final long maxTryCount ;
14
23
@@ -27,6 +36,7 @@ public NewRetryInterceptor(long maxTryCount, long retryBackoffDelay) {
27
36
public Response intercept (@ NotNull Chain chain ) throws IOException {
28
37
int tryCount = 1 ;
29
38
boolean successful ;
39
+ boolean limitReached = false ;
30
40
Response response = null ;
31
41
String msg = "" ;
32
42
do {
@@ -36,7 +46,9 @@ public Response intercept(@NotNull Chain chain) throws IOException {
36
46
response = chain .proceed (chain .request ());
37
47
successful = response .isSuccessful ();
38
48
if (!successful ) {
39
- msg = String .format ("httpCode=%d %s" , response .code (), response .message ());
49
+ msg =
50
+ String .format (
51
+ Locale .getDefault (), "httpCode=%d %s" , response .code (), response .message ());
40
52
if (!shouldRetryHttpErrorCode (response .code ())) {
41
53
return response ;
42
54
}
@@ -47,34 +59,81 @@ public Response intercept(@NotNull Chain chain) throws IOException {
47
59
48
60
} catch (Exception ex ) {
49
61
log .trace ("Error while attempting to make request" , ex );
50
- msg = ex .getMessage ();
62
+ msg = ex .getClass (). getSimpleName () + ": " + ex . getMessage ();
51
63
response = makeErrorResp (chain , msg );
52
64
successful = false ;
53
65
if (!shouldRetryException (ex )) {
54
66
return response ;
55
67
}
56
68
}
57
69
if (!successful ) {
58
- final boolean limitReached = tryCount > maxTryCount ;
70
+ int retryAfterHeaderValue = getRetryAfterHeaderInSeconds (response );
71
+ long backOffDelayMs ;
72
+ if (retryAfterHeaderValue > 0 ) {
73
+ // Use Retry-After header if detected first
74
+ log .trace ("Retry-After header detected: {} seconds" , retryAfterHeaderValue );
75
+ backOffDelayMs = retryAfterHeaderValue * 1000L ;
76
+ } else {
77
+ // Else fallback to a randomized exponential backoff
78
+ backOffDelayMs = retryBackoffDelay * tryCount ;
79
+ }
80
+
81
+ limitReached = tryCount >= maxTryCount ;
59
82
log .warn (
60
83
"Request attempt {} to {} was not successful, [{}]{}" ,
61
84
tryCount ,
62
85
chain .request ().url (),
63
86
msg ,
64
87
limitReached
65
88
? ", retry limited reached"
66
- : String .format (", retrying in %dms" , retryBackoffDelay * tryCount ));
89
+ : String .format (
90
+ Locale .getDefault (),
91
+ ", retrying in %dms (retry-after hdr: %b)" ,
92
+ backOffDelayMs ,
93
+ retryAfterHeaderValue > 0 ));
67
94
68
95
if (!limitReached ) {
69
- sleep (retryBackoffDelay * tryCount );
96
+ sleep (backOffDelayMs );
70
97
}
71
98
}
72
- } while (!successful && tryCount ++ <= maxTryCount );
99
+ tryCount ++;
100
+ } while (!successful && !limitReached );
73
101
74
102
return response ;
75
103
}
76
104
105
+ int getRetryAfterHeaderInSeconds (Response response ) {
106
+ final String retryAfterValue = response .header ("Retry-After" );
107
+ if (retryAfterValue == null ) {
108
+ return 0 ;
109
+ }
110
+
111
+ int seconds = 0 ;
112
+ try {
113
+ seconds = Integer .parseInt (retryAfterValue );
114
+ } catch (NumberFormatException ignored ) {
115
+ }
116
+
117
+ if (seconds <= 0 ) {
118
+ try {
119
+ final Date then = imfDateFormat .parse (retryAfterValue );
120
+ if (then != null ) {
121
+ seconds = (int ) Duration .between (Instant .now (), then .toInstant ()).getSeconds ();
122
+ }
123
+ } catch (ParseException ignored ) {
124
+ }
125
+ }
126
+
127
+ if (seconds < 0 ) {
128
+ seconds = 0 ;
129
+ }
130
+
131
+ return Math .min (seconds , 3600 );
132
+ }
133
+
77
134
private boolean shouldRetryException (Exception ex ) {
135
+ log .debug (
136
+ "should retry exception check: {} - {}" , ex .getClass ().getSimpleName (), ex .getMessage ());
78
137
return true ;
79
138
}
80
139
@@ -87,7 +146,7 @@ private boolean shouldRetryHttpErrorCode(int httpCode) {
87
146
88
147
private Response makeErrorResp (Chain chain , String msg ) {
89
148
return new Response .Builder ()
90
- .code (404 ) /* dummy response: real reason is in the message */
149
+ .code (400 ) /* dummy response: real reason is in the message */
91
150
.request (chain .request ())
92
151
.protocol (Protocol .HTTP_2 )
93
152
.message (msg )
0 commit comments