Skip to content

Commit

Permalink
fix #150 Expose Netty HttpServerCodec options in HttpServer
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
simonbasle authored and smaldini committed Jan 18, 2018
1 parent 1173f35 commit 22c20ba
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 9 deletions.
5 changes: 4 additions & 1 deletion src/main/java/reactor/ipc/netty/http/server/HttpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ protected ContextHandler<Channel> doHandler(

@Override
public void accept(ChannelPipeline p, ContextHandler<Channel> 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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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();
Expand All @@ -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
Expand All @@ -86,6 +124,9 @@ public String toString() {

public static final class Builder extends ServerOptions.Builder<Builder> {
private int minCompressionResponseSize = -1;
private int maxInitialLineLength = 4096;
private int maxHeaderSize = 8192;
private int maxChunkSize = 8192;

private Builder(){
super(new ServerBootstrap());
Expand Down Expand Up @@ -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.
* <p>
* 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.
*
Expand All @@ -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();
}

Expand Down
192 changes: 185 additions & 7 deletions src/test/java/reactor/ipc/netty/http/server/HttpServerOptionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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}}");
}

}

0 comments on commit 22c20ba

Please sign in to comment.