11package io .github .doocs .im .util ;
22
33import io .github .doocs .im .ClientConfiguration ;
4+ import io .github .doocs .im .model .response .BaseGenericResult ;
45import io .github .doocs .im .model .response .GenericResult ;
56import okhttp3 .*;
67
@@ -32,7 +33,7 @@ public class HttpUtil {
3233 .writeTimeout (DEFAULT_CONFIG .getWriteTimeout (), TimeUnit .MILLISECONDS )
3334 .callTimeout (DEFAULT_CONFIG .getCallTimeout (), TimeUnit .MILLISECONDS )
3435 .retryOnConnectionFailure (false )
35- .addInterceptor (new RetryInterceptor (DEFAULT_CONFIG .getMaxRetries (), DEFAULT_CONFIG .getRetryIntervalMs (), DEFAULT_CONFIG .getBusinessRetryCodes (), DEFAULT_CONFIG .isEnableBusinessRetry ()))
36+ .addInterceptor (new RetryInterceptor (DEFAULT_CONFIG .getMaxRetries (), DEFAULT_CONFIG .getRetryIntervalMs (), DEFAULT_CONFIG .getBusinessRetryCodes (), DEFAULT_CONFIG .isEnableBusinessRetry (), BaseGenericResult . class ))
3637 .build ();
3738
3839 private HttpUtil () {
@@ -59,7 +60,7 @@ private static OkHttpClient getClient(ClientConfiguration config) {
5960 .writeTimeout (cfg .getWriteTimeout (), TimeUnit .MILLISECONDS )
6061 .callTimeout (cfg .getCallTimeout (), TimeUnit .MILLISECONDS )
6162 .retryOnConnectionFailure (false )
62- .addInterceptor (new RetryInterceptor (cfg .getMaxRetries (), cfg .getRetryIntervalMs (), DEFAULT_CONFIG .getBusinessRetryCodes (), DEFAULT_CONFIG .isEnableBusinessRetry ()))
63+ .addInterceptor (new RetryInterceptor (cfg .getMaxRetries (), cfg .getRetryIntervalMs (), cfg .getBusinessRetryCodes (), cfg .isEnableBusinessRetry (), BaseGenericResult . class ))
6364 .build ());
6465 }
6566
@@ -103,89 +104,98 @@ class RetryInterceptor implements Interceptor {
103104 Stream .of (408 , 429 , 500 , 502 , 503 , 504 ).collect (Collectors .toSet ())
104105 );
105106 private static final int MAX_DELAY_MS = 10000 ;
107+ private static final int MAX_BODY_SIZE = 1024 * 1024 ;
106108 private final int maxRetries ;
107109 private final long retryIntervalMs ;
108110 private final Set <Integer > businessRetryCodes ;
109111 private final boolean enableBusinessRetry ;
112+ private final Class <? extends GenericResult > resultType ;
113+ private final Random random = new Random ();
110114
111- public RetryInterceptor (int maxRetries , long retryIntervalMs , Set <Integer > businessRetryCodes , boolean enableBusinessRetry ) {
112- this .maxRetries = maxRetries ;
115+ public RetryInterceptor (int maxRetries , long retryIntervalMs , Set <Integer > businessRetryCodes , boolean enableBusinessRetry , Class <? extends GenericResult > resultType ) {
116+ this .maxRetries = maxRetries + 1 ;
113117 this .retryIntervalMs = retryIntervalMs ;
114118 this .businessRetryCodes = businessRetryCodes ;
115119 this .enableBusinessRetry = enableBusinessRetry ;
120+ this .resultType = Objects .requireNonNull (resultType );
116121 }
117122
118123 @ Override
119124 public Response intercept (Chain chain ) throws IOException {
120125 Request request = chain .request ();
121126 Response response = null ;
122127 IOException exception = null ;
123- for (int attempt = 0 ; attempt <= maxRetries ; ++ attempt ) {
124- if (response != null ) {
128+ for (int attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
129+ if (response != null )
125130 response .close ();
126- }
127131 try {
128132 response = chain .proceed (request );
129- if (response .isSuccessful () && !shouldRetry (response )) {
133+ if (response .isSuccessful ()) {
134+ if (shouldRetryForBusiness (response )) {
135+ waitForRetry (attempt );
136+ continue ;
137+ }
130138 return response ;
131- }
132- if (!shouldRetry (response )) {
139+ } else {
140+ if (shouldRetryForHttp (response )) {
141+ waitForRetry (attempt );
142+ continue ;
143+ }
133144 return response ;
134145 }
135146 } catch (IOException e ) {
136- if (attempt >= maxRetries ) {
137- throw e ;
138- }
139147 exception = e ;
140- }
141- if (attempt < maxRetries ) {
148+ if (attempt == maxRetries ) throw e ;
142149 waitForRetry (attempt );
143150 }
144151 }
145152
146- if (response != null ) {
147- return response ;
148- }
149153 if (exception != null ) {
150154 throw exception ;
151- } else {
152- throw new IOException ("Failed to get a valid response after all retries and no exception was caught." );
153155 }
156+ if (response != null ) {
157+ return response ;
158+ }
159+ throw new IOException ("Failed after all retries with no response" );
154160 }
155161
156- private boolean shouldRetry (Response response ) {
157- final int code = response .code ();
158- if (code >= 500 && code < 600 ) {
159- return true ;
160- }
161- if (RETRYABLE_STATUS_CODES .contains (code )) {
162- return true ;
163- }
164- if (enableBusinessRetry ) {
165- return shouldRetryBasedOnBusinessCode (response );
166- }
167- return false ;
162+ private boolean shouldRetryForHttp (Response response ) {
163+ int code = response .code ();
164+ return code >= 500 || RETRYABLE_STATUS_CODES .contains (code );
168165 }
169166
170- private void waitForRetry (int attempt ) {
167+ private void waitForRetry (int attempt ) throws IOException {
171168 try {
172- final long delayMs = Math . min ( MAX_DELAY_MS , retryIntervalMs * ( 1L << attempt ) );
173- TimeUnit .MILLISECONDS .sleep (delayMs );
169+ long delay = calculateBackoff ( attempt );
170+ TimeUnit .MILLISECONDS .sleep (delay );
174171 } catch (InterruptedException e ) {
175172 Thread .currentThread ().interrupt ();
173+ throw new IOException ("Retry interrupted" , e );
176174 }
177175 }
178176
179- private boolean shouldRetryBasedOnBusinessCode (Response response ) {
177+ private long calculateBackoff (int attempt ) {
178+ double jitter = 0.8 + random .nextDouble () * 0.4 ;
179+ long calculated = (long ) (retryIntervalMs * Math .pow (2 , attempt ) * jitter );
180+ return Math .min (calculated , MAX_DELAY_MS );
181+ }
182+
183+ private boolean shouldRetryForBusiness (Response response ) {
184+ if (!enableBusinessRetry ) {
185+ return false ;
186+ }
187+ if (businessRetryCodes == null || businessRetryCodes .isEmpty ()) {
188+ return false ;
189+ }
180190 try {
181- if (businessRetryCodes == null || businessRetryCodes .isEmpty ()) {
191+ ResponseBody peekBody = response .peekBody (MAX_BODY_SIZE );
192+ String responseBody = peekBody .source ().readByteString ().utf8 ();
193+ GenericResult result = JsonUtil .str2Obj (responseBody , resultType );
194+ if (result == null || result .getErrorCode () == null ) {
182195 return false ;
183196 }
184- String responseBody = Objects .requireNonNull (response .body ()).string ();
185- GenericResult genericResult = JsonUtil .str2Obj (responseBody , GenericResult .class );
186- int businessCode = genericResult .getErrorCode ();
187- return businessRetryCodes .contains (businessCode );
188- } catch (IOException | IllegalStateException e ) {
197+ return businessRetryCodes .contains (result .getErrorCode ());
198+ } catch (Exception e ) {
189199 return false ;
190200 }
191201 }
0 commit comments