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