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