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 package org.apache.hc.client5.http.impl.classic;
28
29 import java.io.IOException;
30 import java.io.InterruptedIOException;
31
32 import org.apache.hc.client5.http.HttpRequestRetryStrategy;
33 import org.apache.hc.client5.http.HttpRoute;
34 import org.apache.hc.client5.http.classic.ExecChain;
35 import org.apache.hc.client5.http.classic.ExecChain.Scope;
36 import org.apache.hc.client5.http.classic.ExecChainHandler;
37 import org.apache.hc.client5.http.config.RequestConfig;
38 import org.apache.hc.client5.http.impl.ChainElement;
39 import org.apache.hc.client5.http.protocol.HttpClientContext;
40 import org.apache.hc.core5.annotation.Contract;
41 import org.apache.hc.core5.annotation.Internal;
42 import org.apache.hc.core5.annotation.ThreadingBehavior;
43 import org.apache.hc.core5.http.ClassicHttpRequest;
44 import org.apache.hc.core5.http.ClassicHttpResponse;
45 import org.apache.hc.core5.http.HttpEntity;
46 import org.apache.hc.core5.http.HttpException;
47 import org.apache.hc.core5.http.NoHttpResponseException;
48 import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
49 import org.apache.hc.core5.util.Args;
50 import org.apache.hc.core5.util.TimeValue;
51 import org.apache.hc.core5.util.Timeout;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 @Contract(threading = ThreadingBehavior.STATELESS)
85 @Internal
86 public class HttpRequestRetryExec implements ExecChainHandler {
87
88 private static final Logger LOG = LoggerFactory.getLogger(HttpRequestRetryExec.class);
89
90 private final HttpRequestRetryStrategy retryStrategy;
91
92 public HttpRequestRetryExec(
93 final HttpRequestRetryStrategy retryStrategy) {
94 Args.notNull(retryStrategy, "retryStrategy");
95 this.retryStrategy = retryStrategy;
96 }
97
98 @Override
99 public ClassicHttpResponse execute(
100 final ClassicHttpRequest request,
101 final Scope scope,
102 final ExecChain chain) throws IOException, HttpException {
103 Args.notNull(request, "request");
104 Args.notNull(scope, "scope");
105 final String exchangeId = scope.exchangeId;
106 final HttpRoute route = scope.route;
107 final HttpClientContext context = scope.clientContext;
108 ClassicHttpRequest currentRequest = request;
109
110 for (int execCount = 1;; execCount++) {
111 final ClassicHttpResponse response;
112 try {
113 response = chain.proceed(currentRequest, scope);
114 } catch (final IOException ex) {
115 if (scope.execRuntime.isExecutionAborted()) {
116 throw new RequestFailedException("Request aborted");
117 }
118 final HttpEntity requestEntity = request.getEntity();
119 if (requestEntity != null && !requestEntity.isRepeatable()) {
120 if (LOG.isDebugEnabled()) {
121 LOG.debug("{} cannot retry non-repeatable request", exchangeId);
122 }
123 throw ex;
124 }
125 if (retryStrategy.retryRequest(request, ex, execCount, context)) {
126 if (LOG.isDebugEnabled()) {
127 LOG.debug("{} {}", exchangeId, ex.getMessage(), ex);
128 }
129 if (LOG.isInfoEnabled()) {
130 LOG.info("Recoverable I/O exception ({}) caught when processing request to {}",
131 ex.getClass().getName(), route);
132 }
133 final TimeValue nextInterval = retryStrategy.getRetryInterval(request, ex, execCount, context);
134 if (TimeValue.isPositive(nextInterval)) {
135 try {
136 if (LOG.isDebugEnabled()) {
137 LOG.debug("{} wait for {}", exchangeId, nextInterval);
138 }
139 nextInterval.sleep();
140 } catch (final InterruptedException e) {
141 Thread.currentThread().interrupt();
142 throw new InterruptedIOException();
143 }
144 }
145 currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
146 continue;
147 } else {
148 if (ex instanceof NoHttpResponseException) {
149 final NoHttpResponseException updatedex = new NoHttpResponseException(
150 route.getTargetHost().toHostString() + " failed to respond");
151 updatedex.setStackTrace(ex.getStackTrace());
152 throw updatedex;
153 }
154 throw ex;
155 }
156 }
157
158 try {
159 final HttpEntity entity = request.getEntity();
160 if (entity != null && !entity.isRepeatable()) {
161 if (LOG.isDebugEnabled()) {
162 LOG.debug("{} cannot retry non-repeatable request", exchangeId);
163 }
164 return response;
165 }
166 if (retryStrategy.retryRequest(response, execCount, context)) {
167 final TimeValue nextInterval = retryStrategy.getRetryInterval(response, execCount, context);
168
169 if (TimeValue.isPositive(nextInterval)) {
170 final RequestConfig requestConfig = context.getRequestConfig();
171 final Timeout responseTimeout = requestConfig.getResponseTimeout();
172 if (responseTimeout != null && nextInterval.compareTo(responseTimeout) > 0) {
173 return response;
174 }
175 }
176 response.close();
177 if (TimeValue.isPositive(nextInterval)) {
178 try {
179 if (LOG.isDebugEnabled()) {
180 LOG.debug("{} wait for {}", exchangeId, nextInterval);
181 }
182 nextInterval.sleep();
183 } catch (final InterruptedException e) {
184 Thread.currentThread().interrupt();
185 throw new InterruptedIOException();
186 }
187 }
188 currentRequest = ClassicRequestBuilder.copy(scope.originalRequest).build();
189 } else {
190 return response;
191 }
192 } catch (final RuntimeException ex) {
193 response.close();
194 throw ex;
195 }
196 }
197 }
198
199 }