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 } else {
182 for (final Class<? extends IOException> rejectException : this.nonRetriableIOExceptionClasses) {
183 if (rejectException.isInstance(exception)) {
184 return false;
185 }
186 }
187 }
188 if (request instanceof CancellableDependency && ((CancellableDependency) request).isCancelled()) {
189 return false;
190 }
191
192
193 return handleAsIdempotent(request);
194 }
195
196 @Override
197 public boolean retryRequest(
198 final HttpResponse response,
199 final int execCount,
200 final HttpContext context) {
201 Args.notNull(response, "response");
202
203 return execCount <= this.maxRetries && retriableCodes.contains(response.getCode());
204 }
205
206 @Override
207 public TimeValue getRetryInterval(
208 final HttpResponse response,
209 final int execCount,
210 final HttpContext context) {
211 Args.notNull(response, "response");
212
213 final Header header = response.getFirstHeader(HttpHeaders.RETRY_AFTER);
214 TimeValue retryAfter = null;
215 if (header != null) {
216 final String value = header.getValue();
217 try {
218 retryAfter = TimeValue.ofSeconds(Long.parseLong(value));
219 } catch (final NumberFormatException ignore) {
220 final Instant retryAfterDate = DateUtils.parseStandardDate(value);
221 if (retryAfterDate != null) {
222 retryAfter =
223 TimeValue.ofMilliseconds(retryAfterDate.toEpochMilli() - System.currentTimeMillis());
224 }
225 }
226
227 if (TimeValue.isPositive(retryAfter)) {
228 return retryAfter;
229 }
230 }
231 return this.defaultRetryInterval;
232 }
233
234 protected boolean handleAsIdempotent(final HttpRequest request) {
235 return Method.isIdempotent(request.getMethod());
236 }
237
238 }