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 public static final DefaultHttpRequestRetryStrategy INSTANCE = new DefaultHttpRequestRetryStrategy();
68
69
70
71
72 private final int maxRetries;
73
74
75
76
77 private final TimeValue defaultRetryInterval;
78
79
80
81
82 private final Set<Class<? extends IOException>> nonRetriableIOExceptionClasses;
83
84
85
86
87 private final Set<Integer> retriableCodes;
88
89 protected DefaultHttpRequestRetryStrategy(
90 final int maxRetries,
91 final TimeValue defaultRetryInterval,
92 final Collection<Class<? extends IOException>> clazzes,
93 final Collection<Integer> codes) {
94 Args.notNegative(maxRetries, "maxRetries");
95 Args.notNegative(defaultRetryInterval.getDuration(), "defaultRetryInterval");
96 this.maxRetries = maxRetries;
97 this.defaultRetryInterval = defaultRetryInterval;
98 this.nonRetriableIOExceptionClasses = new HashSet<>(clazzes);
99 this.retriableCodes = new HashSet<>(codes);
100 }
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125 public DefaultHttpRequestRetryStrategy(
126 final int maxRetries,
127 final TimeValue defaultRetryInterval) {
128 this(maxRetries, defaultRetryInterval,
129 Arrays.asList(
130 InterruptedIOException.class,
131 UnknownHostException.class,
132 ConnectException.class,
133 ConnectionClosedException.class,
134 NoRouteToHostException.class,
135 SSLException.class),
136 Arrays.asList(
137 HttpStatus.SC_TOO_MANY_REQUESTS,
138 HttpStatus.SC_SERVICE_UNAVAILABLE));
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 public DefaultHttpRequestRetryStrategy() {
160 this(1, TimeValue.ofSeconds(1L));
161 }
162
163 @Override
164 public boolean retryRequest(
165 final HttpRequest request,
166 final IOException exception,
167 final int execCount,
168 final HttpContext context) {
169 Args.notNull(request, "request");
170 Args.notNull(exception, "exception");
171
172 if (execCount > this.maxRetries) {
173
174 return false;
175 }
176 if (this.nonRetriableIOExceptionClasses.contains(exception.getClass())) {
177 return false;
178 } else {
179 for (final Class<? extends IOException> rejectException : this.nonRetriableIOExceptionClasses) {
180 if (rejectException.isInstance(exception)) {
181 return false;
182 }
183 }
184 }
185 if (request instanceof CancellableDependency && ((CancellableDependency) request).isCancelled()) {
186 return false;
187 }
188
189
190 return handleAsIdempotent(request);
191 }
192
193 @Override
194 public boolean retryRequest(
195 final HttpResponse response,
196 final int execCount,
197 final HttpContext context) {
198 Args.notNull(response, "response");
199
200 return execCount <= this.maxRetries && retriableCodes.contains(response.getCode());
201 }
202
203 @Override
204 public TimeValue getRetryInterval(
205 final HttpResponse response,
206 final int execCount,
207 final HttpContext context) {
208 Args.notNull(response, "response");
209
210 final Header header = response.getFirstHeader(HttpHeaders.RETRY_AFTER);
211 TimeValue retryAfter = null;
212 if (header != null) {
213 final String value = header.getValue();
214 try {
215 retryAfter = TimeValue.ofSeconds(Long.parseLong(value));
216 } catch (final NumberFormatException ignore) {
217 final Instant retryAfterDate = DateUtils.parseStandardDate(value);
218 if (retryAfterDate != null) {
219 retryAfter =
220 TimeValue.ofMilliseconds(retryAfterDate.toEpochMilli() - System.currentTimeMillis());
221 }
222 }
223
224 if (TimeValue.isPositive(retryAfter)) {
225 return retryAfter;
226 }
227 }
228 return this.defaultRetryInterval;
229 }
230
231 protected boolean handleAsIdempotent(final HttpRequest request) {
232 return Method.isIdempotent(request.getMethod());
233 }
234
235 }