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