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.async;
28
29 import java.io.IOException;
30
31 import org.apache.hc.client5.http.HttpRequestRetryStrategy;
32 import org.apache.hc.client5.http.HttpRoute;
33 import org.apache.hc.client5.http.async.AsyncExecCallback;
34 import org.apache.hc.client5.http.async.AsyncExecChain;
35 import org.apache.hc.client5.http.async.AsyncExecChainHandler;
36 import org.apache.hc.client5.http.protocol.HttpClientContext;
37 import org.apache.hc.core5.annotation.Contract;
38 import org.apache.hc.core5.annotation.Internal;
39 import org.apache.hc.core5.annotation.ThreadingBehavior;
40 import org.apache.hc.core5.http.EntityDetails;
41 import org.apache.hc.core5.http.HttpException;
42 import org.apache.hc.core5.http.HttpRequest;
43 import org.apache.hc.core5.http.HttpResponse;
44 import org.apache.hc.core5.http.nio.AsyncDataConsumer;
45 import org.apache.hc.core5.http.nio.AsyncEntityProducer;
46 import org.apache.hc.core5.http.nio.entity.DiscardingEntityConsumer;
47 import org.apache.hc.core5.http.support.BasicRequestBuilder;
48 import org.apache.hc.core5.util.Args;
49 import org.apache.hc.core5.util.TimeValue;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 @Contract(threading = ThreadingBehavior.STATELESS)
68 @Internal
69 public final class AsyncHttpRequestRetryExec implements AsyncExecChainHandler {
70
71 private static final Logger LOG = LoggerFactory.getLogger(AsyncHttpRequestRetryExec.class);
72
73 private final HttpRequestRetryStrategy retryStrategy;
74
75 public AsyncHttpRequestRetryExec(final HttpRequestRetryStrategy retryStrategy) {
76 Args.notNull(retryStrategy, "retryStrategy");
77 this.retryStrategy = retryStrategy;
78 }
79
80 private static class State {
81
82 volatile boolean retrying;
83 volatile TimeValue delay;
84
85 }
86
87 private void internalExecute(
88 final State state,
89 final HttpRequest request,
90 final AsyncEntityProducer entityProducer,
91 final AsyncExecChain.Scope scope,
92 final AsyncExecChain chain,
93 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
94
95 final String exchangeId = scope.exchangeId;
96
97 chain.proceed(BasicRequestBuilder.copy(request).build(), entityProducer, scope, new AsyncExecCallback() {
98
99 @Override
100 public AsyncDataConsumer handleResponse(
101 final HttpResponse response,
102 final EntityDetails entityDetails) throws HttpException, IOException {
103 final HttpClientContext clientContext = scope.clientContext;
104 if (entityProducer != null && !entityProducer.isRepeatable()) {
105 if (LOG.isDebugEnabled()) {
106 LOG.debug("{} cannot retry non-repeatable request", exchangeId);
107 }
108 return asyncExecCallback.handleResponse(response, entityDetails);
109 }
110 state.retrying = retryStrategy.retryRequest(response, scope.execCount.get(), clientContext);
111 if (state.retrying) {
112 state.delay = retryStrategy.getRetryInterval(response, scope.execCount.get(), clientContext);
113 if (LOG.isDebugEnabled()) {
114 LOG.debug("{} retrying request in {}", exchangeId, state.delay);
115 }
116 return new DiscardingEntityConsumer<>();
117 } else {
118 return asyncExecCallback.handleResponse(response, entityDetails);
119 }
120 }
121
122 @Override
123 public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
124 asyncExecCallback.handleInformationResponse(response);
125 }
126
127 @Override
128 public void completed() {
129 if (state.retrying) {
130 scope.execCount.incrementAndGet();
131 if (entityProducer != null) {
132 entityProducer.releaseResources();
133 }
134 scope.scheduler.scheduleExecution(request, entityProducer, scope, asyncExecCallback, state.delay);
135 } else {
136 asyncExecCallback.completed();
137 }
138 }
139
140 @Override
141 public void failed(final Exception cause) {
142 if (cause instanceof IOException) {
143 final HttpRoute route = scope.route;
144 final HttpClientContext clientContext = scope.clientContext;
145 if (entityProducer != null && !entityProducer.isRepeatable()) {
146 if (LOG.isDebugEnabled()) {
147 LOG.debug("{} cannot retry non-repeatable request", exchangeId);
148 }
149 } else if (retryStrategy.retryRequest(request, (IOException) cause, scope.execCount.get(), clientContext)) {
150 if (LOG.isDebugEnabled()) {
151 LOG.debug("{} {}", exchangeId, cause.getMessage(), cause);
152 }
153 if (LOG.isInfoEnabled()) {
154 LOG.info("Recoverable I/O exception ({}) caught when processing request to {}",
155 cause.getClass().getName(), route);
156 }
157 scope.execRuntime.discardEndpoint();
158 if (entityProducer != null) {
159 entityProducer.releaseResources();
160 }
161 state.retrying = true;
162 final int execCount = scope.execCount.incrementAndGet();
163 state.delay = retryStrategy.getRetryInterval(request, (IOException) cause, execCount - 1, clientContext);
164 scope.scheduler.scheduleExecution(request, entityProducer, scope, asyncExecCallback, state.delay);
165 return;
166 }
167 }
168 asyncExecCallback.failed(cause);
169 }
170
171 });
172
173 }
174
175 @Override
176 public void execute(
177 final HttpRequest request,
178 final AsyncEntityProducer entityProducer,
179 final AsyncExecChain.Scope scope,
180 final AsyncExecChain chain,
181 final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
182 final State state = new State();
183 state.retrying = false;
184 internalExecute(state, request, entityProducer, scope, chain, asyncExecCallback);
185 }
186
187 }