Skip to content

Commit 2bebabe

Browse files
committed
Do not validate content-length for 304 and HEAD responses.
1 parent 1274717 commit 2bebabe

File tree

4 files changed

+49
-3
lines changed

4 files changed

+49
-3
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ public final class ChannelAttributeKey {
9191
static final AttributeKey<Boolean> RESPONSE_COMPLETE_KEY = NettyUtils.getOrCreateAttributeKey(
9292
"aws.http.nio.netty.async.responseComplete");
9393

94+
static final AttributeKey<Integer> RESPONSE_STATUS_CODE = NettyUtils.getOrCreateAttributeKey(
95+
"aws.http.nio.netty.async.responseStatusCode");
96+
9497
static final AttributeKey<Long> RESPONSE_CONTENT_LENGTH = NettyUtils.getOrCreateAttributeKey(
9598
"aws.http.nio.netty.async.responseContentLength");
9699

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.RESPONSE_COMPLETE_KEY;
2525
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.RESPONSE_CONTENT_LENGTH;
2626
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.RESPONSE_DATA_READ;
27+
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.RESPONSE_STATUS_CODE;
2728
import static software.amazon.awssdk.http.nio.netty.internal.utils.ExceptionHandlingUtils.tryCatch;
2829
import static software.amazon.awssdk.http.nio.netty.internal.utils.ExceptionHandlingUtils.tryCatchFinally;
2930

@@ -38,6 +39,7 @@
3839
import io.netty.handler.codec.http.HttpHeaders;
3940
import io.netty.handler.codec.http.HttpObject;
4041
import io.netty.handler.codec.http.HttpResponse;
42+
import io.netty.handler.codec.http.HttpResponseStatus;
4143
import io.netty.handler.codec.http.HttpUtil;
4244
import io.netty.util.ReferenceCountUtil;
4345
import java.io.IOException;
@@ -56,6 +58,7 @@
5658
import software.amazon.awssdk.http.Protocol;
5759
import software.amazon.awssdk.http.SdkCancellationException;
5860
import software.amazon.awssdk.http.SdkHttpFullResponse;
61+
import software.amazon.awssdk.http.SdkHttpMethod;
5962
import software.amazon.awssdk.http.SdkHttpResponse;
6063
import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler;
6164
import software.amazon.awssdk.http.nio.netty.internal.http2.Http2ResetSendingSubscription;
@@ -88,6 +91,7 @@ protected void channelRead0(ChannelHandlerContext channelContext, HttpObject msg
8891
.statusCode(response.status().code())
8992
.statusText(response.status().reasonPhrase())
9093
.build();
94+
channelContext.channel().attr(RESPONSE_STATUS_CODE).set(response.status().code());
9195
channelContext.channel().attr(RESPONSE_CONTENT_LENGTH).set(responseContentLength(response));
9296
channelContext.channel().attr(KEEP_ALIVE).set(shouldKeepAlive(response));
9397
requestContext.handler().onHeaders(sdkResponse);
@@ -136,6 +140,10 @@ private Long responseContentLength(HttpResponse response) {
136140
}
137141

138142
private static void validateResponseContentLength(ChannelHandlerContext ctx) throws IOException {
143+
if (!shouldValidateResponseContentLength(ctx)) {
144+
return;
145+
}
146+
139147
Long contentLengthHeader = ctx.channel().attr(RESPONSE_CONTENT_LENGTH).get();
140148
Long actualContentLength = ctx.channel().attr(RESPONSE_DATA_READ).get();
141149

@@ -155,6 +163,23 @@ private static void validateResponseContentLength(ChannelHandlerContext ctx) thr
155163
+ actualContentLength + " bytes before the connection was closed.");
156164
}
157165

166+
private static boolean shouldValidateResponseContentLength(ChannelHandlerContext ctx) {
167+
RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get();
168+
169+
// HEAD requests may return Content-Length without a payload
170+
if (requestContext.executeRequest().request().method() == SdkHttpMethod.HEAD) {
171+
return false;
172+
}
173+
174+
// 304 responses may contain Content-Length without a payload
175+
Integer responseStatusCode = ctx.channel().attr(RESPONSE_STATUS_CODE).get();
176+
if (responseStatusCode != null && responseStatusCode == HttpResponseStatus.NOT_MODIFIED.code()) {
177+
return false;
178+
}
179+
180+
return true;
181+
}
182+
158183

159184
/**
160185
* Finalize the response by completing the execute future and release the channel pool being used.

http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/PublisherAdapterTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.netty.handler.codec.http.HttpResponseStatus;
3939
import io.netty.handler.codec.http.HttpVersion;
4040
import io.reactivex.Flowable;
41+
import java.net.URI;
4142
import java.nio.ByteBuffer;
4243
import java.util.concurrent.CompletableFuture;
4344
import org.junit.Before;
@@ -49,6 +50,8 @@
4950
import org.reactivestreams.Subscriber;
5051
import org.reactivestreams.Subscription;
5152
import software.amazon.awssdk.http.Protocol;
53+
import software.amazon.awssdk.http.SdkHttpMethod;
54+
import software.amazon.awssdk.http.SdkHttpRequest;
5255
import software.amazon.awssdk.http.async.AsyncExecuteRequest;
5356
import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler;
5457
import software.amazon.awssdk.http.nio.netty.internal.nrs.DefaultStreamedHttpResponse;
@@ -86,7 +89,13 @@ public void setUp() throws Exception {
8689
when(fullHttpResponse.content()).thenReturn(new EmptyByteBuf(ByteBufAllocator.DEFAULT));
8790
requestContext = new RequestContext(channelPool,
8891
eventLoopGroup,
89-
AsyncExecuteRequest.builder().responseHandler(responseHandler).build(),
92+
AsyncExecuteRequest.builder()
93+
.request(SdkHttpRequest.builder()
94+
.uri(URI.create("https://localhost"))
95+
.method(SdkHttpMethod.GET)
96+
.build())
97+
.responseHandler(responseHandler)
98+
.build(),
9099
null);
91100

92101
channel = new MockChannel();

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717

1818
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
1919
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
20+
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
2021
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
22+
import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
2123
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
2224
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
2325
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
2426

2527
import io.netty.bootstrap.ServerBootstrap;
28+
import io.netty.buffer.Unpooled;
2629
import io.netty.channel.Channel;
2730
import io.netty.channel.ChannelDuplexHandler;
2831
import io.netty.channel.ChannelHandlerContext;
@@ -42,6 +45,7 @@
4245
import io.netty.handler.ssl.SslContext;
4346
import io.netty.handler.ssl.SslContextBuilder;
4447
import io.netty.handler.ssl.util.SelfSignedCertificate;
48+
import java.nio.charset.StandardCharsets;
4549
import java.util.ArrayList;
4650
import java.util.List;
4751
import org.junit.jupiter.api.AfterEach;
@@ -129,6 +133,7 @@ public void headRequestResponsesHaveNoPayload() {
129133
}
130134

131135
private static class Server extends ChannelInitializer<Channel> {
136+
private static final byte[] CONTENT = "helloworld".getBytes(StandardCharsets.UTF_8);
132137
private ServerBootstrap bootstrap;
133138
private ServerSocketChannel serverSock;
134139
private List<Channel> channels = new ArrayList<>();
@@ -180,8 +185,12 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
180185
status = OK;
181186
}
182187

183-
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);
184-
response.headers().set(CONTENT_LENGTH, 0);
188+
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status,
189+
Unpooled.wrappedBuffer(CONTENT));
190+
191+
response.headers()
192+
.set(CONTENT_TYPE, TEXT_PLAIN)
193+
.setInt(CONTENT_LENGTH, response.content().readableBytes());
185194

186195
if (closeConnection) {
187196
response.headers().set(CONNECTION, CLOSE);

0 commit comments

Comments
 (0)