/*
* Copyright 2013 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.embedded.EmbeddedChannel;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.util.CharsetUtil;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
public class HttpResponseDecoderTest {
/**
* The size of headers should be calculated correctly even if a single header is split into multiple fragments.
* @see <a href="https://github.com/netty/netty/issues/3445">#3445</a>
*/
@Test
public void testMaxHeaderSize1() {
final int maxHeaderSize = 8192;
final EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder(4096, maxHeaderSize, 8192));
final char[] bytes = new char[maxHeaderSize / 2 - 2];
Arrays.fill(bytes, 'a');
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n", CharsetUtil.US_ASCII));
// Write two 4096-byte headers (= 8192 bytes)
ch.writeInbound(Unpooled.copiedBuffer("A:", CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer(bytes, CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII));
assertNull(ch.readInbound());
ch.writeInbound(Unpooled.copiedBuffer("B:", CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer(bytes, CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertNull(res.getDecoderResult().cause());
assertTrue(res.getDecoderResult().isSuccess());
assertNull(ch.readInbound());
assertTrue(ch.finish());
assertThat(ch.readInbound(), instanceOf(LastHttpContent.class));
}
/**
* Complementary test case of {@link #testMaxHeaderSize1()}. When it actually exceeds the maximum, it should fail.
*/
@Test
public void testMaxHeaderSize2() {
final int maxHeaderSize = 8192;
final EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder(4096, maxHeaderSize, 8192));
final char[] bytes = new char[maxHeaderSize / 2 - 2];
Arrays.fill(bytes, 'a');
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n", CharsetUtil.US_ASCII));
// Write a 4096-byte header and a 4097-byte header to test an off-by-one case (= 8193 bytes)
ch.writeInbound(Unpooled.copiedBuffer("A:", CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer(bytes, CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII));
assertNull(ch.readInbound());
ch.writeInbound(Unpooled.copiedBuffer("B: ", CharsetUtil.US_ASCII)); // Note an extra space.
ch.writeInbound(Unpooled.copiedBuffer(bytes, CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII));
ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertTrue(res.getDecoderResult().cause() instanceof TooLongFrameException);
assertFalse(ch.finish());
assertNull(ch.readInbound());
}
@Test
public void testResponseChunked() {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n",
CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
byte[] data = new byte[64];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) i;
}
for (int i = 0; i < 10; i++) {
assertFalse(ch.writeInbound(Unpooled.copiedBuffer(Integer.toHexString(data.length) + "\r\n",
CharsetUtil.US_ASCII)));
assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(data)));
HttpContent content = (HttpContent) ch.readInbound();
assertEquals(data.length, content.content().readableBytes());
byte[] decodedData = new byte[data.length];
content.content().readBytes(decodedData);
assertArrayEquals(data, decodedData);
content.release();
assertFalse(ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
}
// Write the last chunk.
ch.writeInbound(Unpooled.copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII));
// Ensure the last chunk was decoded.
LastHttpContent content = (LastHttpContent) ch.readInbound();
assertFalse(content.content().isReadable());
content.release();
ch.finish();
assertNull(ch.readInbound());
}
@Test
public void testResponseChunkedExceedMaxChunkSize() {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder(4096, 8192, 32));
ch.writeInbound(
Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
byte[] data = new byte[64];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) i;
}
for (int i = 0; i < 10; i++) {
assertFalse(ch.writeInbound(Unpooled.copiedBuffer(Integer.toHexString(data.length) + "\r\n",
CharsetUtil.US_ASCII)));
assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(data)));
byte[] decodedData = new byte[data.length];
HttpContent content = (HttpContent) ch.readInbound();
assertEquals(32, content.content().readableBytes());
content.content().readBytes(decodedData, 0, 32);
content.release();
content = (HttpContent) ch.readInbound();
assertEquals(32, content.content().readableBytes());
content.content().readBytes(decodedData, 32, 32);
assertArrayEquals(data, decodedData);
content.release();
assertFalse(ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
}
// Write the last chunk.
ch.writeInbound(Unpooled.copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII));
// Ensure the last chunk was decoded.
LastHttpContent content = (LastHttpContent) ch.readInbound();
assertFalse(content.content().isReadable());
content.release();
ch.finish();
assertNull(ch.readInbound());
}
@Test
public void testClosureWithoutContentLength1() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n\r\n", CharsetUtil.US_ASCII));
// Read the response headers.
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
assertThat(ch.readInbound(), is(nullValue()));
// Close the connection without sending anything.
assertTrue(ch.finish());
// The decoder should still produce the last content.
LastHttpContent content = (LastHttpContent) ch.readInbound();
assertThat(content.content().isReadable(), is(false));
content.release();
// But nothing more.
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testClosureWithoutContentLength2() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
// Write the partial response.
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n\r\n12345678", CharsetUtil.US_ASCII));
// Read the response headers.
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
// Read the partial content.
HttpContent content = (HttpContent) ch.readInbound();
assertThat(content.content().toString(CharsetUtil.US_ASCII), is("12345678"));
assertThat(content, is(not(instanceOf(LastHttpContent.class))));
content.release();
assertThat(ch.readInbound(), is(nullValue()));
// Close the connection.
assertTrue(ch.finish());
// The decoder should still produce the last content.
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
assertThat(lastContent.content().isReadable(), is(false));
lastContent.release();
// But nothing more.
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testPrematureClosureWithChunkedEncoding1() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(
Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n", CharsetUtil.US_ASCII));
// Read the response headers.
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is("chunked"));
assertThat(ch.readInbound(), is(nullValue()));
// Close the connection without sending anything.
ch.finish();
// The decoder should not generate the last chunk because it's closed prematurely.
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testPrematureClosureWithChunkedEncoding2() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
// Write the partial response.
ch.writeInbound(Unpooled.copiedBuffer(
"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n8\r\n12345678", CharsetUtil.US_ASCII));
// Read the response headers.
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
assertThat(res.headers().get(Names.TRANSFER_ENCODING), is("chunked"));
// Read the partial content.
HttpContent content = (HttpContent) ch.readInbound();
assertThat(content.content().toString(CharsetUtil.US_ASCII), is("12345678"));
assertThat(content, is(not(instanceOf(LastHttpContent.class))));
content.release();
assertThat(ch.readInbound(), is(nullValue()));
// Close the connection.
ch.finish();
// The decoder should not generate the last chunk because it's closed prematurely.
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testLastResponseWithEmptyHeaderAndEmptyContent() {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n\r\n", CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
assertThat(ch.readInbound(), is(nullValue()));
assertThat(ch.finish(), is(true));
LastHttpContent content = (LastHttpContent) ch.readInbound();
assertThat(content.content().isReadable(), is(false));
content.release();
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testLastResponseWithoutContentLengthHeader() {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n\r\n", CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
assertThat(ch.readInbound(), is(nullValue()));
ch.writeInbound(Unpooled.wrappedBuffer(new byte[1024]));
HttpContent content = (HttpContent) ch.readInbound();
assertThat(content.content().readableBytes(), is(1024));
content.release();
assertThat(ch.finish(), is(true));
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
assertThat(lastContent.content().isReadable(), is(false));
lastContent.release();
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testLastResponseWithTrailingHeader() {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer(
"HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"0\r\n" +
"Set-Cookie: t1=t1v1\r\n" +
"Set-Cookie: t2=t2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT\r\n" +
"\r\n",
CharsetUtil.US_ASCII));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
assertThat(lastContent.content().isReadable(), is(false));
HttpHeaders headers = lastContent.trailingHeaders();
assertEquals(1, headers.names().size());
List<String> values = headers.getAll("Set-Cookie");
assertEquals(2, values.size());
assertTrue(values.contains("t1=t1v1"));
assertTrue(values.contains("t2=t2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT"));
lastContent.release();
assertThat(ch.finish(), is(false));
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testLastResponseWithTrailingHeaderFragmented() {
byte[] data = ("HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"0\r\n" +
"Set-Cookie: t1=t1v1\r\n" +
"Set-Cookie: t2=t2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT\r\n" +
"\r\n").getBytes(CharsetUtil.US_ASCII);
for (int i = 1; i < data.length; i++) {
testLastResponseWithTrailingHeaderFragmented(data, i);
}
}
private static void testLastResponseWithTrailingHeaderFragmented(byte[] content, int fragmentSize) {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
int headerLength = 47;
// split up the header
for (int a = 0; a < headerLength;) {
int amount = fragmentSize;
if (a + amount > headerLength) {
amount = headerLength - a;
}
// if header is done it should produce a HttpRequest
boolean headerDone = a + amount == headerLength;
assertEquals(headerDone, ch.writeInbound(Unpooled.wrappedBuffer(content, a, amount)));
a += amount;
}
ch.writeInbound(Unpooled.wrappedBuffer(content, headerLength, content.length - headerLength));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
assertThat(lastContent.content().isReadable(), is(false));
HttpHeaders headers = lastContent.trailingHeaders();
assertEquals(1, headers.names().size());
List<String> values = headers.getAll("Set-Cookie");
assertEquals(2, values.size());
assertTrue(values.contains("t1=t1v1"));
assertTrue(values.contains("t2=t2v2; Expires=Wed, 09-Jun-2021 10:18:14 GMT"));
lastContent.release();
assertThat(ch.finish(), is(false));
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testResponseWithContentLength() {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.copiedBuffer(
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 10\r\n" +
"\r\n", CharsetUtil.US_ASCII));
byte[] data = new byte[10];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) i;
}
ch.writeInbound(Unpooled.wrappedBuffer(data, 0, data.length / 2));
ch.writeInbound(Unpooled.wrappedBuffer(data, 5, data.length / 2));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
HttpContent firstContent = (HttpContent) ch.readInbound();
assertThat(firstContent.content().readableBytes(), is(5));
assertEquals(Unpooled.wrappedBuffer(data, 0, 5), firstContent.content());
firstContent.release();
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
assertEquals(5, lastContent.content().readableBytes());
assertEquals(Unpooled.wrappedBuffer(data, 5, 5), lastContent.content());
lastContent.release();
assertThat(ch.finish(), is(false));
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testResponseWithContentLengthFragmented() {
byte[] data = ("HTTP/1.1 200 OK\r\n" +
"Content-Length: 10\r\n" +
"\r\n").getBytes(CharsetUtil.US_ASCII);
for (int i = 1; i < data.length; i++) {
testResponseWithContentLengthFragmented(data, i);
}
}
private static void testResponseWithContentLengthFragmented(byte[] header, int fragmentSize) {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
// split up the header
for (int a = 0; a < header.length;) {
int amount = fragmentSize;
if (a + amount > header.length) {
amount = header.length - a;
}
ch.writeInbound(Unpooled.wrappedBuffer(header, a, amount));
a += amount;
}
byte[] data = new byte[10];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) i;
}
ch.writeInbound(Unpooled.wrappedBuffer(data, 0, data.length / 2));
ch.writeInbound(Unpooled.wrappedBuffer(data, 5, data.length / 2));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
HttpContent firstContent = (HttpContent) ch.readInbound();
assertThat(firstContent.content().readableBytes(), is(5));
assertEquals(Unpooled.wrappedBuffer(data, 0, 5), firstContent.content());
firstContent.release();
LastHttpContent lastContent = (LastHttpContent) ch.readInbound();
assertEquals(5, lastContent.content().readableBytes());
assertEquals(Unpooled.wrappedBuffer(data, 5, 5), lastContent.content());
lastContent.release();
assertThat(ch.finish(), is(false));
assertThat(ch.readInbound(), is(nullValue()));
}
@Test
public void testWebSocketResponse() {
byte[] data = ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Origin: http://localhost:8080\r\n" +
"Sec-WebSocket-Location: ws://localhost/some/path\r\n" +
"\r\n" +
"1234567812345678").getBytes();
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.wrappedBuffer(data));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS));
HttpContent content = (HttpContent) ch.readInbound();
assertThat(content.content().readableBytes(), is(16));
content.release();
assertThat(ch.finish(), is(false));
assertThat(ch.readInbound(), is(nullValue()));
}
// See https://github.com/netty/netty/issues/2173
@Test
public void testWebSocketResponseWithDataFollowing() {
byte[] data = ("HTTP/1.1 101 WebSocket Protocol Handshake\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Origin: http://localhost:8080\r\n" +
"Sec-WebSocket-Location: ws://localhost/some/path\r\n" +
"\r\n" +
"1234567812345678").getBytes();
byte[] otherData = {1, 2, 3, 4};
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.wrappedBuffer(data, otherData));
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.SWITCHING_PROTOCOLS));
HttpContent content = (HttpContent) ch.readInbound();
assertThat(content.content().readableBytes(), is(16));
content.release();
assertThat(ch.finish(), is(true));
assertEquals(ch.readInbound(), Unpooled.wrappedBuffer(otherData));
}
@Test
public void testGarbageHeaders() {
// A response without headers - from https://github.com/netty/netty/issues/2103
byte[] data = ("<html>\r\n" +
"<head><title>400 Bad Request</title></head>\r\n" +
"<body bgcolor=\"white\">\r\n" +
"<center><h1>400 Bad Request</h1></center>\r\n" +
"<hr><center>nginx/1.1.19</center>\r\n" +
"</body>\r\n" +
"</html>\r\n").getBytes();
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());
ch.writeInbound(Unpooled.wrappedBuffer(data));
// Garbage input should generate the 999 Unknown response.
HttpResponse res = (HttpResponse) ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_0));
assertThat(res.getStatus().code(), is(999));
assertThat(res.getDecoderResult().isFailure(), is(true));
assertThat(res.getDecoderResult().isFinished(), is(true));
assertThat(ch.readInbound(), is(nullValue()));
// More garbage should not generate anything (i.e. the decoder discards anything beyond this point.)
ch.writeInbound(Unpooled.wrappedBuffer(data));
assertThat(ch.readInbound(), is(nullValue()));
// Closing the connection should not generate anything since the protocol has been violated.
ch.finish();
assertThat(ch.readInbound(), is(nullValue()));
}
/**
* Tests if the decoder produces one and only {@link LastHttpContent} when an invalid chunk is received and
* the connection is closed.
*/
@Test
public void testGarbageChunk() {
EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseDecoder());
String responseWithIllegalChunk =
"HTTP/1.1 200 OK\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
"NOT_A_CHUNK_LENGTH\r\n";
channel.writeInbound(Unpooled.copiedBuffer(responseWithIllegalChunk, CharsetUtil.US_ASCII));
assertThat(channel.readInbound(), is(instanceOf(HttpResponse.class)));
// Ensure that the decoder generates the last chunk with correct decoder result.
LastHttpContent invalidChunk = (LastHttpContent) channel.readInbound();
assertThat(invalidChunk.getDecoderResult().isFailure(), is(true));
invalidChunk.release();
// And no more messages should be produced by the decoder.
assertThat(channel.readInbound(), is(nullValue()));
// .. even after the connection is closed.
assertThat(channel.finish(), is(false));
}
}