您的位置:首页 > 编程语言 > Java开发

Servlet & Spring对Multipart数据请求的支持

2016-01-13 16:50 453 查看
参考资料

(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,有两种选择CommonsMultipartResolverStandardServletMultipartResolver,分别基于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" />
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: