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.core5.http.impl.bootstrap;
28
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InterruptedIOException;
32 import java.io.OutputStream;
33 import java.net.InetSocketAddress;
34 import java.net.Proxy;
35 import java.net.Socket;
36 import java.util.Set;
37 import java.util.concurrent.ExecutionException;
38 import java.util.concurrent.Future;
39 import java.util.concurrent.TimeoutException;
40 import java.util.concurrent.atomic.AtomicReference;
41
42 import javax.net.ssl.SSLHandshakeException;
43 import javax.net.ssl.SSLParameters;
44 import javax.net.ssl.SSLSession;
45 import javax.net.ssl.SSLSocket;
46 import javax.net.ssl.SSLSocketFactory;
47
48 import org.apache.hc.core5.annotation.Internal;
49 import org.apache.hc.core5.function.Callback;
50 import org.apache.hc.core5.function.Resolver;
51 import org.apache.hc.core5.http.ClassicHttpRequest;
52 import org.apache.hc.core5.http.ClassicHttpResponse;
53 import org.apache.hc.core5.http.ConnectionClosedException;
54 import org.apache.hc.core5.http.ConnectionRequestTimeoutException;
55 import org.apache.hc.core5.http.HttpEntity;
56 import org.apache.hc.core5.http.HttpException;
57 import org.apache.hc.core5.http.HttpHost;
58 import org.apache.hc.core5.http.URIScheme;
59 import org.apache.hc.core5.http.config.CharCodingConfig;
60 import org.apache.hc.core5.http.config.Http1Config;
61 import org.apache.hc.core5.http.impl.DefaultAddressResolver;
62 import org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnectionFactory;
63 import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
64 import org.apache.hc.core5.http.io.EofSensorInputStream;
65 import org.apache.hc.core5.http.io.EofSensorWatcher;
66 import org.apache.hc.core5.http.io.HttpClientConnection;
67 import org.apache.hc.core5.http.io.HttpClientResponseHandler;
68 import org.apache.hc.core5.http.io.HttpConnectionFactory;
69 import org.apache.hc.core5.http.io.HttpResponseInformationCallback;
70 import org.apache.hc.core5.http.io.SocketConfig;
71 import org.apache.hc.core5.http.io.entity.EntityUtils;
72 import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
73 import org.apache.hc.core5.http.io.ssl.SSLSessionVerifier;
74 import org.apache.hc.core5.http.protocol.HttpContext;
75 import org.apache.hc.core5.http.protocol.HttpProcessor;
76 import org.apache.hc.core5.io.CloseMode;
77 import org.apache.hc.core5.io.Closer;
78 import org.apache.hc.core5.io.ModalCloseable;
79 import org.apache.hc.core5.net.URIAuthority;
80 import org.apache.hc.core5.pool.ConnPoolControl;
81 import org.apache.hc.core5.pool.ManagedConnPool;
82 import org.apache.hc.core5.pool.PoolEntry;
83 import org.apache.hc.core5.pool.PoolStats;
84 import org.apache.hc.core5.util.Args;
85 import org.apache.hc.core5.util.TimeValue;
86 import org.apache.hc.core5.util.Timeout;
87
88
89
90
91
92
93 public class HttpRequester implements ConnPoolControl<HttpHost>, ModalCloseable {
94
95 private final HttpRequestExecutor requestExecutor;
96 private final HttpProcessor httpProcessor;
97 private final ManagedConnPool<HttpHost, HttpClientConnection> connPool;
98 private final SocketConfig socketConfig;
99 private final HttpConnectionFactory<? extends HttpClientConnection> connectFactory;
100 private final SSLSocketFactory sslSocketFactory;
101 private final Callback<SSLParameters> sslSetupHandler;
102 private final SSLSessionVerifier sslSessionVerifier;
103 private final Resolver<HttpHost, InetSocketAddress> addressResolver;
104
105
106
107
108 @Internal
109 public HttpRequester(
110 final HttpRequestExecutor requestExecutor,
111 final HttpProcessor httpProcessor,
112 final ManagedConnPool<HttpHost, HttpClientConnection> connPool,
113 final SocketConfig socketConfig,
114 final HttpConnectionFactory<? extends HttpClientConnection> connectFactory,
115 final SSLSocketFactory sslSocketFactory,
116 final Callback<SSLParameters> sslSetupHandler,
117 final SSLSessionVerifier sslSessionVerifier,
118 final Resolver<HttpHost, InetSocketAddress> addressResolver) {
119 this.requestExecutor = Args.notNull(requestExecutor, "Request executor");
120 this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor");
121 this.connPool = Args.notNull(connPool, "Connection pool");
122 this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
123 this.connectFactory = connectFactory != null ? connectFactory : new DefaultBHttpClientConnectionFactory(
124 Http1Config.DEFAULT, CharCodingConfig.DEFAULT);
125 this.sslSocketFactory = sslSocketFactory != null ? sslSocketFactory : (SSLSocketFactory) SSLSocketFactory.getDefault();
126 this.sslSetupHandler = sslSetupHandler;
127 this.sslSessionVerifier = sslSessionVerifier;
128 this.addressResolver = addressResolver != null ? addressResolver : DefaultAddressResolver.INSTANCE;
129 }
130
131 @Override
132 public PoolStats getTotalStats() {
133 return connPool.getTotalStats();
134 }
135
136 @Override
137 public PoolStats getStats(final HttpHost route) {
138 return connPool.getStats(route);
139 }
140
141 @Override
142 public void setMaxTotal(final int max) {
143 connPool.setMaxTotal(max);
144 }
145
146 @Override
147 public int getMaxTotal() {
148 return connPool.getMaxTotal();
149 }
150
151 @Override
152 public void setDefaultMaxPerRoute(final int max) {
153 connPool.setDefaultMaxPerRoute(max);
154 }
155
156 @Override
157 public int getDefaultMaxPerRoute() {
158 return connPool.getDefaultMaxPerRoute();
159 }
160
161 @Override
162 public void setMaxPerRoute(final HttpHost route, final int max) {
163 connPool.setMaxPerRoute(route, max);
164 }
165
166 @Override
167 public int getMaxPerRoute(final HttpHost route) {
168 return connPool.getMaxPerRoute(route);
169 }
170
171 @Override
172 public void closeIdle(final TimeValue idleTime) {
173 connPool.closeIdle(idleTime);
174 }
175
176 @Override
177 public void closeExpired() {
178 connPool.closeExpired();
179 }
180
181 @Override
182 public Set<HttpHost> getRoutes() {
183 return connPool.getRoutes();
184 }
185
186 public ClassicHttpResponse execute(
187 final HttpClientConnection connection,
188 final ClassicHttpRequest request,
189 final HttpResponseInformationCallback informationCallback,
190 final HttpContext context) throws HttpException, IOException {
191 Args.notNull(connection, "HTTP connection");
192 Args.notNull(request, "HTTP request");
193 Args.notNull(context, "HTTP context");
194 if (!connection.isOpen()) {
195 throw new ConnectionClosedException();
196 }
197 requestExecutor.preProcess(request, httpProcessor, context);
198 final ClassicHttpResponse response = requestExecutor.execute(request, connection, informationCallback, context);
199 requestExecutor.postProcess(response, httpProcessor, context);
200 return response;
201 }
202
203 public ClassicHttpResponse execute(
204 final HttpClientConnection connection,
205 final ClassicHttpRequest request,
206 final HttpContext context) throws HttpException, IOException {
207 return execute(connection, request, null, context);
208 }
209
210 public boolean keepAlive(
211 final HttpClientConnection connection,
212 final ClassicHttpRequest request,
213 final ClassicHttpResponse response,
214 final HttpContext context) throws IOException {
215 final boolean keepAlive = requestExecutor.keepAlive(request, response, connection, context);
216 if (!keepAlive) {
217 connection.close();
218 }
219 return keepAlive;
220 }
221
222 public <T> T execute(
223 final HttpClientConnection connection,
224 final ClassicHttpRequest request,
225 final HttpContext context,
226 final HttpClientResponseHandler<T> responseHandler) throws HttpException, IOException {
227 try (final ClassicHttpResponse response = execute(connection, request, context)) {
228 final T result = responseHandler.handleResponse(response);
229 EntityUtils.consume(response.getEntity());
230 final boolean keepAlive = requestExecutor.keepAlive(request, response, connection, context);
231 if (!keepAlive) {
232 connection.close();
233 }
234 return result;
235 } catch (final HttpException | IOException | RuntimeException ex) {
236 connection.close(CloseMode.IMMEDIATE);
237 throw ex;
238 }
239 }
240
241 private HttpClientConnection createConnection(final Socket sock, final HttpHost targetHost) throws IOException {
242 sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound());
243 sock.setReuseAddress(socketConfig.isSoReuseAddress());
244 sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
245 sock.setKeepAlive(socketConfig.isSoKeepAlive());
246 if (socketConfig.getRcvBufSize() > 0) {
247 sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
248 }
249 if (socketConfig.getSndBufSize() > 0) {
250 sock.setSendBufferSize(socketConfig.getSndBufSize());
251 }
252 final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
253 if (linger >= 0) {
254 sock.setSoLinger(true, linger);
255 }
256
257 final InetSocketAddress targetAddress = addressResolver.resolve(targetHost);
258 sock.connect(targetAddress, socketConfig.getSoTimeout().toMillisecondsIntBound());
259 if (URIScheme.HTTPS.same(targetHost.getSchemeName())) {
260 final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
261 sock, targetHost.getHostName(), targetAddress.getPort(), false);
262 if (this.sslSetupHandler != null) {
263 final SSLParameters sslParameters = sslSocket.getSSLParameters();
264 this.sslSetupHandler.execute(sslParameters);
265 sslSocket.setSSLParameters(sslParameters);
266 }
267 try {
268 sslSocket.startHandshake();
269 final SSLSession session = sslSocket.getSession();
270 if (session == null) {
271 throw new SSLHandshakeException("SSL session not available");
272 }
273 if (sslSessionVerifier != null) {
274 sslSessionVerifier.verify(targetHost, session);
275 }
276 return connectFactory.createConnection(sslSocket, sock);
277 } catch (final IOException ex) {
278 Closer.closeQuietly(sslSocket);
279 throw ex;
280 }
281 } else {
282 return connectFactory.createConnection(sock);
283 }
284 }
285
286 public ClassicHttpResponse execute(
287 final HttpHost targetHost,
288 final ClassicHttpRequest request,
289 final HttpResponseInformationCallback informationCallback,
290 final Timeout connectTimeout,
291 final HttpContext context) throws HttpException, IOException {
292 Args.notNull(targetHost, "HTTP host");
293 Args.notNull(request, "HTTP request");
294 final Future<PoolEntry<HttpHost, HttpClientConnection>> leaseFuture = connPool.lease(targetHost, null, connectTimeout, null);
295 final PoolEntry<HttpHost, HttpClientConnection> poolEntry;
296 final Timeout timeout = Timeout.defaultsToInfinite(connectTimeout);
297 try {
298 poolEntry = leaseFuture.get(timeout.getDuration(), timeout.getTimeUnit());
299 } catch (final InterruptedException ex) {
300 Thread.currentThread().interrupt();
301 throw new InterruptedIOException(ex.getMessage());
302 } catch (final ExecutionException ex) {
303 throw new HttpException("Unexpected failure leasing connection", ex);
304 } catch (final TimeoutException ex) {
305 throw new ConnectionRequestTimeoutException("Connection request timeout");
306 }
307 final PoolEntryHolder connectionHolder = new PoolEntryHolder(poolEntry);
308 try {
309 HttpClientConnection connection = poolEntry.getConnection();
310 if (connection == null) {
311 final Socket sock;
312 if (socketConfig.getSocksProxyAddress() != null) {
313 sock = new Socket(new Proxy(Proxy.Type.SOCKS, socketConfig.getSocksProxyAddress()));
314 } else {
315 sock = new Socket();
316 }
317 try {
318 connection = createConnection(sock, targetHost);
319 poolEntry.assignConnection(connection);
320 } catch (IOException | RuntimeException ex) {
321 Closer.closeQuietly(sock);
322 throw ex;
323 }
324 }
325 if (request.getAuthority() == null) {
326 request.setAuthority(new URIAuthority(targetHost.getHostName(), targetHost.getPort()));
327 }
328 final ClassicHttpResponse response = execute(connection, request, informationCallback, context);
329 final HttpEntity entity = response.getEntity();
330 if (entity != null) {
331 response.setEntity(new HttpEntityWrapper(entity) {
332
333 private void releaseConnection() throws IOException {
334 try {
335 final HttpClientConnection localConn = connectionHolder.getConnection();
336 if (localConn != null) {
337 if (requestExecutor.keepAlive(request, response, localConn, context)) {
338 if (super.isStreaming()) {
339 Closer.close(super.getContent());
340 }
341 connectionHolder.releaseConnection();
342 }
343 }
344 } finally {
345 connectionHolder.discardConnection();
346 }
347 }
348
349 private void abortConnection() {
350 connectionHolder.discardConnection();
351 }
352
353 @Override
354 public boolean isStreaming() {
355 return true;
356 }
357
358 @Override
359 public InputStream getContent() throws IOException {
360 return new EofSensorInputStream(super.getContent(), new EofSensorWatcher() {
361
362 @Override
363 public boolean eofDetected(final InputStream wrapped) throws IOException {
364 releaseConnection();
365 return false;
366 }
367
368 @Override
369 public boolean streamClosed(final InputStream wrapped) throws IOException {
370 releaseConnection();
371 return false;
372 }
373
374 @Override
375 public boolean streamAbort(final InputStream wrapped) throws IOException {
376 abortConnection();
377 return false;
378 }
379
380 });
381 }
382
383 @Override
384 public void writeTo(final OutputStream outStream) throws IOException {
385 try {
386 if (outStream != null) {
387 super.writeTo(outStream);
388 }
389 close();
390 } catch (final IOException | RuntimeException ex) {
391 abortConnection();
392 }
393 }
394
395 @Override
396 public void close() throws IOException {
397 releaseConnection();
398 }
399
400 });
401 } else {
402 final HttpClientConnection localConn = connectionHolder.getConnection();
403 if (!requestExecutor.keepAlive(request, response, localConn, context)) {
404 localConn.close();
405 }
406 connectionHolder.releaseConnection();
407 }
408 return response;
409 } catch (final HttpException | IOException | RuntimeException ex) {
410 connectionHolder.discardConnection();
411 throw ex;
412 }
413 }
414
415 public ClassicHttpResponse execute(
416 final HttpHost targetHost,
417 final ClassicHttpRequest request,
418 final Timeout connectTimeout,
419 final HttpContext context) throws HttpException, IOException {
420 return execute(targetHost, request, null, connectTimeout, context);
421 }
422
423 public <T> T execute(
424 final HttpHost targetHost,
425 final ClassicHttpRequest request,
426 final Timeout connectTimeout,
427 final HttpContext context,
428 final HttpClientResponseHandler<T> responseHandler) throws HttpException, IOException {
429 try (final ClassicHttpResponse response = execute(targetHost, request, null, connectTimeout, context)) {
430 final T result = responseHandler.handleResponse(response);
431 EntityUtils.consume(response.getEntity());
432 return result;
433 }
434 }
435
436 public ConnPoolControl<HttpHost> getConnPoolControl() {
437 return connPool;
438 }
439
440 @Override
441 public void close(final CloseMode closeMode) {
442 connPool.close(closeMode);
443 }
444
445 @Override
446 public void close() throws IOException {
447 connPool.close();
448 }
449
450 private class PoolEntryHolder {
451
452 private final AtomicReference<PoolEntry<HttpHost, HttpClientConnection>> poolEntryRef;
453
454 PoolEntryHolder(final PoolEntry<HttpHost, HttpClientConnection> poolEntry) {
455 this.poolEntryRef = new AtomicReference<>(poolEntry);
456 }
457
458 HttpClientConnection getConnection() {
459 final PoolEntry<HttpHost, HttpClientConnection> poolEntry = poolEntryRef.get();
460 return poolEntry != null ? poolEntry.getConnection() : null;
461 }
462
463 void releaseConnection() {
464 final PoolEntry<HttpHost, HttpClientConnection> poolEntry = poolEntryRef.getAndSet(null);
465 if (poolEntry != null) {
466 final HttpClientConnection connection = poolEntry.getConnection();
467 connPool.release(poolEntry, connection != null && connection.isOpen());
468 }
469 }
470
471 void discardConnection() {
472 final PoolEntry<HttpHost, HttpClientConnection> poolEntry = poolEntryRef.getAndSet(null);
473 if (poolEntry != null) {
474 poolEntry.discardConnection(CloseMode.GRACEFUL);
475 connPool.release(poolEntry, false);
476 }
477 }
478
479 }
480
481 }