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(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             // only checked exceptions are wrapped - error and RTExceptions are rethrown by doPrivileged
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 }