Skip to content

HttpObjectDecoder ignores HTTP trailer header. #8736

@KowalczykBartek

Description

@KowalczykBartek

Hi Netty team !

Behavior

When I was writing test for #8721 in io.netty.handler.codec.http.HttpContentDecoderTest I encountered a problem with parsing trailer header using HttpObjectDecoder.
HttpObjectDecoder is not able to handle following bytes sequence

assertTrue(channel.writeInbound(Unpooled.copiedBuffer("My-Trailer: 42", CharsetUtil.US_ASCII)));
assertTrue(channel.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
assertTrue(channel.writeInbound(Unpooled.copiedBuffer("\r\n\r\n", CharsetUtil.US_ASCII)));

but is able to deal with that

assertTrue(channel.writeInbound(Unpooled.copiedBuffer("My-Trailer: 42\r\n\r\n\r\n", CharsetUtil.US_ASCII)));

After small debugging session, I noticed that HttpObjectDecoder is actually parsing trailer but that line of code (io.netty.handler.codec.http.HttpObjectDecoder:674)

line = headerParser.parse(buffer);
if (line == null) {
    return null;
}

cause to ignore my trailer, because headerParser.parse(buffer) on empty buffer will return null instead of parsed trailer.

Minimal yet complete reproducer code (or URL to code)

Didn't want to make pull-request (almost exact test is already pushed here #8721), but following test (class io.netty.handler.codec.http.HttpContentDecoderTest) will reproduce the problem

@Test
public void testChunkedRequestDecompression() {
    HttpResponseDecoder decoder = new HttpResponseDecoder();
    HttpContentDecoder decompressor = new HttpContentDecompressor();

    EmbeddedChannel channel = new EmbeddedChannel(decoder, decompressor, null);

    String headers = "HTTP/1.1 200 OK\r\n"
            + "Transfer-Encoding: chunked\r\n"
            + "Trailer: My-Trailer\r\n"
            + "Content-Encoding: gzip\r\n\r\n";

    channel.writeInbound(Unpooled.copiedBuffer((headers.getBytes(CharsetUtil.US_ASCII))));

    assertTrue(channel.writeInbound(Unpooled.copiedBuffer(Integer.toHexString(GZ_HELLO_WORLD.length) + "\r\n", CharsetUtil.US_ASCII)));
    assertTrue(channel.writeInbound(Unpooled.copiedBuffer(GZ_HELLO_WORLD)));
    assertTrue(channel.writeInbound(Unpooled.copiedBuffer("\r\n".getBytes(CharsetUtil.US_ASCII))));
    assertTrue(channel.writeInbound(Unpooled.copiedBuffer("0\r\n", CharsetUtil.US_ASCII)));
    assertTrue(channel.writeInbound(Unpooled.copiedBuffer("My-Trailer: 42", CharsetUtil.US_ASCII)));
    assertTrue(channel.writeInbound(Unpooled.copiedBuffer("\r\n", CharsetUtil.US_ASCII)));
    assertTrue(channel.writeInbound(Unpooled.copiedBuffer("\r\n\r\n", CharsetUtil.US_ASCII)));

    Object ob1 = channel.readInbound();
    assertThat(ob1, is(instanceOf(DefaultHttpResponse.class)));

    Object ob2 = channel.readInbound();
    assertThat(ob1, is(instanceOf(DefaultHttpResponse.class)));
    HttpContent content = (HttpContent) ob2;
    assertEquals(HELLO_WORLD, content.content().toString(CharsetUtil.US_ASCII));

    Object ob3 = channel.readInbound();
    assertThat(ob1, is(instanceOf(DefaultHttpResponse.class)));
    LastHttpContent lastContent = (LastHttpContent) ob3;
    assertFalse(lastContent.trailingHeaders().isEmpty());
    assertEquals("42", lastContent.trailingHeaders().get("My-Trailer"));
    assertHasInboundMessages(channel, false);
    assertHasOutboundMessages(channel, false);
    assertFalse(channel.finish());
}

Netty version

4.1.32.Final

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions