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             if (LOG.isDebugEnabled()) {
196                 LOG.debug("{} connecting {}->{} ({})", endpointHost, localAddress, remoteAddress, connectTimeout);
197             }
198             final Socket socket = detachedSocketFactory.create(socksProxy);
199             try {
200                 conn.bind(socket);
201                 if (soTimeout != null) {
202                     socket.setSoTimeout(soTimeout.toMillisecondsIntBound());
203                 }
204                 socket.setReuseAddress(socketConfig.isSoReuseAddress());
205                 socket.setTcpNoDelay(socketConfig.isTcpNoDelay());
206                 socket.setKeepAlive(socketConfig.isSoKeepAlive());
207                 if (socketConfig.getRcvBufSize() > 0) {
208                     socket.setReceiveBufferSize(socketConfig.getRcvBufSize());
209                 }
210                 if (socketConfig.getSndBufSize() > 0) {
211                     socket.setSendBufferSize(socketConfig.getSndBufSize());
212                 }
213 
214                 final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
215                 if (linger >= 0) {
216                     socket.setSoLinger(true, linger);
217                 }
218 
219                 if (localAddress != null) {
220                     socket.bind(localAddress);
221                 }
222                 socket.connect(remoteAddress, TimeValue.isPositive(connectTimeout) ? connectTimeout.toMillisecondsIntBound() : 0);
223                 conn.bind(socket);
224                 if (LOG.isDebugEnabled()) {
225                     LOG.debug("{} {} connected {}->{}", ConnPoolSupport.getId(conn), endpointHost,
226                             conn.getLocalAddress(), conn.getRemoteAddress());
227                 }
228                 conn.setSocketTimeout(soTimeout);
229                 final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(endpointHost.getSchemeName()) : null;
230                 if (tlsSocketStrategy != null) {
231                     final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
232                     if (LOG.isDebugEnabled()) {
233                         LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(conn), tlsName);
234                     }
235                     final Socket upgradedSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
236                     conn.bind(upgradedSocket);
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                 } else {
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 
258     @Override
259     public void upgrade(
260             final ManagedHttpClientConnection conn,
261             final HttpHost host,
262             final HttpContext context) throws IOException {
263         upgrade(conn, host, null, null, context);
264     }
265 
266     @Override
267     public void upgrade(
268             final ManagedHttpClientConnection conn,
269             final HttpHost endpointHost,
270             final NamedEndpoint endpointName,
271             final Object attachment,
272             final HttpContext context) throws IOException {
273         final Socket socket = conn.getSocket();
274         if (socket == null) {
275             throw new ConnectionClosedException("Connection is closed");
276         }
277         final String newProtocol = URIScheme.HTTP.same(endpointHost.getSchemeName()) ? URIScheme.HTTPS.id : endpointHost.getSchemeName();
278         final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(newProtocol) : null;
279         if (tlsSocketStrategy != null) {
280             final NamedEndpoint tlsName = endpointName != null ? endpointName : 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         } else {
287             throw new UnsupportedSchemeException(newProtocol + " protocol is not supported");
288         }
289     }
290 
291 }