View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
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   * HTTP/1.1 client side message exchange initiator.
94   *
95   * @since 5.0
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      * Use {@link RequesterBootstrap} to create instances of this class.
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         // Run this under a doPrivileged to support lib users that run under a SecurityManager this allows granting connect permissions
269         // only to this library
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             // only checked exceptions are wrapped - error and RTExceptions are rethrown by doPrivileged
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 }