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 Socket createSocket(final HttpHost targetHost) throws IOException {
242 final Socket sock;
243 if (socketConfig.getSocksProxyAddress() != null) {
244 sock = new Socket(new Proxy(Proxy.Type.SOCKS, socketConfig.getSocksProxyAddress()));
245 } else {
246 sock = new Socket();
247 }
248 sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound());
249 sock.setReuseAddress(socketConfig.isSoReuseAddress());
250 sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
251 sock.setKeepAlive(socketConfig.isSoKeepAlive());
252 if (socketConfig.getRcvBufSize() > 0) {
253 sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
254 }
255 if (socketConfig.getSndBufSize() > 0) {
256 sock.setSendBufferSize(socketConfig.getSndBufSize());
257 }
258 final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
259 if (linger >= 0) {
260 sock.setSoLinger(true, linger);
261 }
262
263 final InetSocketAddress targetAddress = addressResolver.resolve(targetHost);
264 sock.connect(targetAddress, socketConfig.getSoTimeout().toMillisecondsIntBound());
265 if (URIScheme.HTTPS.same(targetHost.getSchemeName())) {
266 final SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
267 sock, targetHost.getHostName(), targetAddress.getPort(), true);
268 if (this.sslSetupHandler != null) {
269 final SSLParameters sslParameters = sslSocket.getSSLParameters();
270 this.sslSetupHandler.execute(sslParameters);
271 sslSocket.setSSLParameters(sslParameters);
272 }
273 try {
274 sslSocket.startHandshake();
275 final SSLSession session = sslSocket.getSession();
276 if (session == null) {
277 throw new SSLHandshakeException("SSL session not available");
278 }
279 if (sslSessionVerifier != null) {
280 sslSessionVerifier.verify(targetHost, session);
281 }
282 } catch (final IOException ex) {
283 Closer.closeQuietly(sslSocket);
284 throw ex;
285 }
286 return sslSocket;
287 }
288 return sock;
289 }
290
291 public ClassicHttpResponse execute(
292 final HttpHost targetHost,
293 final ClassicHttpRequest request,
294 final HttpResponseInformationCallback informationCallback,
295 final Timeout connectTimeout,
296 final HttpContext context) throws HttpException, IOException {
297 Args.notNull(targetHost, "HTTP host");
298 Args.notNull(request, "HTTP request");
299 final Future<PoolEntry<HttpHost, HttpClientConnection>> leaseFuture = connPool.lease(targetHost, null, connectTimeout, null);
300 final PoolEntry<HttpHost, HttpClientConnection> poolEntry;
301 final Timeout timeout = Timeout.defaultsToInfinite(connectTimeout);
302 try {
303 poolEntry = leaseFuture.get(timeout.getDuration(), timeout.getTimeUnit());
304 } catch (final InterruptedException ex) {
305 Thread.currentThread().interrupt();
306 throw new InterruptedIOException(ex.getMessage());
307 } catch (final ExecutionException ex) {
308 throw new HttpException("Unexpected failure leasing connection", ex);
309 } catch (final TimeoutException ex) {
310 throw new ConnectionRequestTimeoutException("Connection request timeout");
311 }
312 final PoolEntryHolder connectionHolder = new PoolEntryHolder(poolEntry);
313 try {
314 HttpClientConnection connection = poolEntry.getConnection();
315 if (connection == null) {
316 final Socket socket = createSocket(targetHost);
317 connection = connectFactory.createConnection(socket);
318 poolEntry.assignConnection(connection);
319 }
320 if (request.getAuthority() == null) {
321 request.setAuthority(new URIAuthority(targetHost.getHostName(), targetHost.getPort()));
322 }
323 final ClassicHttpResponse response = execute(connection, request, informationCallback, context);
324 final HttpEntity entity = response.getEntity();
325 if (entity != null) {
326 response.setEntity(new HttpEntityWrapper(entity) {
327
328 private void releaseConnection() throws IOException {
329 try {
330 final HttpClientConnection localConn = connectionHolder.getConnection();
331 if (localConn != null) {
332 if (requestExecutor.keepAlive(request, response, localConn, context)) {
333 if (super.isStreaming()) {
334 Closer.close(super.getContent());
335 }
336 connectionHolder.releaseConnection();
337 }
338 }
339 } finally {
340 connectionHolder.discardConnection();
341 }
342 }
343
344 private void abortConnection() {
345 connectionHolder.discardConnection();
346 }
347
348 @Override
349 public boolean isStreaming() {
350 return true;
351 }
352
353 @Override
354 public InputStream getContent() throws IOException {
355 return new EofSensorInputStream(super.getContent(), new EofSensorWatcher() {
356
357 @Override
358 public boolean eofDetected(final InputStream wrapped) throws IOException {
359 releaseConnection();
360 return false;
361 }
362
363 @Override
364 public boolean streamClosed(final InputStream wrapped) throws IOException {
365 releaseConnection();
366 return false;
367 }
368
369 @Override
370 public boolean streamAbort(final InputStream wrapped) throws IOException {
371 abortConnection();
372 return false;
373 }
374
375 });
376 }
377
378 @Override
379 public void writeTo(final OutputStream outStream) throws IOException {
380 try {
381 if (outStream != null) {
382 super.writeTo(outStream);
383 }
384 close();
385 } catch (final IOException | RuntimeException ex) {
386 abortConnection();
387 }
388 }
389
390 @Override
391 public void close() throws IOException {
392 releaseConnection();
393 }
394
395 });
396 } else {
397 final HttpClientConnection localConn = connectionHolder.getConnection();
398 if (!requestExecutor.keepAlive(request, response, localConn, context)) {
399 localConn.close();
400 }
401 connectionHolder.releaseConnection();
402 }
403 return response;
404 } catch (final HttpException | IOException | RuntimeException ex) {
405 connectionHolder.discardConnection();
406 throw ex;
407 }
408 }
409
410 public ClassicHttpResponse execute(
411 final HttpHost targetHost,
412 final ClassicHttpRequest request,
413 final Timeout connectTimeout,
414 final HttpContext context) throws HttpException, IOException {
415 return execute(targetHost, request, null, connectTimeout, context);
416 }
417
418 public <T> T execute(
419 final HttpHost targetHost,
420 final ClassicHttpRequest request,
421 final Timeout connectTimeout,
422 final HttpContext context,
423 final HttpClientResponseHandler<T> responseHandler) throws HttpException, IOException {
424 try (final ClassicHttpResponse response = execute(targetHost, request, null, connectTimeout, context)) {
425 final T result = responseHandler.handleResponse(response);
426 EntityUtils.consume(response.getEntity());
427 return result;
428 }
429 }
430
431 public ConnPoolControl<HttpHost> getConnPoolControl() {
432 return connPool;
433 }
434
435 @Override
436 public void close(final CloseMode closeMode) {
437 connPool.close(closeMode);
438 }
439
440 @Override
441 public void close() throws IOException {
442 connPool.close();
443 }
444
445 private class PoolEntryHolder {
446
447 private final AtomicReference<PoolEntry<HttpHost, HttpClientConnection>> poolEntryRef;
448
449 PoolEntryHolder(final PoolEntry<HttpHost, HttpClientConnection> poolEntry) {
450 this.poolEntryRef = new AtomicReference<>(poolEntry);
451 }
452
453 HttpClientConnection getConnection() {
454 final PoolEntry<HttpHost, HttpClientConnection> poolEntry = poolEntryRef.get();
455 return poolEntry != null ? poolEntry.getConnection() : null;
456 }
457
458 void releaseConnection() {
459 final PoolEntry<HttpHost, HttpClientConnection> poolEntry = poolEntryRef.getAndSet(null);
460 if (poolEntry != null) {
461 final HttpClientConnection connection = poolEntry.getConnection();
462 connPool.release(poolEntry, connection != null && connection.isOpen());
463 }
464 }
465
466 void discardConnection() {
467 final PoolEntry<HttpHost, HttpClientConnection> poolEntry = poolEntryRef.getAndSet(null);
468 if (poolEntry != null) {
469 poolEntry.discardConnection(CloseMode.GRACEFUL);
470 connPool.release(poolEntry, false);
471 }
472 }
473
474 }
475
476 }