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