HttpContentCompressor doesn't need to generate anything if Content-Length is 0

Motivation:

When Content-Encoding is deflate or gzip and Content-Length is 0,
there's no need to generate an empty stream (e.g. 20-byte gzip stream).
We can just produce nothing. At least, it works fine with most modern
browsers. Also, we can skip the instantiation of an encoder entirely by
instantiating it lazily.

Modifications:

Backport HttpContentEncoderTest from 4.0
Add similar tests for HttpContentCompressor to ensure the same tests
work with compression.

Result:

Fixes issue #2321
This commit is contained in:
Trustin Lee 2014-08-05 15:22:24 -07:00
parent 9efe48fc3a
commit 386a06dbfa
3 changed files with 554 additions and 5 deletions

View File

@ -15,9 +15,6 @@
*/
package org.jboss.netty.handler.codec.http;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
@ -28,6 +25,9 @@ import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpChunk}.
* The original content is replaced with the new content encoded by the
@ -54,6 +54,7 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler
private final Queue<String> acceptEncodingQueue = new ConcurrentLinkedQueue<String>();
private volatile EncoderEmbedder<ChannelBuffer> encoder;
private volatile boolean offerred;
/**
* Creates a new instance.
@ -198,16 +199,24 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler
protected abstract String getTargetContentEncoding(String acceptEncoding) throws Exception;
private ChannelBuffer encode(ChannelBuffer buf) {
offerred = true;
encoder.offer(buf);
return ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
}
private ChannelBuffer finishEncode() {
if (encoder == null) {
offerred = false;
return ChannelBuffers.EMPTY_BUFFER;
}
ChannelBuffer result;
if (!offerred) {
// No data was offerred to the encoder since the encoder was created.
// We should offer at least an empty buffer so that the encoder knows its is encoding empty content.
offerred = false;
encoder.offer(ChannelBuffers.EMPTY_BUFFER);
}
if (encoder.finish()) {
result = ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
} else {

View File

@ -15,9 +15,17 @@
*/
package org.jboss.netty.handler.codec.http;
import org.junit.Assert;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
import org.jboss.netty.util.CharsetUtil;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class HttpContentCompressorTest {
@Test
@ -40,7 +48,254 @@ public class HttpContentCompressorTest {
String acceptEncoding = tests[i];
String contentEncoding = tests[i + 1];
String targetEncoding = compressor.getTargetContentEncoding(acceptEncoding);
Assert.assertEquals(contentEncoding, targetEncoding);
assertEquals(contentEncoding, targetEncoding);
}
}
static final class HttpContentCompressorEmbedder extends EncoderEmbedder<Object> {
HttpContentCompressorEmbedder() {
super(new HttpContentCompressor());
}
public void fireMessageReceived(Object msg) {
Channels.fireMessageReceived(getChannel(), msg, null);
}
}
@Test
public void testSplitContent() throws Exception {
HttpContentCompressorEmbedder e = new HttpContentCompressorEmbedder();
e.fireMessageReceived(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setChunked(true);
e.offer(res);
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("Hell", CharsetUtil.US_ASCII)));
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("orld", CharsetUtil.US_ASCII)));
e.offer(HttpChunk.LAST_CHUNK);
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.isChunked(), is(true));
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("gzip"));
HttpChunk chunk;
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("1f8b0800000000000000f248cdc901000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("cad7512807000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("ca2fca4901000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("0300c2a99ae70c000000"));
chunk = (HttpChunk) e.poll();
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertThat(e.poll(), is(nullValue()));
}
@Test
public void testChunkedContent() throws Exception {
HttpContentCompressorEmbedder e = new HttpContentCompressorEmbedder();
e.fireMessageReceived(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.headers().set(Names.TRANSFER_ENCODING, Values.CHUNKED);
e.offer(res);
assertEncodedResponse(e);
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("Hell", CharsetUtil.US_ASCII)));
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("orld", CharsetUtil.US_ASCII)));
e.offer(HttpChunk.LAST_CHUNK);
HttpChunk chunk;
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("1f8b0800000000000000f248cdc901000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("cad7512807000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("ca2fca4901000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("0300c2a99ae70c000000"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readable(), is(false));
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertThat(e.poll(), is(nullValue()));
}
@Test
public void testChunkedContentWithTrailingHeader() throws Exception {
HttpContentCompressorEmbedder e = new HttpContentCompressorEmbedder();
e.fireMessageReceived(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.headers().set(Names.TRANSFER_ENCODING, Values.CHUNKED);
e.offer(res);
assertEncodedResponse(e);
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("Hell", CharsetUtil.US_ASCII)));
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
e.offer(new DefaultHttpChunk(ChannelBuffers.copiedBuffer("orld", CharsetUtil.US_ASCII)));
HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
trailer.trailingHeaders().set("X-Test", "Netty");
e.offer(trailer);
HttpChunk chunk;
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("1f8b0800000000000000f248cdc901000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("cad7512807000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("ca2fca4901000000ffff"));
chunk = (HttpChunk) e.poll();
assertThat(ChannelBuffers.hexDump(chunk.getContent()), is("0300c2a99ae70c000000"));
assertThat(chunk, is(instanceOf(HttpChunk.class)));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readable(), is(false));
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertEquals("Netty", ((HttpChunkTrailer) chunk).trailingHeaders().get("X-Test"));
assertThat(e.poll(), is(nullValue()));
}
@Test
public void testFullContent() throws Exception {
HttpContentCompressorEmbedder e = new HttpContentCompressorEmbedder();
e.fireMessageReceived(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setContent(ChannelBuffers.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
res.headers().set(Names.CONTENT_LENGTH, res.getContent().readableBytes());
e.offer(res);
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.isChunked(), is(false));
assertThat(
ChannelBuffers.hexDump(res.getContent()),
is("1f8b0800000000000000f248cdc9c9d75108cf2fca4901000000ffff0300c6865b260c000000"));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(String.valueOf(res.getContent().readableBytes())));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("gzip"));
assertThat(e.poll(), is(nullValue()));
}
/**
* If the length of the content is unknown, {@link HttpContentEncoder} should not skip encoding the content
* even if the actual length is turned out to be 0.
*/
@Test
public void testEmptySplitContent() throws Exception {
HttpContentCompressorEmbedder e = new HttpContentCompressorEmbedder();
e.fireMessageReceived(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setChunked(true);
e.offer(res);
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.isChunked(), is(true));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("gzip"));
e.offer(HttpChunk.LAST_CHUNK);
HttpChunk chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readableBytes(), is(20)); // an empty gzip stream is 20 bytes long.
assertThat(chunk, is(instanceOf(HttpChunk.class)));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readable(), is(false));
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertThat(e.poll(), is(nullValue()));
}
/**
* If the length of the content is 0 for sure, {@link HttpContentEncoder} should skip encoding.
*/
@Test
public void testEmptyFullContent() throws Exception {
HttpContentCompressorEmbedder ch = new HttpContentCompressorEmbedder();
ch.fireMessageReceived(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setContent(ChannelBuffers.EMPTY_BUFFER);
ch.offer(res);
Object o = ch.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = ch.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
// Content encoding shouldn't be modified.
assertThat(res.headers().get(Names.CONTENT_ENCODING), is(nullValue()));
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.getContent().toString(CharsetUtil.US_ASCII), is(""));
assertThat(ch.poll(), is(nullValue()));
}
private static HttpRequest newRequest() {
HttpRequest req = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
req.headers().set(Names.ACCEPT_ENCODING, "gzip");
return req;
}
private static void assertEncodedResponse(HttpContentCompressorEmbedder e) {
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
HttpResponse res = (HttpResponse) o;
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is("chunked"));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("gzip"));
}
}

View File

@ -0,0 +1,285 @@
/*
* Copyright 2014 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 org.jboss.netty.handler.codec.http;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
import org.jboss.netty.handler.codec.http.HttpHeaders.Names;
import org.jboss.netty.handler.codec.http.HttpHeaders.Values;
import org.jboss.netty.handler.codec.oneone.OneToOneEncoder;
import org.jboss.netty.util.CharsetUtil;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class HttpContentEncoderTest {
private static final class TestEncoder extends HttpContentEncoder {
@Override
protected EncoderEmbedder<ChannelBuffer> newContentEncoder(HttpMessage msg, String acceptEncoding)
throws Exception {
return new EncoderEmbedder<ChannelBuffer>(new OneToOneEncoder() {
@Override
protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
ChannelBuffer in = (ChannelBuffer) msg;
return ChannelBuffers.wrappedBuffer(String.valueOf(in.readableBytes()).getBytes(CharsetUtil.US_ASCII));
}
});
}
@Override
protected String getTargetContentEncoding(String acceptEncoding) throws Exception {
return "test";
}
}
static final class HttpContentEncoderEmbedder extends EncoderEmbedder<Object> {
HttpContentEncoderEmbedder() {
super(new TestEncoder());
}
public void fireMessageReceived(Object msg) {
Channels.fireMessageReceived(getChannel(), msg, null);
}
}
@Test
public void testSplitContent() throws Exception {
HttpContentEncoderEmbedder e = new HttpContentEncoderEmbedder();
e.fireMessageReceived(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setChunked(true);
e.offer(res);
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[3])));
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[2])));
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[1])));
e.offer(HttpChunk.LAST_CHUNK);
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.isChunked(), is(true));
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("test"));
HttpChunk chunk;
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("3"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("2"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("1"));
chunk = (HttpChunk) e.poll();
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertThat(e.poll(), is(nullValue()));
}
@Test
public void testChunkedContent() throws Exception {
HttpContentEncoderEmbedder e = new HttpContentEncoderEmbedder();
e.fireMessageReceived(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.headers().set(Names.TRANSFER_ENCODING, Values.CHUNKED);
e.offer(res);
assertEncodedResponse(e);
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[3])));
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[2])));
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[1])));
e.offer(HttpChunk.LAST_CHUNK);
HttpChunk chunk;
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("3"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("2"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("1"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readable(), is(false));
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertThat(e.poll(), is(nullValue()));
}
@Test
public void testChunkedContentWithTrailingHeader() throws Exception {
HttpContentEncoderEmbedder e = new HttpContentEncoderEmbedder();
e.fireMessageReceived(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.headers().set(Names.TRANSFER_ENCODING, Values.CHUNKED);
e.offer(res);
assertEncodedResponse(e);
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[3])));
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[2])));
e.offer(new DefaultHttpChunk(ChannelBuffers.wrappedBuffer(new byte[1])));
HttpChunkTrailer trailer = new DefaultHttpChunkTrailer();
trailer.trailingHeaders().set("X-Test", "Netty");
e.offer(trailer);
HttpChunk chunk;
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("3"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("2"));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("1"));
assertThat(chunk, is(instanceOf(HttpChunk.class)));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readable(), is(false));
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertEquals("Netty", ((HttpChunkTrailer) chunk).trailingHeaders().get("X-Test"));
assertThat(e.poll(), is(nullValue()));
}
@Test
public void testFullContent() throws Exception {
HttpContentEncoderEmbedder e = new HttpContentEncoderEmbedder();
e.fireMessageReceived(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setContent(ChannelBuffers.wrappedBuffer(new byte[42]));
res.headers().set(Names.CONTENT_LENGTH, 42);
e.offer(res);
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.isChunked(), is(false));
assertThat(res.getContent().readableBytes(), is(2));
assertThat(res.getContent().toString(CharsetUtil.US_ASCII), is("42"));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is("2"));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("test"));
assertThat(e.poll(), is(nullValue()));
}
/**
* If the length of the content is unknown, {@link HttpContentEncoder} should not skip encoding the content
* even if the actual length is turned out to be 0.
*/
@Test
public void testEmptySplitContent() throws Exception {
HttpContentEncoderEmbedder e = new HttpContentEncoderEmbedder();
e.fireMessageReceived(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setChunked(true);
e.offer(res);
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.isChunked(), is(true));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("test"));
e.offer(HttpChunk.LAST_CHUNK);
HttpChunk chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().toString(CharsetUtil.US_ASCII), is("0"));
assertThat(chunk, is(instanceOf(HttpChunk.class)));
chunk = (HttpChunk) e.poll();
assertThat(chunk.getContent().readable(), is(false));
assertThat(chunk, is(instanceOf(HttpChunkTrailer.class)));
assertThat(e.poll(), is(nullValue()));
}
/**
* If the length of the content is 0 for sure, {@link HttpContentEncoder} should skip encoding.
*/
@Test
public void testEmptyFullContent() throws Exception {
HttpContentEncoderEmbedder ch = new HttpContentEncoderEmbedder();
ch.fireMessageReceived(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.setContent(ChannelBuffers.EMPTY_BUFFER);
ch.offer(res);
Object o = ch.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = ch.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
res = (HttpResponse) o;
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is(nullValue()));
// Content encoding shouldn't be modified.
assertThat(res.headers().get(Names.CONTENT_ENCODING), is(nullValue()));
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.getContent().toString(CharsetUtil.US_ASCII), is(""));
assertThat(ch.poll(), is(nullValue()));
}
private static void assertEncodedResponse(HttpContentEncoderEmbedder e) {
Object o = e.poll();
assertThat(o, is(instanceOf(HttpRequest.class)));
o = e.poll();
assertThat(o, is(instanceOf(HttpResponse.class)));
HttpResponse res = (HttpResponse) o;
assertThat(res.getContent().readableBytes(), is(0));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is("chunked"));
assertThat(res.headers().get(Names.CONTENT_LENGTH), is(nullValue()));
assertThat(res.headers().get(Names.CONTENT_ENCODING), is("test"));
}
}