Skip to content

Commit

Permalink
Configure Tomcat to create upload targets
Browse files Browse the repository at this point in the history
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
  • Loading branch information
wilkinsona committed Mar 29, 2019
1 parent f8bd066 commit 70eee61
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 7 deletions.
7 changes: 0 additions & 7 deletions spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc
Expand Up @@ -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]]
Expand Down
Expand Up @@ -206,6 +206,12 @@ protected void prepareContext(Host host, ServletContextInitializer[] initializer
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());
Expand Down
Expand Up @@ -18,6 +18,7 @@

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;
Expand All @@ -30,10 +31,15 @@
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;
Expand Down Expand Up @@ -63,8 +69,18 @@
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;
Expand Down Expand Up @@ -539,6 +555,49 @@ public void destroy() {
}
}

@Test
public void nonExistentUploadDirectoryIsCreatedUponMultipartUpload()
throws IOException, URISyntaxException {
TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(
0);
AtomicReference<ServletContext> 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<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new ByteArrayResource(new byte[1024 * 1024]));
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,
headers);
ResponseEntity<String> 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()
Expand Down

0 comments on commit 70eee61

Please sign in to comment.