netty5/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java

871 lines
35 KiB
Java

/*
* Copyright 2012 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:
*
* https://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.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultEventLoopGroup;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalServerChannel;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.EncoderException;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class HttpContentCompressorTest {
@Test
public void testGetTargetContentEncoding() throws Exception {
HttpContentCompressor compressor = new HttpContentCompressor();
String[] tests = {
// Accept-Encoding -> Content-Encoding
"", null,
"*", "gzip",
"*;q=0.0", null,
"gzip", "gzip",
"compress, gzip;q=0.5", "gzip",
"gzip; q=0.5, identity", "gzip",
"gzip ; q=0.1", "gzip",
"gzip; q=0, deflate", "deflate",
" deflate ; q=0 , *;q=0.5", "gzip",
};
for (int i = 0; i < tests.length; i += 2) {
String acceptEncoding = tests[i];
String contentEncoding = tests[i + 1];
ZlibWrapper targetWrapper = compressor.determineWrapper(acceptEncoding);
String targetEncoding = null;
if (targetWrapper != null) {
switch (targetWrapper) {
case GZIP:
targetEncoding = "gzip";
break;
case ZLIB:
targetEncoding = "deflate";
break;
default:
fail();
}
}
assertEquals(contentEncoding, targetEncoding);
}
}
@Test
public void testSplitContent() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
ch.writeOutbound(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK));
ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII)));
ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
ch.writeOutbound(new DefaultLastHttpContent(Unpooled.copiedBuffer("orld", CharsetUtil.US_ASCII)));
assertEncodedResponse(ch);
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
public void testChunkedContent() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
ch.writeOutbound(res);
assertEncodedResponse(ch);
ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII)));
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
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
public void testChunkedContentWithAssembledResponseIdentityEncoding() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
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);
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(chunk.content().toString(StandardCharsets.UTF_8), is("Hell"));
chunk.release();
chunk = ch.readOutbound();
assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("o, w"));
chunk.release();
chunk = ch.readOutbound();
assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("orld"));
assertThat(chunk, is(instanceOf(LastHttpContent.class)));
chunk.release();
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void testContentWithAssembledResponseIdentityEncodingHttp10() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/"));
HttpResponse res = new AssembledHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII));
ch.writeOutbound(res);
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(chunk.content().toString(StandardCharsets.UTF_8), is("Hell"));
chunk.release();
chunk = ch.readOutbound();
assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("o, w"));
chunk.release();
chunk = ch.readOutbound();
assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("orld"));
assertThat(chunk, is(instanceOf(LastHttpContent.class)));
chunk.release();
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void testChunkedContentWithTrailingHeader() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
ch.writeOutbound(res);
assertEncodedResponse(ch);
ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII)));
ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
LastHttpContent content = new DefaultLastHttpContent(Unpooled.copiedBuffer("orld", CharsetUtil.US_ASCII));
content.trailingHeaders().set(of("X-Test"), of("Netty"));
ch.writeOutbound(content);
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)));
assertEquals("Netty", ((LastHttpContent) chunk).trailingHeaders().get(of("X-Test")));
assertEquals(DecoderResult.SUCCESS, chunk.decoderResult());
chunk.release();
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void testFullContentWithContentLength() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
FullHttpResponse fullRes = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
fullRes.headers().set(HttpHeaderNames.CONTENT_LENGTH, fullRes.content().readableBytes());
ch.writeOutbound(fullRes);
HttpResponse res = ch.readOutbound();
assertThat(res, is(not(instanceOf(HttpContent.class))));
assertThat(res.headers().get(HttpHeaderNames.TRANSFER_ENCODING), is(nullValue()));
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
long contentLengthHeaderValue = HttpUtil.getContentLength(res);
long observedLength = 0;
HttpContent c = ch.readOutbound();
observedLength += c.content().readableBytes();
assertThat(ByteBufUtil.hexDump(c.content()), is("1f8b0800000000000000f248cdc9c9d75108cf2fca4901000000ffff"));
c.release();
c = ch.readOutbound();
observedLength += c.content().readableBytes();
assertThat(ByteBufUtil.hexDump(c.content()), is("0300c6865b260c000000"));
c.release();
LastHttpContent last = ch.readOutbound();
assertThat(last.content().readableBytes(), is(0));
last.release();
assertThat(ch.readOutbound(), is(nullValue()));
assertEquals(contentLengthHeaderValue, observedLength);
}
@Test
public void testFullContent() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
ch.writeOutbound(res);
assertEncodedResponse(ch);
HttpContent c = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(c.content()), is("1f8b0800000000000000f248cdc9c9d75108cf2fca4901000000ffff"));
c.release();
c = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(c.content()), is("0300c6865b260c000000"));
c.release();
LastHttpContent last = ch.readOutbound();
assertThat(last.content().readableBytes(), is(0));
last.release();
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void testExecutorPreserveOrdering() throws Exception {
final EventLoopGroup compressorGroup = new DefaultEventLoopGroup(1);
EventLoopGroup localGroup = new DefaultEventLoopGroup(1);
Channel server = null;
Channel client = null;
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.channel(LocalServerChannel.class)
.group(localGroup)
.childHandler(new ChannelInitializer<LocalChannel>() {
@Override
protected void initChannel(LocalChannel ch) throws Exception {
ch.pipeline()
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(1024))
.addLast(compressorGroup, new HttpContentCompressor())
.addLast(new ChannelOutboundHandlerAdapter() {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
super.write(ctx, msg, promise);
}
})
.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
FullHttpResponse res =
new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
ctx.writeAndFlush(res);
ReferenceCountUtil.release(msg);
return;
}
super.channelRead(ctx, msg);
}
});
}
});
LocalAddress address = new LocalAddress(UUID.randomUUID().toString());
server = bootstrap.bind(address).sync().channel();
final BlockingQueue<HttpObject> responses = new LinkedBlockingQueue<HttpObject>();
client = new Bootstrap()
.channel(LocalChannel.class)
.remoteAddress(address)
.group(localGroup)
.handler(new ChannelInitializer<LocalChannel>() {
@Override
protected void initChannel(LocalChannel ch) throws Exception {
ch.pipeline().addLast(new HttpClientCodec()).addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpObject) {
responses.put((HttpObject) msg);
return;
}
super.channelRead(ctx, msg);
}
});
}
}).connect().sync().channel();
client.writeAndFlush(newRequest()).sync();
assertEncodedResponse((HttpResponse) responses.poll(1, TimeUnit.SECONDS));
HttpContent c = (HttpContent) responses.poll(1, TimeUnit.SECONDS);
assertNotNull(c);
assertThat(ByteBufUtil.hexDump(c.content()),
is("1f8b0800000000000000f248cdc9c9d75108cf2fca4901000000ffff"));
c.release();
c = (HttpContent) responses.poll(1, TimeUnit.SECONDS);
assertNotNull(c);
assertThat(ByteBufUtil.hexDump(c.content()), is("0300c6865b260c000000"));
c.release();
LastHttpContent last = (LastHttpContent) responses.poll(1, TimeUnit.SECONDS);
assertNotNull(last);
assertThat(last.content().readableBytes(), is(0));
last.release();
assertNull(responses.poll(1, TimeUnit.SECONDS));
} finally {
if (client != null) {
client.close().sync();
}
if (server != null) {
server.close().sync();
}
compressorGroup.shutdownGracefully();
localGroup.shutdownGracefully();
}
}
/**
* 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 {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
ch.writeOutbound(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK));
assertEncodedResponse(ch);
ch.writeOutbound(LastHttpContent.EMPTY_LAST_CONTENT);
HttpContent chunk = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(chunk.content()), is("1f8b080000000000000003000000000000000000"));
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()));
}
/**
* If the length of the content is 0 for sure, {@link HttpContentEncoder} should skip encoding.
*/
@Test
public void testEmptyFullContent() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER);
ch.writeOutbound(res);
Object o = ch.readOutbound();
assertThat(o, is(instanceOf(FullHttpResponse.class)));
res = (FullHttpResponse) o;
assertThat(res.headers().get(HttpHeaderNames.TRANSFER_ENCODING), is(nullValue()));
// Content encoding shouldn't be modified.
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is(nullValue()));
assertThat(res.content().readableBytes(), is(0));
assertThat(res.content().toString(CharsetUtil.US_ASCII), is(""));
res.release();
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void testEmptyFullContentWithTrailer() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER);
res.trailingHeaders().set(of("X-Test"), of("Netty"));
ch.writeOutbound(res);
Object o = ch.readOutbound();
assertThat(o, is(instanceOf(FullHttpResponse.class)));
res = (FullHttpResponse) o;
assertThat(res.headers().get(HttpHeaderNames.TRANSFER_ENCODING), is(nullValue()));
// Content encoding shouldn't be modified.
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is(nullValue()));
assertThat(res.content().readableBytes(), is(0));
assertThat(res.content().toString(CharsetUtil.US_ASCII), is(""));
assertEquals("Netty", res.trailingHeaders().get(of("X-Test")));
assertEquals(DecoderResult.SUCCESS, res.decoderResult());
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void test100Continue() throws Exception {
FullHttpRequest request = newRequest();
HttpUtil.set100ContinueExpected(request, true);
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(request);
FullHttpResponse continueResponse = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE, Unpooled.EMPTY_BUFFER);
ch.writeOutbound(continueResponse);
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER);
res.trailingHeaders().set(of("X-Test"), of("Netty"));
ch.writeOutbound(res);
Object o = ch.readOutbound();
assertThat(o, is(instanceOf(FullHttpResponse.class)));
res = (FullHttpResponse) o;
assertSame(continueResponse, res);
res.release();
o = ch.readOutbound();
assertThat(o, is(instanceOf(FullHttpResponse.class)));
res = (FullHttpResponse) o;
assertThat(res.headers().get(HttpHeaderNames.TRANSFER_ENCODING), is(nullValue()));
// Content encoding shouldn't be modified.
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is(nullValue()));
assertThat(res.content().readableBytes(), is(0));
assertThat(res.content().toString(CharsetUtil.US_ASCII), is(""));
assertEquals("Netty", res.trailingHeaders().get(of("X-Test")));
assertEquals(DecoderResult.SUCCESS, res.decoderResult());
assertThat(ch.readOutbound(), is(nullValue()));
}
@Test
public void testTooManyResponses() throws Exception {
FullHttpRequest request = newRequest();
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(request);
ch.writeOutbound(new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER));
try {
ch.writeOutbound(new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.EMPTY_BUFFER));
fail();
} catch (EncoderException e) {
assertTrue(e.getCause() instanceof IllegalStateException);
}
assertTrue(ch.finish());
for (;;) {
Object message = ch.readOutbound();
if (message == null) {
break;
}
ReferenceCountUtil.release(message);
}
for (;;) {
Object message = ch.readInbound();
if (message == null) {
break;
}
ReferenceCountUtil.release(message);
}
}
@Test
public void testIdentity() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
assertTrue(ch.writeInbound(newRequest()));
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
int len = res.content().readableBytes();
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, len);
res.headers().set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.IDENTITY);
assertTrue(ch.writeOutbound(res));
FullHttpResponse response = ch.readOutbound();
assertEquals(String.valueOf(len), response.headers().get(HttpHeaderNames.CONTENT_LENGTH));
assertEquals(HttpHeaderValues.IDENTITY.toString(), response.headers().get(HttpHeaderNames.CONTENT_ENCODING));
assertEquals("Hello, World", response.content().toString(CharsetUtil.US_ASCII));
response.release();
assertTrue(ch.finishAndReleaseAll());
}
@Test
public void testCustomEncoding() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
assertTrue(ch.writeInbound(newRequest()));
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
int len = res.content().readableBytes();
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, len);
res.headers().set(HttpHeaderNames.CONTENT_ENCODING, "ascii");
assertTrue(ch.writeOutbound(res));
FullHttpResponse response = ch.readOutbound();
assertEquals(String.valueOf(len), response.headers().get(HttpHeaderNames.CONTENT_LENGTH));
assertEquals("ascii", response.headers().get(HttpHeaderNames.CONTENT_ENCODING));
assertEquals("Hello, World", response.content().toString(CharsetUtil.US_ASCII));
response.release();
assertTrue(ch.finishAndReleaseAll());
}
@Test
public void testCompressThresholdAllCompress() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
assertTrue(ch.writeInbound(newRequest()));
FullHttpResponse res1023 = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(new byte[1023]));
assertTrue(ch.writeOutbound(res1023));
DefaultHttpResponse response1023 = ch.readOutbound();
assertThat(response1023.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
ch.releaseOutbound();
assertTrue(ch.writeInbound(newRequest()));
FullHttpResponse res1024 = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(new byte[1024]));
assertTrue(ch.writeOutbound(res1024));
DefaultHttpResponse response1024 = ch.readOutbound();
assertThat(response1024.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
assertTrue(ch.finishAndReleaseAll());
}
@Test
public void testCompressThresholdNotCompress() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor(6, 15, 8, 1024));
assertTrue(ch.writeInbound(newRequest()));
FullHttpResponse res1023 = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(new byte[1023]));
assertTrue(ch.writeOutbound(res1023));
DefaultHttpResponse response1023 = ch.readOutbound();
assertFalse(response1023.headers().contains(HttpHeaderNames.CONTENT_ENCODING));
ch.releaseOutbound();
assertTrue(ch.writeInbound(newRequest()));
FullHttpResponse res1024 = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(new byte[1024]));
assertTrue(ch.writeOutbound(res1024));
DefaultHttpResponse response1024 = ch.readOutbound();
assertThat(response1024.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("gzip"));
assertTrue(ch.finishAndReleaseAll());
}
@Test
public void testMultipleAcceptEncodingHeaders() {
FullHttpRequest request = newRequest();
request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "unknown; q=1.0")
.add(HttpHeaderNames.ACCEPT_ENCODING, "gzip; q=0.5")
.add(HttpHeaderNames.ACCEPT_ENCODING, "deflate; q=0");
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
assertTrue(ch.writeInbound(request));
FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Gzip Win", CharsetUtil.US_ASCII));
assertTrue(ch.writeOutbound(res));
assertEncodedResponse(ch);
HttpContent c = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(c.content()), is("1f8b080000000000000072afca2c5008cfcc03000000ffff"));
c.release();
c = ch.readOutbound();
assertThat(ByteBufUtil.hexDump(c.content()), is("03001f2ebf0f08000000"));
c.release();
LastHttpContent last = ch.readOutbound();
assertThat(last.content().readableBytes(), is(0));
last.release();
assertThat(ch.readOutbound(), is(nullValue()));
assertTrue(ch.finishAndReleaseAll());
}
private static FullHttpRequest newRequest() {
FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
req.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "gzip");
return req;
}
private static void assertEncodedResponse(EmbeddedChannel ch) {
Object o = ch.readOutbound();
assertThat(o, is(instanceOf(HttpResponse.class)));
assertEncodedResponse((HttpResponse) o);
}
private static void assertEncodedResponse(HttpResponse res) {
assertThat(res, is(not(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"));
}
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;
}
}
}