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.
|
// so that the message looks like a decoded message.
|
||||||
res.headers().set(HttpHeaderNames.CONTENT_ENCODING, result.targetContentEncoding());
|
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.
|
// Output the rewritten response.
|
||||||
if (isFull) {
|
if (isFull) {
|
||||||
// Convert full message into unfull one.
|
// Convert full message into unfull one.
|
||||||
HttpResponse newRes = new DefaultHttpResponse(res.protocolVersion(), res.status());
|
HttpResponse newRes = new DefaultHttpResponse(res.protocolVersion(), res.status());
|
||||||
newRes.headers().set(res.headers());
|
newRes.headers().set(res.headers());
|
||||||
out.add(newRes);
|
out.add(newRes);
|
||||||
// Fall through to encode the content of the full response.
|
|
||||||
|
ensureContent(res);
|
||||||
|
encodeFullResponse(newRes, (HttpContent) res, out);
|
||||||
|
break;
|
||||||
} else {
|
} 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);
|
out.add(res);
|
||||||
state = State.AWAIT_CONTENT;
|
state = State.AWAIT_CONTENT;
|
||||||
if (!(msg instanceof HttpContent)) {
|
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) {
|
private static boolean isPassthru(HttpVersion version, int code, CharSequence httpMethod) {
|
||||||
return code < 200 || code == 204 || code == 304 ||
|
return code < 200 || code == 204 || code == 304 ||
|
||||||
(httpMethod == ZERO_LENGTH_HEAD || (httpMethod == ZERO_LENGTH_CONNECT && code == 200)) ||
|
(httpMethod == ZERO_LENGTH_HEAD || (httpMethod == ZERO_LENGTH_CONNECT && code == 200)) ||
|
||||||
|
@ -197,15 +197,52 @@ public class HttpContentCompressorTest {
|
|||||||
assertThat(ch.readOutbound(), is(nullValue()));
|
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
|
@Test
|
||||||
public void testFullContent() throws Exception {
|
public void testFullContent() throws Exception {
|
||||||
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
|
EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
|
||||||
ch.writeInbound(newRequest());
|
ch.writeInbound(newRequest());
|
||||||
|
|
||||||
FullHttpResponse res = new DefaultFullHttpResponse(
|
FullHttpResponse res = new DefaultFullHttpResponse(
|
||||||
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
||||||
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
|
Unpooled.copiedBuffer("Hello, World", CharsetUtil.US_ASCII));
|
||||||
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, res.content().readableBytes());
|
|
||||||
ch.writeOutbound(res);
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
assertEncodedResponse(ch);
|
assertEncodedResponse(ch);
|
||||||
|
@ -160,14 +160,41 @@ public class HttpContentEncoderTest {
|
|||||||
assertThat(ch.readOutbound(), is(nullValue()));
|
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
|
@Test
|
||||||
public void testFullContent() throws Exception {
|
public void testFullContent() throws Exception {
|
||||||
EmbeddedChannel ch = new EmbeddedChannel(new TestEncoder());
|
EmbeddedChannel ch = new EmbeddedChannel(new TestEncoder());
|
||||||
ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
|
ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
|
||||||
|
|
||||||
FullHttpResponse res = new DefaultFullHttpResponse(
|
FullHttpResponse res = new DefaultFullHttpResponse(
|
||||||
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(new byte[42]));
|
HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(new byte[42]));
|
||||||
res.headers().set(HttpHeaderNames.CONTENT_LENGTH, 42);
|
|
||||||
ch.writeOutbound(res);
|
ch.writeOutbound(res);
|
||||||
|
|
||||||
assertEncodedResponse(ch);
|
assertEncodedResponse(ch);
|
||||||
|
Loading…
Reference in New Issue
Block a user