Introduce HttpMessageDecoderResult to expose decoded header size (#11068)
Motivation The HttpObjectDecoder accepts input parameters for maxInitialLineLength and maxHeaderSize. These are important variables since both message components must be buffered in memory. As such, many decoders (like Netty and others) introduce constraints. Due to their importance, many users may wish to add instrumentation on the values of successful decoder results, or otherwise be able to access these values to enforce their own supplemental constraints. While users can perhaps estimate the sizes today, they will not be exact, due to the decoder being responsible for consuming optional whitespace and the like. Modifications * Add HttpMessageDecoderResult class. This class extends DecoderResult and is intended for HttpMessage objects successfully decoded by the HttpObjectDecoder. It exposes attributes for the decoded initialLineLength and headerSize. * Modify HttpObjectDecoder to produce HttpMessageDecoderResults upon successfully decoding the last HTTP header. * Add corresponding tests to HttpRequestDecoderTest & HttpResponseDecoderTest. Co-authored-by: Bennett Lynch <Bennett-Lynch@users.noreply.github.com>
This commit is contained in:
parent
24b5a21e46
commit
3f23f59b87
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.handler.codec.http;
|
||||||
|
|
||||||
|
import io.netty.handler.codec.DecoderResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link DecoderResult} for {@link HttpMessage}s as produced by an {@link HttpObjectDecoder}.
|
||||||
|
* <p>
|
||||||
|
* Please note that there is no guarantee that a {@link HttpObjectDecoder} will produce a {@link
|
||||||
|
* HttpMessageDecoderResult}. It may simply produce a regular {@link DecoderResult}. This result is intended for
|
||||||
|
* successful {@link HttpMessage} decoder results.
|
||||||
|
*/
|
||||||
|
public final class HttpMessageDecoderResult extends DecoderResult {
|
||||||
|
|
||||||
|
private final int initialLineLength;
|
||||||
|
private final int headerSize;
|
||||||
|
|
||||||
|
HttpMessageDecoderResult(int initialLineLength, int headerSize) {
|
||||||
|
super(SIGNAL_SUCCESS);
|
||||||
|
this.initialLineLength = initialLineLength;
|
||||||
|
this.headerSize = headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded initial line length (in bytes), as controlled by {@code maxInitialLineLength}.
|
||||||
|
*/
|
||||||
|
public int initialLineLength() {
|
||||||
|
return initialLineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded header size (in bytes), as controlled by {@code maxHeaderSize}.
|
||||||
|
*/
|
||||||
|
public int headerSize() {
|
||||||
|
return headerSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoded initial line length plus the decoded header size (in bytes).
|
||||||
|
*/
|
||||||
|
public int totalSize() {
|
||||||
|
return initialLineLength + headerSize;
|
||||||
|
}
|
||||||
|
}
|
@ -628,6 +628,10 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
|||||||
name = null;
|
name = null;
|
||||||
value = null;
|
value = null;
|
||||||
|
|
||||||
|
// Done parsing initial line and headers. Set decoder result.
|
||||||
|
HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(lineParser.size, headerParser.size);
|
||||||
|
message.setDecoderResult(decoderResult);
|
||||||
|
|
||||||
List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
|
List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
|
||||||
if (!contentLengthFields.isEmpty()) {
|
if (!contentLengthFields.isEmpty()) {
|
||||||
HttpVersion version = message.protocolVersion();
|
HttpVersion version = message.protocolVersion();
|
||||||
@ -893,7 +897,7 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
|||||||
private static class HeaderParser implements ByteProcessor {
|
private static class HeaderParser implements ByteProcessor {
|
||||||
private final AppendableCharSequence seq;
|
private final AppendableCharSequence seq;
|
||||||
private final int maxLength;
|
private final int maxLength;
|
||||||
private int size;
|
int size;
|
||||||
|
|
||||||
HeaderParser(AppendableCharSequence seq, int maxLength) {
|
HeaderParser(AppendableCharSequence seq, int maxLength) {
|
||||||
this.seq = seq;
|
this.seq = seq;
|
||||||
|
@ -20,6 +20,7 @@ import io.netty.channel.embedded.EmbeddedChannel;
|
|||||||
import io.netty.handler.codec.TooLongFrameException;
|
import io.netty.handler.codec.TooLongFrameException;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import io.netty.util.CharsetUtil;
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -478,6 +479,26 @@ public class HttpRequestDecoderTest {
|
|||||||
assertFalse(channel.finish());
|
assertFalse(channel.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHttpMessageDecoderResult() {
|
||||||
|
String requestStr = "PUT /some/path HTTP/1.1\r\n" +
|
||||||
|
"Content-Length: 11\r\n" +
|
||||||
|
"Connection: close\r\n\r\n" +
|
||||||
|
"Lorem ipsum";
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
|
||||||
|
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
|
||||||
|
HttpRequest request = channel.readInbound();
|
||||||
|
assertTrue(request.decoderResult().isSuccess());
|
||||||
|
assertThat(request.decoderResult(), instanceOf(HttpMessageDecoderResult.class));
|
||||||
|
HttpMessageDecoderResult decoderResult = (HttpMessageDecoderResult) request.decoderResult();
|
||||||
|
assertThat(decoderResult.initialLineLength(), is(23));
|
||||||
|
assertThat(decoderResult.headerSize(), is(35));
|
||||||
|
assertThat(decoderResult.totalSize(), is(58));
|
||||||
|
HttpContent c = channel.readInbound();
|
||||||
|
c.release();
|
||||||
|
assertFalse(channel.finish());
|
||||||
|
}
|
||||||
|
|
||||||
private static void testInvalidHeaders0(String requestStr) {
|
private static void testInvalidHeaders0(String requestStr) {
|
||||||
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
|
EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
|
||||||
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
|
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
|
||||||
|
@ -727,4 +727,24 @@ public class HttpResponseDecoderTest {
|
|||||||
assertEquals("netty.io", response.headers().get(HttpHeaderNames.HOST));
|
assertEquals("netty.io", response.headers().get(HttpHeaderNames.HOST));
|
||||||
assertFalse(channel.finish());
|
assertFalse(channel.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHttpMessageDecoderResult() {
|
||||||
|
String responseStr = "HTTP/1.1 200 OK\r\n" +
|
||||||
|
"Content-Length: 11\r\n" +
|
||||||
|
"Connection: close\r\n\r\n" +
|
||||||
|
"Lorem ipsum";
|
||||||
|
EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseDecoder());
|
||||||
|
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(responseStr, CharsetUtil.US_ASCII)));
|
||||||
|
HttpResponse response = channel.readInbound();
|
||||||
|
assertTrue(response.decoderResult().isSuccess());
|
||||||
|
assertThat(response.decoderResult(), instanceOf(HttpMessageDecoderResult.class));
|
||||||
|
HttpMessageDecoderResult decoderResult = (HttpMessageDecoderResult) response.decoderResult();
|
||||||
|
assertThat(decoderResult.initialLineLength(), is(15));
|
||||||
|
assertThat(decoderResult.headerSize(), is(35));
|
||||||
|
assertThat(decoderResult.totalSize(), is(50));
|
||||||
|
HttpContent c = channel.readInbound();
|
||||||
|
c.release();
|
||||||
|
assertFalse(channel.finish());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user