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