Skip to content

Commit 2eaa64f

Browse files
pas2almbhave
authored andcommitted
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
1 parent 6bb5311 commit 2eaa64f

File tree

10 files changed

+283
-2
lines changed

10 files changed

+283
-2
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
2929
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
3030
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
31+
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
3132
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
3233
import org.springframework.boot.web.embedded.undertow.UndertowReactiveWebServerFactory;
3334
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
@@ -76,12 +77,16 @@ static class EmbeddedTomcat {
7677
@Bean
7778
public TomcatReactiveWebServerFactory tomcatReactiveWebServerFactory(
7879
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
79-
ObjectProvider<TomcatContextCustomizer> contextCustomizers) {
80+
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
81+
ObjectProvider<TomcatProtocolHandlerCustomizer> protocolHandlerCustomizers) {
8082
TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory();
8183
factory.getTomcatConnectorCustomizers().addAll(
8284
connectorCustomizers.orderedStream().collect(Collectors.toList()));
8385
factory.getTomcatContextCustomizers().addAll(
8486
contextCustomizers.orderedStream().collect(Collectors.toList()));
87+
factory.getTomcatProtocolHandlerCustomizers()
88+
.addAll(protocolHandlerCustomizers.orderedStream()
89+
.collect(Collectors.toList()));
8590
return factory;
8691
}
8792

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
3636
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
3737
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
38+
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
3839
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
3940
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
4041
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
@@ -66,12 +67,16 @@ public static class EmbeddedTomcat {
6667
@Bean
6768
public TomcatServletWebServerFactory tomcatServletWebServerFactory(
6869
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
69-
ObjectProvider<TomcatContextCustomizer> contextCustomizers) {
70+
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
71+
ObjectProvider<TomcatProtocolHandlerCustomizer> protocolHandlerCustomizers) {
7072
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
7173
factory.getTomcatConnectorCustomizers().addAll(
7274
connectorCustomizers.orderedStream().collect(Collectors.toList()));
7375
factory.getTomcatContextCustomizers().addAll(
7476
contextCustomizers.orderedStream().collect(Collectors.toList()));
77+
factory.getTomcatProtocolHandlerCustomizers()
78+
.addAll(protocolHandlerCustomizers.orderedStream()
79+
.collect(Collectors.toList()));
7580
return factory;
7681
}
7782

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/ReactiveWebServerFactoryAutoConfigurationTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
2424
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
2525
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
26+
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
2627
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
2728
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
2829
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
@@ -131,6 +132,21 @@ public void tomcatContextCustomizerBeanIsAddedToFactory() {
131132
});
132133
}
133134

135+
@Test
136+
public void tomcatProtocolHandlerCustomizerBeanIsAddedToFactory() {
137+
ReactiveWebApplicationContextRunner runner = new ReactiveWebApplicationContextRunner(
138+
AnnotationConfigReactiveWebApplicationContext::new)
139+
.withConfiguration(AutoConfigurations
140+
.of(ReactiveWebServerFactoryAutoConfiguration.class))
141+
.withUserConfiguration(
142+
TomcatProtocolHandlerCustomizerConfiguration.class);
143+
runner.run((context) -> {
144+
TomcatReactiveWebServerFactory factory = context
145+
.getBean(TomcatReactiveWebServerFactory.class);
146+
assertThat(factory.getTomcatProtocolHandlerCustomizers()).hasSize(1);
147+
});
148+
}
149+
134150
@Configuration(proxyBeanMethods = false)
135151
protected static class HttpHandlerConfiguration {
136152

@@ -193,4 +209,15 @@ public TomcatContextCustomizer contextCustomizer() {
193209

194210
}
195211

212+
@Configuration(proxyBeanMethods = false)
213+
static class TomcatProtocolHandlerCustomizerConfiguration {
214+
215+
@Bean
216+
public TomcatProtocolHandlerCustomizer protocolHandlerCustomizer() {
217+
return (protocolHandler) -> {
218+
};
219+
}
220+
221+
}
222+
196223
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/ServletWebServerFactoryAutoConfigurationTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
3333
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
3434
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
35+
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
3536
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
3637
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
3738
import org.springframework.boot.web.servlet.ServletRegistrationBean;
@@ -170,6 +171,21 @@ public void tomcatContextCustomizerBeanIsAddedToFactory() {
170171
});
171172
}
172173

174+
@Test
175+
public void tomcatProtocolHandlerCustomizerBeanIsAddedToFactory() {
176+
WebApplicationContextRunner runner = new WebApplicationContextRunner(
177+
AnnotationConfigServletWebServerApplicationContext::new)
178+
.withConfiguration(AutoConfigurations
179+
.of(ServletWebServerFactoryAutoConfiguration.class))
180+
.withUserConfiguration(
181+
TomcatProtocolHandlerCustomizerConfiguration.class);
182+
runner.run((context) -> {
183+
TomcatServletWebServerFactory factory = context
184+
.getBean(TomcatServletWebServerFactory.class);
185+
assertThat(factory.getTomcatProtocolHandlerCustomizers()).hasSize(1);
186+
});
187+
}
188+
173189
private ContextConsumer<AssertableWebApplicationContext> verifyContext() {
174190
return this::verifyContext;
175191
}
@@ -308,4 +324,15 @@ public TomcatContextCustomizer contextCustomizer() {
308324

309325
}
310326

327+
@Configuration(proxyBeanMethods = false)
328+
static class TomcatProtocolHandlerCustomizerConfiguration {
329+
330+
@Bean
331+
public TomcatProtocolHandlerCustomizer protocolHandlerCustomizer() {
332+
return (protocolHandler) -> {
333+
};
334+
}
335+
336+
}
337+
311338
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/ConfigurableTomcatWebServerFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ public interface ConfigurableTomcatWebServerFactory extends ConfigurableWebServe
6868
*/
6969
void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers);
7070

71+
/**
72+
* Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat
73+
* {@link Connector}.
74+
* @param tomcatProtocolHandlerCustomizers the customizers to add
75+
*/
76+
void addProtocolHandlerCustomizers(
77+
TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers);
78+
7179
/**
7280
* Set the character encoding to use for URL decoding. If not specified 'UTF-8' will
7381
* be used.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.embedded.tomcat;
18+
19+
import org.apache.catalina.connector.Connector;
20+
import org.apache.coyote.ProtocolHandler;
21+
22+
/**
23+
* Callback interface that can be used to customize the {@link ProtocolHandler} on the
24+
* {@link Connector}.
25+
*
26+
* @param <T> specified type for customization based on {@link ProtocolHandler}
27+
* @author Pascal Zwick
28+
* @see ConfigurableTomcatWebServerFactory
29+
* @since 2.2.0
30+
*/
31+
@FunctionalInterface
32+
public interface TomcatProtocolHandlerCustomizer<T extends ProtocolHandler> {
33+
34+
/**
35+
* Customize the protocol handler.
36+
* @param protocolHandler the protocol handler to customize
37+
*/
38+
void customize(T protocolHandler);
39+
40+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac
7272

7373
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>();
7474

75+
private List<TomcatProtocolHandlerCustomizer> tomcatProtocolHandlerCustomizers = new ArrayList<>();
76+
7577
private String protocol = DEFAULT_PROTOCOL;
7678

7779
private Charset uriEncoding = DEFAULT_CHARSET;
@@ -168,6 +170,10 @@ protected void customizeConnector(Connector connector) {
168170
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
169171
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
170172
}
173+
174+
this.tomcatProtocolHandlerCustomizers.forEach(
175+
(customizer) -> customizer.customize(connector.getProtocolHandler()));
176+
171177
if (getUriEncoding() != null) {
172178
connector.setURIEncoding(getUriEncoding().name());
173179
}
@@ -275,6 +281,42 @@ public Collection<TomcatConnectorCustomizer> getTomcatConnectorCustomizers() {
275281
return this.tomcatConnectorCustomizers;
276282
}
277283

284+
/**
285+
* Set {@link TomcatProtocolHandlerCustomizer}s that should be applied to the Tomcat
286+
* {@link Connector}. Calling this method will replace any existing customizers.
287+
* @param tomcatProtocolHandlerCustomizers the customizers to set
288+
*/
289+
public void setTomcatProtocolHandlerCustomizers(
290+
Collection<? extends TomcatProtocolHandlerCustomizer> tomcatProtocolHandlerCustomizers) {
291+
Assert.notNull(tomcatProtocolHandlerCustomizers,
292+
"TomcatProtocolHandlerCustomizers must not be null");
293+
this.tomcatProtocolHandlerCustomizers = new ArrayList<>(
294+
tomcatProtocolHandlerCustomizers);
295+
}
296+
297+
/**
298+
* Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat
299+
* {@link Connector}.
300+
* @param tomcatProtocolHandlerCustomizers the customizers to add
301+
*/
302+
@Override
303+
public void addProtocolHandlerCustomizers(
304+
TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) {
305+
Assert.notNull(tomcatProtocolHandlerCustomizers,
306+
"TomcatProtocolHandlerCustomizers must not be null");
307+
this.tomcatProtocolHandlerCustomizers
308+
.addAll(Arrays.asList(tomcatProtocolHandlerCustomizers));
309+
}
310+
311+
/**
312+
* Returns a mutable collection of the {@link TomcatProtocolHandlerCustomizer}s that
313+
* will be applied to the Tomcat {@link Connector}.
314+
* @return the customizers that will be applied
315+
*/
316+
public Collection<TomcatProtocolHandlerCustomizer> getTomcatProtocolHandlerCustomizers() {
317+
return this.tomcatProtocolHandlerCustomizers;
318+
}
319+
278320
@Override
279321
public void addEngineValves(Valve... engineValves) {
280322
Assert.notNull(engineValves, "Valves must not be null");

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto
117117

118118
private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<>();
119119

120+
private List<TomcatProtocolHandlerCustomizer> tomcatProtocolHandlerCustomizers = new ArrayList<>();
121+
120122
private List<Connector> additionalTomcatConnectors = new ArrayList<>();
121123

122124
private ResourceLoader resourceLoader;
@@ -303,6 +305,10 @@ protected void customizeConnector(Connector connector) {
303305
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
304306
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
305307
}
308+
309+
this.tomcatProtocolHandlerCustomizers.forEach(
310+
(customizer) -> customizer.customize(connector.getProtocolHandler()));
311+
306312
if (getUriEncoding() != null) {
307313
connector.setURIEncoding(getUriEncoding().name());
308314
}
@@ -619,6 +625,42 @@ public Collection<TomcatConnectorCustomizer> getTomcatConnectorCustomizers() {
619625
return this.tomcatConnectorCustomizers;
620626
}
621627

628+
/**
629+
* Set {@link TomcatProtocolHandlerCustomizer}s that should be applied to the Tomcat
630+
* {@link Connector}. Calling this method will replace any existing customizers.
631+
* @param tomcatProtocolHandlerCustomizer the customizers to set
632+
*/
633+
public void setTomcatProtocolHandlerCustomizers(
634+
Collection<? extends TomcatProtocolHandlerCustomizer> tomcatProtocolHandlerCustomizer) {
635+
Assert.notNull(tomcatProtocolHandlerCustomizer,
636+
"TomcatProtocolHandlerCustomizers must not be null");
637+
this.tomcatProtocolHandlerCustomizers = new ArrayList<>(
638+
tomcatProtocolHandlerCustomizer);
639+
}
640+
641+
/**
642+
* Add {@link TomcatProtocolHandlerCustomizer}s that should be added to the Tomcat
643+
* {@link Connector}.
644+
* @param tomcatProtocolHandlerCustomizers the customizers to add
645+
*/
646+
@Override
647+
public void addProtocolHandlerCustomizers(
648+
TomcatProtocolHandlerCustomizer... tomcatProtocolHandlerCustomizers) {
649+
Assert.notNull(tomcatProtocolHandlerCustomizers,
650+
"TomcatProtocolHandlerCustomizers must not be null");
651+
this.tomcatProtocolHandlerCustomizers
652+
.addAll(Arrays.asList(tomcatProtocolHandlerCustomizers));
653+
}
654+
655+
/**
656+
* Returns a mutable collection of the {@link TomcatProtocolHandlerCustomizer}s that
657+
* will be applied to the Tomcat {@link Connector}.
658+
* @return the customizers that will be applied
659+
*/
660+
public Collection<TomcatProtocolHandlerCustomizer> getTomcatProtocolHandlerCustomizers() {
661+
return this.tomcatProtocolHandlerCustomizers;
662+
}
663+
622664
/**
623665
* Add {@link Connector}s in addition to the default connector, e.g. for SSL or AJP
624666
* @param connectors the connectors to add

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.apache.catalina.core.StandardContext;
2727
import org.apache.catalina.startup.Tomcat;
2828
import org.apache.catalina.valves.RemoteIpValve;
29+
import org.apache.coyote.ProtocolHandler;
30+
import org.apache.coyote.http11.AbstractHttp11Protocol;
2931
import org.junit.Test;
3032
import org.mockito.ArgumentCaptor;
3133
import org.mockito.InOrder;
@@ -121,6 +123,24 @@ public void addNullAddConnectorCustomizersShouldThrowException() {
121123
.withMessageContaining("Customizers must not be null");
122124
}
123125

126+
@Test
127+
public void setNullProtocolHandlerCustomizersShouldThrowException() {
128+
TomcatReactiveWebServerFactory factory = getFactory();
129+
assertThatIllegalArgumentException()
130+
.isThrownBy(() -> factory.setTomcatProtocolHandlerCustomizers(null))
131+
.withMessageContaining(
132+
"TomcatProtocolHandlerCustomizers must not be null");
133+
}
134+
135+
@Test
136+
public void addNullProtocolHandlerCustomizersShouldThrowException() {
137+
TomcatReactiveWebServerFactory factory = getFactory();
138+
assertThatIllegalArgumentException().isThrownBy(() -> factory
139+
.addProtocolHandlerCustomizers((TomcatProtocolHandlerCustomizer[]) null))
140+
.withMessageContaining(
141+
"TomcatProtocolHandlerCustomizers must not be null");
142+
}
143+
124144
@Test
125145
public void tomcatConnectorCustomizersShouldBeInvoked() {
126146
TomcatReactiveWebServerFactory factory = getFactory();
@@ -136,6 +156,22 @@ public void tomcatConnectorCustomizersShouldBeInvoked() {
136156
}
137157
}
138158

159+
@Test
160+
public void tomcatProtocolHandlerCustomizersShouldBeInvoked() {
161+
TomcatReactiveWebServerFactory factory = getFactory();
162+
HttpHandler handler = mock(HttpHandler.class);
163+
TomcatProtocolHandlerCustomizer<AbstractHttp11Protocol>[] listeners = new TomcatProtocolHandlerCustomizer[4];
164+
Arrays.setAll(listeners, (i) -> mock(TomcatProtocolHandlerCustomizer.class));
165+
factory.setTomcatProtocolHandlerCustomizers(
166+
Arrays.asList(listeners[0], listeners[1]));
167+
factory.addProtocolHandlerCustomizers(listeners[2], listeners[3]);
168+
this.webServer = factory.getWebServer(handler);
169+
InOrder ordered = inOrder((Object[]) listeners);
170+
for (TomcatProtocolHandlerCustomizer listener : listeners) {
171+
ordered.verify(listener).customize(any(ProtocolHandler.class));
172+
}
173+
}
174+
139175
@Test
140176
public void useForwardedHeaders() {
141177
TomcatReactiveWebServerFactory factory = getFactory();

0 commit comments

Comments
 (0)