1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.impl;
29
30 import java.io.IOException;
31 import java.io.InterruptedIOException;
32 import java.net.ConnectException;
33 import java.net.NoRouteToHostException;
34 import java.net.UnknownHostException;
35 import java.time.Instant;
36 import java.util.Arrays;
37 import java.util.Collection;
38 import java.util.HashSet;
39 import java.util.Set;
40
41 import javax.net.ssl.SSLException;
42
43 import org.apache.hc.client5.http.HttpRequestRetryStrategy;
44 import org.apache.hc.client5.http.utils.DateUtils;
45 import org.apache.hc.core5.annotation.Contract;
46 import org.apache.hc.core5.annotation.ThreadingBehavior;
47 import org.apache.hc.core5.concurrent.CancellableDependency;
48 import org.apache.hc.core5.http.ConnectionClosedException;
49 import org.apache.hc.core5.http.Header;
50 import org.apache.hc.core5.http.HttpHeaders;
51 import org.apache.hc.core5.http.HttpRequest;
52 import org.apache.hc.core5.http.HttpResponse;
53 import org.apache.hc.core5.http.HttpStatus;
54 import org.apache.hc.core5.http.Method;
55 import org.apache.hc.core5.http.protocol.HttpContext;
56 import org.apache.hc.core5.util.Args;
57 import org.apache.hc.core5.util.TimeValue;
58
59
60
61
62
63
64 @Contract(threading = ThreadingBehavior.STATELESS)
65 public class DefaultHttpRequestRetryStrategy implements HttpRequestRetryStrategy {
66
67
68
69
70 public static final DefaultHttpRequestRetryStrategy INSTANCE = new DefaultHttpRequestRetryStrategy();
71
72
73
74
75 private final int maxRetries;
76
77
78
79
80 private final TimeValue defaultRetryInterval;
81
82
83
84
85 private final Set<Class<? extends IOException>> nonRetriableIOExceptionClasses;
86
87
88
89
90 private final Set<Integer> retriableCodes;
91
92 protected DefaultHttpRequestRetryStrategy(
93 final int maxRetries,
94 final TimeValue defaultRetryInterval,
95 final Collection<Class<? extends IOException>> clazzes,
96 final Collection<Integer> codes) {
97 Args.notNegative(maxRetries, "maxRetries");
98 Args.notNegative(defaultRetryInterval.getDuration(), "defaultRetryInterval");
99 this.maxRetries = maxRetries;
100 this.defaultRetryInterval = defaultRetryInterval;
101 this.nonRetriableIOExceptionClasses = new HashSet<>(clazzes);
102 this.retriableCodes = new HashSet<>(codes);
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 public DefaultHttpRequestRetryStrategy(
129 final int maxRetries,
130 final TimeValue defaultRetryInterval) {
131 this(maxRetries, defaultRetryInterval,
132 Arrays.asList(
133 InterruptedIOException.class,
134 UnknownHostException.class,
135 ConnectException.class,
136 ConnectionClosedException.class,
137 NoRouteToHostException.class,
138 SSLException.class),
139 Arrays.asList(
140 HttpStatus.SC_TOO_MANY_REQUESTS,
141 HttpStatus.SC_SERVICE_UNAVAILABLE));
142 }
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 public DefaultHttpRequestRetryStrategy() {
163 this(1, TimeValue.ofSeconds(1L));
164 }
165
166 @Override
167 public boolean retryRequest(
168 final HttpRequest request,
169 final IOException exception,
170 final int execCount,
171 final HttpContext context) {
172 Args.notNull(request, "request");
173 Args.notNull(exception, "exception");
174
175 if (execCount > this.maxRetries) {
176
177 return false;
178 }
179 if (this.nonRetriableIOExceptionClasses.contains(exception.getClass())) {
180 return false;
181 }
182 for (final Class<? extends IOException> rejectException : this.nonRetriableIOExceptionClasses) {
183 if (rejectException.isInstance(exception)) {
184 return false;
185 }
186 }
187 if (request instanceof CancellableDependency && ((CancellableDependency) request).isCancelled()) {
188 return false;
189 }
190
191
192 return handleAsIdempotent(request);
193 }
194
195 @Override
196 public boolean retryRequest(
197 final HttpResponse response,
198 final int execCount,
199 final HttpContext context) {
200 Args.notNull(response, "response");
201
202 return execCount <= this.maxRetries && retriableCodes.contains(response.getCode());
203 }
204
205 @Override
206 public TimeValue getRetryInterval(
207 final HttpResponse response,
208 final int execCount,
209 final HttpContext context) {
210 Args.notNull(response, "response");
211
212 final Header header = response.getFirstHeader(HttpHeaders.RETRY_AFTER);
213 TimeValue retryAfter = null;
214 if (header != null) {
215 final String value = header.getValue();
216 try {
217 retryAfter = TimeValue.ofSeconds(Long.parseLong(value));
218 } catch (final NumberFormatException ignore) {
219 final Instant retryAfterDate = DateUtils.parseStandardDate(value);
220 if (retryAfterDate != null) {
221 retryAfter =
222 TimeValue.ofMilliseconds(retryAfterDate.toEpochMilli() - System.currentTimeMillis());
223 }
224 }
225
226 if (TimeValue.isPositive(retryAfter)) {
227 return retryAfter;
228 }
229 }
230 return this.defaultRetryInterval;
231 }
232
233 protected boolean handleAsIdempotent(final HttpRequest request) {
234 return Method.isIdempotent(request.getMethod());
235 }
236
237 }