diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java index 7a8a3b51392..8d10a1fdc21 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.netty.NettyRouteProvider; +import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; @@ -66,10 +67,11 @@ abstract class ReactiveWebServerFactoryConfiguration { @Bean NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory, - ObjectProvider routes) { + ObjectProvider routes, ObjectProvider serverCustomizers) { NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory(); serverFactory.setResourceFactory(resourceFactory); routes.orderedStream().forEach((route) -> serverFactory.addRouteProviders(route)); + serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList())); return serverFactory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java index 54eb6aaa764..a602ce7d345 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.web.reactive; +import io.undertow.Undertow; import io.undertow.Undertow.Builder; import org.apache.catalina.Context; import org.apache.catalina.connector.Connector; @@ -25,10 +26,14 @@ import org.junit.jupiter.api.Test; import reactor.netty.http.server.HttpServer; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; +import org.springframework.boot.logging.LogLevel; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.embedded.netty.NettyServerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; @@ -49,6 +54,7 @@ import org.springframework.web.server.adapter.ForwardedHeaderTransformer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -279,6 +285,33 @@ class ReactiveWebServerFactoryAutoConfigurationTests { }); } + @Test + void nettyServerCustomizerBeanIsAddedToFactory() { + new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebApplicationContext::new) + .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(Tomcat.class, Server.class, Undertow.class)) + .withUserConfiguration(NettyServerCustomizerConfiguration.class, HttpHandlerConfiguration.class) + .withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO)).run((context) -> { + NettyReactiveWebServerFactory factory = context.getBean(NettyReactiveWebServerFactory.class); + assertThat(factory.getServerCustomizers()).hasSize(1); + }); + } + + @Test + void nettyServerCustomizerRegisteredAsBeanAndViaFactoryIsOnlyCalledOnce() { + new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of(ReactiveWebServerFactoryAutoConfiguration.class)) + .withClassLoader(new FilteredClassLoader(Tomcat.class, Server.class, Undertow.class)) + .withUserConfiguration(DoubleRegistrationNettyServerCustomizerConfiguration.class, + HttpHandlerConfiguration.class) + .withPropertyValues("server.port: 0").run((context) -> { + NettyReactiveWebServerFactory factory = context.getBean(NettyReactiveWebServerFactory.class); + NettyServerCustomizer customizer = context.getBean("serverCustomizer", NettyServerCustomizer.class); + assertThat(factory.getServerCustomizers()).contains(customizer); + verify(customizer, times(1)).apply(any(HttpServer.class)); + }); + } + @Test void forwardedHeaderTransformerShouldBeConfigured() { this.contextRunner.withUserConfiguration(HttpHandlerConfiguration.class) @@ -489,6 +522,37 @@ class ReactiveWebServerFactoryAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class NettyServerCustomizerConfiguration { + + @Bean + NettyServerCustomizer serverCustomizer() { + return (server) -> server; + } + + } + + @Configuration(proxyBeanMethods = false) + static class DoubleRegistrationNettyServerCustomizerConfiguration { + + private final NettyServerCustomizer customizer = mock(NettyServerCustomizer.class); + + DoubleRegistrationNettyServerCustomizerConfiguration() { + given(this.customizer.apply(any(HttpServer.class))).willAnswer((invocation) -> invocation.getArgument(0)); + } + + @Bean + NettyServerCustomizer serverCustomizer() { + return this.customizer; + } + + @Bean + WebServerFactoryCustomizer nettyCustomizer() { + return (netty) -> netty.addServerCustomizers(this.customizer); + } + + } + @Configuration(proxyBeanMethods = false) static class ForwardedHeaderTransformerConfiguration { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java index 127a9013b89..a4a8bb3d372 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyReactiveWebServerFactory.java @@ -21,7 +21,9 @@ import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import reactor.netty.http.HttpProtocol; import reactor.netty.http.server.HttpServer; @@ -43,7 +45,7 @@ import org.springframework.util.Assert; */ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory { - private List serverCustomizers = new ArrayList<>(); + private Set serverCustomizers = new LinkedHashSet<>(); private List routeProviders = new ArrayList<>(); @@ -85,7 +87,7 @@ public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFact */ public void setServerCustomizers(Collection serverCustomizers) { Assert.notNull(serverCustomizers, "ServerCustomizers must not be null"); - this.serverCustomizers = new ArrayList<>(serverCustomizers); + this.serverCustomizers = new LinkedHashSet<>(serverCustomizers); } /**