-
Notifications
You must be signed in to change notification settings - Fork 657
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix #150 Add a configuration API for HttpCodec
This commits allows to configure Netty's HttpServerCodec 5 options for request decoding, through an HttpServer API that consumes a small simple builder which holds the parameters. Parameters are then injected as Channel attributes. This would notably allow the codec to decode requests on very long URIs.
- Loading branch information
1 parent
cabe50f
commit a3ed0a4
Showing
5 changed files
with
387 additions
and
5 deletions.
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
src/main/java/reactor/ipc/netty/http/server/HttpRequestDecoderConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. | ||
* | ||
* Licensed 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 | ||
* | ||
* http://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 reactor.ipc.netty.http.server; | ||
|
||
import java.util.function.Function; | ||
|
||
import io.netty.util.AttributeKey; | ||
import reactor.ipc.netty.tcp.TcpServer; | ||
|
||
/** | ||
* A configuration builder to fine tune the {@link io.netty.handler.codec.http.HttpServerCodec} | ||
* (or more precisely the {@link io.netty.handler.codec.http.HttpServerCodec.HttpServerRequestDecoder}). | ||
* <p> | ||
* Defaults are accessible as constants {@link #DEFAULT_MAX_INITIAL_LINE_LENGTH}, {@link #DEFAULT_MAX_HEADER_SIZE} | ||
* and {@link #DEFAULT_MAX_CHUNK_SIZE}. | ||
* | ||
* @author Simon Baslé | ||
*/ | ||
public final class HttpRequestDecoderConfiguration { | ||
|
||
public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096; | ||
public static final int DEFAULT_MAX_HEADER_SIZE = 8192; | ||
public static final int DEFAULT_MAX_CHUNK_SIZE = 8192; | ||
public static final boolean DEFAULT_VALIDATE_HEADERS = true; | ||
public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128; | ||
|
||
static final AttributeKey<Integer> MAX_INITIAL_LINE_LENGTH = AttributeKey.newInstance("httpCodecMaxInitialLineLength"); | ||
static final AttributeKey<Integer> MAX_HEADER_SIZE = AttributeKey.newInstance("httpCodecMaxHeaderSize"); | ||
static final AttributeKey<Integer> MAX_CHUNK_SIZE = AttributeKey.newInstance("httpCodecMaxChunkSize"); | ||
static final AttributeKey<Boolean> VALIDATE_HEADERS = AttributeKey.newInstance("httpCodecValidateHeaders"); | ||
static final AttributeKey<Integer> INITIAL_BUFFER_SIZE = AttributeKey.newInstance("httpCodecInitialBufferSize"); | ||
|
||
int maxInitialLineLength = DEFAULT_MAX_INITIAL_LINE_LENGTH; | ||
int maxHeaderSize = DEFAULT_MAX_HEADER_SIZE; | ||
int maxChunkSize = DEFAULT_MAX_CHUNK_SIZE; | ||
boolean validateHeaders = DEFAULT_VALIDATE_HEADERS; | ||
int initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE; | ||
|
||
/** | ||
* Configure the maximum length that can be decoded for the HTTP request's initial | ||
* line. Defaults to {@link #DEFAULT_MAX_INITIAL_LINE_LENGTH}. | ||
* | ||
* @param value the value for the maximum initial line length (strictly positive) | ||
* @return this option builder for further configuration | ||
*/ | ||
public HttpRequestDecoderConfiguration maxInitialLineLength(int value) { | ||
if (value <= 0) { | ||
throw new IllegalArgumentException( | ||
"maxInitialLineLength must be strictly positive"); | ||
} | ||
this.maxInitialLineLength = value; | ||
return this; | ||
} | ||
|
||
/** | ||
* Configure the maximum header size that can be decoded for the HTTP request. | ||
* Defaults to {@link #DEFAULT_MAX_HEADER_SIZE}. | ||
* | ||
* @param value the value for the maximum header size (strictly positive) | ||
* @return this option builder for further configuration | ||
*/ | ||
public HttpRequestDecoderConfiguration maxHeaderSize(int value) { | ||
if (value <= 0) { | ||
throw new IllegalArgumentException("maxHeaderSize must be strictly positive"); | ||
} | ||
this.maxHeaderSize = value; | ||
return this; | ||
} | ||
|
||
/** | ||
* Configure the maximum chunk size that can be decoded for the HTTP request. | ||
* Defaults to {@link #DEFAULT_MAX_CHUNK_SIZE}. | ||
* | ||
* @param value the value for the maximum chunk size (strictly positive) | ||
* @return this option builder for further configuration | ||
*/ | ||
public HttpRequestDecoderConfiguration maxChunkSize(int value) { | ||
if (value <= 0) { | ||
throw new IllegalArgumentException("maxChunkSize must be strictly positive"); | ||
} | ||
this.maxChunkSize = value; | ||
return this; | ||
} | ||
|
||
/** | ||
* Configure whether or not to validate headers when decoding requests. Defaults to | ||
* #DEFAULT_VALIDATE_HEADERS. | ||
* | ||
* @param validate true to validate headers, false otherwise | ||
* @return this option builder for further configuration | ||
*/ | ||
public HttpRequestDecoderConfiguration validateHeaders(boolean validate) { | ||
this.validateHeaders = validate; | ||
return this; | ||
} | ||
|
||
/** | ||
* Configure the initial buffer size for HTTP request decoding. Defaults to | ||
* {@link #DEFAULT_INITIAL_BUFFER_SIZE}. | ||
* | ||
* @param value the initial buffer size to use (strictly positive) | ||
* @return | ||
*/ | ||
public HttpRequestDecoderConfiguration initialBufferSize(int value) { | ||
if (value <= 0) { | ||
throw new IllegalArgumentException("initialBufferSize must be strictly positive"); | ||
} | ||
this.initialBufferSize = value; | ||
return this; | ||
} | ||
|
||
/** | ||
* Build a {@link Function} that applies the http request decoder configuration to a | ||
* {@link TcpServer} by enriching its attributes. | ||
*/ | ||
Function<TcpServer, TcpServer> build() { | ||
return tcp -> tcp.selectorAttr(MAX_INITIAL_LINE_LENGTH, maxInitialLineLength) | ||
.selectorAttr(MAX_HEADER_SIZE, maxHeaderSize) | ||
.selectorAttr(MAX_CHUNK_SIZE, maxChunkSize) | ||
.selectorAttr(VALIDATE_HEADERS, validateHeaders) | ||
.selectorAttr(INITIAL_BUFFER_SIZE, initialBufferSize); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
src/test/java/reactor/ipc/netty/http/server/HttpRequestDecoderConfigurationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
* Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. | ||
* | ||
* Licensed 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 | ||
* | ||
* http://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 reactor.ipc.netty.http.server; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | ||
|
||
public class HttpRequestDecoderConfigurationTest { | ||
|
||
private HttpRequestDecoderConfiguration conf; | ||
|
||
@Before | ||
public void init() { | ||
conf = new HttpRequestDecoderConfiguration(); | ||
} | ||
|
||
@Test | ||
public void maxInitialLineLength() { | ||
conf.maxInitialLineLength(123); | ||
|
||
assertThat(conf.maxInitialLineLength).as("initial line length").isEqualTo(123); | ||
|
||
assertThat(conf.maxHeaderSize).as("default header size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_HEADER_SIZE); | ||
assertThat(conf.maxChunkSize).as("default chunk size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_CHUNK_SIZE); | ||
assertThat(conf.validateHeaders).as("default validate headers").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_VALIDATE_HEADERS); | ||
assertThat(conf.initialBufferSize).as("default initial buffer sizez").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_INITIAL_BUFFER_SIZE); | ||
} | ||
|
||
@Test | ||
public void maxInitialLineLengthBadValues() { | ||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.maxInitialLineLength(0)) | ||
.as("rejects 0") | ||
.withMessage("maxInitialLineLength must be strictly positive"); | ||
|
||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.maxInitialLineLength(-1)) | ||
.as("rejects negative") | ||
.withMessage("maxInitialLineLength must be strictly positive"); | ||
} | ||
|
||
@Test | ||
public void maxHeaderSize() { | ||
conf.maxHeaderSize(123); | ||
|
||
assertThat(conf.maxHeaderSize).as("header size").isEqualTo(123); | ||
|
||
assertThat(conf.maxInitialLineLength).as("default initial line length").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_INITIAL_LINE_LENGTH); | ||
assertThat(conf.maxChunkSize).as("default chunk size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_CHUNK_SIZE); | ||
assertThat(conf.validateHeaders).as("default validate headers").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_VALIDATE_HEADERS); | ||
assertThat(conf.initialBufferSize).as("default initial buffer sizez").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_INITIAL_BUFFER_SIZE); | ||
} | ||
|
||
@Test | ||
public void maxHeaderSizeBadValues() { | ||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.maxHeaderSize(0)) | ||
.as("rejects 0") | ||
.withMessage("maxHeaderSize must be strictly positive"); | ||
|
||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.maxHeaderSize(-1)) | ||
.as("rejects negative") | ||
.withMessage("maxHeaderSize must be strictly positive"); | ||
} | ||
|
||
@Test | ||
public void maxChunkSize() { | ||
conf.maxChunkSize(123); | ||
|
||
assertThat(conf.maxChunkSize).as("chunk size").isEqualTo(123); | ||
|
||
assertThat(conf.maxInitialLineLength).as("default initial line length").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_INITIAL_LINE_LENGTH); | ||
assertThat(conf.maxHeaderSize).as("default header size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_HEADER_SIZE); | ||
assertThat(conf.validateHeaders).as("default validate headers").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_VALIDATE_HEADERS); | ||
assertThat(conf.initialBufferSize).as("default initial buffer sizez").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_INITIAL_BUFFER_SIZE); | ||
} | ||
|
||
@Test | ||
public void maxChunkSizeBadValues() { | ||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.maxChunkSize(0)) | ||
.as("rejects 0") | ||
.withMessage("maxChunkSize must be strictly positive"); | ||
|
||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.maxChunkSize(-1)) | ||
.as("rejects negative") | ||
.withMessage("maxChunkSize must be strictly positive"); | ||
} | ||
|
||
@Test | ||
public void validateHeaders() { | ||
conf.validateHeaders(false); | ||
|
||
assertThat(conf.validateHeaders).as("validate headers").isFalse(); | ||
|
||
assertThat(conf.maxInitialLineLength).as("default initial line length").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_INITIAL_LINE_LENGTH); | ||
assertThat(conf.maxHeaderSize).as("default header size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_HEADER_SIZE); | ||
assertThat(conf.maxChunkSize).as("default chunk size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_CHUNK_SIZE); | ||
assertThat(conf.initialBufferSize).as("default initial buffer sizez").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_INITIAL_BUFFER_SIZE); | ||
} | ||
|
||
@Test | ||
public void initialBufferSize() { | ||
conf.initialBufferSize(123); | ||
|
||
assertThat(conf.initialBufferSize).as("initial buffer size").isEqualTo(123); | ||
|
||
assertThat(conf.maxInitialLineLength).as("default initial line length").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_INITIAL_LINE_LENGTH); | ||
assertThat(conf.maxHeaderSize).as("default header size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_HEADER_SIZE); | ||
assertThat(conf.maxChunkSize).as("default chunk size").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_MAX_CHUNK_SIZE); | ||
assertThat(conf.validateHeaders).as("default validate headers").isEqualTo(HttpRequestDecoderConfiguration.DEFAULT_VALIDATE_HEADERS); | ||
} | ||
|
||
@Test | ||
public void initialBufferSizeBadValues() { | ||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.initialBufferSize(0)) | ||
.as("rejects 0") | ||
.withMessage("initialBufferSize must be strictly positive"); | ||
|
||
assertThatExceptionOfType(IllegalArgumentException.class) | ||
.isThrownBy(() -> conf.initialBufferSize(-1)) | ||
.as("rejects negative") | ||
.withMessage("initialBufferSize must be strictly positive"); | ||
} | ||
} |
Oops, something went wrong.