Browse Source

Delete web server's temporary directories when the context is closed

Closes gh-9983
pull/48906/head
Moritz Halbritter 3 weeks ago
parent
commit
c9be84af6e
  1. 4
      documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/cloudfoundry/customcontextpath/MyCloudFoundryConfiguration.java
  2. 4
      documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/actuator/cloudfoundry/customcontextpath/MyCloudFoundryConfiguration.kt
  3. 41
      module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactory.java
  4. 39
      module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatWebServerFactory.java
  5. 30
      module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/reactive/TomcatReactiveWebServerFactory.java
  6. 27
      module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactory.java
  7. 8
      module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/reactive/TomcatReactiveWebServerFactoryTests.java
  8. 8
      module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactoryTests.java
  9. 55
      module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactory.java
  10. 50
      module/spring-boot-web-server/src/test/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactoryTests.java

4
documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/actuator/cloudfoundry/customcontextpath/MyCloudFoundryConfiguration.java

@ -43,8 +43,8 @@ public class MyCloudFoundryConfiguration { @@ -43,8 +43,8 @@ public class MyCloudFoundryConfiguration {
return new TomcatServletWebServerFactory() {
@Override
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
super.prepareContext(host, initializers);
protected void prepareContext(Host host, ServletContextInitializer[] initializers, TempDirs tempDirs) {
super.prepareContext(host, initializers, tempDirs);
StandardContext child = new StandardContext();
child.addLifecycleListener(new Tomcat.FixContextListener());
child.setPath("/cloudfoundryapplication");

4
documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/actuator/cloudfoundry/customcontextpath/MyCloudFoundryConfiguration.kt

@ -41,8 +41,8 @@ class MyCloudFoundryConfiguration { @@ -41,8 +41,8 @@ class MyCloudFoundryConfiguration {
fun servletWebServerFactory(): TomcatServletWebServerFactory {
return object : TomcatServletWebServerFactory() {
override fun prepareContext(host: Host, initializers: Array<ServletContextInitializer>) {
super.prepareContext(host, initializers)
override fun prepareContext(host: Host, initializers: Array<ServletContextInitializer>, tempDirs: TempDirs) {
super.prepareContext(host, initializers, tempDirs)
val child = StandardContext()
child.addLifecycleListener(FixContextListener())
child.path = "/cloudfoundryapplication"

41
module/spring-boot-jetty/src/main/java/org/springframework/boot/jetty/servlet/JettyServletWebServerFactory.java

@ -63,6 +63,7 @@ import org.eclipse.jetty.session.DefaultSessionCache; @@ -63,6 +63,7 @@ import org.eclipse.jetty.session.DefaultSessionCache;
import org.eclipse.jetty.session.FileSessionDataStore;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.util.resource.URLResourceFactory;
@ -161,7 +162,9 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory @@ -161,7 +162,9 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory
InetSocketAddress address = new InetSocketAddress(getAddress(), port);
Server server = createServer(address);
context.setServer(server);
configureWebAppContext(context, initializers);
TempDirs tempDirs = new TempDirs(port);
server.addEventListener(new CleanTempDirsListener(tempDirs));
configureWebAppContext(context, tempDirs, initializers);
server.setHandler(addHandlerWrappers(context));
logger.info("Server initialized with port: " + port);
if (this.getMaxConnections() > -1) {
@ -214,8 +217,23 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory @@ -214,8 +217,23 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory
* Configure the given Jetty {@link WebAppContext} for use.
* @param context the context to configure
* @param initializers the set of initializers to apply
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of
* {@link #configureWebAppContext(WebAppContext, TempDirs, ServletContextInitializer...)}
*/
@Deprecated(forRemoval = true, since = "4.1.0")
protected final void configureWebAppContext(WebAppContext context, ServletContextInitializer... initializers) {
configureWebAppContext(context, new TempDirs(getPort()), initializers);
}
/**
* Configure the given Jetty {@link WebAppContext} for use.
* @param context the context to configure
* @param tempDirs to manage temporary directories
* @param initializers the set of initializers to apply
* @since 4.1.0
*/
protected final void configureWebAppContext(WebAppContext context, TempDirs tempDirs,
ServletContextInitializer... initializers) {
Assert.notNull(context, "'context' must not be null");
context.clearAliasChecks();
if (this.resourceLoader != null) {
@ -224,7 +242,7 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory @@ -224,7 +242,7 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory
String contextPath = getSettings().getContextPath().toString();
context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath : "/");
context.setDisplayName(getSettings().getDisplayName());
configureDocumentRoot(context);
configureDocumentRoot(context, tempDirs);
if (getSettings().isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
@ -289,11 +307,11 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory @@ -289,11 +307,11 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory
}
}
private void configureDocumentRoot(WebAppContext handler) {
private void configureDocumentRoot(WebAppContext handler, TempDirs tempDirs) {
DocumentRoot documentRoot = new DocumentRoot(logger);
documentRoot.setDirectory(this.settings.getDocumentRoot());
File root = documentRoot.getValidDirectory();
File docBase = (root != null) ? root : createTempDir("jetty-docbase");
File docBase = (root != null) ? root : tempDirs.createTempDir("jetty-docbase").toFile();
try {
ResourceFactory resourceFactory = handler.getResourceFactory();
List<Resource> resources = new ArrayList<>();
@ -616,4 +634,19 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory @@ -616,4 +634,19 @@ public class JettyServletWebServerFactory extends JettyWebServerFactory
}
private static class CleanTempDirsListener implements LifeCycle.Listener {
private final TempDirs tempDirs;
CleanTempDirsListener(TempDirs tempDirs) {
this.tempDirs = tempDirs;
}
@Override
public void lifeCycleStopped(LifeCycle event) {
this.tempDirs.cleanup();
}
}
}

39
module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/TomcatWebServerFactory.java

@ -29,6 +29,8 @@ import java.util.Set; @@ -29,6 +29,8 @@ import java.util.Set;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Executor;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Connector;
@ -367,16 +369,34 @@ public class TomcatWebServerFactory extends AbstractConfigurableWebServerFactory @@ -367,16 +369,34 @@ public class TomcatWebServerFactory extends AbstractConfigurableWebServerFactory
this.useApr = useApr;
}
/**
* Creates the {@link Tomcat} web server.
* @return the web server.
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of
* {@link #createTomcat(TempDirs)}
*/
@Deprecated(forRemoval = true, since = "4.1.0")
protected Tomcat createTomcat() {
return createTomcat(new TempDirs(getPort()));
}
/**
* Creates the {@link Tomcat} web server.
* @param tempDirs to manage temporary directories
* @return the web server
* @since 4.1.0
*/
protected Tomcat createTomcat(TempDirs tempDirs) {
if (this.isDisableMBeanRegistry()) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (getBaseDirectory() != null) ? getBaseDirectory() : createTempDir("tomcat");
File baseDir = (getBaseDirectory() != null) ? getBaseDirectory() : tempDirs.createTempDir("tomcat").toFile();
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : getDefaultServerLifecycleListeners()) {
tomcat.getServer().addLifecycleListener(listener);
}
tomcat.getServer().addLifecycleListener(new CleanTempDirsListener(tempDirs));
Connector connector = new Connector(getProtocol());
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
@ -463,4 +483,21 @@ public class TomcatWebServerFactory extends AbstractConfigurableWebServerFactory @@ -463,4 +483,21 @@ public class TomcatWebServerFactory extends AbstractConfigurableWebServerFactory
}
}
private static class CleanTempDirsListener implements LifecycleListener {
private final TempDirs tempDirs;
CleanTempDirsListener(TempDirs tempDirs) {
this.tempDirs = tempDirs;
}
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) {
this.tempDirs.cleanup();
}
}
}
}

30
module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/reactive/TomcatReactiveWebServerFactory.java

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
package org.springframework.boot.tomcat.reactive;
import java.io.File;
import java.nio.file.Path;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
@ -68,20 +68,40 @@ public class TomcatReactiveWebServerFactory extends TomcatWebServerFactory @@ -68,20 +68,40 @@ public class TomcatReactiveWebServerFactory extends TomcatWebServerFactory
@Override
public WebServer getWebServer(HttpHandler httpHandler) {
Tomcat tomcat = createTomcat();
TempDirs tempDirs = new TempDirs(getPort());
Tomcat tomcat = createTomcat(tempDirs);
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
prepareContext(tomcat.getHost(), servlet);
prepareContext(tomcat.getHost(), servlet, tempDirs);
return getTomcatWebServer(tomcat);
}
/**
* Prepares the context.
* @param host the host
* @param servlet the servlet adapter
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of
* {@link #prepareContext(Host, TomcatHttpHandlerAdapter, TempDirs)}
*/
@Deprecated(forRemoval = true, since = "4.1.0")
protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet) {
File docBase = createTempDir("tomcat-docbase");
prepareContext(host, servlet, new TempDirs(getPort()));
}
/**
* Prepares the context.
* @param host the host
* @param servlet the servlet adapter
* @param tempDirs to manage temporary directories
* @since 4.1.0
*/
protected void prepareContext(Host host, TomcatHttpHandlerAdapter servlet, TempDirs tempDirs) {
Path docBase = tempDirs.createTempDir("tomcat-docbase");
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
WebResourceRoot resourceRoot = new StandardRoot(context);
ignoringNoSuchMethodError(() -> resourceRoot.setReadOnly(true));
context.setResources(resourceRoot);
context.setPath("");
context.setDocBase(docBase.getAbsolutePath());
context.setDocBase(docBase.toAbsolutePath().toString());
context.addLifecycleListener(new Tomcat.FixContextListener());
ClassLoader parentClassLoader = ClassUtils.getDefaultClassLoader();
context.setParentClassLoader(parentClassLoader);

27
module/spring-boot-tomcat/src/main/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactory.java

@ -161,12 +161,32 @@ public class TomcatServletWebServerFactory extends TomcatWebServerFactory @@ -161,12 +161,32 @@ public class TomcatServletWebServerFactory extends TomcatWebServerFactory
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = createTomcat();
prepareContext(tomcat.getHost(), initializers);
TempDirs tempDirs = new TempDirs(getPort());
Tomcat tomcat = createTomcat(tempDirs);
prepareContext(tomcat.getHost(), initializers, tempDirs);
return getTomcatWebServer(tomcat);
}
/**
* Prepares the context.
* @param host the host
* @param initializers the servlet context initializers
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of
* {@link #prepareContext(Host, ServletContextInitializer[], TempDirs)}
*/
@Deprecated(forRemoval = true, since = "4.1.0")
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
prepareContext(host, initializers, new TempDirs(getPort()));
}
/**
* Prepares the context.
* @param host the host
* @param initializers the servlet context initializers
* @param tempDirs to manage temporary directories
* @since 4.1.0
*/
protected void prepareContext(Host host, ServletContextInitializer[] initializers, TempDirs tempDirs) {
DocumentRoot documentRoot = new DocumentRoot(logger);
documentRoot.setDirectory(this.settings.getDocumentRoot());
File documentRootFile = documentRoot.getValidDirectory();
@ -179,7 +199,8 @@ public class TomcatServletWebServerFactory extends TomcatWebServerFactory @@ -179,7 +199,8 @@ public class TomcatServletWebServerFactory extends TomcatWebServerFactory
context.setName(contextPath);
context.setDisplayName(this.settings.getDisplayName());
context.setPath(contextPath);
File docBase = (documentRootFile != null) ? documentRootFile : createTempDir("tomcat-docbase");
File docBase = (documentRootFile != null) ? documentRootFile
: tempDirs.createTempDir("tomcat-docbase").toFile();
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
ClassLoader parentClassLoader = (this.resourceLoader != null) ? this.resourceLoader.getClassLoader()

8
module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/reactive/TomcatReactiveWebServerFactoryTests.java

@ -104,7 +104,9 @@ class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFacto @@ -104,7 +104,9 @@ class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFacto
assertThat(factory.getContextLifecycleListeners()).isEmpty();
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
this.webServer = tomcatWebServer;
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners())
.extracting((l) -> l.getClass().getSimpleName())
.containsExactly("CleanTempDirsListener");
}
@Test
@ -113,8 +115,8 @@ class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFacto @@ -113,8 +115,8 @@ class TomcatReactiveWebServerFactoryTests extends AbstractReactiveWebServerFacto
factory.setUseApr(true);
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer(mock(HttpHandler.class));
this.webServer = tomcatWebServer;
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
.isInstanceOf(AprLifecycleListener.class);
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).extracting(Object::getClass)
.contains(AprLifecycleListener.class);
}
@Test

8
module/spring-boot-tomcat/src/test/java/org/springframework/boot/tomcat/servlet/TomcatServletWebServerFactoryTests.java

@ -160,7 +160,9 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory @@ -160,7 +160,9 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory
assertThat(factory.getContextLifecycleListeners()).isEmpty();
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
this.webServer = tomcatWebServer;
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).isEmpty();
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners())
.extracting((listener) -> listener.getClass().getSimpleName())
.containsExactly("CleanTempDirsListener");
}
@Test
@ -169,8 +171,8 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory @@ -169,8 +171,8 @@ class TomcatServletWebServerFactoryTests extends AbstractServletWebServerFactory
factory.setUseApr(true);
TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
this.webServer = tomcatWebServer;
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).singleElement()
.isInstanceOf(AprLifecycleListener.class);
assertThat(tomcatWebServer.getTomcat().getServer().findLifecycleListeners()).extracting(Object::getClass)
.contains(AprLifecycleListener.class);
}
@Test

55
module/spring-boot-web-server/src/main/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactory.java

@ -20,7 +20,9 @@ import java.io.File; @@ -20,7 +20,9 @@ import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@ -33,6 +35,7 @@ import org.springframework.boot.ssl.SslBundles; @@ -33,6 +35,7 @@ import org.springframework.boot.ssl.SslBundles;
import org.springframework.boot.web.error.ErrorPage;
import org.springframework.boot.web.server.Ssl.ServerNameSslBundle;
import org.springframework.util.Assert;
import org.springframework.util.FileSystemUtils;
/**
* Abstract base class for {@link ConfigurableWebServerFactory} implementations.
@ -215,7 +218,9 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab @@ -215,7 +218,9 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
* Return the absolute temp dir for given web server.
* @param prefix server name
* @return the temp dir for given server.
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of {@link TempDirs}.
*/
@Deprecated(since = "4.1.0", forRemoval = true)
protected final File createTempDir(String prefix) {
try {
File tempDir = Files.createTempDirectory(prefix + "." + getPort() + ".").toFile();
@ -228,4 +233,54 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab @@ -228,4 +233,54 @@ public abstract class AbstractConfigurableWebServerFactory implements Configurab
}
}
/**
* Manages temporary directories.
*
* @since 4.1.0
*/
public static class TempDirs {
private final Set<Path> dirs = new HashSet<>();
private final int port;
public TempDirs(int port) {
this.port = port;
}
/**
* Creates a temporary directory and registers it for {@link #cleanup cleanup}.
* @param prefix the directory prefix
* @return the path to the temporary directory
*/
public Path createTempDir(String prefix) {
try {
Path directory = Files.createTempDirectory(prefix + "." + this.port + ".").toAbsolutePath();
this.dirs.add(directory);
return directory;
}
catch (IOException ex) {
throw new WebServerException(
"Unable to create tempDir. java.io.tmpdir is set to " + System.getProperty("java.io.tmpdir"),
ex);
}
}
/**
* Deletes all created temporary directories.
*/
public void cleanup() {
for (Path dir : this.dirs) {
try {
FileSystemUtils.deleteRecursively(dir);
}
catch (IOException ex) {
// Ignore
}
}
this.dirs.clear();
}
}
}

50
module/spring-boot-web-server/src/test/java/org/springframework/boot/web/server/AbstractConfigurableWebServerFactoryTests.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
/*
* Copyright 2012-present 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.server;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory.TempDirs;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AbstractConfigurableWebServerFactory}.
*
* @author Moritz Halbritter
*/
class AbstractConfigurableWebServerFactoryTests {
@Test
void shouldCleanUpTempDirs() throws IOException {
TempDirs tempDirs = new TempDirs(1234);
Path tempDir1 = tempDirs.createTempDir(getClass().getSimpleName());
Path tempDir2 = tempDirs.createTempDir(getClass().getSimpleName());
assertThat(tempDir1).isNotEqualTo(tempDir2);
assertThat(tempDir1).exists();
assertThat(tempDir2).exists();
Files.writeString(tempDir1.resolve("test.txt"), "test");
tempDirs.cleanup();
assertThat(tempDir1).doesNotExist();
assertThat(tempDir2).doesNotExist();
}
}
Loading…
Cancel
Save