Fix exception if Response has content (#11093)
Motivation: If compression is enabled and the HttpResponse also implements HttpContent (but not LastHttpContent) then the buffer will be freed to eagerly. Modification: I retain the buffer the same way that is done for the LastHttpContent case. Note that there is another suspicious looking call a few lines above (if beginEncode returns null). I am not sure if this should also be retained. Result: Fixes #11092
This commit is contained in:
parent
fc90a8d0a9
commit
4f316b7cbd
@ -193,7 +193,7 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpReque
|
|||||||
res.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
|
res.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
|
||||||
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
|
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
|
||||||
|
|
||||||
out.add(res);
|
out.add(ReferenceCountUtil.retain(res));
|
||||||
state = State.AWAIT_CONTENT;
|
state = State.AWAIT_CONTENT;
|
||||||
if (!(msg instanceof HttpContent)) {
|
if (!(msg instanceof HttpContent)) {
|
||||||
// only break out the switch statement if we have not content to process
|
// only break out the switch statement if we have not content to process
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
|
|||||||
|
|
||||||
import io.netty.bootstrap.Bootstrap;
|
import io.netty.bootstrap.Bootstrap;
|
||||||
import io.netty.bootstrap.ServerBootstrap;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufUtil;
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
@ -48,7 +49,13 @@ import static org.hamcrest.CoreMatchers.is;
|
|||||||
import static org.hamcrest.CoreMatchers.not;
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
public class HttpContentCompressorTest {
|
public class HttpContentCompressorTest {
|
||||||
|
|
||||||
@ -168,6 +175,47 @@ public class HttpContentCompressorTest {
|
|||||||
assertThat(ch.readOutbound(), is(nullValue()));
|
assertThat(ch.readOutbound(), is(nullValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChunkedContentWithAssembledResponse() throws Exception {
|
||||||
|
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
|
||||||
|
ch.writeInbound(newRequest());
|
||||||
|
|
||||||
|
HttpResponse res = new AssembledHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
||||||
|
Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII));
|
||||||
|
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
|
||||||
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
|
assertAssembledEncodedResponse(ch);
|
||||||
|
|
||||||
|
ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
|
||||||
|
ch.writeOutbound(new DefaultLastHttpContent(Unpooled.copiedBuffer("orld", CharsetUtil.US_ASCII)));
|
||||||
|
|
||||||
|
HttpContent chunk;
|
||||||
|
chunk = ch.readOutbound();
|
||||||
|
assertThat(ByteBufUtil.hexDump(chunk.content()), is("1f8b0800000000000000f248cdc901000000ffff"));
|
||||||
|
chunk.release();
|
||||||
|
|
||||||
|
chunk = ch.readOutbound();
|
||||||
|
assertThat(ByteBufUtil.hexDump(chunk.content()), is("cad7512807000000ffff"));
|
||||||
|
chunk.release();
|
||||||
|
|
||||||
|
chunk = ch.readOutbound();
|
||||||
|
assertThat(ByteBufUtil.hexDump(chunk.content()), is("ca2fca4901000000ffff"));
|
||||||
|
chunk.release();
|
||||||
|
|
||||||
|
chunk = ch.readOutbound();
|
||||||
|
assertThat(ByteBufUtil.hexDump(chunk.content()), is("0300c2a99ae70c000000"));
|
||||||
|
assertThat(chunk, is(instanceOf(HttpContent.class)));
|
||||||
|
chunk.release();
|
||||||
|
|
||||||
|
chunk = ch.readOutbound();
|
||||||
|
assertThat(chunk.content().isReadable(), is(false));
|
||||||
|
assertThat(chunk, is(instanceOf(LastHttpContent.class)));
|
||||||
|
chunk.release();
|
||||||
|
|
||||||
|
assertThat(ch.readOutbound(), is(nullValue()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testChunkedContentWithTrailingHeader() throws Exception {
|
public void testChunkedContentWithTrailingHeader() throws Exception {
|
||||||
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
|
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
|
||||||
@ -668,4 +716,92 @@ public class HttpContentCompressorTest {
|
|||||||
assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH), is(nullValue()));
|
assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH), is(nullValue()));
|
||||||
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
|
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
|
||||||
}
|
}
|
||||||
|
private static void assertAssembledEncodedResponse(EmbeddedChannel ch) {
|
||||||
|
Object o = ch.readOutbound();
|
||||||
|
assertThat(o, is(instanceOf(AssembledHttpResponse.class)));
|
||||||
|
|
||||||
|
AssembledHttpResponse res = (AssembledHttpResponse) o;
|
||||||
|
try {
|
||||||
|
assertThat(res, is(instanceOf(HttpContent.class)));
|
||||||
|
assertThat(res.headers().get(HttpHeaderNames.TRANSFER_ENCODING), is("chunked"));
|
||||||
|
assertThat(res.headers().get(HttpHeaderNames.CONTENT_LENGTH), is(nullValue()));
|
||||||
|
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
|
||||||
|
} finally {
|
||||||
|
res.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AssembledHttpResponse extends DefaultHttpResponse implements HttpContent {
|
||||||
|
|
||||||
|
private final ByteBuf content;
|
||||||
|
|
||||||
|
AssembledHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content) {
|
||||||
|
super(version, status);
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpContent copy() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpContent duplicate() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpContent retainedDuplicate() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpContent replace(ByteBuf content) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssembledHttpResponse retain() {
|
||||||
|
content.retain();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssembledHttpResponse retain(int increment) {
|
||||||
|
content.retain(increment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf content() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int refCnt() {
|
||||||
|
return content.refCnt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release() {
|
||||||
|
return content.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean release(int decrement) {
|
||||||
|
return content.release(decrement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssembledHttpResponse touch() {
|
||||||
|
content.touch();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssembledHttpResponse touch(Object hint) {
|
||||||
|
content.touch(hint);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user