HttpContentDecoder must continue read when it did not produce any mes… (#8922)

Motivation:

When HttpContentDecoder (and so HttpContentDecompressor) does not produce any message we need to make sure it calls ctx.read() if auto read is false to not stale.

Modifications:

- Keep track if we need to call ctx.read() or not
- Add unit test

Result:

Fixes https://github.com/netty/netty/issues/8915.
This commit is contained in:
Norman Maurer 2019-03-07 10:31:51 +01:00
parent 2689f28cb7
commit 10f06a04e6
2 changed files with 173 additions and 84 deletions

View File

@ -51,9 +51,11 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<HttpObj
protected ChannelHandlerContext ctx;
private EmbeddedChannel decoder;
private boolean continueResponse;
private boolean needRead = true;
@Override
protected void decode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception {
try {
if (msg instanceof HttpResponse && ((HttpResponse) msg).status().code() == 100) {
if (!(msg instanceof LastHttpContent)) {
@ -148,6 +150,9 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<HttpObj
decodeContent(c, out);
}
}
} finally {
needRead = out.isEmpty();
}
}
private void decodeContent(HttpContent c, List<Object> out) {
@ -170,6 +175,20 @@ public abstract class HttpContentDecoder extends MessageToMessageDecoder<HttpObj
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
boolean needRead = this.needRead;
this.needRead = true;
try {
ctx.fireChannelReadComplete();
} finally {
if (needRead && !ctx.channel().config().isAutoRead()) {
ctx.read();
}
}
}
/**
* Returns a new {@link EmbeddedChannel} that decodes the HTTP message
* content encoded in the specified <tt>contentEncoding</tt>.

View File

@ -0,0 +1,70 @@
/*
* Copyright 2019 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 io.netty.handler.codec.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.embedded.EmbeddedChannel;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
public class HttpContentDecompressorTest {
// See https://github.com/netty/netty/issues/8915.
@Test
public void testInvokeReadWhenNotProduceMessage() {
final AtomicInteger readCalled = new AtomicInteger();
EmbeddedChannel channel = new EmbeddedChannel(new ChannelOutboundHandlerAdapter() {
@Override
public void read(ChannelHandlerContext ctx) {
readCalled.incrementAndGet();
ctx.read();
}
}, new HttpContentDecompressor(), new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.fireChannelRead(msg);
ctx.read();
}
});
channel.config().setAutoRead(false);
readCalled.set(0);
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().set(HttpHeaderNames.CONTENT_ENCODING, "gzip");
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
Assert.assertTrue(channel.writeInbound(response));
// we triggered read explicitly
Assert.assertEquals(1, readCalled.get());
Assert.assertTrue(channel.readInbound() instanceof HttpResponse);
Assert.assertFalse(channel.writeInbound(new DefaultHttpContent(Unpooled.EMPTY_BUFFER)));
// read was triggered by the HttpContentDecompressor itself as it did not produce any message to the next
// inbound handler.
Assert.assertEquals(2, readCalled.get());
Assert.assertFalse(channel.finishAndReleaseAll());
}
}