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