暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Tomcat中的文件上传原理与@MultiPartConfig 系列之一

中间件技术讨论圈 2016-08-13
978

在我们做java web项目的时候,一般都会遇到文件上传和下载的东西,但我们一般都是用,从来没有探究过文件上传下载在http协议中的是怎么解析的,并且在Tomcat中是怎么实现的;

我们这里就利用2-3节来分析一下。


1.文件上传的http请求解析

先来看一个例子。

客户端:


可以看到http协议仍旧是post,并没有其他的协议,但是其enctype是这个multipart/form-data的类型的,这点也就是不同。


这个例子中的http协议我们可以通过wireshark等软件进行截取:


在请求头中,其中上面的红线部分,

multipart/form-data的请求头必须包含一个特殊的头信息:Content-Type,且其值也必须规定为multipart/form-data,

同时还需要规定一个内容分割符用于分割请求体中的多个post的内容,如文件内容和文本内容自然需要分割开来,不然接收方就无法正常解析和还原这个文件了。


Content-Type: multipart/form-data; boundary=${bound}      


//其中${bound} 是一个占位符,代表我们规定的分割符,可以自己任意规定,但为了避免和正常文本重复了,尽量要使用复杂一点的内容。

如上面的:--------------------db15a429cce


这个字符串是随机生成的,每个浏览器都不一样;

可以看到这个字符串是非常有用的,例如上面的例子,我们上传了2个文件,怎么看出来的,就是通过这个字符串进行的分隔;


===========》上面就是文件上传的原理,
服务器端其实就是解析reqeust.getInputStream的流,只不过带有文件上传的流比较复杂而已,reqeust.getParameter其实也是一样的,只不过是都是有规律的字符串,逐个字符进行遍历参数那部分,能直接拿到一个数组,而文件上传,可以看到是非常复杂的流,里面不但有字符串间隔,而且里面还有大量的文件数据,文件数据的字符数和结尾数都需要计算,才能正确的转换,差一个字节都不行;


2.文件上传的http请求解析

还是文件上传的这个主题,在servlet3.0之前,文件上传不好处理,这个前面已经讲过,

你要从request.getInputstream拿到流的话,需要对整个http协议的请求头,逐个字符的分析;

虽然不是说不能做,但是JAVA EE 的servlet规范应该提供这种封装,但是这个被apache的一些开源项目给占据了:


其整个原理就是对http请求头进行解析,所以首先必须调用那个parseRequest,上传组件将其流从头到尾遍历了一遍,

然后将表单域给归结到表单域的item,文件域归结到文件的item,

可以看到下面可以开始进行遍历,你挨个取值就行了;


Servlet3.0中提供了@MultipartConfig特性和Request.getPart的方法,可以说是完全替代上面的apache的common-upload组件

让Servlet支持上传,需要做两件事情

  1. 需要添加MultipartConfig注解

  2. 从request对象中获取Part文件对象

但在具体实践中,还是有一些细节处理,诸如设置上传文件的最大值,上传文件的保存路径。

需要熟悉MultipartConfig注解,标注在@WebServlet之上,具有以下属性:

属性名类型是否可选描述





fileSizeThresholdint当数据量大于该值时,内容将被写入文件。
locationString存放生成的文件地址。
maxFileSizelong允许上传的文件最大值。默认值为 -1,表示没有限制。
maxRequestSizelong针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。


一些实践建议:

  1. 若是上传一个文件,仅仅需要设置maxFileSize熟悉即可。

  2. 上传多个文件,可能需要设置maxRequestSize属性,设定一次上传数据的最大量。

  3. 上传过程中无论是单个文件超过maxFileSize值,或者上传总的数据量大于maxRequestSize值都会抛出IllegalStateException异常;

  4. location属性,既是保存路径(在写入的时候,可以忽略路径设定),又是上传过程中临时文件的保存路径,一旦执行Part.write方法之后,临时文件将被自动清除。

  5. 但Servlet 3.0规范同时也说明,不提供获取上传文件名的方法,尽管我们可以通过part.getHeader("content-disposition")方法间接获取得到。

  6. 如何读取MultipartConfig注解属性值,API没有提供直接读取的方法,只能手动获取。

/**
* 多文件上传支持
* @author yongboy
* @date 2011-1-14
* @version 1.0
*/
@MultipartConfig(
 location = "/home/yongboy/tmp/",
 maxFileSize = 1024L * 1024L, // 每一个文件的最大值
 maxRequestSize = 1024L * 1024L * 10L // 一次上传最大值,若每次只能上传一个文件,则设置maxRequestSize意义不大
)

@WebServlet("/uploadFiles")
public class UploadFilesAction extends HttpServlet {

protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");

Collectionparts = null;
 try {
  parts = request.getParts();
 } catch (IllegalStateException ise) {
  // 可能某个文件大于指定文件容量maxFileSize,或者提交数据大于maxRequestSize
  log.info("maybe the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
 } catch (IOException ioe) {
  // 在获取某个文件时遇到拉IO异常错误
  log.error("an I/O error occurred during the retrieval of the Part components of this request");
 } catch (Exception e) {
  log.error("the request body is larger than maxRequestSize, or any Part in the request is larger than maxFileSize");
  e.printStackTrace();
 }

 if (parts == null || parts.isEmpty()) {
  doError(request, response, "上传文件为空!");
  return;
 }

 // 前端具有几个file组件,这里会对应几个Part对象
 ListfileNames = new ArrayList();
 for (Part part : parts) {
  if (part == null) {
   continue;
  }
  // 这里直接以源文件名保存
  String fileName = UploadUtils.getFileName(part);

  if (StringUtils.isBlank(fileName)) {
   continue;
  }

  part.write(fileName);
  fileNames.add(fileName);
 }

 request.setAttribute("fileNames", fileNames);
 request.getRequestDispatcher("/uploadsResult.jsp").forward(request,
   response);
}

private void doError(HttpServletRequest request,
  HttpServletResponse response, String errMsg)
  throws ServletException, IOException {
 request.setAttribute("errMsg", errMsg);

 this.doGet(request, response);
}
}

整体流程基本类似common-upload,只不过这个MultipartConfig 可以通过原注释进行配置,这个比common-upload先进一些,那个只能在配置文件中进行配置;(其实这个MultipartConfig  也可以对应配置文件 )



下一节,我们分析一下Tomcat是怎么实现的上述原注释,敬请期待。


文章转载自中间件技术讨论圈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论