From 2eaa64f82e2dda59aa71a49caecf7f6922a0a5e6 Mon Sep 17 00:00:00 2001 From: Pascal Zwick Date: Thu, 28 Mar 2019 00:21:51 +0100 Subject: [PATCH 1/2] Simplify the configuration of the ProtocolHandler This commit introduces a new callback interface that can be used to customize the ProtocolHandler on a Tomcat Connector. See gh-16342 --- ...ReactiveWebServerFactoryConfiguration.java | 7 ++- .../ServletWebServerFactoryConfiguration.java | 7 ++- ...ebServerFactoryAutoConfigurationTests.java | 27 ++++++++++ ...ebServerFactoryAutoConfigurationTests.java | 27 ++++++++++ .../ConfigurableTomcatWebServerFactory.java | 8 +++ .../TomcatProtocolHandlerCustomizer.java | 40 +++++++++++++++ .../TomcatReactiveWebServerFactory.java | 42 ++++++++++++++++ .../tomcat/TomcatServletWebServerFactory.java | 42 ++++++++++++++++ .../TomcatReactiveWebServerFactoryTests.java | 36 ++++++++++++++ .../TomcatServletWebServerFactoryTests.java | 49 +++++++++++++++++++ 10 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatProtocolHandlerCustomizer.java 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 33f8e494cfb..22d2070a9f7 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.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; +import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; @@ -76,12 +77,16 @@ abstract class ReactiveWebServerFactoryConfiguration { @Bean public TomcatReactiveWebServerFactory tomcatReactiveWebServerFactory( ObjectProvider connectorCustomizers, - ObjectProvider contextCustomizers) { + ObjectProvider contextCustomizers, + ObjectProvider protocolHandlerCustomizers) { TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); factory.getTomcatConnectorCustomizers().addAll( connectorCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatContextCustomizers().addAll( contextCustomizers.orderedStream().collect(Collectors.toList())); + factory.getTomcatProtocolHandlerCustomizers() + .addAll(protocolHandlerCustomizers.orderedStream() + .collect(Collectors.toList())); return factory; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java index 3b717947912..a336f9ccd41 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java @@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; +import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; @@ -66,12 +67,16 @@ class ServletWebServerFactoryConfiguration { @Bean public TomcatServletWebServerFactory tomcatServletWebServerFactory( ObjectProvider connectorCustomizers, - ObjectProvider contextCustomizers) { + ObjectProvider contextCustomizers, + ObjectProvider protocolHandlerCustomizers) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.getTomcatConnectorCustomizers().addAll( connectorCustomizers.orderedStream().collect(Collectors.toList())); factory.getTomcatContextCustomizers().addAll( contextCustomizers.orderedStream().collect(Collectors.toList())); + factory.getTomcatProtocolHandlerCustomizers() + .addAll(protocolHandlerCustomizers.orderedStream() + .collect(Collectors.toList())); return factory; } 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 715030d09e1..f116f961740 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 @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; +import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; @@ -131,6 +132,21 @@ public class ReactiveWebServerFactoryAutoConfigurationTests { }); } + @Test + public void tomcatProtocolHandlerCustomizerBeanIsAddedToFactory() { + ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner( + AnnotationConfigReactiveWebApplicationContext::new) + .withConfiguration(AutoConfigurations + .of(ReactiveWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration( + TomcatProtocolHandlerCustomizerConfiguration.class); + runner.run((context) -> { + TomcatReactiveWebServerFactory factory = context + .getBean(TomcatReactiveWebServerFactory.class); + assertThat(factory.getTomcatProtocolHandlerCustomizers()).hasSize(1); + }); + } + @Configuration(proxyBeanMethods = false) protected static class HttpHandlerConfiguration { @@ -193,4 +209,15 @@ public class ReactiveWebServerFactoryAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TomcatProtocolHandlerCustomizerConfiguration { + + @Bean + public TomcatProtocolHandlerCustomizer protocolHandlerCustomizer() { + return (protocolHandler) -> { + }; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java index 8fe9b41ca24..33b11dbd8bf 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java @@ -32,6 +32,7 @@ import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer; +import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.ServletRegistrationBean; @@ -170,6 +171,21 @@ public class ServletWebServerFactoryAutoConfigurationTests { }); } + @Test + public void tomcatProtocolHandlerCustomizerBeanIsAddedToFactory() { + WebApplicationContextRunner runner = new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations + .of(ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration( + TomcatProtocolHandlerCustomizerConfiguration.class); + runner.run((context) -> { + TomcatServletWebServerFactory factory = context + .getBean(TomcatServletWebServerFactory.class); + assertThat(factory.getTomcatProtocolHandlerCustomizers()).hasSize(1); + }); + } + private ContextConsumer verifyContext() { return this::verifyContext; } @@ -308,4 +324,15 @@ public class ServletWebServerFactoryAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class TomcatProtocolHandlerCustomizerConfiguration { + + @Bean + public TomcatProtocolHandlerCustomizer protocolHandlerCustomizer() { + return (protocolHandler) -> { + }; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java index e96327494f7..bcbabb7289c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java @@ -68,6 +68,14 @@ public interface ConfigurableTomcatWebServerFactory extends ConfigurableWebServe */ void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers); + /** + * Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat + * {@link Connector}. + * @param tomcatProtocolHandlerCustomizers the customizers to add + */ + void addProtocolHandlerCustomizers( + TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers); + /** * Set the character encoding to use for URL decoding. If not specified 'UTF-8' will * be used. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatProtocolHandlerCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatProtocolHandlerCustomizer.java new file mode 100644 index 00000000000..e1c3ad8ec3e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatProtocolHandlerCustomizer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * 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 org.springframework.boot.web.embedded.tomcat; + +import org.apache.catalina.connector.Connector; +import org.apache.coyote.ProtocolHandler; + +/** + * Callback interface that can be used to customize the {@link ProtocolHandler} on the + * {@link Connector}. + * + * @param specified type for customization based on {@link ProtocolHandler} + * @author Pascal Zwick + * @see ConfigurableTomcatWebServerFactory + * @since 2.2.0 + */ +@FunctionalInterface +public interface TomcatProtocolHandlerCustomizer { + + /** + * Customize the protocol handler. + * @param protocolHandler the protocol handler to customize + */ + void customize(T protocolHandler); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java index f2e9f4b3280..d97737945c4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java @@ -72,6 +72,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac private List tomcatConnectorCustomizers = new ArrayList<>(); + private List tomcatProtocolHandlerCustomizers = new ArrayList<>(); + private String protocol = DEFAULT_PROTOCOL; private Charset uriEncoding = DEFAULT_CHARSET; @@ -168,6 +170,10 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac if (connector.getProtocolHandler() instanceof AbstractProtocol) { customizeProtocol((AbstractProtocol) connector.getProtocolHandler()); } + + this.tomcatProtocolHandlerCustomizers.forEach( + (customizer) -> customizer.customize(connector.getProtocolHandler())); + if (getUriEncoding() != null) { connector.setURIEncoding(getUriEncoding().name()); } @@ -275,6 +281,42 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac return this.tomcatConnectorCustomizers; } + /** + * Set {@link TomcatProtocolHandlerCustomizer}s that should be applied to the Tomcat + * {@link Connector}. Calling this method will replace any existing customizers. + * @param tomcatProtocolHandlerCustomizers the customizers to set + */ + public void setTomcatProtocolHandlerCustomizers( + Collection tomcatProtocolHandlerCustomizers) { + Assert.notNull(tomcatProtocolHandlerCustomizers, + "TomcatProtocolHandlerCustomizers must not be null"); + this.tomcatProtocolHandlerCustomizers = new ArrayList<>( + tomcatProtocolHandlerCustomizers); + } + + /** + * Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat + * {@link Connector}. + * @param tomcatProtocolHandlerCustomizers the customizers to add + */ + @Override + public void addProtocolHandlerCustomizers( + TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) { + Assert.notNull(tomcatProtocolHandlerCustomizers, + "TomcatProtocolHandlerCustomizers must not be null"); + this.tomcatProtocolHandlerCustomizers + .addAll(Arrays.asList(tomcatProtocolHandlerCustomizers)); + } + + /** + * Returns a mutable collection of the {@link TomcatProtocolHandlerCustomizer}s that + * will be applied to the Tomcat {@link Connector}. + * @return the customizers that will be applied + */ + public Collection getTomcatProtocolHandlerCustomizers() { + return this.tomcatProtocolHandlerCustomizers; + } + @Override public void addEngineValves(Valve... engineValves) { Assert.notNull(engineValves, "Valves must not be null"); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java index 0d03687c31a..1c1e41f92e9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java @@ -117,6 +117,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto private List tomcatConnectorCustomizers = new ArrayList<>(); + private List tomcatProtocolHandlerCustomizers = new ArrayList<>(); + private List additionalTomcatConnectors = new ArrayList<>(); private ResourceLoader resourceLoader; @@ -303,6 +305,10 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto if (connector.getProtocolHandler() instanceof AbstractProtocol) { customizeProtocol((AbstractProtocol) connector.getProtocolHandler()); } + + this.tomcatProtocolHandlerCustomizers.forEach( + (customizer) -> customizer.customize(connector.getProtocolHandler())); + if (getUriEncoding() != null) { connector.setURIEncoding(getUriEncoding().name()); } @@ -619,6 +625,42 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto return this.tomcatConnectorCustomizers; } + /** + * Set {@link TomcatProtocolHandlerCustomizer}s that should be applied to the Tomcat + * {@link Connector}. Calling this method will replace any existing customizers. + * @param tomcatProtocolHandlerCustomizer the customizers to set + */ + public void setTomcatProtocolHandlerCustomizers( + Collection tomcatProtocolHandlerCustomizer) { + Assert.notNull(tomcatProtocolHandlerCustomizer, + "TomcatProtocolHandlerCustomizers must not be null"); + this.tomcatProtocolHandlerCustomizers = new ArrayList<>( + tomcatProtocolHandlerCustomizer); + } + + /** + * Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat + * {@link Connector}. + * @param tomcatProtocolHandlerCustomizers the customizers to add + */ + @Override + public void addProtocolHandlerCustomizers( + TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) { + Assert.notNull(tomcatProtocolHandlerCustomizers, + "TomcatProtocolHandlerCustomizers must not be null"); + this.tomcatProtocolHandlerCustomizers + .addAll(Arrays.asList(tomcatProtocolHandlerCustomizers)); + } + + /** + * Returns a mutable collection of the {@link TomcatProtocolHandlerCustomizer}s that + * will be applied to the Tomcat {@link Connector}. + * @return the customizers that will be applied + */ + public Collection getTomcatProtocolHandlerCustomizers() { + return this.tomcatProtocolHandlerCustomizers; + } + /** * Add {@link Connector}s in addition to the default connector, e.g. for SSL or AJP * @param connectors the connectors to add diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java index ca44a9ee0cf..0ac70a498cf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java @@ -26,6 +26,8 @@ import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.core.StandardContext; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.RemoteIpValve; +import org.apache.coyote.ProtocolHandler; +import org.apache.coyote.http11.AbstractHttp11Protocol; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; @@ -121,6 +123,24 @@ public class TomcatReactiveWebServerFactoryTests .withMessageContaining("Customizers must not be null"); } + @Test + public void setNullProtocolHandlerCustomizersShouldThrowException() { + TomcatReactiveWebServerFactory factory = getFactory(); + assertThatIllegalArgumentException() + .isThrownBy(() -> factory.setTomcatProtocolHandlerCustomizers(null)) + .withMessageContaining( + "TomcatProtocolHandlerCustomizers must not be null"); + } + + @Test + public void addNullProtocolHandlerCustomizersShouldThrowException() { + TomcatReactiveWebServerFactory factory = getFactory(); + assertThatIllegalArgumentException().isThrownBy(() -> factory + .addProtocolHandlerCustomizers((TomcatProtocolHandlerCustomizer[]) null)) + .withMessageContaining( + "TomcatProtocolHandlerCustomizers must not be null"); + } + @Test public void tomcatConnectorCustomizersShouldBeInvoked() { TomcatReactiveWebServerFactory factory = getFactory(); @@ -136,6 +156,22 @@ public class TomcatReactiveWebServerFactoryTests } } + @Test + public void tomcatProtocolHandlerCustomizersShouldBeInvoked() { + TomcatReactiveWebServerFactory factory = getFactory(); + HttpHandler handler = mock(HttpHandler.class); + TomcatProtocolHandlerCustomizer[] listeners = new TomcatProtocolHandlerCustomizer[4]; + Arrays.setAll(listeners, (i) -> mock(TomcatProtocolHandlerCustomizer.class)); + factory.setTomcatProtocolHandlerCustomizers( + Arrays.asList(listeners[0], listeners[1])); + factory.addProtocolHandlerCustomizers(listeners[2], listeners[3]); + this.webServer = factory.getWebServer(handler); + InOrder ordered = inOrder((Object[]) listeners); + for (TomcatProtocolHandlerCustomizer listener : listeners) { + ordered.verify(listener).customize(any(ProtocolHandler.class)); + } + } + @Test public void useForwardedHeaders() { TomcatReactiveWebServerFactory factory = getFactory(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index e3b8ed42275..a61e24e1d62 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -55,6 +55,8 @@ import org.apache.catalina.startup.Tomcat; import org.apache.catalina.util.CharsetMapper; import org.apache.catalina.valves.RemoteIpValve; import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory; +import org.apache.coyote.ProtocolHandler; +import org.apache.coyote.http11.AbstractHttp11Protocol; import org.apache.jasper.servlet.JspServlet; import org.apache.tomcat.JarScanFilter; import org.apache.tomcat.JarScanType; @@ -196,6 +198,35 @@ public class TomcatServletWebServerFactoryTests } } + @Test + public void tomcatProtocolHandlerCustomizersShouldBeInvoked() { + TomcatServletWebServerFactory factory = getFactory(); + TomcatProtocolHandlerCustomizer[] listeners = new TomcatProtocolHandlerCustomizer[4]; + Arrays.setAll(listeners, (i) -> mock(TomcatProtocolHandlerCustomizer.class)); + factory.setTomcatProtocolHandlerCustomizers( + Arrays.asList(listeners[0], listeners[1])); + factory.addProtocolHandlerCustomizers(listeners[2], listeners[3]); + this.webServer = factory.getWebServer(); + InOrder ordered = inOrder((Object[]) listeners); + for (TomcatProtocolHandlerCustomizer listener : listeners) { + ordered.verify(listener).customize(any(ProtocolHandler.class)); + } + } + + @Test + public void tomcatProtocolHandlerCanBeCustomized() { + TomcatServletWebServerFactory factory = getFactory(); + TomcatProtocolHandlerCustomizer customizer = ( + protocolHandler) -> protocolHandler.setProcessorCache(250); + factory.addProtocolHandlerCustomizers(customizer); + Tomcat tomcat = getTomcat(factory); + Connector connector = ((TomcatWebServer) this.webServer).getServiceConnectors() + .get(tomcat.getService())[0]; + AbstractHttp11Protocol protocolHandler = (AbstractHttp11Protocol) connector + .getProtocolHandler(); + assertThat(protocolHandler.getProcessorCache()).isEqualTo(250); + } + @Test public void tomcatAdditionalConnectors() { TomcatServletWebServerFactory factory = getFactory(); @@ -280,6 +311,24 @@ public class TomcatServletWebServerFactoryTests .withMessageContaining("TomcatConnectorCustomizers must not be null"); } + @Test + public void setNullTomcatProtocolHandlerCustomizersThrows() { + TomcatServletWebServerFactory factory = getFactory(); + assertThatIllegalArgumentException() + .isThrownBy(() -> factory.setTomcatProtocolHandlerCustomizers(null)) + .withMessageContaining( + "TomcatProtocolHandlerCustomizers must not be null"); + } + + @Test + public void addNullTomcatProtocolHandlerCustomizersThrows() { + TomcatServletWebServerFactory factory = getFactory(); + assertThatIllegalArgumentException().isThrownBy(() -> factory + .addProtocolHandlerCustomizers((TomcatProtocolHandlerCustomizer[]) null)) + .withMessageContaining( + "TomcatProtocolHandlerCustomizers must not be null"); + } + @Test public void uriEncoding() { TomcatServletWebServerFactory factory = getFactory(); From ad767ca4e150bb25b2d73ce794950d51b7cb2502 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Thu, 4 Apr 2019 12:25:13 -0700 Subject: [PATCH 2/2] Polish "Simplify the configuration of the ProtocolHandler" Closes gh-16342 --- ...ReactiveWebServerFactoryConfiguration.java | 2 +- .../ServletWebServerFactoryConfiguration.java | 2 +- .../ConfigurableTomcatWebServerFactory.java | 2 +- .../TomcatReactiveWebServerFactory.java | 24 ++++++++++++------- .../tomcat/TomcatServletWebServerFactory.java | 24 ++++++++++++------- 5 files changed, 35 insertions(+), 19 deletions(-) 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 22d2070a9f7..78f7bd2ecd0 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 @@ -78,7 +78,7 @@ abstract class ReactiveWebServerFactoryConfiguration { public TomcatReactiveWebServerFactory tomcatReactiveWebServerFactory( ObjectProvider connectorCustomizers, ObjectProvider contextCustomizers, - ObjectProvider protocolHandlerCustomizers) { + ObjectProvider> protocolHandlerCustomizers) { TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory(); factory.getTomcatConnectorCustomizers().addAll( connectorCustomizers.orderedStream().collect(Collectors.toList())); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java index a336f9ccd41..fceccda3698 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java @@ -68,7 +68,7 @@ class ServletWebServerFactoryConfiguration { public TomcatServletWebServerFactory tomcatServletWebServerFactory( ObjectProvider connectorCustomizers, ObjectProvider contextCustomizers, - ObjectProvider protocolHandlerCustomizers) { + ObjectProvider> protocolHandlerCustomizers) { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.getTomcatConnectorCustomizers().addAll( connectorCustomizers.orderedStream().collect(Collectors.toList())); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java index bcbabb7289c..f4160f8bcec 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java @@ -74,7 +74,7 @@ public interface ConfigurableTomcatWebServerFactory extends ConfigurableWebServe * @param tomcatProtocolHandlerCustomizers the customizers to add */ void addProtocolHandlerCustomizers( - TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers); + TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers); /** * Set the character encoding to use for URL decoding. If not specified 'UTF-8' will diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java index d97737945c4..cb97180cf9d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java @@ -34,9 +34,11 @@ import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http2.Http2Protocol; import org.apache.tomcat.util.scan.StandardJarScanFilter; +import org.springframework.boot.util.LambdaSafe; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.boot.web.server.WebServer; @@ -72,7 +74,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac private List tomcatConnectorCustomizers = new ArrayList<>(); - private List tomcatProtocolHandlerCustomizers = new ArrayList<>(); + private List> tomcatProtocolHandlerCustomizers = new ArrayList<>(); private String protocol = DEFAULT_PROTOCOL; @@ -170,10 +172,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac if (connector.getProtocolHandler() instanceof AbstractProtocol) { customizeProtocol((AbstractProtocol) connector.getProtocolHandler()); } - - this.tomcatProtocolHandlerCustomizers.forEach( - (customizer) -> customizer.customize(connector.getProtocolHandler())); - + invokeProtocolHandlerCustomizers(connector); if (getUriEncoding() != null) { connector.setURIEncoding(getUriEncoding().name()); } @@ -190,6 +189,15 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac } } + @SuppressWarnings("unchecked") + private void invokeProtocolHandlerCustomizers(Connector connector) { + ProtocolHandler protocolHandler = connector.getProtocolHandler(); + LambdaSafe + .callbacks(TomcatProtocolHandlerCustomizer.class, + this.tomcatProtocolHandlerCustomizers, protocolHandler) + .invoke((customizer) -> customizer.customize(protocolHandler)); + } + private void customizeProtocol(AbstractProtocol protocol) { if (getAddress() != null) { protocol.setAddress(getAddress()); @@ -287,7 +295,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac * @param tomcatProtocolHandlerCustomizers the customizers to set */ public void setTomcatProtocolHandlerCustomizers( - Collection tomcatProtocolHandlerCustomizers) { + Collection> tomcatProtocolHandlerCustomizers) { Assert.notNull(tomcatProtocolHandlerCustomizers, "TomcatProtocolHandlerCustomizers must not be null"); this.tomcatProtocolHandlerCustomizers = new ArrayList<>( @@ -301,7 +309,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac */ @Override public void addProtocolHandlerCustomizers( - TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) { + TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) { Assert.notNull(tomcatProtocolHandlerCustomizers, "TomcatProtocolHandlerCustomizers must not be null"); this.tomcatProtocolHandlerCustomizers @@ -313,7 +321,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac * will be applied to the Tomcat {@link Connector}. * @return the customizers that will be applied */ - public Collection getTomcatProtocolHandlerCustomizers() { + public Collection> getTomcatProtocolHandlerCustomizers() { return this.tomcatProtocolHandlerCustomizers; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java index 1c1e41f92e9..7b0e9eb653e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java @@ -58,9 +58,11 @@ import org.apache.catalina.webresources.AbstractResourceSet; import org.apache.catalina.webresources.EmptyResource; import org.apache.catalina.webresources.StandardRoot; import org.apache.coyote.AbstractProtocol; +import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http2.Http2Protocol; import org.apache.tomcat.util.scan.StandardJarScanFilter; +import org.springframework.boot.util.LambdaSafe; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.WebServer; @@ -117,7 +119,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto private List tomcatConnectorCustomizers = new ArrayList<>(); - private List tomcatProtocolHandlerCustomizers = new ArrayList<>(); + private List> tomcatProtocolHandlerCustomizers = new ArrayList<>(); private List additionalTomcatConnectors = new ArrayList<>(); @@ -305,10 +307,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto if (connector.getProtocolHandler() instanceof AbstractProtocol) { customizeProtocol((AbstractProtocol) connector.getProtocolHandler()); } - - this.tomcatProtocolHandlerCustomizers.forEach( - (customizer) -> customizer.customize(connector.getProtocolHandler())); - + invokeProtocolHandlerCustomizers(connector); if (getUriEncoding() != null) { connector.setURIEncoding(getUriEncoding().name()); } @@ -331,6 +330,15 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto } } + @SuppressWarnings("unchecked") + private void invokeProtocolHandlerCustomizers(Connector connector) { + ProtocolHandler protocolHandler = connector.getProtocolHandler(); + LambdaSafe + .callbacks(TomcatProtocolHandlerCustomizer.class, + this.tomcatProtocolHandlerCustomizers, protocolHandler) + .invoke((customizer) -> customizer.customize(protocolHandler)); + } + private void customizeSsl(Connector connector) { new SslConnectorCustomizer(getSsl(), getSslStoreProvider()).customize(connector); if (getHttp2() != null && getHttp2().isEnabled()) { @@ -631,7 +639,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto * @param tomcatProtocolHandlerCustomizer the customizers to set */ public void setTomcatProtocolHandlerCustomizers( - Collection tomcatProtocolHandlerCustomizer) { + Collection> tomcatProtocolHandlerCustomizer) { Assert.notNull(tomcatProtocolHandlerCustomizer, "TomcatProtocolHandlerCustomizers must not be null"); this.tomcatProtocolHandlerCustomizers = new ArrayList<>( @@ -645,7 +653,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto */ @Override public void addProtocolHandlerCustomizers( - TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) { + TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) { Assert.notNull(tomcatProtocolHandlerCustomizers, "TomcatProtocolHandlerCustomizers must not be null"); this.tomcatProtocolHandlerCustomizers @@ -657,7 +665,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto * will be applied to the Tomcat {@link Connector}. * @return the customizers that will be applied */ - public Collection getTomcatProtocolHandlerCustomizers() { + public Collection> getTomcatProtocolHandlerCustomizers() { return this.tomcatProtocolHandlerCustomizers; }