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.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   * HTTP/1.1 client side message exchange initiator.
90   *
91   * @since 5.0
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      * Use {@link RequesterBootstrap} to create instances of this class.
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 }