Skip to content

Commit 70eee61

Browse files
committed
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
1 parent f8bd066 commit 70eee61

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,13 +2306,6 @@ Spring Boot includes support for embedded Tomcat, Jetty, and Undertow servers. M
23062306
developers will simply use the appropriate '`Starter`' to obtain a fully configured
23072307
instance. By default the embedded server will listen for HTTP requests on port `8080`.
23082308

2309-
WARNING: If you choose to use Tomcat on CentOS be aware that, by default, a temporary
2310-
directory is used to store compiled JSPs, file uploads etc. This directory may be
2311-
deleted by `tmpwatch` while your application is running leading to failures. To avoid
2312-
this, you may want to customize your `tmpwatch` configuration so that `tomcat.*`
2313-
directories are not deleted, or configure `server.tomcat.basedir` so that embedded Tomcat
2314-
uses a different location.
2315-
23162309

23172310

23182311
[[boot-features-embedded-container-servlets-filters-listeners]]

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,12 @@ protected void prepareContext(Host host, ServletContextInitializer[] initializer
206206
catch (NoSuchMethodError ex) {
207207
// Tomcat is < 8.0.30. Continue
208208
}
209+
try {
210+
context.setCreateUploadTargets(true);
211+
}
212+
catch (NoSuchMethodError ex) {
213+
// Tomcat is < 8.5.39. Continue.
214+
}
209215
SkipPatternJarScanner.apply(context, this.tldSkipPatterns);
210216
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
211217
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());

spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.net.URISyntaxException;
2122
import java.nio.charset.Charset;
2223
import java.util.Arrays;
2324
import java.util.Locale;
@@ -30,10 +31,15 @@
3031
import javax.servlet.Filter;
3132
import javax.servlet.FilterChain;
3233
import javax.servlet.FilterConfig;
34+
import javax.servlet.MultipartConfigElement;
3335
import javax.servlet.ServletContext;
3436
import javax.servlet.ServletException;
37+
import javax.servlet.ServletRegistration.Dynamic;
3538
import javax.servlet.ServletRequest;
3639
import javax.servlet.ServletResponse;
40+
import javax.servlet.http.HttpServlet;
41+
import javax.servlet.http.HttpServletRequest;
42+
import javax.servlet.http.HttpServletResponse;
3743

3844
import org.apache.catalina.Container;
3945
import org.apache.catalina.Context;
@@ -63,8 +69,18 @@
6369
import org.springframework.boot.context.embedded.Ssl;
6470
import org.springframework.boot.testutil.InternalOutputCapture;
6571
import org.springframework.boot.web.servlet.ServletContextInitializer;
72+
import org.springframework.core.io.ByteArrayResource;
73+
import org.springframework.http.HttpEntity;
74+
import org.springframework.http.HttpHeaders;
75+
import org.springframework.http.HttpStatus;
76+
import org.springframework.http.MediaType;
77+
import org.springframework.http.ResponseEntity;
6678
import org.springframework.test.util.ReflectionTestUtils;
79+
import org.springframework.util.FileSystemUtils;
80+
import org.springframework.util.LinkedMultiValueMap;
81+
import org.springframework.util.MultiValueMap;
6782
import org.springframework.util.SocketUtils;
83+
import org.springframework.web.client.RestTemplate;
6884

6985
import static org.assertj.core.api.Assertions.assertThat;
7086
import static org.junit.Assert.fail;
@@ -539,6 +555,49 @@ public void destroy() {
539555
}
540556
}
541557

558+
@Test
559+
public void nonExistentUploadDirectoryIsCreatedUponMultipartUpload()
560+
throws IOException, URISyntaxException {
561+
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(
562+
0);
563+
AtomicReference<ServletContext> servletContextReference = new AtomicReference<>();
564+
factory.addInitializers(new ServletContextInitializer() {
565+
566+
@Override
567+
public void onStartup(ServletContext servletContext) throws ServletException {
568+
servletContextReference.set(servletContext);
569+
Dynamic servlet = servletContext.addServlet("upload", new HttpServlet() {
570+
571+
@Override
572+
protected void doPost(HttpServletRequest req,
573+
HttpServletResponse resp)
574+
throws ServletException, IOException {
575+
req.getParts();
576+
}
577+
578+
});
579+
servlet.addMapping("/upload");
580+
servlet.setMultipartConfig(new MultipartConfigElement((String) null));
581+
}
582+
583+
});
584+
this.container = factory.getEmbeddedServletContainer();
585+
this.container.start();
586+
File temp = (File) servletContextReference.get()
587+
.getAttribute(ServletContext.TEMPDIR);
588+
FileSystemUtils.deleteRecursively(temp);
589+
RestTemplate restTemplate = new RestTemplate();
590+
HttpHeaders headers = new HttpHeaders();
591+
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
592+
body.add("file", new ByteArrayResource(new byte[1024 * 1024]));
593+
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
594+
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,
595+
headers);
596+
ResponseEntity<String> response = restTemplate
597+
.postForEntity(getLocalUrl("/upload"), requestEntity, String.class);
598+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
599+
}
600+
542601
@Override
543602
protected JspServlet getJspServlet() throws ServletException {
544603
Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()

0 commit comments

Comments
 (0)