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