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