Skip to content

Commit 8a19cce

Browse files
committed
Factor out LastHttpContentSwallower and add tests
1 parent 985ec92 commit 8a19cce

File tree

3 files changed

+137
-18
lines changed

3 files changed

+137
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2010-2019 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;
17+
18+
import io.netty.channel.ChannelHandler;
19+
import io.netty.channel.ChannelHandlerContext;
20+
import io.netty.channel.SimpleChannelInboundHandler;
21+
import io.netty.handler.codec.http.HttpObject;
22+
import io.netty.handler.codec.http.LastHttpContent;
23+
import software.amazon.awssdk.annotations.SdkInternalApi;
24+
25+
/**
26+
* Simple handler that swallows the next read object if it is an instance of
27+
* {@code LastHttpContent}, then removes itself from the pipeline.
28+
* <p>
29+
* This handler is sharable.
30+
*/
31+
@SdkInternalApi
32+
@ChannelHandler.Sharable
33+
final class LastHttpContentSwallower extends SimpleChannelInboundHandler<HttpObject> {
34+
private static final LastHttpContentSwallower INSTANCE = new LastHttpContentSwallower();
35+
36+
private LastHttpContentSwallower() {
37+
}
38+
39+
@Override
40+
protected void channelRead0(ChannelHandlerContext ctx, HttpObject obj) {
41+
if (obj instanceof LastHttpContent) {
42+
// Queue another read to make up for the one we just ignored
43+
ctx.read();
44+
} else {
45+
ctx.fireChannelRead(obj);
46+
}
47+
// Remove self from pipeline since we only care about potentially
48+
// ignoring the very first message
49+
ctx.pipeline().remove(this);
50+
}
51+
52+
public static LastHttpContentSwallower getInstance() {
53+
return INSTANCE;
54+
}
55+
}

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

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import io.netty.handler.codec.http.HttpObject;
3434
import io.netty.handler.codec.http.HttpResponse;
3535
import io.netty.handler.codec.http.HttpUtil;
36-
import io.netty.handler.codec.http.LastHttpContent;
3736
import io.netty.handler.timeout.ReadTimeoutException;
3837
import io.netty.handler.timeout.WriteTimeoutException;
3938
import io.netty.util.AttributeKey;
@@ -94,7 +93,7 @@ protected void channelRead0(ChannelHandlerContext channelContext, HttpObject msg
9493
// Be prepared to take care of (ignore) a trailing LastHttpResponse
9594
// from the HttpClientCodec if there is one.
9695
channelContext.pipeline().replace(HttpStreamsClientHandler.class,
97-
channelContext.name() + "-LastHttpContentSwallower", new LastHttpContentSwallower());
96+
channelContext.name() + "-LastHttpContentSwallower", LastHttpContentSwallower.getInstance());
9897

9998
ByteBuf fullContent = ((FullHttpResponse) msg).content();
10099
ByteBuffer bb = copyToByteBuffer(fullContent);
@@ -348,22 +347,6 @@ public void cancel() {
348347
}
349348
}
350349

351-
private static class LastHttpContentSwallower extends SimpleChannelInboundHandler<HttpObject> {
352-
353-
@Override
354-
protected void channelRead0(ChannelHandlerContext ctx, HttpObject obj) throws Exception {
355-
if (obj instanceof LastHttpContent) {
356-
// Queue another read to make up for the one we just ignored
357-
ctx.read();
358-
} else {
359-
ctx.fireChannelRead(obj);
360-
}
361-
// Remove self from pipeline since we only care about potentially
362-
// ignoring the very first message
363-
ctx.pipeline().remove(this);
364-
}
365-
}
366-
367350
private Throwable wrapException(Throwable originalCause) {
368351
if (originalCause instanceof ReadTimeoutException) {
369352
return new IOException("Read timed out", originalCause);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2010-2019 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;
17+
18+
import static org.mockito.Mockito.eq;
19+
import static org.mockito.Mockito.mock;
20+
import static org.mockito.Mockito.times;
21+
import static org.mockito.Mockito.verify;
22+
import static org.mockito.Mockito.when;
23+
24+
import io.netty.channel.ChannelHandlerContext;
25+
import io.netty.channel.ChannelPipeline;
26+
import io.netty.handler.codec.http.HttpContent;
27+
import io.netty.handler.codec.http.HttpObject;
28+
import io.netty.handler.codec.http.LastHttpContent;
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
32+
/**
33+
* Tests for {@link LastHttpContentSwallower}.
34+
*/
35+
public class LastHttpContentSwallowerTest {
36+
private final LastHttpContentSwallower lastHttpContentSwallower = LastHttpContentSwallower.getInstance();
37+
private ChannelHandlerContext mockCtx;
38+
private ChannelPipeline mockPipeline;
39+
40+
@Before
41+
public void setUp() {
42+
mockCtx = mock(ChannelHandlerContext.class);
43+
mockPipeline = mock(ChannelPipeline.class);
44+
when(mockCtx.pipeline()).thenReturn(mockPipeline);
45+
}
46+
47+
@Test
48+
public void testOtherHttpObjectRead_removesSelfFromPipeline() {
49+
HttpObject contentObject = mock(HttpContent.class);
50+
lastHttpContentSwallower.channelRead0(mockCtx, contentObject);
51+
verify(mockPipeline).remove(eq(lastHttpContentSwallower));
52+
}
53+
54+
@Test
55+
public void testLastHttpContentRead_removesSelfFromPipeline() {
56+
LastHttpContent lastContent = mock(LastHttpContent.class);
57+
lastHttpContentSwallower.channelRead0(mockCtx, lastContent);
58+
verify(mockPipeline).remove(eq(lastHttpContentSwallower));
59+
}
60+
61+
@Test
62+
public void testLastHttpContentRead_swallowsObject() {
63+
LastHttpContent lastContent = mock(LastHttpContent.class);
64+
lastHttpContentSwallower.channelRead0(mockCtx, lastContent);
65+
verify(mockCtx, times(0)).fireChannelRead(eq(lastContent));
66+
}
67+
68+
@Test
69+
public void testOtherHttpObjectRead_doesNotSwallowObject() {
70+
HttpContent content = mock(HttpContent.class);
71+
lastHttpContentSwallower.channelRead0(mockCtx, content);
72+
verify(mockCtx).fireChannelRead(eq(content));
73+
}
74+
75+
@Test
76+
public void testCallsReadAfterSwallowingContent() {
77+
LastHttpContent lastContent = mock(LastHttpContent.class);
78+
lastHttpContentSwallower.channelRead0(mockCtx, lastContent);
79+
verify(mockCtx).read();
80+
}
81+
}

0 commit comments

Comments
 (0)