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