1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.http.apache; 17 18 import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY; 19 import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME; 20 import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY; 21 import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY; 22 import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES; 23 import static software.amazon.awssdk.http.apache.internal.conn.ClientConnectionRequestFactory.THREAD_LOCAL_REQUEST_METRIC_COLLECTOR; 24 import static software.amazon.awssdk.utils.NumericUtils.saturatedCast; 25 26 import java.io.IOException; 27 import java.net.InetAddress; 28 import java.security.KeyManagementException; 29 import java.security.NoSuchAlgorithmException; 30 import java.security.cert.CertificateException; 31 import java.security.cert.X509Certificate; 32 import java.time.Duration; 33 import java.util.Optional; 34 import java.util.concurrent.TimeUnit; 35 import javax.net.ssl.HostnameVerifier; 36 import javax.net.ssl.KeyManager; 37 import javax.net.ssl.SSLContext; 38 import javax.net.ssl.TrustManager; 39 import javax.net.ssl.X509TrustManager; 40 import org.apache.http.Header; 41 import org.apache.http.HeaderIterator; 42 import org.apache.http.HttpResponse; 43 import org.apache.http.client.CredentialsProvider; 44 import org.apache.http.client.methods.HttpRequestBase; 45 import org.apache.http.client.protocol.HttpClientContext; 46 import org.apache.http.config.Registry; 47 import org.apache.http.config.RegistryBuilder; 48 import org.apache.http.config.SocketConfig; 49 import org.apache.http.conn.ConnectionKeepAliveStrategy; 50 import org.apache.http.conn.DnsResolver; 51 import org.apache.http.conn.HttpClientConnectionManager; 52 import org.apache.http.conn.routing.HttpRoutePlanner; 53 import org.apache.http.conn.socket.ConnectionSocketFactory; 54 import org.apache.http.conn.socket.PlainConnectionSocketFactory; 55 import org.apache.http.conn.ssl.NoopHostnameVerifier; 56 import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 57 import org.apache.http.conn.ssl.SSLInitializationException; 58 import org.apache.http.impl.client.HttpClientBuilder; 59 import org.apache.http.impl.client.HttpClients; 60 import org.apache.http.impl.conn.DefaultSchemePortResolver; 61 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 62 import org.apache.http.pool.PoolStats; 63 import org.apache.http.protocol.HttpRequestExecutor; 64 import software.amazon.awssdk.annotations.SdkPublicApi; 65 import software.amazon.awssdk.annotations.SdkTestInternalApi; 66 import software.amazon.awssdk.http.AbortableInputStream; 67 import software.amazon.awssdk.http.ExecutableHttpRequest; 68 import software.amazon.awssdk.http.HttpExecuteRequest; 69 import software.amazon.awssdk.http.HttpExecuteResponse; 70 import software.amazon.awssdk.http.SdkHttpClient; 71 import software.amazon.awssdk.http.SdkHttpConfigurationOption; 72 import software.amazon.awssdk.http.SdkHttpResponse; 73 import software.amazon.awssdk.http.SystemPropertyTlsKeyManagersProvider; 74 import software.amazon.awssdk.http.TlsKeyManagersProvider; 75 import software.amazon.awssdk.http.TlsTrustManagersProvider; 76 import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig; 77 import software.amazon.awssdk.http.apache.internal.DefaultConfiguration; 78 import software.amazon.awssdk.http.apache.internal.SdkConnectionReuseStrategy; 79 import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner; 80 import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory; 81 import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper; 82 import software.amazon.awssdk.http.apache.internal.conn.SdkConnectionKeepAliveStrategy; 83 import software.amazon.awssdk.http.apache.internal.conn.SdkTlsSocketFactory; 84 import software.amazon.awssdk.http.apache.internal.impl.ApacheHttpRequestFactory; 85 import software.amazon.awssdk.http.apache.internal.impl.ApacheSdkHttpClient; 86 import software.amazon.awssdk.http.apache.internal.impl.ConnectionManagerAwareHttpClient; 87 import software.amazon.awssdk.http.apache.internal.utils.ApacheUtils; 88 import software.amazon.awssdk.metrics.MetricCollector; 89 import software.amazon.awssdk.metrics.NoOpMetricCollector; 90 import software.amazon.awssdk.utils.AttributeMap; 91 import software.amazon.awssdk.utils.Logger; 92 import software.amazon.awssdk.utils.Validate; 93 94 /** 95 * An implementation of {@link SdkHttpClient} that uses Apache HTTP client to communicate with the service. This is the most 96 * powerful synchronous client that adds an extra dependency and additional startup latency in exchange for more functionality, 97 * like support for HTTP proxies. 98 * 99 * <p>See software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient for an alternative implementation.</p> 100 * 101 * <p>This can be created via {@link #builder()}</p> 102 */ 103 @SdkPublicApi 104 public final class ApacheHttpClient implements SdkHttpClient { 105 106 public static final String CLIENT_NAME = "Apache"; 107 108 private static final Logger log = Logger.loggerFor(ApacheHttpClient.class); 109 110 private final ApacheHttpRequestFactory apacheHttpRequestFactory = new ApacheHttpRequestFactory(); 111 private final ConnectionManagerAwareHttpClient httpClient; 112 private final ApacheHttpRequestConfig requestConfig; 113 private final AttributeMap resolvedOptions; 114 115 @SdkTestInternalApi ApacheHttpClient(ConnectionManagerAwareHttpClient httpClient, ApacheHttpRequestConfig requestConfig, AttributeMap resolvedOptions)116 ApacheHttpClient(ConnectionManagerAwareHttpClient httpClient, 117 ApacheHttpRequestConfig requestConfig, 118 AttributeMap resolvedOptions) { 119 this.httpClient = httpClient; 120 this.requestConfig = requestConfig; 121 this.resolvedOptions = resolvedOptions; 122 } 123 ApacheHttpClient(DefaultBuilder builder, AttributeMap resolvedOptions)124 private ApacheHttpClient(DefaultBuilder builder, AttributeMap resolvedOptions) { 125 this.httpClient = createClient(builder, resolvedOptions); 126 this.requestConfig = createRequestConfig(builder, resolvedOptions); 127 this.resolvedOptions = resolvedOptions; 128 } 129 builder()130 public static Builder builder() { 131 return new DefaultBuilder(); 132 } 133 134 /** 135 * Create a {@link ApacheHttpClient} with the default properties 136 * 137 * @return an {@link ApacheHttpClient} 138 */ create()139 public static SdkHttpClient create() { 140 return new DefaultBuilder().build(); 141 } 142 createClient(ApacheHttpClient.DefaultBuilder configuration, AttributeMap standardOptions)143 private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBuilder configuration, 144 AttributeMap standardOptions) { 145 ApacheConnectionManagerFactory cmFactory = new ApacheConnectionManagerFactory(); 146 147 HttpClientBuilder builder = HttpClients.custom(); 148 // Note that it is important we register the original connection manager with the 149 // IdleConnectionReaper as it's required for the successful deregistration of managers 150 // from the reaper. See https://github.com/aws/aws-sdk-java/issues/722. 151 HttpClientConnectionManager cm = cmFactory.create(configuration, standardOptions); 152 153 builder.setRequestExecutor(new HttpRequestExecutor()) 154 // SDK handles decompression 155 .disableContentCompression() 156 .setKeepAliveStrategy(buildKeepAliveStrategy(standardOptions)) 157 .disableRedirectHandling() 158 .disableAutomaticRetries() 159 .setUserAgent("") // SDK will set the user agent header in the pipeline. Don't let Apache waste time 160 .setConnectionReuseStrategy(new SdkConnectionReuseStrategy()) 161 .setConnectionManager(ClientConnectionManagerFactory.wrap(cm)); 162 163 addProxyConfig(builder, configuration); 164 165 if (useIdleConnectionReaper(standardOptions)) { 166 IdleConnectionReaper.getInstance().registerConnectionManager( 167 cm, standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis()); 168 } 169 170 return new ApacheSdkHttpClient(builder.build(), cm); 171 } 172 addProxyConfig(HttpClientBuilder builder, DefaultBuilder configuration)173 private void addProxyConfig(HttpClientBuilder builder, 174 DefaultBuilder configuration) { 175 ProxyConfiguration proxyConfiguration = configuration.proxyConfiguration; 176 177 Validate.isTrue(configuration.httpRoutePlanner == null || !isProxyEnabled(proxyConfiguration), 178 "The httpRoutePlanner and proxyConfiguration can't both be configured."); 179 Validate.isTrue(configuration.credentialsProvider == null || !isAuthenticatedProxy(proxyConfiguration), 180 "The credentialsProvider and proxyConfiguration username/password can't both be configured."); 181 182 HttpRoutePlanner routePlanner = configuration.httpRoutePlanner; 183 if (isProxyEnabled(proxyConfiguration)) { 184 log.debug(() -> "Configuring Proxy. Proxy Host: " + proxyConfiguration.host()); 185 routePlanner = new SdkProxyRoutePlanner(proxyConfiguration.host(), 186 proxyConfiguration.port(), 187 proxyConfiguration.scheme(), 188 proxyConfiguration.nonProxyHosts()); 189 } 190 191 CredentialsProvider credentialsProvider = configuration.credentialsProvider; 192 if (isAuthenticatedProxy(proxyConfiguration)) { 193 credentialsProvider = ApacheUtils.newProxyCredentialsProvider(proxyConfiguration); 194 } 195 196 if (routePlanner != null) { 197 builder.setRoutePlanner(routePlanner); 198 } 199 200 if (credentialsProvider != null) { 201 builder.setDefaultCredentialsProvider(credentialsProvider); 202 } 203 } 204 buildKeepAliveStrategy(AttributeMap standardOptions)205 private ConnectionKeepAliveStrategy buildKeepAliveStrategy(AttributeMap standardOptions) { 206 long maxIdle = standardOptions.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis(); 207 return maxIdle > 0 ? new SdkConnectionKeepAliveStrategy(maxIdle) : null; 208 } 209 useIdleConnectionReaper(AttributeMap standardOptions)210 private boolean useIdleConnectionReaper(AttributeMap standardOptions) { 211 return Boolean.TRUE.equals(standardOptions.get(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS)); 212 } 213 isAuthenticatedProxy(ProxyConfiguration proxyConfiguration)214 private boolean isAuthenticatedProxy(ProxyConfiguration proxyConfiguration) { 215 return proxyConfiguration.username() != null && proxyConfiguration.password() != null; 216 } 217 isProxyEnabled(ProxyConfiguration proxyConfiguration)218 private boolean isProxyEnabled(ProxyConfiguration proxyConfiguration) { 219 return proxyConfiguration.host() != null 220 && proxyConfiguration.port() > 0; 221 } 222 223 @Override prepareRequest(HttpExecuteRequest request)224 public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) { 225 MetricCollector metricCollector = request.metricCollector().orElseGet(NoOpMetricCollector::create); 226 metricCollector.reportMetric(HTTP_CLIENT_NAME, clientName()); 227 HttpRequestBase apacheRequest = toApacheRequest(request); 228 return new ExecutableHttpRequest() { 229 @Override 230 public HttpExecuteResponse call() throws IOException { 231 HttpExecuteResponse executeResponse = execute(apacheRequest, metricCollector); 232 collectPoolMetric(metricCollector); 233 return executeResponse; 234 } 235 236 @Override 237 public void abort() { 238 apacheRequest.abort(); 239 } 240 }; 241 } 242 243 @Override 244 public void close() { 245 HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager(); 246 IdleConnectionReaper.getInstance().deregisterConnectionManager(cm); 247 cm.shutdown(); 248 } 249 250 private HttpExecuteResponse execute(HttpRequestBase apacheRequest, MetricCollector metricCollector) throws IOException { 251 HttpClientContext localRequestContext = ApacheUtils.newClientContext(requestConfig.proxyConfiguration()); 252 THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.set(metricCollector); 253 try { 254 HttpResponse httpResponse = httpClient.execute(apacheRequest, localRequestContext); 255 return createResponse(httpResponse, apacheRequest); 256 } finally { 257 THREAD_LOCAL_REQUEST_METRIC_COLLECTOR.remove(); 258 } 259 } 260 261 private HttpRequestBase toApacheRequest(HttpExecuteRequest request) { 262 return apacheHttpRequestFactory.create(request, requestConfig); 263 } 264 265 /** 266 * Creates and initializes an HttpResponse object suitable to be passed to an HTTP response 267 * handler object. 268 * 269 * @return The new, initialized HttpResponse object ready to be passed to an HTTP response handler object. 270 * @throws IOException If there were any problems getting any response information from the 271 * HttpClient method object. 272 */ 273 private HttpExecuteResponse createResponse(org.apache.http.HttpResponse apacheHttpResponse, 274 HttpRequestBase apacheRequest) throws IOException { 275 SdkHttpResponse.Builder responseBuilder = 276 SdkHttpResponse.builder() 277 .statusCode(apacheHttpResponse.getStatusLine().getStatusCode()) 278 .statusText(apacheHttpResponse.getStatusLine().getReasonPhrase()); 279 280 HeaderIterator headerIterator = apacheHttpResponse.headerIterator(); 281 while (headerIterator.hasNext()) { 282 Header header = headerIterator.nextHeader(); 283 responseBuilder.appendHeader(header.getName(), header.getValue()); 284 } 285 286 AbortableInputStream responseBody = apacheHttpResponse.getEntity() != null ? 287 toAbortableInputStream(apacheHttpResponse, apacheRequest) : null; 288 289 return HttpExecuteResponse.builder().response(responseBuilder.build()).responseBody(responseBody).build(); 290 291 } 292 293 private AbortableInputStream toAbortableInputStream(HttpResponse apacheHttpResponse, HttpRequestBase apacheRequest) 294 throws IOException { 295 return AbortableInputStream.create(apacheHttpResponse.getEntity().getContent(), apacheRequest::abort); 296 } 297 298 private ApacheHttpRequestConfig createRequestConfig(DefaultBuilder builder, 299 AttributeMap resolvedOptions) { 300 return ApacheHttpRequestConfig.builder() 301 .socketTimeout(resolvedOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT)) 302 .connectionTimeout(resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT)) 303 .connectionAcquireTimeout( 304 resolvedOptions.get(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT)) 305 .proxyConfiguration(builder.proxyConfiguration) 306 .localAddress(Optional.ofNullable(builder.localAddress).orElse(null)) 307 .expectContinueEnabled(Optional.ofNullable(builder.expectContinueEnabled) 308 .orElse(DefaultConfiguration.EXPECT_CONTINUE_ENABLED)) 309 .build(); 310 } 311 312 private void collectPoolMetric(MetricCollector metricCollector) { 313 HttpClientConnectionManager cm = httpClient.getHttpClientConnectionManager(); 314 if (cm instanceof PoolingHttpClientConnectionManager && !(metricCollector instanceof NoOpMetricCollector)) { 315 PoolingHttpClientConnectionManager poolingCm = (PoolingHttpClientConnectionManager) cm; 316 PoolStats totalStats = poolingCm.getTotalStats(); 317 metricCollector.reportMetric(MAX_CONCURRENCY, totalStats.getMax()); 318 metricCollector.reportMetric(AVAILABLE_CONCURRENCY, totalStats.getAvailable()); 319 metricCollector.reportMetric(LEASED_CONCURRENCY, totalStats.getLeased()); 320 metricCollector.reportMetric(PENDING_CONCURRENCY_ACQUIRES, totalStats.getPending()); 321 } 322 } 323 324 @Override 325 public String clientName() { 326 return CLIENT_NAME; 327 } 328 329 /** 330 * Builder for creating an instance of {@link SdkHttpClient}. The factory can be configured through the builder {@link 331 * #builder()}, once built it can create a {@link SdkHttpClient} via {@link #build()} or can be passed to the SDK 332 * client builders directly to have the SDK create and manage the HTTP client. See documentation on the service's respective 333 * client builder for more information on configuring the HTTP layer. 334 * 335 * <pre class="brush: java"> 336 * SdkHttpClient httpClient = 337 * ApacheHttpClient.builder() 338 * .socketTimeout(Duration.ofSeconds(10)) 339 * .build(); 340 * </pre> 341 */ 342 public interface Builder extends SdkHttpClient.Builder<ApacheHttpClient.Builder> { 343 344 /** 345 * The amount of time to wait for data to be transferred over an established, open connection before the connection is 346 * timed out. A duration of 0 means infinity, and is not recommended. 347 */ 348 Builder socketTimeout(Duration socketTimeout); 349 350 /** 351 * The amount of time to wait when initially establishing a connection before giving up and timing out. A duration of 0 352 * means infinity, and is not recommended. 353 */ 354 Builder connectionTimeout(Duration connectionTimeout); 355 356 /** 357 * The amount of time to wait when acquiring a connection from the pool before giving up and timing out. 358 * @param connectionAcquisitionTimeout the timeout duration 359 * @return this builder for method chaining. 360 */ 361 Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout); 362 363 /** 364 * The maximum number of connections allowed in the connection pool. Each built HTTP client has its own private 365 * connection pool. 366 */ 367 Builder maxConnections(Integer maxConnections); 368 369 /** 370 * Configuration that defines how to communicate via an HTTP proxy. 371 */ 372 Builder proxyConfiguration(ProxyConfiguration proxyConfiguration); 373 374 /** 375 * Configure the local address that the HTTP client should use for communication. 376 */ 377 Builder localAddress(InetAddress localAddress); 378 379 /** 380 * Configure whether the client should send an HTTP expect-continue handshake before each request. 381 */ 382 Builder expectContinueEnabled(Boolean expectContinueEnabled); 383 384 /** 385 * The maximum amount of time that a connection should be allowed to remain open, regardless of usage frequency. 386 */ 387 Builder connectionTimeToLive(Duration connectionTimeToLive); 388 389 /** 390 * Configure the maximum amount of time that a connection should be allowed to remain open while idle. 391 */ 392 Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout); 393 394 /** 395 * Configure whether the idle connections in the connection pool should be closed asynchronously. 396 * <p> 397 * When enabled, connections left idling for longer than {@link #connectionMaxIdleTime(Duration)} will be 398 * closed. This will not close connections currently in use. By default, this is enabled. 399 */ 400 Builder useIdleConnectionReaper(Boolean useConnectionReaper); 401 402 /** 403 * Configuration that defines a DNS resolver. If no matches are found, the default resolver is used. 404 */ 405 Builder dnsResolver(DnsResolver dnsResolver); 406 407 /** 408 * Configuration that defines a custom Socket factory. If set to a null value, a default factory is used. 409 * <p> 410 * When set to a non-null value, the use of a custom factory implies the configuration options TRUST_ALL_CERTIFICATES, 411 * TLS_TRUST_MANAGERS_PROVIDER, and TLS_KEY_MANAGERS_PROVIDER are ignored. 412 */ 413 Builder socketFactory(ConnectionSocketFactory socketFactory); 414 415 /** 416 * Configuration that defines an HTTP route planner that computes the route an HTTP request should take. 417 * May not be used in conjunction with {@link #proxyConfiguration(ProxyConfiguration)}. 418 */ 419 Builder httpRoutePlanner(HttpRoutePlanner proxyConfiguration); 420 421 /** 422 * Configuration that defines a custom credential provider for HTTP requests. 423 * May not be used in conjunction with {@link ProxyConfiguration#username()} and {@link ProxyConfiguration#password()}. 424 */ 425 Builder credentialsProvider(CredentialsProvider credentialsProvider); 426 427 /** 428 * Configure whether to enable or disable TCP KeepAlive. 429 * The configuration will be passed to the socket option {@link java.net.SocketOptions#SO_KEEPALIVE}. 430 * <p> 431 * By default, this is disabled. 432 * <p> 433 * When enabled, the actual KeepAlive mechanism is dependent on the Operating System and therefore additional TCP 434 * KeepAlive values (like timeout, number of packets, etc) must be configured via the Operating System (sysctl on 435 * Linux/Mac, and Registry values on Windows). 436 */ 437 Builder tcpKeepAlive(Boolean keepConnectionAlive); 438 439 /** 440 * Configure the {@link TlsKeyManagersProvider} that will provide the {@link javax.net.ssl.KeyManager}s to use 441 * when constructing the SSL context. 442 * <p> 443 * The default used by the client will be {@link SystemPropertyTlsKeyManagersProvider}. Configure an instance of 444 * {@link software.amazon.awssdk.internal.http.NoneTlsKeyManagersProvider} or another implementation of 445 * {@link TlsKeyManagersProvider} to override it. 446 */ 447 Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider); 448 449 /** 450 * Configure the {@link TlsTrustManagersProvider} that will provide the {@link javax.net.ssl.TrustManager}s to use 451 * when constructing the SSL context. 452 */ 453 Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider); 454 } 455 456 private static final class DefaultBuilder implements Builder { 457 private final AttributeMap.Builder standardOptions = AttributeMap.builder(); 458 private ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder().build(); 459 private InetAddress localAddress; 460 private Boolean expectContinueEnabled; 461 private HttpRoutePlanner httpRoutePlanner; 462 private CredentialsProvider credentialsProvider; 463 private DnsResolver dnsResolver; 464 private ConnectionSocketFactory socketFactory; 465 466 private DefaultBuilder() { 467 } 468 469 @Override 470 public Builder socketTimeout(Duration socketTimeout) { 471 standardOptions.put(SdkHttpConfigurationOption.READ_TIMEOUT, socketTimeout); 472 return this; 473 } 474 475 public void setSocketTimeout(Duration socketTimeout) { 476 socketTimeout(socketTimeout); 477 } 478 479 @Override 480 public Builder connectionTimeout(Duration connectionTimeout) { 481 standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout); 482 return this; 483 } 484 485 public void setConnectionTimeout(Duration connectionTimeout) { 486 connectionTimeout(connectionTimeout); 487 } 488 489 /** 490 * The amount of time to wait when acquiring a connection from the pool before giving up and timing out. 491 * @param connectionAcquisitionTimeout the timeout duration 492 * @return this builder for method chaining. 493 */ 494 @Override 495 public Builder connectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { 496 Validate.isPositive(connectionAcquisitionTimeout, "connectionAcquisitionTimeout"); 497 standardOptions.put(SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT, connectionAcquisitionTimeout); 498 return this; 499 } 500 501 public void setConnectionAcquisitionTimeout(Duration connectionAcquisitionTimeout) { 502 connectionAcquisitionTimeout(connectionAcquisitionTimeout); 503 } 504 505 @Override 506 public Builder maxConnections(Integer maxConnections) { 507 standardOptions.put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConnections); 508 return this; 509 } 510 511 public void setMaxConnections(Integer maxConnections) { 512 maxConnections(maxConnections); 513 } 514 515 @Override 516 public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) { 517 this.proxyConfiguration = proxyConfiguration; 518 return this; 519 } 520 521 public void setProxyConfiguration(ProxyConfiguration proxyConfiguration) { 522 proxyConfiguration(proxyConfiguration); 523 } 524 525 @Override 526 public Builder localAddress(InetAddress localAddress) { 527 this.localAddress = localAddress; 528 return this; 529 } 530 531 public void setLocalAddress(InetAddress localAddress) { 532 localAddress(localAddress); 533 } 534 535 @Override 536 public Builder expectContinueEnabled(Boolean expectContinueEnabled) { 537 this.expectContinueEnabled = expectContinueEnabled; 538 return this; 539 } 540 541 public void setExpectContinueEnabled(Boolean useExpectContinue) { 542 this.expectContinueEnabled = useExpectContinue; 543 } 544 545 @Override 546 public Builder connectionTimeToLive(Duration connectionTimeToLive) { 547 standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE, connectionTimeToLive); 548 return this; 549 } 550 551 public void setConnectionTimeToLive(Duration connectionTimeToLive) { 552 connectionTimeToLive(connectionTimeToLive); 553 } 554 555 @Override 556 public Builder connectionMaxIdleTime(Duration maxIdleConnectionTimeout) { 557 standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, maxIdleConnectionTimeout); 558 return this; 559 } 560 561 public void setConnectionMaxIdleTime(Duration connectionMaxIdleTime) { 562 connectionMaxIdleTime(connectionMaxIdleTime); 563 } 564 565 @Override 566 public Builder useIdleConnectionReaper(Boolean useIdleConnectionReaper) { 567 standardOptions.put(SdkHttpConfigurationOption.REAP_IDLE_CONNECTIONS, useIdleConnectionReaper); 568 return this; 569 } 570 571 public void setUseIdleConnectionReaper(Boolean useIdleConnectionReaper) { 572 useIdleConnectionReaper(useIdleConnectionReaper); 573 } 574 575 @Override 576 public Builder dnsResolver(DnsResolver dnsResolver) { 577 this.dnsResolver = dnsResolver; 578 return this; 579 } 580 581 public void setDnsResolver(DnsResolver dnsResolver) { 582 dnsResolver(dnsResolver); 583 } 584 585 @Override 586 public Builder socketFactory(ConnectionSocketFactory socketFactory) { 587 this.socketFactory = socketFactory; 588 return this; 589 } 590 591 public void setSocketFactory(ConnectionSocketFactory socketFactory) { 592 socketFactory(socketFactory); 593 } 594 595 @Override 596 public Builder httpRoutePlanner(HttpRoutePlanner httpRoutePlanner) { 597 this.httpRoutePlanner = httpRoutePlanner; 598 return this; 599 } 600 601 public void setHttpRoutePlanner(HttpRoutePlanner httpRoutePlanner) { 602 httpRoutePlanner(httpRoutePlanner); 603 } 604 605 @Override 606 public Builder credentialsProvider(CredentialsProvider credentialsProvider) { 607 this.credentialsProvider = credentialsProvider; 608 return this; 609 } 610 611 public void setCredentialsProvider(CredentialsProvider credentialsProvider) { 612 credentialsProvider(credentialsProvider); 613 } 614 615 @Override 616 public Builder tcpKeepAlive(Boolean keepConnectionAlive) { 617 standardOptions.put(SdkHttpConfigurationOption.TCP_KEEPALIVE, keepConnectionAlive); 618 return this; 619 } 620 621 public void setTcpKeepAlive(Boolean keepConnectionAlive) { 622 tcpKeepAlive(keepConnectionAlive); 623 } 624 625 @Override 626 public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) { 627 standardOptions.put(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER, tlsKeyManagersProvider); 628 return this; 629 } 630 631 public void setTlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvider) { 632 tlsKeyManagersProvider(tlsKeyManagersProvider); 633 } 634 635 @Override 636 public Builder tlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) { 637 standardOptions.put(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER, tlsTrustManagersProvider); 638 return this; 639 } 640 641 public void setTlsTrustManagersProvider(TlsTrustManagersProvider tlsTrustManagersProvider) { 642 tlsTrustManagersProvider(tlsTrustManagersProvider); 643 } 644 645 @Override 646 public SdkHttpClient buildWithDefaults(AttributeMap serviceDefaults) { 647 AttributeMap resolvedOptions = standardOptions.build().merge(serviceDefaults).merge( 648 SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS); 649 return new ApacheHttpClient(this, resolvedOptions); 650 } 651 } 652 653 private static class ApacheConnectionManagerFactory { 654 655 public HttpClientConnectionManager create(ApacheHttpClient.DefaultBuilder configuration, 656 AttributeMap standardOptions) { 657 ConnectionSocketFactory sslsf = getPreferredSocketFactory(configuration, standardOptions); 658 659 PoolingHttpClientConnectionManager cm = new 660 PoolingHttpClientConnectionManager( 661 createSocketFactoryRegistry(sslsf), 662 null, 663 DefaultSchemePortResolver.INSTANCE, 664 configuration.dnsResolver, 665 standardOptions.get(SdkHttpConfigurationOption.CONNECTION_TIME_TO_LIVE).toMillis(), 666 TimeUnit.MILLISECONDS); 667 668 cm.setDefaultMaxPerRoute(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS)); 669 cm.setMaxTotal(standardOptions.get(SdkHttpConfigurationOption.MAX_CONNECTIONS)); 670 cm.setDefaultSocketConfig(buildSocketConfig(standardOptions)); 671 672 return cm; 673 } 674 675 private ConnectionSocketFactory getPreferredSocketFactory(ApacheHttpClient.DefaultBuilder configuration, 676 AttributeMap standardOptions) { 677 return Optional.ofNullable(configuration.socketFactory) 678 .orElseGet(() -> new SdkTlsSocketFactory(getSslContext(standardOptions), 679 getHostNameVerifier(standardOptions))); 680 } 681 682 private HostnameVerifier getHostNameVerifier(AttributeMap standardOptions) { 683 return standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES) 684 ? NoopHostnameVerifier.INSTANCE 685 : SSLConnectionSocketFactory.getDefaultHostnameVerifier(); 686 } 687 688 private SSLContext getSslContext(AttributeMap standardOptions) { 689 Validate.isTrue(standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) == null || 690 !standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES), 691 "A TlsTrustManagerProvider can't be provided if TrustAllCertificates is also set"); 692 693 TrustManager[] trustManagers = null; 694 if (standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) != null) { 695 trustManagers = standardOptions.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER).trustManagers(); 696 } 697 698 if (standardOptions.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) { 699 log.warn(() -> "SSL Certificate verification is disabled. This is not a safe setting and should only be " 700 + "used for testing."); 701 trustManagers = trustAllTrustManager(); 702 } 703 704 TlsKeyManagersProvider provider = standardOptions.get(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER); 705 KeyManager[] keyManagers = provider.keyManagers(); 706 707 try { 708 SSLContext sslcontext = SSLContext.getInstance("TLS"); 709 // http://download.java.net/jdk9/docs/technotes/guides/security/jsse/JSSERefGuide.html 710 sslcontext.init(keyManagers, trustManagers, null); 711 return sslcontext; 712 } catch (final NoSuchAlgorithmException | KeyManagementException ex) { 713 throw new SSLInitializationException(ex.getMessage(), ex); 714 } 715 } 716 717 /** 718 * Insecure trust manager to trust all certs. Should only be used for testing. 719 */ 720 private static TrustManager[] trustAllTrustManager() { 721 return new TrustManager[] { 722 new X509TrustManager() { 723 @Override 724 public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { 725 log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN()); 726 } 727 728 @Override 729 public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { 730 log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN()); 731 } 732 733 @Override 734 public X509Certificate[] getAcceptedIssuers() { 735 return new X509Certificate[0]; 736 } 737 } 738 }; 739 } 740 741 private SocketConfig buildSocketConfig(AttributeMap standardOptions) { 742 return SocketConfig.custom() 743 .setSoKeepAlive(standardOptions.get(SdkHttpConfigurationOption.TCP_KEEPALIVE)) 744 .setSoTimeout( 745 saturatedCast(standardOptions.get(SdkHttpConfigurationOption.READ_TIMEOUT) 746 .toMillis())) 747 .setTcpNoDelay(true) 748 .build(); 749 } 750 751 private Registry<ConnectionSocketFactory> createSocketFactoryRegistry(ConnectionSocketFactory sslSocketFactory) { 752 return RegistryBuilder.<ConnectionSocketFactory>create() 753 .register("http", PlainConnectionSocketFactory.getSocketFactory()) 754 .register("https", sslSocketFactory) 755 .build(); 756 } 757 } 758 } 759