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 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 }