Merge pull request #16342 Pascal Zwick

* pr/16342:
  Polish "Simplify the configuration of the ProtocolHandler"
  Simplify the configuration of the ProtocolHandler
This commit is contained in:
Madhura Bhave 2019-04-04 12:41:15 -07:00
commit 7587af343d
10 changed files with 299 additions and 2 deletions

View File

@ -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<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers) {
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> 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;
}

View File

@ -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<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers) {
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> 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;
}

View File

@ -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) -> {
};
}
}
}

View File

@ -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<AssertableWebApplicationContext> verifyContext() {
return this::verifyContext;
}
@ -308,4 +324,15 @@ public class ServletWebServerFactoryAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TomcatProtocolHandlerCustomizerConfiguration {
@Bean
public TomcatProtocolHandlerCustomizer protocolHandlerCustomizer() {
return (protocolHandler) -> {
};
}
}
}

View File

@ -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.

View File

@ -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 <T> specified type for customization based on {@link ProtocolHandler}
* @author Pascal Zwick
* @see ConfigurableTomcatWebServerFactory
* @since 2.2.0
*/
@FunctionalInterface
public interface TomcatProtocolHandlerCustomizer<T extends ProtocolHandler> {
/**
* Customize the protocol handler.
* @param protocolHandler the protocol handler to customize
*/
void customize(T protocolHandler);
}

View File

@ -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,6 +74,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>();
private List<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new ArrayList<>();
private String protocol = DEFAULT_PROTOCOL;
private Charset uriEncoding = DEFAULT_CHARSET;
@ -168,6 +172,7 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
}
invokeProtocolHandlerCustomizers(connector);
if (getUriEncoding() != null) {
connector.setURIEncoding(getUriEncoding().name());
}
@ -184,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());
@ -275,6 +289,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<? extends TomcatProtocolHandlerCustomizer<?>> 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<TomcatProtocolHandlerCustomizer<?>> getTomcatProtocolHandlerCustomizers() {
return this.tomcatProtocolHandlerCustomizers;
}
@Override
public void addEngineValves(Valve... engineValves) {
Assert.notNull(engineValves, "Valves must not be null");

View File

@ -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,6 +119,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>();
private List<TomcatProtocolHandlerCustomizer<?>> tomcatProtocolHandlerCustomizers = new ArrayList<>();
private List<Connector> additionalTomcatConnectors = new ArrayList<>();
private ResourceLoader resourceLoader;
@ -303,6 +307,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
}
invokeProtocolHandlerCustomizers(connector);
if (getUriEncoding() != null) {
connector.setURIEncoding(getUriEncoding().name());
}
@ -325,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()) {
@ -619,6 +633,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<? extends TomcatProtocolHandlerCustomizer<?>> 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<TomcatProtocolHandlerCustomizer<?>> 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

View File

@ -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<AbstractHttp11Protocol>[] 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();

View File

@ -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<AbstractHttp11Protocol>[] 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<AbstractHttp11Protocol> 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();