-
Notifications
You must be signed in to change notification settings - Fork 914
Broke up UrlConnectionHttpClient into multiple classes. Hopefully this is an improvement? #3054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.awssdk.http.urlconnection.internal; | ||
|
||
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; | ||
import static software.amazon.awssdk.utils.NumericUtils.saturatedCast; | ||
|
||
import java.net.HttpURLConnection; | ||
import java.net.URI; | ||
import java.security.KeyManagementException; | ||
import java.security.NoSuchAlgorithmException; | ||
import javax.net.ssl.HttpsURLConnection; | ||
import javax.net.ssl.KeyManager; | ||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.SSLSocketFactory; | ||
import javax.net.ssl.TrustManager; | ||
import software.amazon.awssdk.annotations.SdkInternalApi; | ||
import software.amazon.awssdk.http.SdkHttpConfigurationOption; | ||
import software.amazon.awssdk.http.TlsKeyManagersProvider; | ||
import software.amazon.awssdk.http.urlconnection.UrlConnectionFactory; | ||
import software.amazon.awssdk.utils.AttributeMap; | ||
import software.amazon.awssdk.utils.Logger; | ||
import software.amazon.awssdk.utils.Validate; | ||
|
||
/** | ||
* The default implementation of {@link UrlConnectionFactory}. | ||
*/ | ||
@SdkInternalApi | ||
public class DefaultUrlConnectionFactory implements UrlConnectionFactory { | ||
private static final Logger log = UrlConnectionLogger.LOG; | ||
|
||
private final SSLSocketFactory socketFactory; | ||
private final AttributeMap options; | ||
|
||
private DefaultUrlConnectionFactory(AttributeMap options) { | ||
this.options = options; | ||
|
||
// Note: This socket factory MUST be reused between requests because the connection pool in the JVM is keyed by both | ||
// URL and SSLSocketFactory. If the socket factory is not reused, connections will not be reused between requests. | ||
this.socketFactory = getSslContext().getSocketFactory(); | ||
|
||
} | ||
|
||
public static UrlConnectionFactory get(AttributeMap options, UrlConnectionFactory configuredFactory) { | ||
if (configuredFactory != null) { | ||
return configuredFactory; | ||
} | ||
|
||
return new DefaultUrlConnectionFactory(options); | ||
} | ||
|
||
@Override | ||
public HttpURLConnection createConnection(URI uri) { | ||
return createDefaultConnection(uri, socketFactory); | ||
} | ||
|
||
private SSLContext getSslContext() { | ||
Validate.isTrue(options.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) == null || | ||
!options.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES), | ||
"A TlsTrustManagerProvider can't be provided if TrustAllCertificates is also set"); | ||
|
||
TrustManager[] trustManagers = null; | ||
if (options.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER) != null) { | ||
trustManagers = options.get(SdkHttpConfigurationOption.TLS_TRUST_MANAGERS_PROVIDER).trustManagers(); | ||
} | ||
|
||
if (options.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) { | ||
log.warn(() -> "SSL Certificate verification is disabled. This is not a safe setting and should only be " | ||
+ "used for testing."); | ||
trustManagers = new TrustManager[] { TrustAllManager.INSTANCE }; | ||
} | ||
|
||
TlsKeyManagersProvider provider = options.get(SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER); | ||
KeyManager[] keyManagers = provider.keyManagers(); | ||
|
||
try { | ||
SSLContext context = SSLContext.getInstance("TLS"); | ||
context.init(keyManagers, trustManagers, null); | ||
return context; | ||
} catch (NoSuchAlgorithmException | KeyManagementException ex) { | ||
throw new RuntimeException(ex.getMessage(), ex); | ||
} | ||
} | ||
|
||
private HttpURLConnection createDefaultConnection(URI uri, SSLSocketFactory socketFactory) { | ||
HttpURLConnection connection = invokeSafely(() -> (HttpURLConnection) uri.toURL().openConnection()); | ||
|
||
if (connection instanceof HttpsURLConnection) { | ||
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; | ||
|
||
if (options.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)) { | ||
httpsConnection.setHostnameVerifier(NoOpHostNameVerifier.INSTANCE); | ||
} | ||
httpsConnection.setSSLSocketFactory(socketFactory); | ||
} | ||
|
||
connection.setConnectTimeout(saturatedCast(options.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT).toMillis())); | ||
connection.setReadTimeout(saturatedCast(options.get(SdkHttpConfigurationOption.READ_TIMEOUT).toMillis())); | ||
|
||
return connection; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.awssdk.http.urlconnection.internal; | ||
|
||
import javax.net.ssl.HostnameVerifier; | ||
import javax.net.ssl.SSLSession; | ||
import software.amazon.awssdk.annotations.SdkInternalApi; | ||
|
||
@SdkInternalApi | ||
public class NoOpHostNameVerifier implements HostnameVerifier { | ||
public static final NoOpHostNameVerifier INSTANCE = new NoOpHostNameVerifier(); | ||
|
||
@Override | ||
public boolean verify(String s, SSLSession sslSession) { | ||
return true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.awssdk.http.urlconnection.internal; | ||
|
||
import java.security.cert.X509Certificate; | ||
import javax.net.ssl.X509TrustManager; | ||
import software.amazon.awssdk.annotations.SdkInternalApi; | ||
import software.amazon.awssdk.utils.Logger; | ||
|
||
/** | ||
* Insecure trust manager to trust all certs. Should only be used for testing. | ||
*/ | ||
@SdkInternalApi | ||
public class TrustAllManager implements X509TrustManager { | ||
public static final TrustAllManager INSTANCE = new TrustAllManager(); | ||
|
||
private static final Logger log = UrlConnectionLogger.LOG; | ||
|
||
@Override | ||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { | ||
log.debug(() -> "Accepting a client certificate: " + x509Certificates[0].getSubjectDN()); | ||
} | ||
|
||
@Override | ||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { | ||
log.debug(() -> "Accepting a server certificate: " + x509Certificates[0].getSubjectDN()); | ||
} | ||
|
||
@Override | ||
public X509Certificate[] getAcceptedIssuers() { | ||
return new X509Certificate[0]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.awssdk.http.urlconnection.internal; | ||
|
||
import static software.amazon.awssdk.http.Header.CONTENT_LENGTH; | ||
import static software.amazon.awssdk.http.HttpStatusFamily.CLIENT_ERROR; | ||
import static software.amazon.awssdk.http.HttpStatusFamily.SERVER_ERROR; | ||
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.io.UncheckedIOException; | ||
import java.net.HttpURLConnection; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import software.amazon.awssdk.annotations.SdkInternalApi; | ||
import software.amazon.awssdk.http.AbortableInputStream; | ||
import software.amazon.awssdk.http.ContentStreamProvider; | ||
import software.amazon.awssdk.http.ExecutableHttpRequest; | ||
import software.amazon.awssdk.http.HttpExecuteRequest; | ||
import software.amazon.awssdk.http.HttpExecuteResponse; | ||
import software.amazon.awssdk.http.HttpStatusFamily; | ||
import software.amazon.awssdk.http.SdkHttpResponse; | ||
import software.amazon.awssdk.http.urlconnection.UrlConnectionFactory; | ||
import software.amazon.awssdk.http.urlconnection.internal.connection.DefaultHttpConnection; | ||
import software.amazon.awssdk.http.urlconnection.internal.connection.Expect100BugHttpConnection; | ||
import software.amazon.awssdk.http.urlconnection.internal.connection.HttpConnection; | ||
import software.amazon.awssdk.http.urlconnection.internal.connection.ResponseCodeBugHttpConnection; | ||
import software.amazon.awssdk.utils.IoUtils; | ||
|
||
/** | ||
* Executes a {@link HttpExecuteRequest} against a {@link HttpURLConnection} | ||
*/ | ||
@SdkInternalApi | ||
public class UrlConnectionExecutableHttpRequest implements ExecutableHttpRequest { | ||
private final HttpConnection connection; | ||
private final HttpExecuteRequest request; | ||
|
||
public UrlConnectionExecutableHttpRequest(UrlConnectionFactory connectionFactory, | ||
HttpExecuteRequest executeRequest) { | ||
this.request = executeRequest; | ||
|
||
HttpURLConnection httpUrlConnection = connectionFactory.createConnection(executeRequest.httpRequest().getUri()); | ||
initializeConnection(httpUrlConnection); | ||
|
||
HttpConnection connection = new DefaultHttpConnection(httpUrlConnection); | ||
connection = new ResponseCodeBugHttpConnection(connection); | ||
connection = new Expect100BugHttpConnection(connection, executeRequest.httpRequest()); | ||
this.connection = connection; | ||
} | ||
|
||
private void initializeConnection(HttpURLConnection connection) { | ||
// Disable following redirects since it breaks SDK error handling and matches Apache. | ||
// See: https://github.com/aws/aws-sdk-java-v2/issues/975 | ||
connection.setInstanceFollowRedirects(false); | ||
|
||
request.httpRequest() | ||
.headers() | ||
.forEach((key, values) -> values.forEach(value -> connection.setRequestProperty(key, value))); | ||
|
||
invokeSafely(() -> connection.setRequestMethod(request.httpRequest().method().name())); | ||
|
||
if (request.contentStreamProvider().isPresent()) { | ||
connection.setDoOutput(true); | ||
} | ||
|
||
request.httpRequest().firstMatchingHeader(CONTENT_LENGTH).map(Long::parseLong) | ||
.ifPresent(connection::setFixedLengthStreamingMode); | ||
} | ||
|
||
@Override | ||
public HttpExecuteResponse call() throws IOException { | ||
try { | ||
return doCall(); | ||
} catch (UncheckedIOException e) { | ||
throw e.getCause(); | ||
} | ||
} | ||
|
||
private HttpExecuteResponse doCall() throws IOException { | ||
connection.connect(); | ||
|
||
Optional<ContentStreamProvider> requestContent = request.contentStreamProvider(); | ||
|
||
if (requestContent.isPresent()) { | ||
OutputStream outputStream = connection.getRequestStream(); | ||
if (outputStream != null) { | ||
IoUtils.copy(requestContent.get().newStream(), outputStream); | ||
} | ||
} | ||
|
||
int responseCode = connection.getResponseCode(); | ||
boolean isErrorResponse = HttpStatusFamily.of(responseCode).isOneOf(CLIENT_ERROR, SERVER_ERROR); | ||
InputStream responseContent = isErrorResponse ? connection.getResponseErrorStream() | ||
: connection.getResponseStream(); | ||
AbortableInputStream responseBody = responseContent != null ? AbortableInputStream.create(responseContent) | ||
: null; | ||
|
||
return HttpExecuteResponse.builder() | ||
.response(SdkHttpResponse.builder() | ||
.statusCode(responseCode) | ||
.statusText(connection.getResponseMessage()) | ||
.headers(responseHeaders()) | ||
.build()) | ||
.responseBody(responseBody) | ||
.build(); | ||
} | ||
|
||
private Map<String, List<String>> responseHeaders() { | ||
return connection.getResponseHeaders() | ||
.entrySet().stream() | ||
.filter(e -> e.getKey() != null) | ||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); | ||
} | ||
|
||
@Override | ||
public void abort() { | ||
connection.disconnect(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.awssdk.http.urlconnection.internal; | ||
|
||
import software.amazon.awssdk.annotations.SdkInternalApi; | ||
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; | ||
import software.amazon.awssdk.utils.Logger; | ||
|
||
/** | ||
* A logger used by classes in the URL connection HTTP client package. | ||
*/ | ||
@SdkInternalApi | ||
public class UrlConnectionLogger { | ||
public static final Logger LOG = Logger.loggerFor(UrlConnectionHttpClient.class); | ||
Comment on lines
+22
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this needed? Is there any harm in exposing the underlying class name that's logging, even if it's an internal class? It somewhat unifies logs from a user perspective but it also makes things a little bit more difficult to trace. |
||
|
||
private UrlConnectionLogger() { | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very elegant usage of wrapping!