Skip to content

Commit b673b84

Browse files
authored
Update Apache to not reuse connections if they received a 5xx error. (#2960)
This was already the behavior for Netty, and cannot be made the behavior for the URL connection client.
1 parent 1d39dda commit b673b84

File tree

6 files changed

+92
-0
lines changed

6 files changed

+92
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Apache HTTP Client",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Do not reuse connections that receive a 5xx service response."
6+
}

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/ApacheHttpClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import software.amazon.awssdk.http.TlsTrustManagersProvider;
8181
import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig;
8282
import software.amazon.awssdk.http.apache.internal.DefaultConfiguration;
83+
import software.amazon.awssdk.http.apache.internal.SdkConnectionReuseStrategy;
8384
import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner;
8485
import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory;
8586
import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper;
@@ -161,6 +162,7 @@ private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBu
161162
.disableRedirectHandling()
162163
.disableAutomaticRetries()
163164
.setUserAgent("") // SDK will set the user agent header in the pipeline. Don't let Apache waste time
165+
.setConnectionReuseStrategy(new SdkConnectionReuseStrategy())
164166
.setConnectionManager(ClientConnectionManagerFactory.wrap(cm));
165167

166168
addProxyConfig(builder, configuration);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.internal;
17+
18+
import org.apache.http.HttpResponse;
19+
import org.apache.http.impl.client.DefaultClientConnectionReuseStrategy;
20+
import org.apache.http.protocol.HttpContext;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
23+
/**
24+
* Do not reuse connections that returned a 5xx error.
25+
*
26+
* <p>This is not strictly the behavior we would want in an AWS client, because sometimes we might want to keep a connection open
27+
* (e.g. an undocumented service's 503 'SlowDown') and sometimes we might want to close the connection (e.g. S3's 400
28+
* RequestTimeout or Glacier's 408 RequestTimeoutException), but this is good enough for the majority of services, and the ones
29+
* for which it is not should not be impacted too harshly.
30+
*/
31+
@SdkInternalApi
32+
public class SdkConnectionReuseStrategy extends DefaultClientConnectionReuseStrategy {
33+
@Override
34+
public boolean keepAlive(HttpResponse response, HttpContext context) {
35+
if (!super.keepAlive(response, context)) {
36+
return false;
37+
}
38+
39+
if (response == null || response.getStatusLine() == null) {
40+
return false;
41+
}
42+
43+
return !is500(response);
44+
}
45+
46+
private boolean is500(HttpResponse httpResponse) {
47+
return httpResponse.getStatusLine().getStatusCode() / 100 == 5;
48+
}
49+
}

http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWireMockTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ protected SdkHttpClient createSdkHttpClient(SdkHttpClientOptions options) {
4141
return builder.buildWithDefaults(attributeMap.build());
4242
}
4343

44+
@Override
45+
public void connectionsAreNotReusedOn5xxErrors() {
46+
// We cannot support this because the URL connection client doesn't allow us to disable connection reuse
47+
}
48+
4449
@AfterEach
4550
public void reset() {
4651
HttpsURLConnection.setDefaultSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());

http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWithCustomCreateWireMockTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public void testTrustAllWorks() {
5555
public void testCustomTlsTrustManagerAndTrustAllFails() {
5656
}
5757

58+
@Disabled
59+
@Override
60+
public void connectionsAreNotReusedOn5xxErrors() throws Exception {
61+
// We cannot support this because the URL connection client doesn't allow us to disable connection reuse
62+
}
63+
5864
@Test
5965
public void testGetResponseCodeNpeIsWrappedAsIo() throws Exception {
6066
connectionInterceptor = safeFunction(connection -> {

test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientTestSuite.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,30 @@ public void connectionPoolingWorks() throws Exception {
144144
assertThat(CONNECTION_COUNTER.openedConnections()).isEqualTo(initialOpenedConnections + 1);
145145
}
146146

147+
@Test
148+
public void connectionsAreNotReusedOn5xxErrors() throws Exception {
149+
int initialOpenedConnections = CONNECTION_COUNTER.openedConnections();
150+
151+
SdkHttpClientOptions httpClientOptions = new SdkHttpClientOptions();
152+
httpClientOptions.trustAll(true);
153+
SdkHttpClient client = createSdkHttpClient(httpClientOptions);
154+
155+
stubForMockRequest(503);
156+
157+
for (int i = 0; i < 5; i++) {
158+
SdkHttpFullRequest req = mockSdkRequest("http://localhost:" + mockServer.port(), SdkHttpMethod.POST);
159+
HttpExecuteResponse response =
160+
client.prepareRequest(HttpExecuteRequest.builder()
161+
.request(req)
162+
.contentStreamProvider(req.contentStreamProvider().orElse(null))
163+
.build())
164+
.call();
165+
response.responseBody().ifPresent(IoUtils::drainInputStream);
166+
}
167+
168+
assertThat(CONNECTION_COUNTER.openedConnections()).isEqualTo(initialOpenedConnections + 5);
169+
}
170+
147171
@Test
148172
public void testCustomTlsTrustManager() throws Exception {
149173
WireMockServer selfSignedServer = HttpTestUtils.createSelfSignedServer();

0 commit comments

Comments
 (0)