Servlet & Spring对Multipart数据请求的支持
2016-01-13 16:50
453 查看
参考资料:
(1)RFC 1867
(2)Java Servlet Specification 3.1;
(3)《Java Web高级编程》;
有两个地方是使用Multipart的关键:
(1)对于POST请求来说,enctype的默认值是application/x-www-form-urlencoded,而这里要是用
(2)
如果是文件类型字段,还将有filename,匹配MIME类型的Content-Type;
使用下面的表单提交单个文件和其他文本域:
请求数据内容:
可以看到每个部分有边界像分隔,这个边界值由客户端决定,是随机生成的(这里我使用chrome做的实验,因此可以看到“WebKit”)。
请求头Content-Type:
请求数据内容:
基于chrome得到的结果,没有出现一个Content-type为
结合前面的测试表单,我用一个Servlet和Jsp来演示处理的具体方法:
UploadServlet2:
通过ServletContainerInitializer配置:
必须启用multipart支持,配置一样有三种方法:注解,编程(ServletContextListener/ServletContainerInitializer),XML,但是基本的配置项是一样的:
location的配置需要注意,Servlet规范中的说明:
location元被解析为一个绝对路径且默认为 javax.servlet.context.tempdir。如果指定了相对地址,它将是相对于 tempdir 位置。绝对路径与相对地址的测试必须使用 java.io.File.isAbsolute。
启用Multipart,这里通过Spring的WebApplicationInitializer初始化器进行配置,本质是通过ServletContainerInitializer。启用指定DispatcherServlet的Multipart支持。还是通过
Spring同时兼容了基于Commons FileUpload和Servlet 3.0标准API两种方式的解析。
Spring提供了一个Multipart的解析器:MultipartResolver,因此在
编写Controller method,使用Spring的
(1)name:表单字段名;
(2)size:Part数据内容的大小;
(3)submittedFileName:Servlet 3.1新增方法,获取文件名,之前必须通过
(4)contentType:如果
(5)getHeader:获取指定头;
(6)getInputStream:获取输入流;
对于表单数据的 Content-Disposition,即使没有文件名,也可使用 part 的名称通过 HttpServletRequest 的
getParameter 和 getParameterValues 方法得到 part 的字符串值。
(1)RFC 1867
(2)Java Servlet Specification 3.1;
(3)《Java Web高级编程》;
1. Multipart FormData
Multipart是HTML中表单文件上传的基本格式,一般通过如下方法可以通过HTTP上传文件:<form action="_URL_" method="POST" enctype="multipart/form-data"> <input type="text" name="username" /> <input type="file" name="userfile1" /> <input type="submit" value="submit" /> </form>
有两个地方是使用Multipart的关键:
(1)对于POST请求来说,enctype的默认值是application/x-www-form-urlencoded,而这里要是用
multipart/form-data;
(2)
<input />的type设置为
file;
1.1 Multipart的数据格式
基于Multipart,请求的每个部分都有指定的边界分隔开,都有一个值为form-data的Content-Disposition和匹配表单输入名称的name。如果是文件类型字段,还将有filename,匹配MIME类型的Content-Type;
使用下面的表单提交单个文件和其他文本域:
测试1:单文件上传
<form action="/s/upload/1" method="post" enctype="multipart/form-data"> <fieldset> <legend>测试1:单文件上传</legend> <p><label for="name">名称 </label><input id="name" type="text" name="name" /></p> <p><label for="files">文件 </label><input id="files" type="file" name="files" /></p> <p><label for="location">地区 </label><input id="location" type="text" name="location" /></p> <input type="submit" /> </fieldset> </form>
请求数据内容:
------WebKitFormBoundaryBDujyAl87MaTQd9J Content-Disposition: form-data; name="name" 串个沙 ------WebKitFormBoundaryBDujyAl87MaTQd9J Content-Disposition: form-data; name="files"; filename="说明.txt" Content-Type: text/plain ------WebKitFormBoundaryBDujyAl87MaTQd9J Content-Disposition: form-data; name="location" 地球 ------WebKitFormBoundaryBDujyAl87MaTQd9J--
可以看到每个部分有边界像分隔,这个边界值由客户端决定,是随机生成的(这里我使用chrome做的实验,因此可以看到“WebKit”)。
测试2:多文件上传
<form action="/s/upload/1" method="post" enctype="multipart/form-data"> <fieldset> <legend>测试2:多文件上传</legend> <p><label for="name">名称 </label><input id="name1" type="text" name="name" /></p> <p><label for="files">文件 </label><input id="files1" type="file" name="files1" multiple="multiple" /></p> <p><label for="location">地区 </label><input id="location1" type="text" name="location" /></p> <input type="submit" /> </fieldset> </form>
请求头Content-Type:
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryZGDkB6nM3LvHX0KI
请求数据内容:
------WebKitFormBoundaryLnO8YOO2DPoP3M6y Content-Disposition: form-data; name="name" 2个文件 ------WebKitFormBoundaryLnO8YOO2DPoP3M6y Content-Disposition: form-data; name="files1"; filename="说明.txt" Content-Type: text/plain ------WebKitFormBoundaryLnO8YOO2DPoP3M6y Content-Disposition: form-data; name="files1"; filename="说明 (copy).txt" Content-Type: text/plain ------WebKitFormBoundaryLnO8YOO2DPoP3M6y Content-Disposition: form-data; name="location" 中国 ------WebKitFormBoundaryLnO8YOO2DPoP3M6y--
基于chrome得到的结果,没有出现一个Content-type为
multipart/mixed的嵌套部分,而是用多个文件字段部分表示。
2. Servlet 3.0对Multipart的支持
在Servlet3.0之前对于文件上传的支持,可以通过Commons FileUpload等第三方工具来实现。Java EE 6中Servlet 3.0新增了对它的支持,这样可以不再依赖第三方库。主要通过HttpServletRequest的getParts()和
getPart()方法。
结合前面的测试表单,我用一个Servlet和Jsp来演示处理的具体方法:
UploadServlet2:
@WebServlet ( name = "uploadServlet2", urlPatterns = "/upload2", loadOnStartup = 1 ) @MultipartConfig ( fileSizeThreshold = 5_242_880, maxFileSize = 20_971_520L, maxRequestSize = 41_943_040L ) public class UploadServlet2 extends HttpServlet { private static final Logger logger = LogManager.getLogger(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("/p/upload/upload.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Collection<Part> parts = req.getParts(); logger.debug("uploading..."); req.setAttribute("parts", parts); req.getRequestDispatcher("/p/upload/resolvePart.jsp").forward(req, resp); } }
通过ServletContainerInitializer配置:
@Override public void contextInitialized(ServletContextEvent sce) { logger.info("ServletContextListener invoked"); //获取ServletContext对象 ServletContext servletContext = sce.getServletContext(); //添加Servlet ServletRegistration.Dynamic uploadRegistration = servletContext.addServlet("uploadServlet", UploadServlet.class); uploadRegistration.addMapping("/upload/*"); uploadRegistration.setLoadOnStartup(1); MultipartConfigElement configElement = new MultipartConfigElement(".", 52428800, 52428800, 0); uploadRegistration.setMultipartConfig(configElement); }
必须启用multipart支持,配置一样有三种方法:注解,编程(ServletContextListener/ServletContainerInitializer),XML,但是基本的配置项是一样的:
location:临时文件目录,一般可以使用默认的由服务器软件决定;
fileSizehreshold:超过这个阈值放入临时文件目录,否则在内存中有垃圾回收处理;
maxFileSize和
maxRequestSize;
location的配置需要注意,Servlet规范中的说明:
location元被解析为一个绝对路径且默认为 javax.servlet.context.tempdir。如果指定了相对地址,它将是相对于 tempdir 位置。绝对路径与相对地址的测试必须使用 java.io.File.isAbsolute。
3. Spring对Multipart的支持
服务端主要就是解析Multipart数据。启用Multipart,这里通过Spring的WebApplicationInitializer初始化器进行配置,本质是通过ServletContainerInitializer。启用指定DispatcherServlet的Multipart支持。还是通过
Registration配置的和上面的ServletContextListener一样。这里配置了自定义的临时目录,如果指定的目录不存在或是无法访问,通过”.”配置,因为前面引用的Serlvet规范已经说明了,相对目录是基于容器的临时目录的。
Spring同时兼容了基于Commons FileUpload和Servlet 3.0标准API两种方式的解析。
@Order(1) public class BootStrap implements WebApplicationInitializer { /* 略 */ @Override public void onStartup(ServletContext container) throws ServletException { /* 略 */ DispatcherServlet dispatcherServlet = new DispatcherServlet(cgContext); dispatcherServlet.setThrowExceptionIfNoHandlerFound(true); ServletRegistration.Dynamic dispatcherCG = container.addServlet("cgServlet", dispatcherServlet); dispatcherCG.setLoadOnStartup(1); //文件上传支持 //设置临时文件目录 File path = new File(container.getRealPath("/tmp/web_yjh_files/")); if(path.exists() || path.mkdirs()) { dispatcherCG.setMultipartConfig(new MultipartConfigElement( path.getAbsolutePath(), 200_971_520L, 401_943_040L, 0 )); } else { dispatcherCG.setMultipartConfig(new MultipartConfigElement( ".", 200_971_520L, 401_943_040L, 0 )); } /* 略 */ } }
Spring提供了一个Multipart的解析器:MultipartResolver,因此在
@Configuration类中添加一个Bean,有两种选择CommonsMultipartResolver和StandardServletMultipartResolver,分别基于Commons File Upload和Servlet 3.0标准API;
@Bean public MultipartResolver multipartResolver() { return new CommonsMultipartResolver(); }
编写Controller method,使用Spring的
MultipartFile:
@RequestMapping(value = "upload", method = RequestMethod.POST, produces = "text/html") @ResponseBody public String upload(String username, @RequestParam(value = "attachment") MultipartFile parts) throws Exception { return "success " + "name:" + parts.getName() + "size:" + parts.getSize() + "contentType:" + parts.getContentType() + (parts.getContentType() == null ? "" : ("filename" + parts.getOriginalFilename())) + "" + IOUtils.toString(parts.getInputStream()) + "size:" + parts.getSize(); }
使用Part API:
Serlvet Part的标准API包括:(1)name:表单字段名;
(2)size:Part数据内容的大小;
(3)submittedFileName:Servlet 3.1新增方法,获取文件名,之前必须通过
getHeader("content-disposition")解析;
(4)contentType:如果
<input>类型是type这个值是
null,如果是文件,可以获得它的MIME类型;
(5)getHeader:获取指定头;
(6)getInputStream:获取输入流;
对于表单数据的 Content-Disposition,即使没有文件名,也可使用 part 的名称通过 HttpServletRequest 的
getParameter 和 getParameterValues 方法得到 part 的字符串值。
<h1>Multipart解析</h1> <c:forEach items="${parts}" var="part"> <p> <h3>Part: ${part.name}</h3> <span>Size: ${part.size}</span><br /> <span>SubmittedFileName: ${part.submittedFileName},Servlet 3.1新增方法</span><br /> <span>ContentType: ${part.contentType}</span><br /> <c:choose> <c:when test="${part.contentType == null || part.contentType eq 'text/plain'}"> <p> ${IOUtils.toString(part.inputStream)} </p> </c:when> <c:when test="${part.contentType eq 'application/octet-stream'}"> <p> ${part.getHeader("content-disposition")} </p> </c:when> </c:choose> </p> </c:forEach> <c:import url="upload.jsp" />
相关文章推荐
- Spring注解@Component、@Repository、@Service、@Controller @Resource、@Autowired、@Qualifier 解析
- Java读书笔记(8)-单例模式
- maven-使用myeclipse创建maven项目
- java socket编程
- 怎么计算两个经纬度之间的距离
- spring配置文件dubbo标签报错解决办法
- java学习--学生成绩管理系统
- Spring扫包时导致未加事务
- Eclipse创建mvn web工程
- spring cloud教程之使用spring boot创建一个应用
- struts2提交表单时Error setting expression 'user.username' with value……的错误
- 使用proguard混淆java web项目代码
- java与mysql数据类型对应表
- spring mvc web应用启动时就执行特定处理(线程启动)
- MyEclipse + Maven开发Web工程的详细配置过程
- 用java解析在OpenStreetMap上下载的地图数据
- 7天学会spring cloud教程
- Java中系统属性Properties介绍 System.getProperty()参数大全
- java加密解密研究-MAC算法家族
- E326:判断一个数是3的幂