Skip to content

Commit

Permalink
Adjust Content-Length header when encoding Full Responses
Browse files Browse the repository at this point in the history
Motivation:
If a full HttpResponse with a Content-Length header is encoded by the HttpContentEncoder subtypes the Content-Length header is removed and the message is set to Transfer-Encoder: chunked. This is an unnecessary loss of information about the message content.

Modifications:
- If a full HttpResponse has a Content-Length header, the header is adjusted after encoding.

Result:
Complete messages continue to have the Content-Length header after encoding.
  • Loading branch information
Bryce Anderson authored and normanmaurer committed Jun 6, 2017
1 parent f8788a9 commit 9fa3e55
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,18 +164,21 @@ protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> ou
// so that the message looks like a decoded message.
res.headers().set(HttpHeaderNames.CONTENT_ENCODING, result.targetContentEncoding());

// Make the response chunked to simplify content transformation.
res.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);

// Output the rewritten response.
if (isFull) {
// Convert full message into unfull one.
HttpResponse newRes = new DefaultHttpResponse(res.protocolVersion(), res.status());
newRes.headers().set(res.headers());
out.add(newRes);
// Fall through to encode the content of the full response.

ensureContent(res);
encodeFullResponse(newRes, (HttpContent) res, out);
break;
} else {
// Make the response chunked to simplify content transformation.
res.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);

out.add(res);
state = State.AWAIT_CONTENT;
if (!(msg instanceof HttpContent)) {
Expand Down Expand Up @@ -205,6 +208,25 @@ protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> ou
}
}

private void encodeFullResponse(HttpResponse newRes, HttpContent content, List<Object> out) {
int existingMessages = out.size();
encodeContent(content, out);

if (HttpUtil.isContentLengthSet(newRes)) {
// adjust the content-length header
int messageSize = 0;
for (int i = existingMessages; i < out.size(); i++) {
Object item = out.get(i);
if (item instanceof HttpContent) {
messageSize += ((HttpContent) item).content().readableBytes();
}
}
HttpUtil.setContentLength(newRes, messageSize);
} else {
newRes.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
}
}

private static boolean isPassthru(HttpVersion version, int code, CharSequence httpMethod) {
return code < 200 || code == 204 || code == 304 ||
(httpMethod == ZERO_LENGTH_HEAD || (httpMethod == ZERO_LENGTH_CONNECT && code == 200)) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,51 @@ public void testChunkedContentWithTrailingHeader() throws Exception {
}

@Test
public void testFullContent() throws Exception {
public void testFullContentWithContentLength() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
ch.writeInbound(newRequest());

FullHttpResponse res = new DefaultFullHttpResponse(
FullHttpResponse fullRes = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,41 @@ public void testChunkedContentWithTrailingHeader() throws Exception {
assertThat(ch.readOutbound(), is(nullValue()));
}

@Test
public void testFullContentWithContentLength() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new TestEncoder());
ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));

FullHttpResponse fullRes = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(new byte[42]));
fullRes.headers().set(HttpHeaderNames.CONTENT_LENGTH, 42);
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_LENGTH), is("2"));
assertThat(res.headers().get(HttpHeaderNames.CONTENT_ENCODING), is("test"));

HttpContent c = ch.readOutbound();
assertThat(c.content().readableBytes(), is(2));
assertThat(c.content().toString(CharsetUtil.US_ASCII), is("42"));
c.release();

LastHttpContent last = ch.readOutbound();
assertThat(last.content().readableBytes(), is(0));
last.release();

assertThat(ch.readOutbound(), is(nullValue()));
}

@Test
public void testFullContent() throws Exception {
EmbeddedChannel ch = new EmbeddedChannel(new TestEncoder());
ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));

FullHttpResponse res = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(new byte[42]));
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, 42);
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(new byte[42]));
ch.writeOutbound(res);

assertEncodedResponse(ch);
Expand Down

0 comments on commit 9fa3e55

Please sign in to comment.