Skip to content

Commit 145d8d2

Browse files
committed
Defer removal of Connectors until after ServletContext initialization
Previously, we removed the Connectors from Tomcat's Service before the Context was started. The removal of the Connectors is required as it prevents Tomcat from accepting requests before we're ready to handle them. Part of starting the Context is creating and initializing the ServletContext. ServerProperties uses a ServletContextInitializer to set the session tracking modes and Tomcat rejects the SSL tracking mode if there is no SSL-enabled connector available. With the previous arrangement this led to a failure as the Connectors had been removed so the SSL-enabled connector could not be found. This commit updates the embedded Tomcat container to defer the removal of the Connectors until after the context has been started but still at a point that is before the Connectors themselves would have been started. Closes gh-12058
1 parent f3989b1 commit 145d8d2

File tree

3 files changed

+110
-39
lines changed

3 files changed

+110
-39
lines changed

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/ServerPropertiesTests.java

Lines changed: 91 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.EnumSet;
2424
import java.util.HashMap;
2525
import java.util.Map;
26+
import java.util.concurrent.atomic.AtomicReference;
2627

2728
import javax.servlet.ServletContext;
2829
import javax.servlet.ServletException;
@@ -43,6 +44,7 @@
4344

4445
import org.springframework.beans.MutablePropertyValues;
4546
import org.springframework.boot.bind.RelaxedDataBinder;
47+
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
4648
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
4749
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
4850
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
@@ -53,9 +55,7 @@
5355
import org.springframework.mock.env.MockEnvironment;
5456

5557
import static org.assertj.core.api.Assertions.assertThat;
56-
import static org.mockito.BDDMockito.given;
5758
import static org.mockito.Matchers.anyBoolean;
58-
import static org.mockito.Mockito.atLeastOnce;
5959
import static org.mockito.Mockito.mock;
6060
import static org.mockito.Mockito.never;
6161
import static org.mockito.Mockito.spy;
@@ -314,7 +314,22 @@ public void testCustomizeDisplayName() throws Exception {
314314
}
315315

316316
@Test
317-
public void customizeSessionProperties() throws Exception {
317+
public void customizeSessionPropertiesWithJetty() throws Exception {
318+
customizeSessionProperties(new TomcatEmbeddedServletContainerFactory(0));
319+
}
320+
321+
@Test
322+
public void customizeSessionPropertiesWithTomcat() throws Exception {
323+
customizeSessionProperties(new TomcatEmbeddedServletContainerFactory(0));
324+
}
325+
326+
@Test
327+
public void customizeSessionPropertiesWithUndertow() throws Exception {
328+
customizeSessionProperties(new UndertowEmbeddedServletContainerFactory(0));
329+
}
330+
331+
private void customizeSessionProperties(
332+
AbstractEmbeddedServletContainerFactory factory) {
318333
Map<String, String> map = new HashMap<String, String>();
319334
map.put("server.session.timeout", "123");
320335
map.put("server.session.tracking-modes", "cookie,url");
@@ -326,38 +341,80 @@ public void customizeSessionProperties() throws Exception {
326341
map.put("server.session.cookie.secure", "true");
327342
map.put("server.session.cookie.max-age", "60");
328343
bindProperties(map);
329-
ConfigurableEmbeddedServletContainer factory = mock(
330-
ConfigurableEmbeddedServletContainer.class);
331-
ServletContext servletContext = mock(ServletContext.class);
332-
SessionCookieConfig sessionCookieConfig = mock(SessionCookieConfig.class);
333-
given(servletContext.getSessionCookieConfig()).willReturn(sessionCookieConfig);
334344
this.properties.customize(factory);
335-
triggerInitializers(factory, servletContext);
336-
verify(factory).setSessionTimeout(123);
337-
verify(servletContext).setSessionTrackingModes(
338-
EnumSet.of(SessionTrackingMode.COOKIE, SessionTrackingMode.URL));
339-
verify(sessionCookieConfig).setName("testname");
340-
verify(sessionCookieConfig).setDomain("testdomain");
341-
verify(sessionCookieConfig).setPath("/testpath");
342-
verify(sessionCookieConfig).setComment("testcomment");
343-
verify(sessionCookieConfig).setHttpOnly(true);
344-
verify(sessionCookieConfig).setSecure(true);
345-
verify(sessionCookieConfig).setMaxAge(60);
346-
}
347-
348-
private void triggerInitializers(ConfigurableEmbeddedServletContainer container,
349-
ServletContext servletContext) throws ServletException {
350-
verify(container, atLeastOnce())
351-
.addInitializers(this.initializersCaptor.capture());
352-
for (Object initializers : this.initializersCaptor.getAllValues()) {
353-
if (initializers instanceof ServletContextInitializer) {
354-
((ServletContextInitializer) initializers).onStartup(servletContext);
355-
}
356-
else {
357-
for (ServletContextInitializer initializer : (ServletContextInitializer[]) initializers) {
358-
initializer.onStartup(servletContext);
359-
}
360-
}
345+
final AtomicReference<ServletContext> servletContextReference = new AtomicReference<ServletContext>();
346+
EmbeddedServletContainer container = factory
347+
.getEmbeddedServletContainer(new ServletContextInitializer() {
348+
349+
@Override
350+
public void onStartup(ServletContext servletContext)
351+
throws ServletException {
352+
servletContextReference.set(servletContext);
353+
}
354+
355+
});
356+
try {
357+
container.start();
358+
SessionCookieConfig sessionCookieConfig = servletContextReference.get()
359+
.getSessionCookieConfig();
360+
assertThat(factory.getSessionTimeout()).isEqualTo(123);
361+
assertThat(sessionCookieConfig.getName()).isEqualTo("testname");
362+
assertThat(sessionCookieConfig.getDomain()).isEqualTo("testdomain");
363+
assertThat(sessionCookieConfig.getPath()).isEqualTo("/testpath");
364+
assertThat(sessionCookieConfig.getComment()).isEqualTo("testcomment");
365+
assertThat(sessionCookieConfig.isHttpOnly()).isTrue();
366+
assertThat(sessionCookieConfig.isSecure()).isTrue();
367+
assertThat(sessionCookieConfig.getMaxAge()).isEqualTo(60);
368+
assertThat(servletContextReference.get().getEffectiveSessionTrackingModes())
369+
.isEqualTo(EnumSet.of(SessionTrackingMode.COOKIE,
370+
SessionTrackingMode.URL));
371+
}
372+
finally {
373+
container.stop();
374+
}
375+
}
376+
377+
@Test
378+
public void sslSessionTrackingWithJetty() throws Exception {
379+
sslSessionTracking(new JettyEmbeddedServletContainerFactory(0));
380+
}
381+
382+
@Test
383+
public void sslSessionTrackingWithTomcat() throws Exception {
384+
sslSessionTracking(new TomcatEmbeddedServletContainerFactory(0));
385+
}
386+
387+
@Test
388+
public void sslSessionTrackingWithUndertow() throws Exception {
389+
sslSessionTracking(new UndertowEmbeddedServletContainerFactory(0));
390+
}
391+
392+
private void sslSessionTracking(AbstractEmbeddedServletContainerFactory factory) {
393+
Map<String, String> map = new HashMap<String, String>();
394+
map.put("server.ssl.enabled", "true");
395+
map.put("server.ssl.key-store", "src/test/resources/test.jks");
396+
map.put("server.ssl.key-password", "password");
397+
map.put("server.session.tracking-modes", "ssl");
398+
bindProperties(map);
399+
this.properties.customize(factory);
400+
final AtomicReference<ServletContext> servletContextReference = new AtomicReference<ServletContext>();
401+
EmbeddedServletContainer container = factory
402+
.getEmbeddedServletContainer(new ServletContextInitializer() {
403+
404+
@Override
405+
public void onStartup(ServletContext servletContext)
406+
throws ServletException {
407+
servletContextReference.set(servletContext);
408+
}
409+
410+
});
411+
try {
412+
container.start();
413+
assertThat(servletContextReference.get().getEffectiveSessionTrackingModes())
414+
.isEqualTo(EnumSet.of(SessionTrackingMode.SSL));
415+
}
416+
finally {
417+
container.stop();
361418
}
362419
}
363420

Binary file not shown.

spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,10 @@
2525
import org.apache.catalina.Container;
2626
import org.apache.catalina.Context;
2727
import org.apache.catalina.Engine;
28+
import org.apache.catalina.Lifecycle;
29+
import org.apache.catalina.LifecycleEvent;
2830
import org.apache.catalina.LifecycleException;
31+
import org.apache.catalina.LifecycleListener;
2932
import org.apache.catalina.LifecycleState;
3033
import org.apache.catalina.Service;
3134
import org.apache.catalina.connector.Connector;
@@ -91,17 +94,28 @@ private void initialize() throws EmbeddedServletContainerException {
9194
try {
9295
addInstanceIdToEngineName();
9396
try {
94-
// Remove service connectors to that protocol binding doesn't happen
95-
// yet
96-
removeServiceConnectors();
97+
final Context context = findContext();
98+
context.addLifecycleListener(new LifecycleListener() {
99+
100+
@Override
101+
public void lifecycleEvent(LifecycleEvent event) {
102+
if (context.equals(event.getSource())
103+
&& Lifecycle.START_EVENT.equals(event.getType())) {
104+
// Remove service connectors so that protocol
105+
// binding doesn't happen when the service is
106+
// started.
107+
removeServiceConnectors();
108+
}
109+
}
110+
111+
});
97112

98113
// Start the server to trigger initialization listeners
99114
this.tomcat.start();
100115

101116
// We can re-throw failure exception directly in the main thread
102117
rethrowDeferredStartupExceptions();
103118

104-
Context context = findContext();
105119
try {
106120
ContextBindings.bindClassLoader(context, getNamingToken(context),
107121
getClass().getClassLoader());

0 commit comments

Comments
 (0)