From 70eee612ff2a2b1e58cbcb18a4d46e464895c18a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 28 Mar 2019 16:52:51 +0000 Subject: [PATCH] Configure Tomcat to create upload targets Previously, if the directory to which Tomcat would write a file upload did not exist the upload attempt would fail and a 500 response would be returned to the client. This could happen when, for example, Tomcat is using a temporary directory for file uploads and tmpwatch has deleted the directory. This commit configures Tomcat so that, during multipart request parsing, it will automatically create the directory to which the parts will be written if it does not already exist. Closes gh-9616 --- .../main/asciidoc/spring-boot-features.adoc | 7 --- ...TomcatEmbeddedServletContainerFactory.java | 6 ++ ...tEmbeddedServletContainerFactoryTests.java | 59 +++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 98c35f63e5..09d2deea18 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -2306,13 +2306,6 @@ Spring Boot includes support for embedded Tomcat, Jetty, and Undertow servers. M developers will simply use the appropriate '`Starter`' to obtain a fully configured instance. By default the embedded server will listen for HTTP requests on port `8080`. -WARNING: If you choose to use Tomcat on CentOS be aware that, by default, a temporary -directory is used to store compiled JSPs, file uploads etc. This directory may be -deleted by `tmpwatch` while your application is running leading to failures. To avoid -this, you may want to customize your `tmpwatch` configuration so that `tomcat.*` -directories are not deleted, or configure `server.tomcat.basedir` so that embedded Tomcat -uses a different location. - [[boot-features-embedded-container-servlets-filters-listeners]] diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index 18c1acd71f..40fa8a61c4 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -206,6 +206,12 @@ public class TomcatEmbeddedServletContainerFactory catch (NoSuchMethodError ex) { // Tomcat is < 8.0.30. Continue } + try { + context.setCreateUploadTargets(true); + } + catch (NoSuchMethodError ex) { + // Tomcat is < 8.5.39. Continue. + } SkipPatternJarScanner.apply(context, this.tldSkipPatterns); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java index c65253735d..a2e5b6ce85 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.context.embedded.tomcat; import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Locale; @@ -30,10 +31,15 @@ import javax.naming.NamingException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; +import javax.servlet.MultipartConfigElement; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletRegistration.Dynamic; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -63,8 +69,18 @@ import org.springframework.boot.context.embedded.EmbeddedServletContainerExcepti import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.util.FileSystemUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.SocketUtils; +import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -539,6 +555,49 @@ public class TomcatEmbeddedServletContainerFactoryTests } } + @Test + public void nonExistentUploadDirectoryIsCreatedUponMultipartUpload() + throws IOException, URISyntaxException { + TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory( + 0); + AtomicReference servletContextReference = new AtomicReference<>(); + factory.addInitializers(new ServletContextInitializer() { + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + servletContextReference.set(servletContext); + Dynamic servlet = servletContext.addServlet("upload", new HttpServlet() { + + @Override + protected void doPost(HttpServletRequest req, + HttpServletResponse resp) + throws ServletException, IOException { + req.getParts(); + } + + }); + servlet.addMapping("/upload"); + servlet.setMultipartConfig(new MultipartConfigElement((String) null)); + } + + }); + this.container = factory.getEmbeddedServletContainer(); + this.container.start(); + File temp = (File) servletContextReference.get() + .getAttribute(ServletContext.TEMPDIR); + FileSystemUtils.deleteRecursively(temp); + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", new ByteArrayResource(new byte[1024 * 1024])); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpEntity> requestEntity = new HttpEntity<>(body, + headers); + ResponseEntity response = restTemplate + .postForEntity(getLocalUrl("/upload"), requestEntity, String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + } + @Override protected JspServlet getJspServlet() throws ServletException { Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()