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