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