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.classic;
29
30 import java.io.IOException;
31 import java.util.concurrent.ExecutionException;
32 import java.util.concurrent.TimeoutException;
33 import java.util.concurrent.atomic.AtomicReference;
34
35 import org.apache.hc.client5.http.HttpRoute;
36 import org.apache.hc.client5.http.classic.ExecRuntime;
37 import org.apache.hc.client5.http.config.RequestConfig;
38 import org.apache.hc.client5.http.impl.ConnPoolSupport;
39 import org.apache.hc.client5.http.io.ConnectionEndpoint;
40 import org.apache.hc.client5.http.io.HttpClientConnectionManager;
41 import org.apache.hc.client5.http.io.LeaseRequest;
42 import org.apache.hc.client5.http.protocol.HttpClientContext;
43 import org.apache.hc.core5.concurrent.Cancellable;
44 import org.apache.hc.core5.concurrent.CancellableDependency;
45 import org.apache.hc.core5.http.ClassicHttpRequest;
46 import org.apache.hc.core5.http.ClassicHttpResponse;
47 import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
48 import org.apache.hc.core5.http.HttpException;
49 import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
50 import org.apache.hc.core5.io.CloseMode;
51 import org.apache.hc.core5.util.Args;
52 import org.apache.hc.core5.util.TimeValue;
53 import org.apache.hc.core5.util.Timeout;
54 import org.slf4j.Logger;
55
56 class InternalExecRuntime implements ExecRuntime, Cancellable {
57
58 private final Logger log;
59
60 private final HttpClientConnectionManager manager;
61 private final HttpRequestExecutor requestExecutor;
62 private final CancellableDependency cancellableDependency;
63 private final AtomicReference<ConnectionEndpoint> endpointRef;
64
65 private volatile boolean reusable;
66 private volatile Object state;
67 private volatile TimeValue validDuration;
68
69 InternalExecRuntime(
70 final Logger log,
71 final HttpClientConnectionManager manager,
72 final HttpRequestExecutor requestExecutor,
73 final CancellableDependency cancellableDependency) {
74 super();
75 this.log = log;
76 this.manager = manager;
77 this.requestExecutor = requestExecutor;
78 this.cancellableDependency = cancellableDependency;
79 this.endpointRef = new AtomicReference<>();
80 this.validDuration = TimeValue.NEG_ONE_MILLISECOND;
81 }
82
83 @Override
84 public boolean isExecutionAborted() {
85 return cancellableDependency != null && cancellableDependency.isCancelled();
86 }
87
88 @Override
89 public boolean isEndpointAcquired() {
90 return endpointRef.get() != null;
91 }
92
93 @Override
94 public void acquireEndpoint(
95 final String id, final HttpRoute route, final Object object, final HttpClientContext context) throws IOException {
96 Args.notNull(route, "Route");
97 if (endpointRef.get() == null) {
98 final RequestConfig requestConfig = context.getRequestConfig();
99 final Timeout connectionRequestTimeout = requestConfig.getConnectionRequestTimeout();
100 if (log.isDebugEnabled()) {
101 log.debug("{} acquiring endpoint ({})", id, connectionRequestTimeout);
102 }
103 final LeaseRequest connRequest = manager.lease(id, route, connectionRequestTimeout, object);
104 state = object;
105 if (cancellableDependency != null) {
106 cancellableDependency.setDependency(connRequest);
107 }
108 try {
109 final ConnectionEndpoint connectionEndpoint = connRequest.get(connectionRequestTimeout);
110 endpointRef.set(connectionEndpoint);
111 reusable = connectionEndpoint.isConnected();
112 if (cancellableDependency != null) {
113 cancellableDependency.setDependency(this);
114 }
115 if (log.isDebugEnabled()) {
116 log.debug("{} acquired endpoint {}", id, ConnPoolSupport.getId(connectionEndpoint));
117 }
118 } catch(final TimeoutException ex) {
119 throw new ConnectionRequestTimeoutException(ex.getMessage());
120 } catch(final InterruptedException interrupted) {
121 Thread.currentThread().interrupt();
122 throw new RequestFailedException("Request aborted", interrupted);
123 } catch(final ExecutionException ex) {
124 Throwable cause = ex.getCause();
125 if (cause == null) {
126 cause = ex;
127 }
128 throw new RequestFailedException("Request execution failed", cause);
129 }
130 } else {
131 throw new IllegalStateException("Endpoint already acquired");
132 }
133 }
134
135 ConnectionEndpoint ensureValid() {
136 final ConnectionEndpoint endpoint = endpointRef.get();
137 if (endpoint == null) {
138 throw new IllegalStateException("Endpoint not acquired / already released");
139 }
140 return endpoint;
141 }
142
143 @Override
144 public boolean isEndpointConnected() {
145 final ConnectionEndpoint endpoint = endpointRef.get();
146 return endpoint != null && endpoint.isConnected();
147 }
148
149 private void connectEndpoint(final ConnectionEndpoint endpoint, final HttpClientContext context) throws IOException {
150 if (isExecutionAborted()) {
151 throw new RequestFailedException("Request aborted");
152 }
153 final RequestConfig requestConfig = context.getRequestConfig();
154 @SuppressWarnings("deprecation")
155 final Timeout connectTimeout = requestConfig.getConnectTimeout();
156 if (log.isDebugEnabled()) {
157 log.debug("{} connecting endpoint ({})", ConnPoolSupport.getId(endpoint), connectTimeout);
158 }
159 manager.connect(endpoint, connectTimeout, context);
160 if (log.isDebugEnabled()) {
161 log.debug("{} endpoint connected", ConnPoolSupport.getId(endpoint));
162 }
163 }
164
165 @Override
166 public void connectEndpoint(final HttpClientContext context) throws IOException {
167 final ConnectionEndpoint endpoint = ensureValid();
168 if (!endpoint.isConnected()) {
169 connectEndpoint(endpoint, context);
170 }
171 }
172
173 @Override
174 public void disconnectEndpoint() throws IOException {
175 final ConnectionEndpoint endpoint = endpointRef.get();
176 if (endpoint != null) {
177 endpoint.close();
178 if (log.isDebugEnabled()) {
179 log.debug("{} endpoint closed", ConnPoolSupport.getId(endpoint));
180 }
181 }
182 }
183
184 @Override
185 public void upgradeTls(final HttpClientContext context) throws IOException {
186 final ConnectionEndpoint endpoint = ensureValid();
187 if (log.isDebugEnabled()) {
188 log.debug("{} upgrading endpoint", ConnPoolSupport.getId(endpoint));
189 }
190 manager.upgrade(endpoint, context);
191 }
192
193 @Override
194 public ClassicHttpResponse execute(
195 final String id,
196 final ClassicHttpRequest request,
197 final HttpClientContext context) throws IOException, HttpException {
198 final ConnectionEndpoint endpoint = ensureValid();
199 if (!endpoint.isConnected()) {
200 connectEndpoint(endpoint, context);
201 }
202 if (isExecutionAborted()) {
203 throw new RequestFailedException("Request aborted");
204 }
205 final RequestConfig requestConfig = context.getRequestConfig();
206 final Timeout responseTimeout = requestConfig.getResponseTimeout();
207 if (responseTimeout != null) {
208 endpoint.setSocketTimeout(responseTimeout);
209 }
210 if (log.isDebugEnabled()) {
211 log.debug("{} start execution {}", ConnPoolSupport.getId(endpoint), id);
212 }
213 return endpoint.execute(id, request, requestExecutor, context);
214 }
215
216 @Override
217 public boolean isConnectionReusable() {
218 return reusable;
219 }
220
221 @Override
222 public void markConnectionReusable(final Object state, final TimeValue validDuration) {
223 this.reusable = true;
224 this.state = state;
225 this.validDuration = validDuration;
226 }
227
228 @Override
229 public void markConnectionNonReusable() {
230 reusable = false;
231 }
232
233 private void discardEndpoint(final ConnectionEndpoint endpoint) {
234 try {
235 endpoint.close(CloseMode.IMMEDIATE);
236 if (log.isDebugEnabled()) {
237 log.debug("{} endpoint closed", ConnPoolSupport.getId(endpoint));
238 }
239 } finally {
240 if (log.isDebugEnabled()) {
241 log.debug("{} discarding endpoint", ConnPoolSupport.getId(endpoint));
242 }
243 manager.release(endpoint, null, TimeValue.ZERO_MILLISECONDS);
244 }
245 }
246
247 @Override
248 public void releaseEndpoint() {
249 final ConnectionEndpoint endpoint = endpointRef.getAndSet(null);
250 if (endpoint != null) {
251 if (reusable) {
252 if (log.isDebugEnabled()) {
253 log.debug("{} releasing valid endpoint", ConnPoolSupport.getId(endpoint));
254 }
255 manager.release(endpoint, state, validDuration);
256 } else {
257 discardEndpoint(endpoint);
258 }
259 }
260 }
261
262 @Override
263 public void discardEndpoint() {
264 final ConnectionEndpoint endpoint = endpointRef.getAndSet(null);
265 if (endpoint != null) {
266 discardEndpoint(endpoint);
267 }
268 }
269
270 @Override
271 public boolean cancel() {
272 final boolean alreadyReleased = endpointRef.get() == null;
273 final ConnectionEndpoint endpoint = endpointRef.getAndSet(null);
274 if (endpoint != null) {
275 if (log.isDebugEnabled()) {
276 log.debug("{} cancel", ConnPoolSupport.getId(endpoint));
277 }
278 discardEndpoint(endpoint);
279 }
280 return !alreadyReleased;
281 }
282
283 @Override
284 public ExecRuntime fork(final CancellableDependency cancellableDependency) {
285 return new InternalExecRuntime(log, manager, requestExecutor, cancellableDependency);
286 }
287
288 }