@@ -67,6 +67,9 @@ public class Java11HttpTransportFactory implements HttpTransportFactory, Initial
6767 ThreadLocal .withInitial (() -> new SimpleDateFormat ("EEE MMMd HH:mm:ss yyyy" , Locale .ENGLISH )));
6868
6969 static final String HINT = "Java11Client" ;
70+ private static final int MAX_RETRY_ATTEMPTS = 5 ;
71+ private static final int DEFAULT_RETRY_DELAY_SECONDS = 5 ;
72+
7073 @ Requirement
7174 ProxyHelper proxyHelper ;
7275 @ Requirement
@@ -124,47 +127,106 @@ public <T> T get(ResponseConsumer<T> consumer) throws IOException {
124127 throw new InterruptedIOException ();
125128 }
126129 }
130+
131+ private boolean shouldRetry (int statusCode ) {
132+ return statusCode == 503 || statusCode == 429 ;
133+ }
134+
135+ private long getRetryDelay (HttpResponse <?> response ) {
136+ String retryAfterHeader = response .headers ().firstValue ("Retry-After" ).orElse (null );
137+ if (retryAfterHeader == null || retryAfterHeader .isBlank ()) {
138+ return DEFAULT_RETRY_DELAY_SECONDS ;
139+ }
140+
141+ // Try to parse as seconds (integer)
142+ if (Character .isDigit (retryAfterHeader .charAt (0 ))) {
143+ try {
144+ return Long .parseLong (retryAfterHeader .trim ());
145+ } catch (NumberFormatException e ) {
146+ // Fall through to date parsing
147+ }
148+ }
149+
150+ // Try to parse as HTTP date
151+ for (ThreadLocal <DateFormat > dateFormat : DATE_PATTERNS ) {
152+ try {
153+ long retryTime = dateFormat .get ().parse (retryAfterHeader ).getTime ();
154+ long currentTime = System .currentTimeMillis ();
155+ long delayMillis = retryTime - currentTime ;
156+ if (delayMillis > 0 ) {
157+ return TimeUnit .MILLISECONDS .toSeconds (delayMillis );
158+ }
159+ } catch (ParseException e ) {
160+ // Try next pattern
161+ }
162+ }
163+
164+ // Default if parsing fails
165+ return DEFAULT_RETRY_DELAY_SECONDS ;
166+ }
127167
128168 private <T > T performGet (ResponseConsumer <T > consumer , HttpClient httpClient )
129169 throws IOException , InterruptedException {
130- HttpRequest request = builder .GET ().timeout (Duration .ofSeconds (TIMEOUT_SECONDS )).build ();
131- HttpResponse <InputStream > response = httpClient .send (request , BodyHandlers .ofInputStream ());
132- try (ResponseImplementation <InputStream > implementation = new ResponseImplementation <>(response ) {
133-
134- @ Override
135- public void close () {
136- if (response .version () == Version .HTTP_1_1 ) {
137- // discard any remaining data and close the stream to return the connection to
138- // the pool..
139- try (InputStream stream = response .body ()) {
140- int discarded = 0 ;
141- while (discarded < MAX_DISCARD ) {
142- int read = stream .read (DUMMY_BUFFER );
143- if (read < 0 ) {
144- break ;
170+ int retriesLeft = MAX_RETRY_ATTEMPTS ;
171+ while (retriesLeft > 0 ) {
172+ retriesLeft --;
173+ HttpRequest request = builder .GET ().timeout (Duration .ofSeconds (TIMEOUT_SECONDS )).build ();
174+ HttpResponse <InputStream > response = httpClient .send (request , BodyHandlers .ofInputStream ());
175+
176+ int statusCode = response .statusCode ();
177+ if (shouldRetry (statusCode ) && retriesLeft > 0 ) {
178+ long delaySeconds = getRetryDelay (response );
179+ logger .info ("Server returned status " + statusCode + " for " + uri
180+ + ", waiting " + delaySeconds + " seconds before retry. "
181+ + retriesLeft + " retries left." );
182+ // Close the response stream before retrying
183+ try (InputStream stream = response .body ()) {
184+ // Discard any content
185+ } catch (IOException e ) {
186+ // Ignore
187+ }
188+ TimeUnit .SECONDS .sleep (delaySeconds );
189+ continue ;
190+ }
191+
192+ try (ResponseImplementation <InputStream > implementation = new ResponseImplementation <>(response ) {
193+
194+ @ Override
195+ public void close () {
196+ if (response .version () == Version .HTTP_1_1 ) {
197+ // discard any remaining data and close the stream to return the connection to
198+ // the pool..
199+ try (InputStream stream = response .body ()) {
200+ int discarded = 0 ;
201+ while (discarded < MAX_DISCARD ) {
202+ int read = stream .read (DUMMY_BUFFER );
203+ if (read < 0 ) {
204+ break ;
205+ }
206+ discarded += read ;
145207 }
146- discarded += read ;
208+ } catch (IOException e ) {
209+ // don't care...
210+ }
211+ } else {
212+ // just closing should be enough to signal to the framework...
213+ try (InputStream stream = response .body ()) {
214+ } catch (IOException e ) {
215+ // don't care...
147216 }
148- } catch (IOException e ) {
149- // don't care...
150- }
151- } else {
152- // just closing should be enough to signal to the framework...
153- try (InputStream stream = response .body ()) {
154- } catch (IOException e ) {
155- // don't care...
156217 }
157218 }
158- }
159219
160- @ Override
161- public void transferTo (OutputStream outputStream , ContentEncoding transportEncoding )
162- throws IOException {
163- transportEncoding .decode (response .body ()).transferTo (outputStream );
220+ @ Override
221+ public void transferTo (OutputStream outputStream , ContentEncoding transportEncoding )
222+ throws IOException {
223+ transportEncoding .decode (response .body ()).transferTo (outputStream );
224+ }
225+ }) {
226+ return consumer .handleResponse (implementation );
164227 }
165- }) {
166- return consumer .handleResponse (implementation );
167228 }
229+ throw new IOException ("Maximum retry attempts exceeded for " + uri );
168230 }
169231
170232 @ Override
@@ -187,22 +249,38 @@ public Response head() throws IOException {
187249 }
188250
189251 private Response doHead (HttpClient httpClient ) throws IOException , InterruptedException {
190- HttpResponse <Void > response = httpClient .send (
191- builder .method ("HEAD" , BodyPublishers .noBody ()).timeout (Duration .ofSeconds (TIMEOUT_SECONDS ))
192- .build (),
193- BodyHandlers .discarding ());
194- return new ResponseImplementation <>(response ) {
195- @ Override
196- public void close () {
197- // nothing...
252+ int retriesLeft = MAX_RETRY_ATTEMPTS ;
253+ while (retriesLeft > 0 ) {
254+ retriesLeft --;
255+ HttpResponse <Void > response = httpClient .send (
256+ builder .method ("HEAD" , BodyPublishers .noBody ()).timeout (Duration .ofSeconds (TIMEOUT_SECONDS ))
257+ .build (),
258+ BodyHandlers .discarding ());
259+
260+ int statusCode = response .statusCode ();
261+ if (shouldRetry (statusCode ) && retriesLeft > 0 ) {
262+ long delaySeconds = getRetryDelay (response );
263+ logger .info ("Server returned status " + statusCode + " for HEAD " + uri
264+ + ", waiting " + delaySeconds + " seconds before retry. "
265+ + retriesLeft + " retries left." );
266+ TimeUnit .SECONDS .sleep (delaySeconds );
267+ continue ;
198268 }
269+
270+ return new ResponseImplementation <>(response ) {
271+ @ Override
272+ public void close () {
273+ // nothing...
274+ }
199275
200- @ Override
201- public void transferTo (OutputStream outputStream , ContentEncoding transportEncoding )
202- throws IOException {
203- throw new IOException ("HEAD returns no body" );
204- }
205- };
276+ @ Override
277+ public void transferTo (OutputStream outputStream , ContentEncoding transportEncoding )
278+ throws IOException {
279+ throw new IOException ("HEAD returns no body" );
280+ }
281+ };
282+ }
283+ throw new IOException ("Maximum retry attempts exceeded for HEAD " + uri );
206284 }
207285
208286 }
0 commit comments