From 673ba2eec1fce5c03f5bb5c41050571810c2f0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Thu, 4 Jan 2018 14:08:44 +0100 Subject: [PATCH] fix #150 Expose Netty HttpServerCodec options in HttpServer This commits exposes options from Netty's HttpServerCodec (that is maxInitialLineLength, maxHeaderSize and maxChunkSize) through our HttpServerOptions. This should allow the codec to decode requests on very long URIs. --- .../ipc/netty/http/server/HttpServer.java | 5 +- .../netty/http/server/HttpServerOptions.java | 72 ++++++- .../http/server/HttpServerOptionsTest.java | 192 +++++++++++++++++- 3 files changed, 260 insertions(+), 9 deletions(-) diff --git a/src/main/java/reactor/ipc/netty/http/server/HttpServer.java b/src/main/java/reactor/ipc/netty/http/server/HttpServer.java index d1539a0c0d..d675ae6067 100644 --- a/src/main/java/reactor/ipc/netty/http/server/HttpServer.java +++ b/src/main/java/reactor/ipc/netty/http/server/HttpServer.java @@ -254,7 +254,10 @@ protected ContextHandler doHandler( @Override public void accept(ChannelPipeline p, ContextHandler c) { - p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec()); + p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec( + options.httpCodecMaxInitialLineLength(), + options.httpCodecMaxHeaderSize(), + options.httpCodecMaxChunkSize())); if(options.minCompressionResponseSize() >= 0) { p.addLast(NettyPipeline.CompressionHandler, new CompressionHandler(options.minCompressionResponseSize())); diff --git a/src/main/java/reactor/ipc/netty/http/server/HttpServerOptions.java b/src/main/java/reactor/ipc/netty/http/server/HttpServerOptions.java index 58fb79421f..f1c5097cfa 100644 --- a/src/main/java/reactor/ipc/netty/http/server/HttpServerOptions.java +++ b/src/main/java/reactor/ipc/netty/http/server/HttpServerOptions.java @@ -38,10 +38,16 @@ public static HttpServerOptions.Builder builder() { } private final int minCompressionResponseSize; + private final int maxInitialLineLength; + private final int maxHeaderSize; + private final int maxChunkSize; private HttpServerOptions(HttpServerOptions.Builder builder) { super(builder); this.minCompressionResponseSize = builder.minCompressionResponseSize; + this.maxInitialLineLength = builder.maxInitialLineLength; + this.maxHeaderSize = builder.maxHeaderSize; + this.maxChunkSize = builder.maxChunkSize; } /** @@ -54,6 +60,36 @@ public int minCompressionResponseSize() { return minCompressionResponseSize; } + /** + * Returns the maximum length configured for the initial HTTP line. + * + * @return the initial HTTP line maximum length + * @see io.netty.handler.codec.http.HttpServerCodec + */ + public int httpCodecMaxInitialLineLength() { + return maxInitialLineLength; + } + + /** + * Returns the configured HTTP header maximum size. + * + * @return the configured HTTP header maximum size + * @see io.netty.handler.codec.http.HttpServerCodec + */ + public int httpCodecMaxHeaderSize() { + return maxHeaderSize; + } + + /** + * Returns the configured HTTP chunk maximum size. + * + * @return the configured HTTP chunk maximum size + * @see io.netty.handler.codec.http.HttpServerCodec + */ + public int httpCodecMaxChunkSize() { + return maxChunkSize; + } + @Override public HttpServerOptions duplicate() { return builder().from(this).build(); @@ -76,7 +112,9 @@ public String asSimpleString() { @Override public String asDetailedString() { return super.asDetailedString() + - ", minCompressionResponseSize=" + minCompressionResponseSize; + ", minCompressionResponseSize=" + minCompressionResponseSize + + ", httpCodecSizes={initialLine=" + this.maxInitialLineLength + + ",header=" + this.maxHeaderSize + ",chunk="+ this.maxChunkSize + "}"; } @Override @@ -86,6 +124,9 @@ public String toString() { public static final class Builder extends ServerOptions.Builder { private int minCompressionResponseSize = -1; + private int maxInitialLineLength = 4096; + private int maxHeaderSize = 8192; + private int maxChunkSize = 8192; private Builder(){ super(new ServerBootstrap()); @@ -120,6 +161,32 @@ public final Builder compression(int minResponseSize) { return get(); } + /** + * Configure the {@link io.netty.handler.codec.http.HttpServerCodec HTTP codec} + * maximum initial HTTP line length, header size and chunk size. + *

+ * Negative or zero values are not valid, but will be interpreted as "don't change + * the current configuration for that field": on first call the Netty defaults of + * {@code (4096,8192,8192)} will be used. + * + * @param maxInitialLineLength the HTTP initial line maximum length. Use 0 to ignore/keep default. + * @param maxHeaderSize the HTTP header maximum size. Use 0 to ignore/keep default. + * @param maxChunkSize the HTTP chunk maximum size. Use 0 to ignore/keep default. + * @return {@code this} + */ + public final Builder httpCodecOptions(int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { + if (maxInitialLineLength > 0) { + this.maxInitialLineLength = maxInitialLineLength; + } + if (maxHeaderSize > 0) { + this.maxHeaderSize = maxHeaderSize; + } + if (maxChunkSize > 0) { + this.maxChunkSize = maxChunkSize; + } + return get(); + } + /** * Fill the builder with attribute values from the provided options. * @@ -129,6 +196,9 @@ public final Builder compression(int minResponseSize) { public final Builder from(HttpServerOptions options) { super.from(options); this.minCompressionResponseSize = options.minCompressionResponseSize; + this.maxInitialLineLength = options.maxInitialLineLength; + this.maxHeaderSize = options.maxHeaderSize; + this.maxChunkSize = options.maxChunkSize; return get(); } diff --git a/src/test/java/reactor/ipc/netty/http/server/HttpServerOptionsTest.java b/src/test/java/reactor/ipc/netty/http/server/HttpServerOptionsTest.java index e93e106a9f..2be7e405ba 100644 --- a/src/test/java/reactor/ipc/netty/http/server/HttpServerOptionsTest.java +++ b/src/test/java/reactor/ipc/netty/http/server/HttpServerOptionsTest.java @@ -48,6 +48,151 @@ public void minResponseForCompressionPositive() { assertThat(builder.build().minCompressionResponseSize()).isEqualTo(10); } + @Test + public void httpCodecSizesModified() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + + @Test + public void httpCodecSizesDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(4096); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(8192); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(8192); + } + + @Test + public void httpCodecSizesLineNegativeDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(-1, 456, 789); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(4096); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + + @Test + public void httpCodecSizesLineZeroDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(0, 456, 789); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(4096); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + + @Test + public void httpCodecSizesLineNegativeIgnored() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789) + .httpCodecOptions(-1, 1, 2); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(1); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(2); + } + + @Test + public void httpCodecSizesLineZeroIgnored() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789) + .httpCodecOptions(0, 1, 2); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(1); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(2); + } + + @Test + public void httpCodecSizesHeaderNegativeDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, -1, 789); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(8192); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + + @Test + public void httpCodecSizesHeaderZeroDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 0, 789); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(8192); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + + @Test + public void httpCodecSizesHeaderNegativeIgnored() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789) + .httpCodecOptions(1, -1, 2); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(1); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(2); + } + + @Test + public void httpCodecSizesHeaderZeroIgnored() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789) + .httpCodecOptions(1, 0, 2); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(1); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(2); + } + + @Test + public void httpCodecSizesChunkNegativeDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, -1); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(8192); + } + + @Test + public void httpCodecSizesChunkZeroDefaults() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 0); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(123); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(456); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(8192); + } + + @Test + public void httpCodecSizesChunkNegativeIgnored() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789) + .httpCodecOptions(1, 2, -1); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(1); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(2); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + + @Test + public void httpCodecSizesChunkZeroIgnored() { + HttpServerOptions.Builder builder = HttpServerOptions.builder(); + builder.httpCodecOptions(123, 456, 789) + .httpCodecOptions(1, 2, 0); + + assertThat(builder.build().httpCodecMaxInitialLineLength()).isEqualTo(1); + assertThat(builder.build().httpCodecMaxHeaderSize()).isEqualTo(2); + assertThat(builder.build().httpCodecMaxChunkSize()).isEqualTo(789); + } + @Test public void asSimpleString() { HttpServerOptions.Builder builder = HttpServerOptions.builder(); @@ -69,30 +214,62 @@ public void asSimpleString() { } @Test - public void asDetailedString() { + public void asDetailedStringAddressAndCompression() { HttpServerOptions.Builder builder = HttpServerOptions.builder(); assertThat(builder.build().asDetailedString()) .matches("^address=(0\\.0\\.0\\.0/0\\.0\\.0\\.0:0|/0:0:0:0:0:0:0:1).*") - .endsWith(", minCompressionResponseSize=-1"); + .contains(", minCompressionResponseSize=-1"); //address builder.host("foo").port(123); assertThat(builder.build().asDetailedString()) .startsWith("address=foo:123") - .endsWith(", minCompressionResponseSize=-1"); + .contains(", minCompressionResponseSize=-1"); //gzip builder.compression(true); assertThat(builder.build().asDetailedString()) .startsWith("address=foo:123") - .endsWith(", minCompressionResponseSize=0"); + .contains(", minCompressionResponseSize=0"); //gzip with threshold builder.compression(534); assertThat(builder.build().asDetailedString()) .startsWith("address=foo:123") - .endsWith(", minCompressionResponseSize=534"); + .endsWith(", minCompressionResponseSize=534, httpCodecSizes={initialLine=4096,header=8192,chunk=8192}"); + } + + @Test + public void asDetailedStringHttpCodecSizes() { + //defaults + assertThat(HttpServerOptions.builder() + .build().asDetailedString()) + .endsWith(", httpCodecSizes={initialLine=4096,header=8192,chunk=8192}"); + + //changed line length + assertThat(HttpServerOptions.builder() + .httpCodecOptions(123, 0, -1) + .build().asDetailedString()) + .endsWith(", httpCodecSizes={initialLine=123,header=8192,chunk=8192}"); + + //changed header size + assertThat(HttpServerOptions.builder() + .httpCodecOptions(0, 123, -1) + .build().asDetailedString()) + .endsWith(", httpCodecSizes={initialLine=4096,header=123,chunk=8192}"); + + //changed chunk size + assertThat(HttpServerOptions.builder() + .httpCodecOptions(0, -1, 123) + .build().asDetailedString()) + .endsWith(", httpCodecSizes={initialLine=4096,header=8192,chunk=123}"); + + //changed all sizes + assertThat(HttpServerOptions.builder() + .httpCodecOptions(123, 456, 789) + .build().asDetailedString()) + .endsWith(", httpCodecSizes={initialLine=123,header=456,chunk=789}"); } @Test @@ -101,10 +278,11 @@ public void toStringContainsAsDetailedString() { .compression(534) .host("google.com") .port(123); - assertThat(builder.build().toString()) + HttpServerOptions options = builder.build(); + assertThat(options.toString()) .startsWith("HttpServerOptions{address=google.com") .contains(":123") - .endsWith(", minCompressionResponseSize=534}"); + .endsWith(", minCompressionResponseSize=534, httpCodecSizes={initialLine=4096,header=8192,chunk=8192}}"); } } \ No newline at end of file