Adjust Content-Length header when encoding Full Responses
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.
This commit is contained in:
parent
f8788a9f6c
commit
9fa3e556f3
@ -164,18 +164,21 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpReque
|
||||
// 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)) {
|
||||
@ -205,6 +208,25 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpReque
|
||||
}
|
||||
}
|
||||
|
||||
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)) ||
|
||||
|
@ -197,15 +197,52 @@ public class HttpContentCompressorTest {
|
||||
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));
|
||||
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
||||
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
|
||||
ch.writeOutbound(res);
|
||||
|
||||
assertEncodedResponse(ch);
|
||||
|
@ -160,14 +160,41 @@ public class HttpContentEncoderTest {
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user