From 54e4471a434b907352d38e544902b0cec7510d7a Mon Sep 17 00:00:00 2001 From: Violeta Georgieva Date: Fri, 12 Apr 2024 16:28:47 +0300 Subject: [PATCH] Add HTTP/3 initial settings Related to #1531 --- .../reactor/netty/http/Http3SettingsSpec.java | 217 ++++++++++++++++++ .../reactor/netty/http/server/HttpServer.java | 21 ++ .../netty/http/server/HttpServerConfig.java | 15 ++ .../netty/http/Http3SettingsSpecTest.java | 100 ++++++++ 4 files changed, 353 insertions(+) create mode 100644 reactor-netty-http/src/main/java/reactor/netty/http/Http3SettingsSpec.java create mode 100644 reactor-netty-http/src/test/java/reactor/netty/http/Http3SettingsSpecTest.java diff --git a/reactor-netty-http/src/main/java/reactor/netty/http/Http3SettingsSpec.java b/reactor-netty-http/src/main/java/reactor/netty/http/Http3SettingsSpec.java new file mode 100644 index 0000000000..092c07cdd0 --- /dev/null +++ b/reactor-netty-http/src/main/java/reactor/netty/http/Http3SettingsSpec.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2024 VMware, Inc. or its affiliates, 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 + * + * 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 reactor.netty.http; + +/** + * A configuration builder to fine tune the HTTP/3 settings. + * + * @author Violeta Georgieva + * @since 1.2.0 + */ +public final class Http3SettingsSpec { + + public interface Builder { + + /** + * Build a new {@link Http3SettingsSpec}. + * + * @return a new {@link Http3SettingsSpec} + */ + Http3SettingsSpec build(); + + /** + * Set the initial maximum data limit. + * See + * set_initial_max_data. + * Default to {@link Build#DEFAULT_MAX_DATA} + * + * @param maxData the initial maximum data limit + * @return {@code this} + */ + Builder maxData(long maxData); + + /** + * Set the initial maximum data limit for local bidirectional streams. + * See + * + * set_initial_max_stream_data_bidi_local. + * Default to {@link Build#DEFAULT_MAX_STREAM_DATA_BIDIRECTIONAL_LOCAL} + * + * @param maxStreamDataBidirectionalLocal the initial maximum data limit for local bidirectional streams + * @return {@code this} + */ + Builder maxStreamDataBidirectionalLocal(long maxStreamDataBidirectionalLocal); + + /** + * Set the initial maximum data limit for remote bidirectional streams. + * See + * + * set_initial_max_stream_data_bidi_remote. + * Default to {@link Build#DEFAULT_MAX_STREAM_DATA_BIDIRECTIONAL_REMOTE} + * + * @param maxStreamDataBidirectionalRemote the initial maximum data limit for remote bidirectional streams + * @return {@code this} + */ + Builder maxStreamDataBidirectionalRemote(long maxStreamDataBidirectionalRemote); + + /** + * Set the initial maximum stream limit for bidirectional streams. + * See + * + * set_initial_max_streams_bidi. + * Default to {@link Build#DEFAULT_MAX_STREAMS_BIDIRECTIONAL} + * + * @param maxStreamsBidirectional the initial maximum stream limit for bidirectional streams + * @return {@code this} + */ + Builder maxStreamsBidirectional(long maxStreamsBidirectional); + } + + /** + * Creates a builder for {@link Http3SettingsSpec}. + * + * @return a new {@link Http3SettingsSpec.Builder} + */ + public static Http3SettingsSpec.Builder builder() { + return new Http3SettingsSpec.Build(); + } + + /** + * Return the configured initial maximum data limit. + * + * @return the configured initial maximum data limit + */ + public long maxData() { + return maxData; + } + + /** + * Return the configured initial maximum data limit for local bidirectional streams. + * + * @return the configured initial maximum data limit for local bidirectional streams + */ + public long maxStreamDataBidirectionalLocal() { + return maxStreamDataBidirectionalLocal; + } + + /** + * Return the configured initial maximum data limit for remote bidirectional streams. + * + * @return the configured initial maximum data limit for remote bidirectional streams + */ + public long maxStreamDataBidirectionalRemote() { + return maxStreamDataBidirectionalRemote; + } + + /** + * Return the configured initial maximum stream limit for bidirectional streams. + * + * @return the configured initial maximum stream limit for bidirectional streams + */ + public long maxStreamsBidirectional() { + return maxStreamsBidirectional; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Http3SettingsSpec)) { + return false; + } + Http3SettingsSpec that = (Http3SettingsSpec) o; + return maxData == that.maxData && + maxStreamDataBidirectionalLocal == that.maxStreamDataBidirectionalLocal && + maxStreamDataBidirectionalRemote == that.maxStreamDataBidirectionalRemote && + maxStreamsBidirectional == that.maxStreamsBidirectional; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + Long.hashCode(maxData); + result = 31 * result + Long.hashCode(maxStreamDataBidirectionalLocal); + result = 31 * result + Long.hashCode(maxStreamDataBidirectionalRemote); + result = 31 * result + Long.hashCode(maxStreamsBidirectional); + return result; + } + + final long maxData; + final long maxStreamDataBidirectionalLocal; + final long maxStreamDataBidirectionalRemote; + final long maxStreamsBidirectional; + + Http3SettingsSpec(Build build) { + this.maxData = build.maxData; + this.maxStreamDataBidirectionalLocal = build.maxStreamDataBidirectionalLocal; + this.maxStreamDataBidirectionalRemote = build.maxStreamDataBidirectionalRemote; + this.maxStreamsBidirectional = build.maxStreamsBidirectional; + } + + static final class Build implements Builder { + static final long DEFAULT_MAX_DATA = 0L; + static final long DEFAULT_MAX_STREAM_DATA_BIDIRECTIONAL_LOCAL = 0L; + static final long DEFAULT_MAX_STREAM_DATA_BIDIRECTIONAL_REMOTE = 0L; + static final long DEFAULT_MAX_STREAMS_BIDIRECTIONAL = 0L; + + long maxData = DEFAULT_MAX_DATA; + long maxStreamDataBidirectionalLocal = DEFAULT_MAX_STREAM_DATA_BIDIRECTIONAL_LOCAL; + long maxStreamDataBidirectionalRemote = DEFAULT_MAX_STREAM_DATA_BIDIRECTIONAL_REMOTE; + long maxStreamsBidirectional = DEFAULT_MAX_STREAMS_BIDIRECTIONAL; + + @Override + public Http3SettingsSpec build() { + return new Http3SettingsSpec(this); + } + + @Override + public Builder maxData(long maxData) { + if (maxData < 0) { + throw new IllegalArgumentException("maxData must be positive or zero"); + } + this.maxData = maxData; + return this; + } + + @Override + public Builder maxStreamDataBidirectionalLocal(long maxStreamDataBidirectionalLocal) { + if (maxStreamDataBidirectionalLocal < 0) { + throw new IllegalArgumentException("maxStreamDataBidirectionalLocal must be positive or zero"); + } + this.maxStreamDataBidirectionalLocal = maxStreamDataBidirectionalLocal; + return this; + } + + @Override + public Builder maxStreamDataBidirectionalRemote(long maxStreamDataBidirectionalRemote) { + if (maxStreamDataBidirectionalRemote < 0) { + throw new IllegalArgumentException("maxStreamDataBidirectionalRemote must be positive or zero"); + } + this.maxStreamDataBidirectionalRemote = maxStreamDataBidirectionalRemote; + return this; + } + + @Override + public Builder maxStreamsBidirectional(long maxStreamsBidirectional) { + if (maxStreamsBidirectional < 0) { + throw new IllegalArgumentException("maxStreamsBidirectional must be positive or zero"); + } + this.maxStreamsBidirectional = maxStreamsBidirectional; + return this; + } + } +} diff --git a/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServer.java b/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServer.java index 4f435e6fd8..950fe65b65 100644 --- a/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServer.java +++ b/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServer.java @@ -41,6 +41,7 @@ import reactor.netty.ConnectionObserver; import reactor.netty.channel.ChannelMetricsRecorder; import reactor.netty.http.Http2SettingsSpec; +import reactor.netty.http.Http3SettingsSpec; import reactor.netty.http.HttpProtocol; import reactor.netty.http.logging.HttpMessageLogFactory; import reactor.netty.http.logging.ReactorNettyHttpMessageLogFactory; @@ -454,6 +455,26 @@ public final HttpServer http2Settings(Consumer http2S return dup; } + /** + * Apply HTTP/3 configuration. + * + * @param http3Settings configures {@link Http3SettingsSpec} before requesting + * @return a new {@link HttpServer} + * @since 1.2.0 + */ + public final HttpServer http3Settings(Consumer http3Settings) { + Objects.requireNonNull(http3Settings, "http3Settings"); + Http3SettingsSpec.Builder builder = Http3SettingsSpec.builder(); + http3Settings.accept(builder); + Http3SettingsSpec settings = builder.build(); + if (settings.equals(configuration().http3Settings)) { + return this; + } + HttpServer dup = duplicate(); + dup.configuration().http3Settings = settings; + return dup; + } + /** * Apply HTTP form decoder configuration. * The configuration is used when {@link HttpServerRequest#receiveForm()} is invoked. diff --git a/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServerConfig.java b/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServerConfig.java index a746dc7ba5..4e6de407f6 100644 --- a/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServerConfig.java +++ b/reactor-netty-http/src/main/java/reactor/netty/http/server/HttpServerConfig.java @@ -59,6 +59,7 @@ import reactor.netty.channel.ChannelMetricsRecorder; import reactor.netty.channel.ChannelOperations; import reactor.netty.http.Http2SettingsSpec; +import reactor.netty.http.Http3SettingsSpec; import reactor.netty.http.HttpProtocol; import reactor.netty.http.HttpResources; import reactor.netty.http.logging.HttpMessageLogFactory; @@ -159,6 +160,16 @@ public Http2SettingsSpec http2SettingsSpec() { return http2Settings; } + /** + * Return the HTTP/3 configuration. + * + * @return the HTTP/3 configuration + * @since 1.2.0 + */ + public Http3SettingsSpec http3SettingsSpec() { + return http3Settings; + } + /** * Return the configured idle timeout for the connection when it is waiting for an HTTP request or null. * @@ -299,6 +310,7 @@ public Function uriTagValue() { HttpServerFormDecoderProvider formDecoderProvider; BiFunction forwardedHeaderHandler; Http2SettingsSpec http2Settings; + Http3SettingsSpec http3Settings; HttpMessageLogFactory httpMessageLogFactory; Duration idleTimeout; BiFunction, ? super Connection, ? extends Mono> @@ -341,6 +353,7 @@ public Function uriTagValue() { this.formDecoderProvider = parent.formDecoderProvider; this.forwardedHeaderHandler = parent.forwardedHeaderHandler; this.http2Settings = parent.http2Settings; + this.http3Settings = parent.http3Settings; this.httpMessageLogFactory = parent.httpMessageLogFactory; this.idleTimeout = parent.idleTimeout; this.mapHandle = parent.mapHandle; @@ -1214,6 +1227,7 @@ static final class HttpServerChannelInitializer implements ChannelPipelineConfig final HttpServerFormDecoderProvider formDecoderProvider; final BiFunction forwardedHeaderHandler; final Http2SettingsSpec http2SettingsSpec; + final Http3SettingsSpec http3SettingsSpec; final HttpMessageLogFactory httpMessageLogFactory; final Duration idleTimeout; final BiFunction, ? super Connection, ? extends Mono> @@ -1242,6 +1256,7 @@ static final class HttpServerChannelInitializer implements ChannelPipelineConfig this.formDecoderProvider = config.formDecoderProvider; this.forwardedHeaderHandler = config.forwardedHeaderHandler; this.http2SettingsSpec = config.http2Settings; + this.http3SettingsSpec = config.http3Settings; this.httpMessageLogFactory = config.httpMessageLogFactory; this.idleTimeout = config.idleTimeout; this.mapHandle = config.mapHandle; diff --git a/reactor-netty-http/src/test/java/reactor/netty/http/Http3SettingsSpecTest.java b/reactor-netty-http/src/test/java/reactor/netty/http/Http3SettingsSpecTest.java new file mode 100644 index 0000000000..5a055d8d27 --- /dev/null +++ b/reactor-netty-http/src/test/java/reactor/netty/http/Http3SettingsSpecTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 VMware, Inc. or its affiliates, 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 + * + * 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 reactor.netty.http; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +class Http3SettingsSpecTest { + + private Http3SettingsSpec.Builder builder; + + @BeforeEach + void setUp() { + builder = Http3SettingsSpec.builder(); + } + + @Test + void maxData() { + builder.maxData(123); + Http3SettingsSpec spec = builder.build(); + assertThat(spec.maxData()).isEqualTo(123); + assertThat(spec.maxStreamDataBidirectionalLocal()).isEqualTo(0); + assertThat(spec.maxStreamDataBidirectionalRemote()).isEqualTo(0); + assertThat(spec.maxStreamsBidirectional()).isEqualTo(0); + } + + @Test + void maxDataBadValues() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> builder.maxData(-1)) + .withMessageContaining("maxData must be positive or zero"); + } + + @Test + void maxStreamDataBidirectionalLocal() { + builder.maxStreamDataBidirectionalLocal(123); + Http3SettingsSpec spec = builder.build(); + assertThat(spec.maxData()).isEqualTo(0); + assertThat(spec.maxStreamDataBidirectionalLocal()).isEqualTo(123); + assertThat(spec.maxStreamDataBidirectionalRemote()).isEqualTo(0); + assertThat(spec.maxStreamsBidirectional()).isEqualTo(0); + } + + @Test + void maxStreamDataBidirectionalLocalBadValues() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> builder.maxStreamDataBidirectionalLocal(-1)) + .withMessageContaining("maxStreamDataBidirectionalLocal must be positive or zero"); + } + + @Test + void maxStreamDataBidirectionalRemote() { + builder.maxStreamDataBidirectionalRemote(123); + Http3SettingsSpec spec = builder.build(); + assertThat(spec.maxData()).isEqualTo(0); + assertThat(spec.maxStreamDataBidirectionalLocal()).isEqualTo(0); + assertThat(spec.maxStreamDataBidirectionalRemote()).isEqualTo(123); + assertThat(spec.maxStreamsBidirectional()).isEqualTo(0); + } + + @Test + void maxStreamDataBidirectionalRemoteBadValues() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> builder.maxStreamDataBidirectionalRemote(-1)) + .withMessageContaining("maxStreamDataBidirectionalRemote must be positive or zero"); + } + + @Test + void maxStreamsBidirectional() { + builder.maxStreamsBidirectional(123); + Http3SettingsSpec spec = builder.build(); + assertThat(spec.maxData()).isEqualTo(0); + assertThat(spec.maxStreamDataBidirectionalLocal()).isEqualTo(0); + assertThat(spec.maxStreamDataBidirectionalRemote()).isEqualTo(0); + assertThat(spec.maxStreamsBidirectional()).isEqualTo(123); + } + + @Test + void maxStreamsBidirectionalBadValues() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> builder.maxStreamsBidirectional(-1)) + .withMessageContaining("maxStreamsBidirectional must be positive or zero"); + } +}