Skip to content

Commit 3d2e2e5

Browse files
committed
Mark a connection as unreusable if there was a 5xx error so that new request will execute on new connection
1 parent 854e94f commit 3d2e2e5

File tree

10 files changed

+499
-15
lines changed

10 files changed

+499
-15
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "Netty NIO HTTP Client",
3+
"type": "bugfix",
4+
"description": "Mark a connection as unreusable if there was a 5xx server error so that a new request will establish a new connection."
5+
}

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.slf4j.Logger;
5353
import org.slf4j.LoggerFactory;
5454
import software.amazon.awssdk.annotations.SdkInternalApi;
55+
import software.amazon.awssdk.http.HttpStatusFamily;
5556
import software.amazon.awssdk.http.Protocol;
5657
import software.amazon.awssdk.http.SdkCancellationException;
5758
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -85,7 +86,7 @@ protected void channelRead0(ChannelHandlerContext channelContext, HttpObject msg
8586
.statusCode(response.status().code())
8687
.statusText(response.status().reasonPhrase())
8788
.build();
88-
channelContext.channel().attr(KEEP_ALIVE).set(HttpUtil.isKeepAlive(response));
89+
channelContext.channel().attr(KEEP_ALIVE).set(shouldKeepAlive(response));
8990
requestContext.handler().onHeaders(sdkResponse);
9091
}
9192

@@ -128,6 +129,13 @@ private static void finalizeResponse(RequestContext requestContext, ChannelHandl
128129
}
129130
}
130131

132+
private boolean shouldKeepAlive(HttpResponse response) {
133+
if (HttpStatusFamily.of(response.status().code()) == HttpStatusFamily.SERVER_ERROR) {
134+
return false;
135+
}
136+
return HttpUtil.isKeepAlive(response);
137+
}
138+
131139
@Override
132140
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
133141
RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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.nio.netty.internal.http2;
17+
18+
import software.amazon.awssdk.annotations.SdkInternalApi;
19+
20+
/**
21+
* Exception indicating a connection is terminating
22+
*/
23+
@SdkInternalApi
24+
final class Http2ConnectionTerminatingException extends RuntimeException {
25+
26+
Http2ConnectionTerminatingException(String message) {
27+
super(message);
28+
}
29+
}

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/Http2MultiplexedChannelPool.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ public void channelInactive(ChannelHandlerContext ctx) {
426426

427427
@Override
428428
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
429-
if (cause instanceof Http2StreamExceptionHandler.Http2StreamIoException) {
429+
if (cause instanceof Http2ConnectionTerminatingException) {
430430
closeConnectionToNewRequests(ctx, cause);
431431
} else {
432432
closeAndReleaseParent(ctx, cause);

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/Http2StreamExceptionHandler.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import io.netty.channel.ChannelHandler;
2020
import io.netty.channel.ChannelHandlerContext;
2121
import io.netty.channel.ChannelInboundHandlerAdapter;
22-
import io.netty.handler.codec.http2.Http2Stream;
2322
import io.netty.handler.timeout.TimeoutException;
2423
import java.io.IOException;
2524
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -31,7 +30,7 @@
3130
@ChannelHandler.Sharable
3231
@SdkInternalApi
3332
public final class Http2StreamExceptionHandler extends ChannelInboundHandlerAdapter {
34-
private static final Logger log = Logger.loggerFor(Http2Stream.class);
33+
private static final Logger log = Logger.loggerFor(Http2StreamExceptionHandler.class);
3534
private static final Http2StreamExceptionHandler INSTANCE = new Http2StreamExceptionHandler();
3635

3736
private Http2StreamExceptionHandler() {
@@ -46,8 +45,9 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
4645
if (isIoError(cause) && ctx.channel().parent() != null) {
4746
Channel parent = ctx.channel().parent();
4847
log.debug(() -> "An I/O error occurred on an Http2 stream, notifying the connection channel " + parent);
49-
parent.pipeline().fireExceptionCaught(new Http2StreamIoException("An I/O error occurred on an associated Http2 "
50-
+ "stream"));
48+
parent.pipeline().fireExceptionCaught(new Http2ConnectionTerminatingException("An I/O error occurred on an "
49+
+ "associated Http2 "
50+
+ "stream " + ctx.channel()));
5151
}
5252

5353
ctx.fireExceptionCaught(cause);
@@ -56,10 +56,4 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
5656
private boolean isIoError(Throwable cause) {
5757
return cause instanceof TimeoutException || cause instanceof IOException;
5858
}
59-
60-
static final class Http2StreamIoException extends IOException {
61-
Http2StreamIoException(String message) {
62-
super(message);
63-
}
64-
}
6559
}

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/Http2ToHttpInboundAdapter.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
package software.amazon.awssdk.http.nio.netty.internal.http2;
1717

1818
import io.netty.buffer.ByteBuf;
19+
import io.netty.channel.Channel;
1920
import io.netty.channel.ChannelHandlerContext;
2021
import io.netty.channel.SimpleChannelInboundHandler;
2122
import io.netty.handler.codec.http.DefaultHttpContent;
2223
import io.netty.handler.codec.http.DefaultLastHttpContent;
2324
import io.netty.handler.codec.http.HttpObject;
25+
import io.netty.handler.codec.http.HttpResponse;
2426
import io.netty.handler.codec.http2.Http2DataFrame;
2527
import io.netty.handler.codec.http2.Http2Error;
2628
import io.netty.handler.codec.http2.Http2Exception;
@@ -30,13 +32,16 @@
3032
import io.netty.handler.codec.http2.HttpConversionUtil;
3133
import java.io.IOException;
3234
import software.amazon.awssdk.annotations.SdkInternalApi;
35+
import software.amazon.awssdk.http.HttpStatusFamily;
36+
import software.amazon.awssdk.utils.Logger;
3337

3438
/**
3539
* Converts {@link Http2Frame}s to {@link HttpObject}s. Ignores the majority of {@link Http2Frame}s like PING
3640
* or SETTINGS.
3741
*/
3842
@SdkInternalApi
3943
public class Http2ToHttpInboundAdapter extends SimpleChannelInboundHandler<Http2Frame> {
44+
private static final Logger log = Logger.loggerFor(Http2ToHttpInboundAdapter.class);
4045

4146
@Override
4247
protected void channelRead0(ChannelHandlerContext ctx, Http2Frame frame) throws Exception {
@@ -54,7 +59,22 @@ protected void channelRead0(ChannelHandlerContext ctx, Http2Frame frame) throws
5459
}
5560

5661
private void onHeadersRead(Http2HeadersFrame headersFrame, ChannelHandlerContext ctx) throws Http2Exception {
57-
ctx.fireChannelRead(HttpConversionUtil.toHttpResponse(headersFrame.stream().id(), headersFrame.headers(), true));
62+
63+
HttpResponse httpResponse = HttpConversionUtil.toHttpResponse(headersFrame.stream().id(), headersFrame.headers(), true);
64+
ctx.fireChannelRead(httpResponse);
65+
66+
if (HttpStatusFamily.of(httpResponse.status().code()) == HttpStatusFamily.SERVER_ERROR) {
67+
fireConnectionExceptionForServerError(ctx);
68+
}
69+
}
70+
71+
private void fireConnectionExceptionForServerError(ChannelHandlerContext ctx) {
72+
if (ctx.channel().parent() != null) {
73+
Channel parent = ctx.channel().parent();
74+
log.debug(() -> "A 5xx server error occurred on an Http2 stream, notifying the connection channel " + ctx.channel());
75+
parent.pipeline().fireExceptionCaught(new Http2ConnectionTerminatingException("A 5xx server error occurred on an "
76+
+ "Http2 stream " + ctx.channel()));
77+
}
5878
}
5979

6080
private void onDataRead(Http2DataFrame dataFrame, ChannelHandlerContext ctx) throws Http2Exception {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.nio.netty;
17+
18+
19+
import io.reactivex.Flowable;
20+
import java.nio.ByteBuffer;
21+
import java.util.concurrent.CompletableFuture;
22+
import org.reactivestreams.Publisher;
23+
import software.amazon.awssdk.http.SdkHttpFullRequest;
24+
import software.amazon.awssdk.http.SdkHttpMethod;
25+
import software.amazon.awssdk.http.SdkHttpResponse;
26+
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
27+
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
28+
import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler;
29+
30+
public class TestUtils {
31+
32+
public static CompletableFuture<Void> sendGetRequest(int serverPort, SdkAsyncHttpClient client) {
33+
AsyncExecuteRequest req = AsyncExecuteRequest.builder()
34+
.responseHandler(new SdkAsyncHttpResponseHandler() {
35+
private SdkHttpResponse headers;
36+
37+
@Override
38+
public void onHeaders(SdkHttpResponse headers) {
39+
this.headers = headers;
40+
}
41+
42+
@Override
43+
public void onStream(Publisher<ByteBuffer> stream) {
44+
Flowable.fromPublisher(stream).forEach(b -> {
45+
});
46+
}
47+
48+
@Override
49+
public void onError(Throwable error) {
50+
}
51+
})
52+
.request(SdkHttpFullRequest.builder()
53+
.method(SdkHttpMethod.GET)
54+
.protocol("https")
55+
.host("localhost")
56+
.port(serverPort)
57+
.build())
58+
.requestContentPublisher(new EmptyPublisher())
59+
.build();
60+
61+
return client.execute(req);
62+
}
63+
}

0 commit comments

Comments
 (0)