Commit e8e59ea6 authored by Josh Long's avatar Josh Long Committed by Dave Syer

MultipartConfigElement autoconfig support.

Fixes gh-708
parent 5be3f1d5
...@@ -16,33 +16,70 @@ ...@@ -16,33 +16,70 @@
package org.springframework.boot.autoconfigure.web; package org.springframework.boot.autoconfigure.web;
import javax.servlet.MultipartConfigElement; import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.Servlet;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
import org.springframework.boot.context.embedded.MultipartConfigFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.support.StandardServletMultipartResolver; import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for multi-part uploads. Adds a * {@link EnableAutoConfiguration Auto-configuration} for multi-part uploads. Adds a
* {@link StandardServletMultipartResolver} when a {@link MultipartConfigElement} bean is * {@link StandardServletMultipartResolver} if none is present, and adds a
* defined. The {@link EmbeddedWebApplicationContext} will associated the * {@link javax.servlet.MultipartConfigElement multipartConfigElement} if none is otherwise defined.
* The {@link EmbeddedWebApplicationContext} will associate the
* {@link MultipartConfigElement} bean to any {@link Servlet} beans. * {@link MultipartConfigElement} bean to any {@link Servlet} beans.
* * <p/>
* The {@link javax.servlet.MultipartConfigElement} is a Servlet API that's used to configure how the container handles
* file uploads. By default
*
* @author Greg Turnquist * @author Greg Turnquist
* @author Josh Long
*/ */
@Configuration @Configuration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class }) @ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class})
@ConditionalOnBean(MultipartConfigElement.class) @EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration { public class MultipartAutoConfiguration {
@Bean @Autowired
public StandardServletMultipartResolver multipartResolver() { private MultipartProperties multipartProperties = new MultipartProperties();
return new StandardServletMultipartResolver();
} @Bean
@ConditionalOnMissingBean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
if (StringUtils.hasText(this.multipartProperties.getFileSizeThreshold())) {
factory.setFileSizeThreshold(this.multipartProperties.getFileSizeThreshold());
}
if (StringUtils.hasText(this.multipartProperties.getLocation())) {
factory.setLocation(this.multipartProperties.getLocation());
}
if (StringUtils.hasText(this.multipartProperties.getMaxRequestSize())) {
factory.setMaxRequestSize(this.multipartProperties.getMaxRequestSize());
}
if (StringUtils.hasText(this.multipartProperties.getMaxFileSize())) {
factory.setMaxFileSize(this.multipartProperties.getMaxFileSize());
}
return factory.createMultipartConfig();
}
@Bean
@ConditionalOnMissingBean
public StandardServletMultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
} }
/*
* Copyright 2012-2013 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
*
* http://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.autoconfigure.web;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties to be used in configuring
* a <A href="http://docs.oracle.com/javaee/6/api/javax/servlet/MultipartConfigElement.html">javax.servlet.MultipartConfigElement</a>.
* <p/>
* {@literal multipart.fileSizeThreshold} specifies the size threshold after which files will be written to disk. Default is 0, which means that the file will be written to disk immediately.
* {@literal multipart.location} specifies the directory where files will be stored. The default is "". A common value is to use the system's temporary directory, which can be obtained
* {@literal multipart.maxFileSize} specifies the maximum size permitted for uploaded files. The default is unlimited.
* {@literal multipart.maxRequestSize} specifies the maximum size allowed for {@literal multipart/form-data} requests.
* <p/>
* These properties are ultimately passed through {@link org.springframework.boot.context.embedded.MultipartConfigFactory}
* which means you may specify the values using {@literal long} values or using more readable {@literal String}
* variants that accept {@literal KB} or {@literal MB} suffixes.
*
* @author Josh Long
*/
@ConfigurationProperties(prefix = "multipart", ignoreUnknownFields = false)
public class MultipartProperties {
private String maxFileSize = "1Mb";
private String maxRequestSize = "10Mb";
private String fileSizeThreshold = null;
private String location = null;
public String getMaxFileSize() {
return maxFileSize;
}
public String getMaxRequestSize() {
return maxRequestSize;
}
public String getFileSizeThreshold() {
return fileSizeThreshold;
}
public String getLocation() {
return location;
}
public void setMaxFileSize(String maxFileSize) {
this.maxFileSize = maxFileSize;
}
public void setMaxRequestSize(String maxRequestSize) {
this.maxRequestSize = maxRequestSize;
}
public void setLocation(String location) {
this.location = location;
}
public void setFileSizeThreshold(String fileSizeThreshold) {
this.fileSizeThreshold = fileSizeThreshold;
}
}
...@@ -20,7 +20,7 @@ import javax.servlet.MultipartConfigElement; ...@@ -20,7 +20,7 @@ import javax.servlet.MultipartConfigElement;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.boot.context.embedded.MultiPartConfigFactory; import org.springframework.boot.context.embedded.MultipartConfigFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean; import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
...@@ -120,7 +120,7 @@ public class DispatcherServletAutoConfigurationTests { ...@@ -120,7 +120,7 @@ public class DispatcherServletAutoConfigurationTests {
@Bean @Bean
public MultipartConfigElement multipartConfig() { public MultipartConfigElement multipartConfig() {
MultiPartConfigFactory factory = new MultiPartConfigFactory(); MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("128KB"); factory.setMaxFileSize("128KB");
factory.setMaxRequestSize("128KB"); factory.setMaxRequestSize("128KB");
return factory.createMultipartConfig(); return factory.createMultipartConfig();
......
...@@ -28,6 +28,7 @@ import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletCon ...@@ -28,6 +28,7 @@ import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletCon
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.env.PropertySource;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
...@@ -38,15 +39,17 @@ import org.springframework.web.servlet.DispatcherServlet; ...@@ -38,15 +39,17 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
/** /**
* Tests for {@link MultipartAutoConfiguration}. Tests an empty configuration, no * Tests for {@link MultipartAutoConfiguration}. Tests an empty configuration, no
* multipart configuration, and a multipart configuration (with both Jetty and Tomcat). * multipart configuration, and a multipart configuration (with both Jetty and Tomcat).
* *
* @author Greg Turnquist * @author Greg Turnquist
* @author Dave Syer * @author Dave Syer
* @author Josh Long
*/ */
public class MultipartAutoConfigurationTests { public class MultipartAutoConfigurationTests {
...@@ -67,11 +70,11 @@ public class MultipartAutoConfigurationTests { ...@@ -67,11 +70,11 @@ public class MultipartAutoConfigurationTests {
this.context = new AnnotationConfigEmbeddedWebApplicationContext( this.context = new AnnotationConfigEmbeddedWebApplicationContext(
ContainerWithNothing.class, BaseConfiguration.class); ContainerWithNothing.class, BaseConfiguration.class);
DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class);
assertNull(servlet.getMultipartResolver()); assertNotNull(servlet.getMultipartResolver());
assertEquals(0, assertEquals(1,
this.context.getBeansOfType(StandardServletMultipartResolver.class) this.context.getBeansOfType(StandardServletMultipartResolver.class)
.size()); .size());
assertEquals(0, this.context.getBeansOfType(MultipartResolver.class).size()); assertEquals(1, this.context.getBeansOfType(MultipartResolver.class).size());
} }
@Configuration @Configuration
...@@ -83,11 +86,11 @@ public class MultipartAutoConfigurationTests { ...@@ -83,11 +86,11 @@ public class MultipartAutoConfigurationTests {
this.context = new AnnotationConfigEmbeddedWebApplicationContext( this.context = new AnnotationConfigEmbeddedWebApplicationContext(
ContainerWithNoMultipartJetty.class, BaseConfiguration.class); ContainerWithNoMultipartJetty.class, BaseConfiguration.class);
DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class);
assertNull(servlet.getMultipartResolver()); assertNotNull(servlet.getMultipartResolver());
assertEquals(0, assertEquals(1,
this.context.getBeansOfType(StandardServletMultipartResolver.class) this.context.getBeansOfType(StandardServletMultipartResolver.class)
.size()); .size());
assertEquals(0, this.context.getBeansOfType(MultipartResolver.class).size()); assertEquals(1, this.context.getBeansOfType(MultipartResolver.class).size());
verifyServletWorks(); verifyServletWorks();
} }
...@@ -110,10 +113,10 @@ public class MultipartAutoConfigurationTests { ...@@ -110,10 +113,10 @@ public class MultipartAutoConfigurationTests {
ContainerWithNoMultipartTomcat.class, BaseConfiguration.class); ContainerWithNoMultipartTomcat.class, BaseConfiguration.class);
DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class);
assertNull(servlet.getMultipartResolver()); assertNull(servlet.getMultipartResolver());
assertEquals(0, assertEquals(1,
this.context.getBeansOfType(StandardServletMultipartResolver.class) this.context.getBeansOfType(StandardServletMultipartResolver.class)
.size()); .size());
assertEquals(0, this.context.getBeansOfType(MultipartResolver.class).size()); assertEquals(1, this.context.getBeansOfType(MultipartResolver.class).size());
verifyServletWorks(); verifyServletWorks();
} }
...@@ -131,20 +134,36 @@ public class MultipartAutoConfigurationTests { ...@@ -131,20 +134,36 @@ public class MultipartAutoConfigurationTests {
public void containerWithAutomatedMultipartTomcatConfiguration() throws Exception { public void containerWithAutomatedMultipartTomcatConfiguration() throws Exception {
this.context = new AnnotationConfigEmbeddedWebApplicationContext( this.context = new AnnotationConfigEmbeddedWebApplicationContext(
ContainerWithEverythingTomcat.class, BaseConfiguration.class); ContainerWithEverythingTomcat.class, BaseConfiguration.class);
new RestTemplate().getForObject("http://localhost:" new RestTemplate().getForObject("http://localhost:8080/", String.class);
+ this.context.getEmbeddedServletContainer().getPort() + "/",
String.class);
this.context.getBean(MultipartConfigElement.class); this.context.getBean(MultipartConfigElement.class);
assertSame(this.context.getBean(DispatcherServlet.class).getMultipartResolver(), assertSame(this.context.getBean(DispatcherServlet.class).getMultipartResolver(),
this.context.getBean(StandardServletMultipartResolver.class)); this.context.getBean(StandardServletMultipartResolver.class));
verifyServletWorks(); verifyServletWorks();
} }
@Test
public void containerWithMultipartConfigDisabled() {
this.context = new AnnotationConfigEmbeddedWebApplicationContext();
this.context.getEnvironment().getPropertySources()
.addFirst(new PropertySource<Object>("test") {
@Override
public Object getProperty(String name) {
if (name.toLowerCase().contains("multipart.enabled"))
return "false";
return null;
}
});
this.context.register(ContainerWithNoMultipartTomcat.class,
BaseConfiguration.class);
this.context.refresh();
assertEquals(this.context.getBeansOfType(MultipartConfigElement.class).size(), 0);
}
private void verifyServletWorks() { private void verifyServletWorks() {
RestTemplate restTemplate = new RestTemplate(); RestTemplate restTemplate = new RestTemplate();
assertEquals(restTemplate.getForObject("http://localhost:" assertEquals(restTemplate.getForObject("http://localhost:8080/", String.class),
+ this.context.getEmbeddedServletContainer().getPort() + "/", "Hello");
String.class), "Hello");
} }
@Configuration @Configuration
...@@ -153,13 +172,6 @@ public class MultipartAutoConfigurationTests { ...@@ -153,13 +172,6 @@ public class MultipartAutoConfigurationTests {
ServerPropertiesAutoConfiguration.class }) ServerPropertiesAutoConfiguration.class })
protected static class BaseConfiguration { protected static class BaseConfiguration {
@Bean
public ServerProperties serverProperties() {
ServerProperties properties = new ServerProperties();
properties.setPort(0);
return properties;
}
} }
@Configuration @Configuration
...@@ -216,8 +228,7 @@ public class MultipartAutoConfigurationTests { ...@@ -216,8 +228,7 @@ public class MultipartAutoConfigurationTests {
@Controller @Controller
public static class WebController { public static class WebController {
@RequestMapping("/") @RequestMapping("/")
public @ResponseBody public @ResponseBody String index() {
String index() {
return "Hello"; return "Hello";
} }
} }
......
...@@ -726,6 +726,23 @@ then you can take control completely and do everything manually using ...@@ -726,6 +726,23 @@ then you can take control completely and do everything manually using
See the {sc-spring-boot-autoconfigure}/web/WebMvcAutoConfiguration.{sc-ext}[`WebMvcAutoConfiguration`] See the {sc-spring-boot-autoconfigure}/web/WebMvcAutoConfiguration.{sc-ext}[`WebMvcAutoConfiguration`]
source code for more details. source code for more details.
[[howto-multipart-file-upload-configuration]]
=== Handling Multipart File Uploads
Spring Boot embraces the Servlet 3 `javax.servlet.http.Part` API to support uploading files. By default
Spring Boot configures Spring MVC with a maximum file of 1Mb per
file and a maximum of 10Mb of file data in a single request. You may override these values, as well as the location
to which intermediate data is stored (e.g., to the `/tmp` directory) and the threshold past which data is flushed to
disk by using the properties exposed in the `MultipartAutoConfiguration` class. If you want to specify that files be unlimited,
for example, set the `multipart.maxFileSize` property to `-1`.
The multipart support is helpful when you want to receive multipart encoded file data as a `@RequestParam`-annotated
parameter of type `MultipartFile` in a Spring MVC controller handler method.
See the {sc-spring-boot-autoconfigure}/web/MultipartAutoConfiguration.{sc-ext}[`MultipartAutoConfiguration`] source for more details.
[[howto-switch-off-the-spring-mvc-dispatcherservlet]] [[howto-switch-off-the-spring-mvc-dispatcherservlet]]
......
...@@ -32,7 +32,7 @@ import org.springframework.util.Assert; ...@@ -32,7 +32,7 @@ import org.springframework.util.Assert;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class MultiPartConfigFactory { public class MultipartConfigFactory {
private String location; private String location;
......
...@@ -24,15 +24,15 @@ import static org.hamcrest.Matchers.equalTo; ...@@ -24,15 +24,15 @@ import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
/** /**
* Tests for {@link MultiPartConfigFactory}. * Tests for {@link MultipartConfigFactory}.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class MultiPartConfigFactoryTests { public class MultipartConfigFactoryTests {
@Test @Test
public void sensibleDefaults() { public void sensibleDefaults() {
MultiPartConfigFactory factory = new MultiPartConfigFactory(); MultipartConfigFactory factory = new MultipartConfigFactory();
MultipartConfigElement config = factory.createMultipartConfig(); MultipartConfigElement config = factory.createMultipartConfig();
assertThat(config.getLocation(), equalTo("")); assertThat(config.getLocation(), equalTo(""));
assertThat(config.getMaxFileSize(), equalTo(-1L)); assertThat(config.getMaxFileSize(), equalTo(-1L));
...@@ -42,7 +42,7 @@ public class MultiPartConfigFactoryTests { ...@@ -42,7 +42,7 @@ public class MultiPartConfigFactoryTests {
@Test @Test
public void create() throws Exception { public void create() throws Exception {
MultiPartConfigFactory factory = new MultiPartConfigFactory(); MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setLocation("loc"); factory.setLocation("loc");
factory.setMaxFileSize(1); factory.setMaxFileSize(1);
factory.setMaxRequestSize(2); factory.setMaxRequestSize(2);
...@@ -56,7 +56,7 @@ public class MultiPartConfigFactoryTests { ...@@ -56,7 +56,7 @@ public class MultiPartConfigFactoryTests {
@Test @Test
public void createWithStringSizes() throws Exception { public void createWithStringSizes() throws Exception {
MultiPartConfigFactory factory = new MultiPartConfigFactory(); MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("1"); factory.setMaxFileSize("1");
factory.setMaxRequestSize("2kB"); factory.setMaxRequestSize("2kB");
factory.setFileSizeThreshold("3Mb"); factory.setFileSizeThreshold("3Mb");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment