1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
70
71
72
73
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
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
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 }