Remove unnecessary code from HttpObjectDecoder and re-enable all HTTP tests

- Since Netty 4, HTTP decoder does not generate a full message at all.  Therefore, there's no need to keep separate states for the content smaller than maxChunkSize.
- maxChunkSize must be greater than 0. Setting it to 0 should not disable chunked encoding. We have a dedicated flag for that.
- Uncommented the tests that were commented out for an unknown reason, with some fixes.
- Added more tests for HTTP decoder.
- Removed the Ignore annotation on some tests.
This commit is contained in:
Trustin Lee 2013-12-17 00:17:35 +09:00
parent 3e8a1ed611
commit fa336db891
4 changed files with 219 additions and 225 deletions

View File

@ -26,7 +26,7 @@ import io.netty.util.internal.AppendableCharSequence;
import java.util.List;
import static io.netty.buffer.ByteBufUtil.readBytes;
import static io.netty.buffer.ByteBufUtil.*;
/**
* Decodes {@link ByteBuf}s into {@link HttpMessage}s and
@ -107,11 +107,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
private final boolean chunkedSupported;
protected final boolean validateHeaders;
private ByteBuf content;
private HttpMessage message;
private long chunkSize;
private int headerSize;
private int contentRead;
private final AppendableCharSequence sb = new AppendableCharSequence(128);
/**
@ -123,12 +121,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
READ_INITIAL,
READ_HEADER,
READ_VARIABLE_LENGTH_CONTENT,
READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS,
READ_FIXED_LENGTH_CONTENT,
READ_FIXED_LENGTH_CONTENT_AS_CHUNKS,
READ_CHUNK_SIZE,
READ_CHUNKED_CONTENT,
READ_CHUNKED_CONTENT_AS_CHUNKS,
READ_CHUNK_DELIMITER,
READ_CHUNK_FOOTER,
BAD_MESSAGE
@ -170,7 +165,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
"maxHeaderSize must be a positive integer: " +
maxHeaderSize);
}
if (maxChunkSize < 0) {
if (maxChunkSize <= 0) {
throw new IllegalArgumentException(
"maxChunkSize must be a positive integer: " +
maxChunkSize);
@ -221,39 +216,28 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
}
if (nextState == State.SKIP_CONTROL_CHARS) {
// No content is expected.
reset(out);
out.add(message);
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
reset();
return;
}
long contentLength = HttpHeaders.getContentLength(message, -1);
if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
content = Unpooled.EMPTY_BUFFER;
reset(out);
out.add(message);
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
reset();
return;
}
switch (nextState) {
case READ_FIXED_LENGTH_CONTENT:
if (contentLength > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
// Generate FullHttpMessage first. HttpChunks will follow.
checkpoint(State.READ_FIXED_LENGTH_CONTENT_AS_CHUNKS);
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT_AS_CHUNKS
// state reads data chunk by chunk.
chunkSize = HttpHeaders.getContentLength(message, -1);
out.add(message);
return;
}
break;
case READ_VARIABLE_LENGTH_CONTENT:
if (buffer.readableBytes() > maxChunkSize || HttpHeaders.is100ContinueExpected(message)) {
// Generate FullHttpMessage first. HttpChunks will follow.
checkpoint(State.READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS);
out.add(message);
return;
}
break;
default:
throw new IllegalStateException("Unexpected state: " + nextState);
assert nextState == State.READ_FIXED_LENGTH_CONTENT || nextState == State.READ_VARIABLE_LENGTH_CONTENT;
out.add(message);
if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by chunk.
chunkSize = HttpHeaders.getContentLength(message, -1);
}
// We return here, this forces decode to be called again where we will decode the content
return;
} catch (Exception e) {
@ -261,35 +245,25 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
return;
}
case READ_VARIABLE_LENGTH_CONTENT: {
int toRead = actualReadableBytes();
if (toRead > maxChunkSize) {
toRead = maxChunkSize;
}
out.add(message);
out.add(new DefaultHttpContent(readBytes(ctx.alloc(), buffer, toRead)));
return;
}
case READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS: {
// Keep reading data as a chunk until the end of connection is reached.
int toRead = actualReadableBytes();
if (toRead > maxChunkSize) {
toRead = maxChunkSize;
}
ByteBuf content = readBytes(ctx.alloc(), buffer, toRead);
if (!buffer.isReadable()) {
int toRead = Math.min(actualReadableBytes(), maxChunkSize);
if (toRead > 0) {
ByteBuf content = readBytes(ctx.alloc(), buffer, toRead);
if (buffer.isReadable()) {
out.add(new DefaultHttpContent(content));
} else {
// End of connection.
out.add(new DefaultLastHttpContent(content, validateHeaders));
reset();
}
} else if (!buffer.isReadable()) {
// End of connection.
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
reset();
out.add(new DefaultLastHttpContent(content, validateHeaders));
return;
}
out.add(new DefaultHttpContent(content));
return;
}
case READ_FIXED_LENGTH_CONTENT: {
readFixedLengthContent(ctx, buffer, out);
return;
}
case READ_FIXED_LENGTH_CONTENT_AS_CHUNKS: {
long chunkSize = this.chunkSize;
int readLimit = actualReadableBytes();
// Check if the buffer is readable first as we use the readable byte count
@ -302,28 +276,20 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
return;
}
int toRead = readLimit;
if (toRead > maxChunkSize) {
toRead = maxChunkSize;
}
int toRead = Math.min(readLimit, maxChunkSize);
if (toRead > chunkSize) {
toRead = (int) chunkSize;
}
ByteBuf content = readBytes(ctx.alloc(), buffer, toRead);
if (chunkSize > toRead) {
chunkSize -= toRead;
} else {
chunkSize = 0;
}
this.chunkSize = chunkSize;
chunkSize -= toRead;
if (chunkSize == 0) {
// Read all content.
reset();
out.add(new DefaultLastHttpContent(content, validateHeaders));
return;
reset();
} else {
out.add(new DefaultHttpContent(content));
}
out.add(new DefaultHttpContent(content));
return;
}
/**
@ -337,9 +303,6 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
if (chunkSize == 0) {
checkpoint(State.READ_CHUNK_FOOTER);
return;
} else if (chunkSize > maxChunkSize) {
// A chunk is too large. Split them into multiple chunks again.
checkpoint(State.READ_CHUNKED_CONTENT_AS_CHUNKS);
} else {
checkpoint(State.READ_CHUNKED_CONTENT);
}
@ -349,48 +312,19 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
}
case READ_CHUNKED_CONTENT: {
assert chunkSize <= Integer.MAX_VALUE;
HttpContent chunk = new DefaultHttpContent(readBytes(ctx.alloc(), buffer, (int) chunkSize));
checkpoint(State.READ_CHUNK_DELIMITER);
out.add(chunk);
return;
}
case READ_CHUNKED_CONTENT_AS_CHUNKS: {
assert chunkSize <= Integer.MAX_VALUE;
int chunkSize = (int) this.chunkSize;
int readLimit = actualReadableBytes();
int toRead = Math.min((int) chunkSize, maxChunkSize);
// Check if the buffer is readable first as we use the readable byte count
// to create the HttpChunk. This is needed as otherwise we may end up with
// create a HttpChunk instance that contains an empty buffer and so is
// handled like it is the last HttpChunk.
//
// See https://github.com/netty/netty/issues/433
if (readLimit == 0) {
return;
}
int toRead = chunkSize;
if (toRead > maxChunkSize) {
toRead = maxChunkSize;
}
if (toRead > readLimit) {
toRead = readLimit;
}
HttpContent chunk = new DefaultHttpContent(readBytes(ctx.alloc(), buffer, toRead));
if (chunkSize > toRead) {
chunkSize -= toRead;
} else {
chunkSize = 0;
}
this.chunkSize = chunkSize;
chunkSize -= toRead;
out.add(chunk);
if (chunkSize == 0) {
// Read all content.
checkpoint(State.READ_CHUNK_DELIMITER);
} else {
return;
}
out.add(chunk);
return;
}
case READ_CHUNK_DELIMITER: {
for (;;) {
@ -410,16 +344,9 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
}
case READ_CHUNK_FOOTER: try {
LastHttpContent trailer = readTrailingHeaders(buffer);
if (maxChunkSize == 0) {
// Chunked encoding disabled.
reset(out);
return;
} else {
reset();
// The last chunk, which is empty
out.add(trailer);
return;
}
out.add(trailer);
reset();
return;
} catch (Exception e) {
out.add(invalidChunk(e));
return;
@ -427,10 +354,6 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
case BAD_MESSAGE: {
// Keep discarding until disconnection.
buffer.skipBytes(actualReadableBytes());
return;
}
default: {
throw new Error("Shouldn't reach here.");
}
}
}
@ -441,14 +364,8 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
// Handle the last unfinished message.
if (message != null) {
// Get the length of the content received so far for the last message.
HttpMessage message = this.message;
int actualContentLength;
if (content != null) {
actualContentLength = content.readableBytes();
} else {
actualContentLength = 0;
}
this.message = null;
// Check if the closure of the connection signifies the end of the content.
boolean prematureClosure;
@ -460,15 +377,11 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
// If the 'Content-Length' header is absent, the length of the content is determined by the end of the
// connection, so it is perfectly fine.
long expectedContentLength = HttpHeaders.getContentLength(message, -1);
prematureClosure = expectedContentLength >= 0 && actualContentLength != expectedContentLength;
prematureClosure = expectedContentLength > 0;
}
if (!prematureClosure) {
if (actualContentLength == 0) {
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
} else {
out.add(new DefaultLastHttpContent(content, validateHeaders));
}
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
}
}
}
@ -484,11 +397,8 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
// - http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html Section 4.4
// - https://github.com/netty/netty/issues/222
if (code >= 100 && code < 200) {
if (code == 101 && !res.headers().contains(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT)) {
// It's Hixie 76 websocket handshake response
return false;
}
return true;
// One exception: Hixie 76 websocket handshake response
return !(code == 101 && !res.headers().contains(HttpHeaders.Names.SEC_WEBSOCKET_ACCEPT));
}
switch (code) {
@ -500,28 +410,7 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
}
private void reset() {
reset(null);
}
private void reset(List<Object> out) {
if (out != null) {
HttpMessage message = this.message;
ByteBuf content = this.content;
LastHttpContent httpContent;
if (content == null || !content.isReadable()) {
httpContent = LastHttpContent.EMPTY_LAST_CONTENT;
} else {
httpContent = new DefaultLastHttpContent(content, validateHeaders);
}
out.add(message);
out.add(httpContent);
}
content = null;
message = null;
checkpoint(State.SKIP_CONTROL_CHARS);
}
@ -554,28 +443,6 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder<HttpObjectDecod
}
}
private void readFixedLengthContent(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
//we have a content-length so we just read the correct number of bytes
long length = HttpHeaders.getContentLength(message, -1);
assert length <= Integer.MAX_VALUE;
int toRead = (int) length - contentRead;
if (toRead > actualReadableBytes()) {
toRead = actualReadableBytes();
}
contentRead += toRead;
if (length < contentRead) {
out.add(message);
out.add(new DefaultHttpContent(readBytes(ctx.alloc(), buffer, toRead)));
return;
}
if (content == null) {
content = readBytes(ctx.alloc(), buffer, (int) length);
} else {
content.writeBytes(buffer, (int) length);
}
reset(out);
}
private State readHeaders(ByteBuf buffer) {
headerSize = 0;
final HttpMessage message = this.message;

View File

@ -20,7 +20,6 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.DecoderResult;
import io.netty.util.CharsetUtil;
import org.junit.Ignore;
import org.junit.Test;
import java.util.Random;
@ -42,7 +41,6 @@ public class HttpInvalidMessageTest {
ensureInboundTrafficDiscarded(ch);
}
@Ignore("expected ATM")
@Test
public void testRequestWithBadHeader() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpRequestDecoder());
@ -70,7 +68,6 @@ public class HttpInvalidMessageTest {
ensureInboundTrafficDiscarded(ch);
}
@Ignore("expected ATM")
@Test
public void testResponseWithBadHeader() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder());

View File

@ -19,15 +19,17 @@ package io.netty.handler.codec.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.*;
public class HttpRequestDecoderTest {
private static final byte[] CONTENT_CRLF_DELIMITERS = createContent("\r\n");
private static final byte[] CONTENT_LF_DELIMITERS = createContent("\n");
private static final byte[] CONTENT_MIXED_DELIMITERS = createContent("\r\n", "\n");
private static final int CONTENT_LENGTH = 8;
private static byte[] createContent(String... lineDelimiters) {
String lineDelimiter;
@ -46,7 +48,7 @@ public class HttpRequestDecoderTest {
"Origin: http://localhost:8080" + lineDelimiter +
"Sec-WebSocket-Key1: 10 28 8V7 8 48 0" + lineDelimiter2 +
"Sec-WebSocket-Key2: 8 Xt754O3Q3QW 0 _60" + lineDelimiter +
"Content-Length: 8" + lineDelimiter2 +
"Content-Length: " + CONTENT_LENGTH + lineDelimiter2 +
"\r\n" +
"12345678").getBytes(CharsetUtil.US_ASCII);
}
@ -68,34 +70,36 @@ public class HttpRequestDecoderTest {
private static void testDecodeWholeRequestAtOnce(byte[] content) {
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
Assert.assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(content)));
assertTrue(channel.writeInbound(Unpooled.wrappedBuffer(content)));
HttpRequest req = channel.readInbound();
Assert.assertNotNull(req);
assertNotNull(req);
checkHeaders(req.headers());
LastHttpContent c = channel.readInbound();
Assert.assertEquals(8, c.content().readableBytes());
Assert.assertEquals(Unpooled.wrappedBuffer(content, content.length - 8, 8), c.content().readBytes(8));
assertEquals(CONTENT_LENGTH, c.content().readableBytes());
assertEquals(
Unpooled.wrappedBuffer(content, content.length - CONTENT_LENGTH, CONTENT_LENGTH),
c.content().readBytes(CONTENT_LENGTH));
c.release();
Assert.assertFalse(channel.finish());
Assert.assertNull(channel.readInbound());
assertFalse(channel.finish());
assertNull(channel.readInbound());
}
private static void checkHeaders(HttpHeaders headers) {
Assert.assertEquals(7, headers.names().size());
assertEquals(7, headers.names().size());
checkHeader(headers, "Upgrade", "WebSocket");
checkHeader(headers, "Connection", "Upgrade");
checkHeader(headers, "Host", "localhost");
checkHeader(headers, "Origin", "http://localhost:8080");
checkHeader(headers, "Sec-WebSocket-Key1", "10 28 8V7 8 48 0");
checkHeader(headers, "Sec-WebSocket-Key2", "8 Xt754O3Q3QW 0 _60");
checkHeader(headers, "Content-Length", "8");
checkHeader(headers, "Content-Length", String.valueOf(CONTENT_LENGTH));
}
private static void checkHeader(HttpHeaders headers, String name, String value) {
List<String> header1 = headers.getAll(name);
Assert.assertEquals(1, header1.size());
Assert.assertEquals(value, header1.get(0));
assertEquals(1, header1.size());
assertEquals(value, header1.get(0));
}
@Test
@ -121,7 +125,7 @@ public class HttpRequestDecoderTest {
private static void testDecodeWholeRequestInMultipleSteps(byte[] content, int fragmentSize) {
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
int headerLength = content.length - 8;
int headerLength = content.length - CONTENT_LENGTH;
// split up the header
for (int a = 0; a < headerLength;) {
@ -136,24 +140,29 @@ public class HttpRequestDecoderTest {
a += amount;
}
for (int i = 8; i > 0; i--) {
for (int i = CONTENT_LENGTH; i > 0; i --) {
// Should produce HttpContent
channel.writeInbound(Unpooled.wrappedBuffer(content, content.length - i, 1));
}
HttpRequest req = channel.readInbound();
Assert.assertNotNull(req);
assertNotNull(req);
checkHeaders(req.headers());
LastHttpContent c = channel.readInbound();
Assert.assertEquals(8, c.content().readableBytes());
for (int i = 8; i > 1; i--) {
Assert.assertEquals(content[content.length - i], c.content().readByte());
for (int i = CONTENT_LENGTH; i > 1; i --) {
HttpContent c = channel.readInbound();
assertEquals(1, c.content().readableBytes());
assertEquals(content[content.length - i], c.content().readByte());
c.release();
}
LastHttpContent c = channel.readInbound();
assertEquals(1, c.content().readableBytes());
assertEquals(content[content.length - 1], c.content().readByte());
c.release();
Assert.assertFalse(channel.finish());
Assert.assertNull(channel.readInbound());
assertFalse(channel.finish());
assertNull(channel.readInbound());
}
@Test
@ -165,6 +174,6 @@ public class HttpRequestDecoderTest {
"EmptyHeader:" + crlf + crlf;
channel.writeInbound(Unpooled.wrappedBuffer(request.getBytes(CharsetUtil.US_ASCII)));
HttpRequest req = channel.readInbound();
Assert.assertEquals("", req.headers().get("EmptyHeader"));
assertEquals("", req.headers().get("EmptyHeader"));
}
}

View File

@ -17,6 +17,7 @@ package io.netty.handler.codec.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.util.CharsetUtil;
import org.junit.Test;
@ -27,14 +28,13 @@ import static org.junit.Assert.*;
public class HttpResponseDecoderTest {
/*
@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();
HttpResponse res = ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
@ -47,7 +47,7 @@ public class HttpResponseDecoderTest {
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();
HttpContent content = ch.readInbound();
assertEquals(data.length, content.content().readableBytes());
byte[] decodedData = new byte[data.length];
@ -57,22 +57,26 @@ public class HttpResponseDecoderTest {
assertFalse(ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
}
assertTrue(ch.finish());
LastHttpContent content = (LastHttpContent) ch.readInbound();
// Write the last chunk.
ch.writeInbound(Unpooled.copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII));
// Ensure the last chunk was decoded.
LastHttpContent content = 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));
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();
HttpResponse res = ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
@ -87,11 +91,11 @@ public class HttpResponseDecoderTest {
assertTrue(ch.writeInbound(Unpooled.wrappedBuffer(data)));
byte[] decodedData = new byte[data.length];
HttpContent content = (HttpContent) ch.readInbound();
HttpContent content = ch.readInbound();
assertEquals(32, content.content().readableBytes());
content.content().readBytes(decodedData, 0, 32);
content = (HttpContent) ch.readInbound();
content = ch.readInbound();
assertEquals(32, content.content().readableBytes());
content.content().readBytes(decodedData, 32, 32);
@ -101,15 +105,122 @@ public class HttpResponseDecoderTest {
assertFalse(ch.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
}
assertTrue(ch.finish());
LastHttpContent content = (LastHttpContent) ch.readInbound();
// Write the last chunk.
ch.writeInbound(Unpooled.copiedBuffer("0\r\n\r\n", CharsetUtil.US_ASCII));
// Ensure the last chunk was decoded.
LastHttpContent content = 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 = 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 = 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 = ch.readInbound();
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
// Read the partial content.
HttpContent content = 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 = 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 = 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 = 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 = 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() {
@ -281,10 +392,15 @@ public class HttpResponseDecoderTest {
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
LastHttpContent content = ch.readInbound();
assertEquals(10, content.content().readableBytes());
assertEquals(Unpooled.wrappedBuffer(data), content.content());
content.release();
HttpContent firstContent = ch.readInbound();
assertThat(firstContent.content().readableBytes(), is(5));
assertEquals(Unpooled.wrappedBuffer(data, 0, 5), firstContent.content());
firstContent.release();
LastHttpContent lastContent = 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()));
@ -324,9 +440,14 @@ public class HttpResponseDecoderTest {
assertThat(res.getProtocolVersion(), sameInstance(HttpVersion.HTTP_1_1));
assertThat(res.getStatus(), is(HttpResponseStatus.OK));
HttpContent firstContent = ch.readInbound();
assertThat(firstContent.content().readableBytes(), is(5));
assertEquals(Unpooled.wrappedBuffer(data, 0, 5), firstContent.content());
firstContent.release();
LastHttpContent lastContent = ch.readInbound();
assertEquals(10, lastContent.content().readableBytes());
assertEquals(Unpooled.wrappedBuffer(data), lastContent.content());
assertEquals(5, lastContent.content().readableBytes());
assertEquals(Unpooled.wrappedBuffer(data, 5, 5), lastContent.content());
lastContent.release();
assertThat(ch.finish(), is(false));