Respect Tomcat's failCtxIfServletStartFails flag

Ensure that if the user has set `failCtxIfServletStartFails` to `true`
using a `ContextCustomizers` any Servlet init exceptions stop the
application from running.

Closes gh-14448
pull/14774/merge
Phillip Webb 6 years ago
parent 042d495d92
commit 21ebb94d49

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,10 +17,12 @@
package org.springframework.boot.web.embedded.tomcat; package org.springframework.boot.web.embedded.tomcat;
import org.apache.catalina.Container; import org.apache.catalina.Container;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Manager; import org.apache.catalina.Manager;
import org.apache.catalina.core.StandardContext; import org.apache.catalina.core.StandardContext;
import org.apache.catalina.session.ManagerBase; import org.apache.catalina.session.ManagerBase;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -59,7 +61,7 @@ class TomcatEmbeddedContext extends StandardContext {
super.setManager(manager); super.setManager(manager);
} }
public void deferredLoadOnStartup() { public void deferredLoadOnStartup() throws LifecycleException {
// Some older Servlet frameworks (e.g. Struts, BIRT) use the Thread context class // Some older Servlet frameworks (e.g. Struts, BIRT) use the Thread context class
// loader to create servlet instances in this phase. If they do that and then try // loader to create servlet instances in this phase. If they do that and then try
// to initialize them later the class loader may have changed, so wrap the call to // to initialize them later the class loader may have changed, so wrap the call to
@ -70,15 +72,20 @@ class TomcatEmbeddedContext extends StandardContext {
if (classLoader != null) { if (classLoader != null) {
existingLoader = ClassUtils.overrideThreadContextClassLoader(classLoader); existingLoader = ClassUtils.overrideThreadContextClassLoader(classLoader);
} }
try {
if (this.overrideLoadOnStart) { if (this.overrideLoadOnStart) {
// Earlier versions of Tomcat used a version that returned void. If that // Earlier versions of Tomcat used a version that returned void. If that
// version is used our overridden loadOnStart method won't have been called // version is used our overridden loadOnStart method won't have been
// and the original will have already run. // called and the original will have already run.
super.loadOnStartup(findChildren()); boolean started = super.loadOnStartup(findChildren());
Assert.state(started,
"Unable to start embedded tomcat context " + getName());
}
} }
if (existingLoader != null) { finally {
ClassUtils.overrideThreadContextClassLoader(existingLoader); if (existingLoader != null) {
ClassUtils.overrideThreadContextClassLoader(existingLoader);
}
} }
} }

@ -223,10 +223,15 @@ public class TomcatWebServer implements WebServer {
} }
private void checkThatConnectorsHaveStarted() { private void checkThatConnectorsHaveStarted() {
checkConnectorHasStarted(this.tomcat.getConnector());
for (Connector connector : this.tomcat.getService().findConnectors()) { for (Connector connector : this.tomcat.getService().findConnectors()) {
if (LifecycleState.FAILED.equals(connector.getState())) { checkConnectorHasStarted(connector);
throw new ConnectorStartFailedException(connector.getPort()); }
} }
private void checkConnectorHasStarted(Connector connector) {
if (LifecycleState.FAILED.equals(connector.getState())) {
throw new ConnectorStartFailedException(connector.getPort());
} }
} }

@ -30,6 +30,7 @@ import java.util.Set;
import javax.naming.InitialContext; import javax.naming.InitialContext;
import javax.naming.NamingException; import javax.naming.NamingException;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import org.apache.catalina.Container; import org.apache.catalina.Container;
import org.apache.catalina.Context; import org.apache.catalina.Context;
@ -41,6 +42,7 @@ import org.apache.catalina.SessionIdGenerator;
import org.apache.catalina.Valve; import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector; import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper; import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.util.CharsetMapper; import org.apache.catalina.util.CharsetMapper;
@ -427,6 +429,21 @@ public class TomcatServletWebServerFactoryTests
assertThat(context.getUseHttpOnly()).isFalse(); assertThat(context.getUseHttpOnly()).isFalse();
} }
@Test
public void exceptionThrownOnLoadFailureWhenFailCtxIfServletStartFailsIsTrue() {
TomcatServletWebServerFactory factory = getFactory();
factory.addContextCustomizers((context) -> {
if (context instanceof StandardContext) {
((StandardContext) context).setFailCtxIfServletStartFails(true);
}
});
this.webServer = factory.getWebServer((context) -> {
context.addServlet("failing", FailingServlet.class).setLoadOnStartup(0);
});
this.thrown.expect(WebServerException.class);
this.webServer.start();
}
@Override @Override
protected JspServlet getJspServlet() throws ServletException { protected JspServlet getJspServlet() throws ServletException {
Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat(); Tomcat tomcat = ((TomcatWebServer) this.webServer).getTomcat();
@ -475,4 +492,13 @@ public class TomcatServletWebServerFactoryTests
assertThat(((ConnectorStartFailedException) ex).getPort()).isEqualTo(blockedPort); assertThat(((ConnectorStartFailedException) ex).getPort()).isEqualTo(blockedPort);
} }
static class FailingServlet extends HttpServlet {
@Override
public void init() throws ServletException {
throw new RuntimeException("Init Failure");
}
}
} }

Loading…
Cancel
Save