2020import io .fabric8 .kubernetes .client .http .AsyncBody .Consumer ;
2121import io .fabric8 .kubernetes .client .http .Interceptor .RequestTags ;
2222import io .fabric8 .kubernetes .client .http .WebSocket .Listener ;
23+ import io .fabric8 .kubernetes .client .utils .AsyncUtils ;
2324import io .fabric8 .kubernetes .client .utils .ExponentialBackoffIntervalCalculator ;
2425import io .fabric8 .kubernetes .client .utils .Utils ;
2526import org .slf4j .Logger ;
3435import java .util .Optional ;
3536import java .util .concurrent .CompletableFuture ;
3637import java .util .concurrent .CompletionException ;
37- import java .util .concurrent .Future ;
3838import java .util .concurrent .TimeUnit ;
39- import java .util .concurrent .TimeoutException ;
4039import java .util .function .BiConsumer ;
41- import java .util .function .Function ;
4240import java .util .function .Supplier ;
41+ import java .util .function .ToIntFunction ;
4342
4443public abstract class StandardHttpClient <C extends HttpClient , F extends HttpClient .Factory , T extends StandardHttpClientBuilder <C , F , ?>>
4544 implements HttpClient , RequestTags {
4645
4746 // pads the fail-safe timeout to ensure we don't inadvertently timeout a request
48- private static final long ADDITIONAL_REQEUST_TIMEOUT = TimeUnit .SECONDS .toMillis (5 );
47+ private static final long MAX_ADDITIONAL_REQUEST_TIMEOUT = TimeUnit .SECONDS .toMillis (5 );
4948
5049 private static final Logger LOG = LoggerFactory .getLogger (StandardHttpClient .class );
5150
@@ -81,13 +80,12 @@ public <V> CompletableFuture<HttpResponse<V>> sendAsync(HttpRequest request, Cla
8180
8281 @ Override
8382 public CompletableFuture <HttpResponse <AsyncBody >> consumeBytes (HttpRequest request , Consumer <List <ByteBuffer >> consumer ) {
84- CompletableFuture <HttpResponse <AsyncBody >> result = new CompletableFuture <>();
85- StandardHttpRequest standardHttpRequest = (StandardHttpRequest ) request ;
86-
87- retryWithExponentialBackoff (result , () -> consumeBytesOnce (standardHttpRequest , consumer ), request .uri (),
88- HttpResponse ::code ,
89- r -> r .body ().cancel (), standardHttpRequest .getTimeout ());
90- return result ;
83+ final StandardHttpRequest standardHttpRequest = (StandardHttpRequest ) request ;
84+ return retryWithExponentialBackoff (
85+ standardHttpRequest ,
86+ () -> consumeBytesOnce (standardHttpRequest , consumer ),
87+ r -> r .body ().cancel (),
88+ HttpResponse ::code );
9189 }
9290
9391 private CompletableFuture <HttpResponse <AsyncBody >> consumeBytesOnce (StandardHttpRequest standardHttpRequest ,
@@ -143,69 +141,48 @@ private CompletableFuture<HttpResponse<AsyncBody>> consumeBytesOnce(StandardHttp
143141 };
144142 }
145143
146- public <V > CompletableFuture <V > orTimeout (CompletableFuture <V > future , Duration timeout ) {
147- if (timeout != null && !timeout .isNegative () && !timeout .isZero ()) {
148- long millis = timeout .toMillis ();
149- millis += (Math .min (millis , ADDITIONAL_REQEUST_TIMEOUT ));
150- Future <?> scheduled = Utils .schedule (Runnable ::run , () -> future .completeExceptionally (new TimeoutException ()),
151- millis , TimeUnit .MILLISECONDS );
152- future .whenComplete ((v , t ) -> scheduled .cancel (true ));
153- }
154- return future ;
155- }
156-
157144 /**
158145 * Will retry the action if needed based upon the retry settings provided by the ExponentialBackoffIntervalCalculator.
159146 */
160- protected <V > void retryWithExponentialBackoff (CompletableFuture <V > result ,
161- Supplier <CompletableFuture <V >> action , URI uri , Function <V , Integer > codeExtractor ,
162- java .util .function .Consumer <V > cancel , ExponentialBackoffIntervalCalculator retryIntervalCalculator ,
163- Duration timeout ) {
164-
165- orTimeout (action .get (), timeout )
166- .whenComplete ((response , throwable ) -> {
167- if (retryIntervalCalculator .shouldRetry () && !result .isDone ()) {
168- long retryInterval = retryIntervalCalculator .nextReconnectInterval ();
169- boolean retry = false ;
170- if (response != null ) {
171- Integer code = codeExtractor .apply (response );
172- if (code != null && code >= 500 ) {
173- LOG .debug ("HTTP operation on url: {} should be retried as the response code was {}, retrying after {} millis" ,
174- uri , code , retryInterval );
175- retry = true ;
176- cancel .accept (response );
177- }
178- } else {
179- if (throwable instanceof CompletionException ) {
180- throwable = ((CompletionException ) throwable ).getCause ();
181- }
182- if (throwable instanceof IOException ) {
183- LOG .debug (String .format ("HTTP operation on url: %s should be retried after %d millis because of IOException" ,
184- uri , retryInterval ), throwable );
185- retry = true ;
186- }
147+ private <V > CompletableFuture <V > retryWithExponentialBackoff (
148+ StandardHttpRequest request , Supplier <CompletableFuture <V >> action , java .util .function .Consumer <V > onCancel ,
149+ ToIntFunction <V > codeExtractor ) {
150+ final URI uri = request .uri ();
151+ final RequestConfig requestConfig = getTag (RequestConfig .class );
152+ final ExponentialBackoffIntervalCalculator retryIntervalCalculator = ExponentialBackoffIntervalCalculator
153+ .from (requestConfig );
154+ final Duration timeout ;
155+ if (request .getTimeout () != null && !request .getTimeout ().isNegative () && !request .getTimeout ().isZero ()) {
156+ timeout = request .getTimeout ().plusMillis (Math .min (request .getTimeout ().toMillis (), MAX_ADDITIONAL_REQUEST_TIMEOUT ));
157+ } else {
158+ timeout = null ;
159+ }
160+ return AsyncUtils .retryWithExponentialBackoff (action , onCancel , timeout , retryIntervalCalculator ,
161+ (response , throwable , retryInterval ) -> {
162+ if (response != null ) {
163+ final int code = codeExtractor .applyAsInt (response );
164+ if (code >= 500 ) {
165+ LOG .debug (
166+ "HTTP operation on url: {} should be retried as the response code was {}, retrying after {} millis" ,
167+ uri , code , retryInterval );
168+ return true ;
169+ }
170+ } else {
171+ if (throwable instanceof CompletionException ) {
172+ throwable = throwable .getCause ();
187173 }
188- if (retry ) {
189- Utils .schedule (Runnable ::run ,
190- () -> retryWithExponentialBackoff (result , action , uri , codeExtractor , cancel , retryIntervalCalculator ,
191- timeout ),
192- retryInterval ,
193- TimeUnit .MILLISECONDS );
194- return ;
174+ if (throwable instanceof IOException ) {
175+ LOG .debug (
176+ String .format ("HTTP operation on url: %s should be retried after %d millis because of IOException" ,
177+ uri , retryInterval ),
178+ throwable );
179+ return true ;
195180 }
196181 }
197- completeOrCancel ( cancel , result ). accept ( response , throwable ) ;
182+ return false ;
198183 });
199184 }
200185
201- protected <V > void retryWithExponentialBackoff (CompletableFuture <V > result ,
202- Supplier <CompletableFuture <V >> action , URI uri , Function <V , Integer > codeExtractor ,
203- java .util .function .Consumer <V > cancel , Duration timeout ) {
204- RequestConfig requestConfig = getTag (RequestConfig .class );
205- retryWithExponentialBackoff (result , action , uri , codeExtractor , cancel ,
206- ExponentialBackoffIntervalCalculator .from (requestConfig ), timeout );
207- }
208-
209186 @ Override
210187 public io .fabric8 .kubernetes .client .http .WebSocket .Builder newWebSocketBuilder () {
211188 return new StandardWebSocketBuilder (this );
@@ -220,13 +197,11 @@ public HttpRequest.Builder newHttpRequestBuilder() {
220197 final CompletableFuture <WebSocket > buildWebSocket (StandardWebSocketBuilder standardWebSocketBuilder ,
221198 Listener listener ) {
222199
223- CompletableFuture <WebSocketResponse > intermediate = new CompletableFuture <>();
224- StandardHttpRequest request = standardWebSocketBuilder .asHttpRequest ();
225-
226- retryWithExponentialBackoff (intermediate , () -> buildWebSocketOnce (standardWebSocketBuilder , listener ),
227- request .uri (),
228- r -> Optional .of (r .webSocketUpgradeResponse ).map (HttpResponse ::code ).orElse (null ),
229- r -> Optional .ofNullable (r .webSocket ).ifPresent (w -> w .sendClose (1000 , null )), request .getTimeout ());
200+ final CompletableFuture <WebSocketResponse > intermediate = retryWithExponentialBackoff (
201+ standardWebSocketBuilder .asHttpRequest (),
202+ () -> buildWebSocketOnce (standardWebSocketBuilder , listener ),
203+ r -> Optional .ofNullable (r .webSocket ).ifPresent (w -> w .sendClose (1000 , null )),
204+ r -> Optional .of (r .webSocketUpgradeResponse ).map (HttpResponse ::code ).orElse (null ));
230205
231206 CompletableFuture <WebSocket > result = new CompletableFuture <>();
232207
0 commit comments