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:
parent
9efe48fc3a
commit
386a06dbfa
@ -15,9 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jboss.netty.handler.codec.http;
|
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.ChannelBuffer;
|
||||||
import org.jboss.netty.buffer.ChannelBuffers;
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
import org.jboss.netty.channel.ChannelHandlerContext;
|
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.channel.SimpleChannelHandler;
|
||||||
import org.jboss.netty.handler.codec.embedder.EncoderEmbedder;
|
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}.
|
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpChunk}.
|
||||||
* The original content is replaced with the new content encoded by the
|
* 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 final Queue<String> acceptEncodingQueue = new ConcurrentLinkedQueue<String>();
|
||||||
private volatile EncoderEmbedder<ChannelBuffer> encoder;
|
private volatile EncoderEmbedder<ChannelBuffer> encoder;
|
||||||
|
private volatile boolean offerred;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
@ -198,16 +199,24 @@ public abstract class HttpContentEncoder extends SimpleChannelHandler
|
|||||||
protected abstract String getTargetContentEncoding(String acceptEncoding) throws Exception;
|
protected abstract String getTargetContentEncoding(String acceptEncoding) throws Exception;
|
||||||
|
|
||||||
private ChannelBuffer encode(ChannelBuffer buf) {
|
private ChannelBuffer encode(ChannelBuffer buf) {
|
||||||
|
offerred = true;
|
||||||
encoder.offer(buf);
|
encoder.offer(buf);
|
||||||
return ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
|
return ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChannelBuffer finishEncode() {
|
private ChannelBuffer finishEncode() {
|
||||||
if (encoder == null) {
|
if (encoder == null) {
|
||||||
|
offerred = false;
|
||||||
return ChannelBuffers.EMPTY_BUFFER;
|
return ChannelBuffers.EMPTY_BUFFER;
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelBuffer result;
|
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()) {
|
if (encoder.finish()) {
|
||||||
result = ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
|
result = ChannelBuffers.wrappedBuffer(encoder.pollAll(new ChannelBuffer[encoder.size()]));
|
||||||
} else {
|
} else {
|
||||||
|
@ -15,9 +15,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.jboss.netty.handler.codec.http;
|
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 org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class HttpContentCompressorTest {
|
public class HttpContentCompressorTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -40,7 +48,254 @@ public class HttpContentCompressorTest {
|
|||||||
String acceptEncoding = tests[i];
|
String acceptEncoding = tests[i];
|
||||||
String contentEncoding = tests[i + 1];
|
String contentEncoding = tests[i + 1];
|
||||||
String targetEncoding = compressor.getTargetContentEncoding(acceptEncoding);
|
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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user