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 org.apache.hc.client5.http.ConnectExceptionSupport;
39  import org.apache.hc.client5.http.DnsResolver;
40  import org.apache.hc.client5.http.SchemePortResolver;
41  import org.apache.hc.client5.http.SystemDefaultDnsResolver;
42  import org.apache.hc.client5.http.UnsupportedSchemeException;
43  import org.apache.hc.client5.http.impl.ConnPoolSupport;
44  import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
45  import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
46  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
47  import org.apache.hc.client5.http.protocol.HttpClientContext;
48  import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
49  import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
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.config.Lookup;
56  import org.apache.hc.core5.http.io.SocketConfig;
57  import org.apache.hc.core5.http.protocol.HttpContext;
58  import org.apache.hc.core5.util.Args;
59  import org.apache.hc.core5.util.TimeValue;
60  import org.apache.hc.core5.util.Timeout;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  /**
65   * Default implementation of {@link HttpClientConnectionOperator} used as default in Http client,
66   * when no instance provided by user to {@link BasicHttpClientConnectionManager} or {@link
67   * PoolingHttpClientConnectionManager} constructor.
68   *
69   * @since 4.4
70   */
71  @Internal
72  @Contract(threading = ThreadingBehavior.STATELESS)
73  public class DefaultHttpClientConnectionOperator implements HttpClientConnectionOperator {
74  
75      static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry";
76  
77      private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpClientConnectionOperator.class);
78  
79      private final Lookup<ConnectionSocketFactory> socketFactoryRegistry;
80      private final SchemePortResolver schemePortResolver;
81      private final DnsResolver dnsResolver;
82  
83      public DefaultHttpClientConnectionOperator(
84              final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
85              final SchemePortResolver schemePortResolver,
86              final DnsResolver dnsResolver) {
87          super();
88          Args.notNull(socketFactoryRegistry, "Socket factory registry");
89          this.socketFactoryRegistry = socketFactoryRegistry;
90          this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
91              DefaultSchemePortResolver.INSTANCE;
92          this.dnsResolver = dnsResolver != null ? dnsResolver :
93              SystemDefaultDnsResolver.INSTANCE;
94      }
95  
96      @SuppressWarnings("unchecked")
97      private Lookup<ConnectionSocketFactory> getSocketFactoryRegistry(final HttpContext context) {
98          Lookup<ConnectionSocketFactory> reg = (Lookup<ConnectionSocketFactory>) context.getAttribute(
99                  SOCKET_FACTORY_REGISTRY);
100         if (reg == null) {
101             reg = this.socketFactoryRegistry;
102         }
103         return reg;
104     }
105 
106     @Override
107     public void connect(
108             final ManagedHttpClientConnection conn,
109             final HttpHost host,
110             final InetSocketAddress localAddress,
111             final TimeValue connectTimeout,
112             final SocketConfig socketConfig,
113             final HttpContext context) throws IOException {
114         final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null;
115         connect(conn, host, localAddress, timeout, socketConfig, null, context);
116     }
117 
118     @Override
119     public void connect(
120             final ManagedHttpClientConnection conn,
121             final HttpHost host,
122             final InetSocketAddress localAddress,
123             final Timeout connectTimeout,
124             final SocketConfig socketConfig,
125             final Object attachment,
126             final HttpContext context) throws IOException {
127         Args.notNull(conn, "Connection");
128         Args.notNull(host, "Host");
129         Args.notNull(socketConfig, "Socket config");
130         Args.notNull(context, "Context");
131         final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);
132         final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
133         if (sf == null) {
134             throw new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported");
135         }
136         final InetAddress[] remoteAddresses;
137         if (host.getAddress() != null) {
138             remoteAddresses = new InetAddress[] { host.getAddress() };
139         } else {
140             if (LOG.isDebugEnabled()) {
141                 LOG.debug("{} resolving remote address", host.getHostName());
142             }
143 
144             remoteAddresses = this.dnsResolver.resolve(host.getHostName());
145 
146             if (LOG.isDebugEnabled()) {
147                 LOG.debug("{} resolved to {}", host.getHostName(), remoteAddresses == null ? "null" : Arrays.asList(remoteAddresses));
148             }
149 
150             if (remoteAddresses == null || remoteAddresses.length == 0) {
151               throw new UnknownHostException(host.getHostName());
152           }
153         }
154 
155         final Timeout soTimeout = socketConfig.getSoTimeout();
156         final SocketAddress socksProxyAddress = socketConfig.getSocksProxyAddress();
157         final Proxy proxy = socksProxyAddress != null ? new Proxy(Proxy.Type.SOCKS, socksProxyAddress) : null;
158         final int port = this.schemePortResolver.resolve(host);
159         for (int i = 0; i < remoteAddresses.length; i++) {
160             final InetAddress address = remoteAddresses[i];
161             final boolean last = i == remoteAddresses.length - 1;
162 
163             Socket sock = sf.createSocket(proxy, context);
164             if (soTimeout != null) {
165                 sock.setSoTimeout(soTimeout.toMillisecondsIntBound());
166             }
167             sock.setReuseAddress(socketConfig.isSoReuseAddress());
168             sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
169             sock.setKeepAlive(socketConfig.isSoKeepAlive());
170             if (socketConfig.getRcvBufSize() > 0) {
171                 sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
172             }
173             if (socketConfig.getSndBufSize() > 0) {
174                 sock.setSendBufferSize(socketConfig.getSndBufSize());
175             }
176 
177             final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
178             if (linger >= 0) {
179                 sock.setSoLinger(true, linger);
180             }
181             conn.bind(sock);
182 
183             final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
184             if (LOG.isDebugEnabled()) {
185                 LOG.debug("{}:{} connecting {}->{} ({})",
186                         host.getHostName(), host.getPort(), localAddress, remoteAddress, connectTimeout);
187             }
188             try {
189                 sock = sf.connectSocket(sock, host, remoteAddress, localAddress, connectTimeout, attachment, context);
190                 conn.bind(sock);
191                 conn.setSocketTimeout(soTimeout);
192                 if (LOG.isDebugEnabled()) {
193                     LOG.debug("{}:{} connected {}->{} as {}",
194                             host.getHostName(), host.getPort(), localAddress, remoteAddress, ConnPoolSupport.getId(conn));
195                 }
196                 return;
197             } catch (final IOException ex) {
198                 if (last) {
199                     if (LOG.isDebugEnabled()) {
200                         LOG.debug("{}:{} connection to {} failed ({}); terminating operation",
201                                 host.getHostName(), host.getPort(), remoteAddress, ex.getClass());
202                     }
203                     throw ConnectExceptionSupport.enhance(ex, host, remoteAddresses);
204                 } else {
205                     if (LOG.isDebugEnabled()) {
206                         LOG.debug("{}:{} connection to {} failed ({}); retrying connection to the next address",
207                                 host.getHostName(), host.getPort(), remoteAddress, ex.getClass());
208                     }
209                 }
210             }
211         }
212     }
213 
214     @Override
215     public void upgrade(
216             final ManagedHttpClientConnection conn,
217             final HttpHost host,
218             final HttpContext context) throws IOException {
219         upgrade(conn, host, null, context);
220     }
221 
222     @Override
223     public void upgrade(
224             final ManagedHttpClientConnection conn,
225             final HttpHost host,
226             final Object attachment,
227             final HttpContext context) throws IOException {
228         final HttpClientContext clientContext = HttpClientContext.adapt(context);
229         final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(clientContext);
230         final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());
231         if (sf == null) {
232             throw new UnsupportedSchemeException(host.getSchemeName() +
233                     " protocol is not supported");
234         }
235         if (!(sf instanceof LayeredConnectionSocketFactory)) {
236             throw new UnsupportedSchemeException(host.getSchemeName() +
237                     " protocol does not support connection upgrade");
238         }
239         final LayeredConnectionSocketFactory lsf = (LayeredConnectionSocketFactory) sf;
240         Socket sock = conn.getSocket();
241         if (sock == null) {
242             throw new ConnectionClosedException("Connection is closed");
243         }
244         final int port = this.schemePortResolver.resolve(host);
245         sock = lsf.createLayeredSocket(sock, host.getHostName(), port, attachment, context);
246         conn.bind(sock);
247     }
248 
249 }