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.client5.http.impl.io;
28  
29  import java.io.IOException;
30  import java.net.InetSocketAddress;
31  import java.net.Proxy;
32  import java.net.Socket;
33  import java.net.SocketAddress;
34  import java.util.Arrays;
35  import java.util.Collections;
36  import java.util.List;
37  
38  import javax.net.ssl.SSLSocket;
39  
40  import jdk.net.ExtendedSocketOptions;
41  import jdk.net.Sockets;
42  import org.apache.hc.client5.http.ConnectExceptionSupport;
43  import org.apache.hc.client5.http.DnsResolver;
44  import org.apache.hc.client5.http.SchemePortResolver;
45  import org.apache.hc.client5.http.SystemDefaultDnsResolver;
46  import org.apache.hc.client5.http.UnsupportedSchemeException;
47  import org.apache.hc.client5.http.config.TlsConfig;
48  import org.apache.hc.client5.http.impl.ConnPoolSupport;
49  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
50  import org.apache.hc.client5.http.io.DetachedSocketFactory;
51  import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
52  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
53  import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
54  import org.apache.hc.core5.annotation.Contract;
55  import org.apache.hc.core5.annotation.Internal;
56  import org.apache.hc.core5.annotation.ThreadingBehavior;
57  import org.apache.hc.core5.http.ConnectionClosedException;
58  import org.apache.hc.core5.http.HttpHost;
59  import org.apache.hc.core5.http.URIScheme;
60  import org.apache.hc.core5.http.config.Lookup;
61  import org.apache.hc.core5.http.io.SocketConfig;
62  import org.apache.hc.core5.http.protocol.HttpContext;
63  import org.apache.hc.core5.io.Closer;
64  import org.apache.hc.core5.net.NamedEndpoint;
65  import org.apache.hc.core5.util.Args;
66  import org.apache.hc.core5.util.TimeValue;
67  import org.apache.hc.core5.util.Timeout;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  /**
72   * Default implementation of {@link HttpClientConnectionOperator} used as default in Http client,
73   * when no instance provided by user to {@link BasicHttpClientConnectionManager} or {@link
74   * PoolingHttpClientConnectionManager} constructor.
75   *
76   * @since 4.4
77   */
78  @Internal
79  @Contract(threading = ThreadingBehavior.STATELESS)
80  public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {
81  
82      private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpClientConnectionOperator.class);
83  
84      @SuppressWarnings("Since15")
85      private static final boolean SUPPORTS_KEEPALIVE_OPTIONS = Sockets.supportedOptions(Socket.class)
86              .containsAll(Arrays.asList(ExtendedSocketOptions.TCP_KEEPIDLE, ExtendedSocketOptions.TCP_KEEPINTERVAL,
87                      ExtendedSocketOptions.TCP_KEEPCOUNT));
88  
89      static final DetachedSocketFactory PLAIN_SOCKET_FACTORY = socksProxy -> socksProxy == null ? new Socket() : new Socket(socksProxy);
90  
91      private final DetachedSocketFactory detachedSocketFactory;
92      private final Lookup<TlsSocketStrategy> tlsSocketStrategyLookup;
93      private final SchemePortResolver schemePortResolver;
94      private final DnsResolver dnsResolver;
95  
96      /**
97       * @deprecated Provided for backward compatibility
98       */
99      @Deprecated
100     static Lookup<TlsSocketStrategy> adapt(final Lookup<org.apache.hc.client5.http.socket.ConnectionSocketFactory> lookup) {
101 
102         return name -> {
103             final org.apache.hc.client5.http.socket.ConnectionSocketFactory sf = lookup.lookup(name);
104             return sf instanceof org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory ? (socket, target, port, attachment, context) ->
105                     (SSLSocket) ((org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory) sf).createLayeredSocket(socket, target, port, attachment, context) : null;
106         };
107 
108     }
109 
110 
111     public DefaultHttpClientConnectionOperator(
112             final DetachedSocketFactory detachedSocketFactory,
113             final SchemePortResolver schemePortResolver,
114             final DnsResolver dnsResolver,
115             final Lookup<TlsSocketStrategy> tlsSocketStrategyLookup) {
116         super();
117         this.detachedSocketFactory = Args.notNull(detachedSocketFactory, "Plain socket factory");
118         this.tlsSocketStrategyLookup = Args.notNull(tlsSocketStrategyLookup, "Socket factory registry");
119         this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
120                 DefaultSchemePortResolver.INSTANCE;
121         this.dnsResolver = dnsResolver != null ? dnsResolver :
122                 SystemDefaultDnsResolver.INSTANCE;
123     }
124 
125     /**
126      * @deprecated Do not use.
127      */
128     @Deprecated
129     public DefaultHttpClientConnectionOperator(
130             final Lookup<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry,
131             final SchemePortResolver schemePortResolver,
132             final DnsResolver dnsResolver) {
133         this(PLAIN_SOCKET_FACTORY, schemePortResolver, dnsResolver, adapt(socketFactoryRegistry));
134     }
135 
136     public DefaultHttpClientConnectionOperator(
137             final SchemePortResolver schemePortResolver,
138             final DnsResolver dnsResolver,
139             final Lookup<TlsSocketStrategy> tlsSocketStrategyLookup) {
140         this(PLAIN_SOCKET_FACTORY, schemePortResolver, dnsResolver, tlsSocketStrategyLookup);
141     }
142 
143     @Override
144     public void connect(
145             final ManagedHttpClientConnection conn,
146             final HttpHost host,
147             final InetSocketAddress localAddress,
148             final TimeValue connectTimeout,
149             final SocketConfig socketConfig,
150             final HttpContext context) throws IOException {
151         final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null;
152         connect(conn, host, null, localAddress, timeout, socketConfig, null, context);
153     }
154 
155     @SuppressWarnings("Since15")
156     @Override
157     public void connect(
158             final ManagedHttpClientConnection conn,
159             final HttpHost endpointHost,
160             final NamedEndpoint endpointName,
161             final InetSocketAddress localAddress,
162             final Timeout connectTimeout,
163             final SocketConfig socketConfig,
164             final Object attachment,
165             final HttpContext context) throws IOException {
166 
167         Args.notNull(conn, "Connection");
168         Args.notNull(endpointHost, "Host");
169         Args.notNull(socketConfig, "Socket config");
170         Args.notNull(context, "Context");
171 
172         final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress();
173         final Proxy socksProxy = socksProxyAddress != null ? new Proxy(Proxy.Type.SOCKS, socksProxyAddress) : null;
174 
175         final List<InetSocketAddress> remoteAddresses;
176         if (endpointHost.getAddress() != null) {
177             remoteAddresses = Collections.singletonList(
178                     new InetSocketAddress(endpointHost.getAddress(), this.schemePortResolver.resolve(endpointHost.getSchemeName(), endpointHost)));
179         } else {
180             final int port = this.schemePortResolver.resolve(endpointHost.getSchemeName(), endpointHost);
181             remoteAddresses = this.dnsResolver.resolve(endpointHost.getHostName(), port);
182         }
183         for (int i = 0; i < remoteAddresses.size(); i++) {
184             final InetSocketAddress remoteAddress = remoteAddresses.get(i);
185             final boolean last = i == remoteAddresses.size() - 1;
186             onBeforeSocketConnect(context, endpointHost);
187             if (LOG.isDebugEnabled()) {
188                 LOG.debug("{} connecting {}->{} ({})", endpointHost, localAddress, remoteAddress, connectTimeout);
189             }
190             final Socket socket = detachedSocketFactory.create(endpointHost.getSchemeName(), socksProxy);
191             try {
192                 // Always bind to the local address if it's provided.
193                 if (localAddress != null) {
194                     socket.bind(localAddress);
195                 }
196                 conn.bind(socket);
197                 final Timeout socketTimeout = socketConfig.getSoTimeout();
198                 if (socketTimeout != null) {
199                     socket.setSoTimeout(socketTimeout.toMillisecondsIntBound());
200                 }
201                 socket.setReuseAddress(socketConfig.isSoReuseAddress());
202                 socket.setTcpNoDelay(socketConfig.isTcpNoDelay());
203                 socket.setKeepAlive(socketConfig.isSoKeepAlive());
204                 if (socketConfig.getRcvBufSize() > 0) {
205                     socket.setReceiveBufferSize(socketConfig.getRcvBufSize());
206                 }
207                 if (socketConfig.getSndBufSize() > 0) {
208                     socket.setSendBufferSize(socketConfig.getSndBufSize());
209                 }
210                 if (SUPPORTS_KEEPALIVE_OPTIONS) {
211                     if (socketConfig.getTcpKeepIdle() > 0) {
212                         Sockets.setOption(socket, ExtendedSocketOptions.TCP_KEEPIDLE, socketConfig.getTcpKeepIdle());
213                     }
214                     if (socketConfig.getTcpKeepInterval() > 0) {
215                         Sockets.setOption(socket, ExtendedSocketOptions.TCP_KEEPINTERVAL,
216                                 socketConfig.getTcpKeepInterval());
217                     }
218                     if (socketConfig.getTcpKeepCount() > 0) {
219                         Sockets.setOption(socket, ExtendedSocketOptions.TCP_KEEPCOUNT, socketConfig.getTcpKeepCount());
220                     }
221                 }
222                 final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
223                 if (linger >= 0) {
224                     socket.setSoLinger(true, linger);
225                 }
226                 socket.connect(remoteAddress, TimeValue.isPositive(connectTimeout) ? connectTimeout.toMillisecondsIntBound() : 0);
227                 conn.bind(socket);
228                 onAfterSocketConnect(context, endpointHost);
229                 if (LOG.isDebugEnabled()) {
230                     LOG.debug("{} {} connected {}->{}", ConnPoolSupport.getId(conn), endpointHost, conn.getLocalAddress(), conn.getRemoteAddress());
231                 }
232                 final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(endpointHost.getSchemeName()) : null;
233                 if (tlsSocketStrategy != null) {
234                     final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
235                     onBeforeTlsHandshake(context, endpointHost);
236                     if (LOG.isDebugEnabled()) {
237                         LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(conn), tlsName);
238                     }
239                     final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
240                     final int soTimeout = socket.getSoTimeout();
241                     final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout() != null ? tlsConfig.getHandshakeTimeout() : connectTimeout;
242                     if (handshakeTimeout != null) {
243                         socket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
244                     }
245                     final SSLSocket sslSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
246                     conn.bind(sslSocket, socket);
247                     socket.setSoTimeout(soTimeout);
248                     onAfterTlsHandshake(context, endpointHost);
249                     if (LOG.isDebugEnabled()) {
250                         LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(conn), tlsName);
251                     }
252                 }
253                 return;
254             } catch (final RuntimeException ex) {
255                 Closer.closeQuietly(socket);
256                 throw ex;
257             } catch (final IOException ex) {
258                 Closer.closeQuietly(socket);
259                 if (last) {
260                     if (LOG.isDebugEnabled()) {
261                         LOG.debug("{} connection to {} failed ({}); terminating operation", endpointHost, remoteAddress, ex.getClass());
262                     }
263                     throw ConnectExceptionSupport.enhance(ex, endpointHost);
264                 }
265                 if (LOG.isDebugEnabled()) {
266                     LOG.debug("{} connection to {} failed ({}); retrying connection to the next address", endpointHost, remoteAddress, ex.getClass());
267                 }
268             }
269         }
270     }
271 
272     @Override
273     public void upgrade(
274             final ManagedHttpClientConnection conn,
275             final HttpHost host,
276             final HttpContext context) throws IOException {
277         upgrade(conn, host, null, null, context);
278     }
279 
280     @Override
281     public void upgrade(
282             final ManagedHttpClientConnection conn,
283             final HttpHost endpointHost,
284             final NamedEndpoint endpointName,
285             final Object attachment,
286             final HttpContext context) throws IOException {
287         final Socket socket = conn.getSocket();
288         if (socket == null) {
289             throw new ConnectionClosedException("Connection is closed");
290         }
291         final String newProtocol = URIScheme.HTTP.same(endpointHost.getSchemeName()) ? URIScheme.HTTPS.id : endpointHost.getSchemeName();
292         final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(newProtocol) : null;
293         if (tlsSocketStrategy != null) {
294             final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
295             onBeforeTlsHandshake(context, endpointHost);
296             if (LOG.isDebugEnabled()) {
297                 LOG.debug("{} upgrading to TLS {}:{}", ConnPoolSupport.getId(conn), tlsName.getHostName(), tlsName.getPort());
298             }
299             final SSLSocket upgradedSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
300             conn.bind(upgradedSocket, socket);
301             onAfterTlsHandshake(context, endpointHost);
302             if (LOG.isDebugEnabled()) {
303                 LOG.debug("{} upgraded to TLS {}:{}", ConnPoolSupport.getId(conn), tlsName.getHostName(), tlsName.getPort());
304             }
305         } else {
306             throw new UnsupportedSchemeException(newProtocol + " protocol is not supported");
307         }
308     }
309 
310     protected void onBeforeSocketConnect(final HttpContext httpContext, final HttpHost endpointHost) {
311     }
312 
313     protected void onAfterSocketConnect(final HttpContext httpContext, final HttpHost endpointHost) {
314     }
315 
316     protected void onBeforeTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
317     }
318 
319     protected void onAfterTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
320     }
321 
322 }