diff --git a/build.gradle b/build.gradle index af551a828..9f58739e8 100644 --- a/build.gradle +++ b/build.gradle @@ -118,6 +118,7 @@ ext { } nettyIoUringVersion = '0.0.25.Final' nettyQuicVersion = '0.0.62.Final' + nettyHttp3Version = '0.0.28.Final' // Testing brotli4jVersion = '1.16.0' diff --git a/reactor-netty-core/build.gradle b/reactor-netty-core/build.gradle index ba9a4577f..0aabb2662 100644 --- a/reactor-netty-core/build.gradle +++ b/reactor-netty-core/build.gradle @@ -246,7 +246,10 @@ task japicmp(type: JapicmpTask) { // Deprecated methods are removed 'reactor.netty.tcp.SslProvider$SslContextSpec#sslContext(io.netty.handler.ssl.SslContextBuilder)', 'reactor.netty.tcp.SslProvider#getDefaultConfigurationType()', - 'reactor.netty.tcp.SslProvider#updateDefaultConfiguration(reactor.netty.tcp.SslProvider, reactor.netty.tcp.SslProvider$DefaultConfigurationType)' + 'reactor.netty.tcp.SslProvider#updateDefaultConfiguration(reactor.netty.tcp.SslProvider, reactor.netty.tcp.SslProvider$DefaultConfigurationType)', + + // New method is added + 'reactor.netty.tcp.SslProvider$SslContextSpec#sslContext(reactor.netty.tcp.SslProvider$GenericSslContextSpec)' ] classExcludes = [ diff --git a/reactor-netty-core/src/main/java/reactor/netty/tcp/SslProvider.java b/reactor-netty-core/src/main/java/reactor/netty/tcp/SslProvider.java index 3124f91fc..4634e9ee0 100644 --- a/reactor-netty-core/src/main/java/reactor/netty/tcp/SslProvider.java +++ b/reactor-netty-core/src/main/java/reactor/netty/tcp/SslProvider.java @@ -224,6 +224,17 @@ public interface Builder { public interface SslContextSpec { + /** + * SslContext builder that provides, specific for the protocol, default configuration + * e.g. {@link DefaultSslContextSpec}, {@link TcpSslContextSpec} etc. + * The default configuration is applied before any other custom configuration. + * + * @param spec SslContext builder that provides, specific for the protocol, default configuration + * @return {@literal this} + * @since 1.2.0 + */ + Builder sslContext(GenericSslContextSpec spec); + /** * SslContext builder that provides, specific for the protocol, default configuration * e.g. {@link DefaultSslContextSpec}, {@link TcpSslContextSpec} etc. @@ -246,20 +257,21 @@ public interface SslContextSpec { } /** - * SslContext builder that provides, specific for the protocol, default configuration. + * Generic SslContext builder that provides, specific for the protocol, default configuration. * The default configuration is applied prior any other custom configuration. * - * @since 1.0.6 + * @param specific for the protocol SslContext builder + * @since 1.2.0 */ - public interface ProtocolSslContextSpec { + public interface GenericSslContextSpec { /** - * Configures the underlying {@link SslContextBuilder}. + * Configures the underlying {@link SslContext}. * - * @param sslCtxBuilder a callback for configuring the underlying {@link SslContextBuilder} + * @param sslCtxBuilder a callback for configuring the underlying {@link SslContext} * @return {@code this} */ - ProtocolSslContextSpec configure(Consumer sslCtxBuilder); + GenericSslContextSpec configure(Consumer sslCtxBuilder); /** * Create a new {@link SslContext} instance with the configured settings. @@ -270,6 +282,18 @@ public interface ProtocolSslContextSpec { SslContext sslContext() throws SSLException; } + /** + * SslContext builder that provides, specific for the protocol, default configuration. + * The default configuration is applied prior any other custom configuration. + * + * @since 1.0.6 + */ + public interface ProtocolSslContextSpec extends GenericSslContextSpec { + + @Override + ProtocolSslContextSpec configure(Consumer sslCtxBuilder); + } + final SslContext sslContext; final long handshakeTimeoutMillis; final long closeNotifyFlushTimeoutMillis; @@ -282,9 +306,9 @@ public interface ProtocolSslContextSpec { SslProvider(SslProvider.Build builder) { if (builder.sslContext == null) { - if (builder.protocolSslContextSpec != null) { + if (builder.genericSslContextSpec != null) { try { - this.sslContext = builder.protocolSslContextSpec.sslContext(); + this.sslContext = builder.genericSslContextSpec.sslContext(); } catch (SSLException e) { throw Exceptions.propagate(e); @@ -457,7 +481,7 @@ static final class Build implements SslContextSpec, Builder { ReactorNetty.SSL_HANDSHAKE_TIMEOUT, "10000")); - ProtocolSslContextSpec protocolSslContextSpec; + GenericSslContextSpec genericSslContextSpec; SslContext sslContext; Consumer handlerConfigurator; long handshakeTimeoutMillis = DEFAULT_SSL_HANDSHAKE_TIMEOUT; @@ -469,9 +493,15 @@ static final class Build implements SslContextSpec, Builder { // SslContextSpec + @Override + public Builder sslContext(GenericSslContextSpec genericSslContextSpec) { + this.genericSslContextSpec = genericSslContextSpec; + return this; + } + @Override public Builder sslContext(ProtocolSslContextSpec protocolSslContextSpec) { - this.protocolSslContextSpec = protocolSslContextSpec; + this.genericSslContextSpec = protocolSslContextSpec; return this; } @@ -597,7 +627,7 @@ public boolean equals(Object o) { Objects.equals(handlerConfigurator, build.handlerConfigurator) && Objects.equals(serverNames, build.serverNames) && confPerDomainName.equals(build.confPerDomainName) && - Objects.equals(protocolSslContextSpec, build.protocolSslContextSpec); + Objects.equals(genericSslContextSpec, build.genericSslContextSpec); } @Override @@ -610,7 +640,7 @@ public int hashCode() { result = 31 * result + Long.hashCode(closeNotifyReadTimeoutMillis); result = 31 * result + Objects.hashCode(serverNames); result = 31 * result + Objects.hashCode(confPerDomainName); - result = 31 * result + Objects.hashCode(protocolSslContextSpec); + result = 31 * result + Objects.hashCode(genericSslContextSpec); return result; } diff --git a/reactor-netty-http/build.gradle b/reactor-netty-http/build.gradle index 652b342c1..7653d6caf 100644 --- a/reactor-netty-http/build.gradle +++ b/reactor-netty-http/build.gradle @@ -28,6 +28,7 @@ ext { "io.netty.channel.kqueue;resolution:=optional;version=\"[4.1,5)\"", "io.netty.handler.codec.haproxy;resolution:=optional;version=\"[4.1,5)\"", "io.netty.incubator.channel.uring;resolution:=optional", + "io.netty.incubator.codec.http3;resolution:=optional", "io.micrometer.*;resolution:=optional", "*" ].join(","), @@ -81,6 +82,7 @@ dependencies { api "io.netty:netty-resolver-dns-native-macos:$nettyVersion" } compileOnly "io.netty:netty-codec-haproxy:$nettyVersion" + compileOnly "io.netty.incubator:netty-incubator-codec-http3:$nettyHttp3Version" //transport resolution: typical build forces epoll but not kqueue transitively //on the other hand, if we want to make transport-specific tests, we'll make all // native optional at compile time and add correct native/nio to testRuntime diff --git a/reactor-netty-http/src/main/java/reactor/netty/http/Http3SslContextSpec.java b/reactor-netty-http/src/main/java/reactor/netty/http/Http3SslContextSpec.java new file mode 100644 index 000000000..be9c0b467 --- /dev/null +++ b/reactor-netty-http/src/main/java/reactor/netty/http/Http3SslContextSpec.java @@ -0,0 +1,113 @@ +/* + * 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 io.netty.handler.ssl.SslContext; +import io.netty.incubator.codec.quic.QuicSslContextBuilder; +import reactor.netty.tcp.SslProvider; +import reactor.util.annotation.Nullable; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; +import java.io.File; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Objects; +import java.util.function.Consumer; + +import static io.netty.incubator.codec.http3.Http3.supportedApplicationProtocols; + +/** + * SslContext builder that provides default configuration specific to HTTP/3 as follows: + *
    + *
  • Supported application protocols
  • + *
+ *

The default configuration is applied prior any other custom configuration.

+ * + * @author Violeta Georgieva + * @since 1.2.0 + * @see io.netty.incubator.codec.http3.Http3#supportedApplicationProtocols() + */ +public final class Http3SslContextSpec implements SslProvider.GenericSslContextSpec { + + /** + * Creates a builder for new client-side {@link SslContext}. + * + * @see QuicSslContextBuilder#forClient() + */ + public static Http3SslContextSpec forClient() { + return new Http3SslContextSpec(QuicSslContextBuilder.forClient()); + } + + /** + * Creates a builder for new server-side {@link SslContext}. + * + * @see QuicSslContextBuilder#forServer(File, String, File) + */ + public static Http3SslContextSpec forServer(File keyFile, @Nullable String keyPassword, File certChainFile) { + return new Http3SslContextSpec(QuicSslContextBuilder.forServer(keyFile, keyPassword, certChainFile)); + } + + /** + * Creates a builder for new server-side {@link SslContext}. + * + * @see QuicSslContextBuilder#forServer(KeyManager, String) + */ + public static Http3SslContextSpec forServer(KeyManager keyManager, @Nullable String keyPassword) { + return new Http3SslContextSpec(QuicSslContextBuilder.forServer(keyManager, keyPassword)); + } + + /** + * Creates a builder for new server-side {@link SslContext}. + * + * @see QuicSslContextBuilder#forServer(KeyManagerFactory, String) + */ + public static Http3SslContextSpec forServer(KeyManagerFactory keyManagerFactory, @Nullable String password) { + return new Http3SslContextSpec(QuicSslContextBuilder.forServer(keyManagerFactory, password)); + } + + /** + * Creates a builder for new server-side {@link SslContext}. + * + * @see QuicSslContextBuilder#forServer(PrivateKey, String, X509Certificate...) + */ + public static Http3SslContextSpec forServer(PrivateKey key, @Nullable String keyPassword, X509Certificate... certChain) { + return new Http3SslContextSpec(QuicSslContextBuilder.forServer(key, keyPassword, certChain)); + } + + @Override + public Http3SslContextSpec configure(Consumer sslCtxBuilder) { + Objects.requireNonNull(sslCtxBuilder, "sslCtxBuilder"); + sslCtxBuilder.accept(sslContextBuilder); + return this; + } + + @Override + public SslContext sslContext() throws SSLException { + return sslContextBuilder.build(); + } + + final QuicSslContextBuilder sslContextBuilder; + + Http3SslContextSpec(QuicSslContextBuilder sslContextBuilder) { + this.sslContextBuilder = sslContextBuilder; + configure(DEFAULT_CONFIGURATOR); + } + + static final Consumer DEFAULT_CONFIGURATOR = + sslCtxBuilder -> sslCtxBuilder.applicationProtocols(supportedApplicationProtocols()); +}