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