天天看点

apache fileupload源码分析文件上传格式文件上传注意点:apache fileupload的解析

先来看下含有文件上传时的表单提交是怎样的格式

form表单提交内容如下

apache fileupload源码分析文件上传格式文件上传注意点:apache fileupload的解析

从上面可以看到,含有文件上传的格式是这样组织的。

文件类型字段

其他类型字段

结束

对于上面的文件内容,chrome浏览器是不显示的,换成firefox可以看到,如下图所示

apache fileupload源码分析文件上传格式文件上传注意点:apache fileupload的解析

同时我们还可以注意到,不同的浏览器,分隔符是不一样的,在请求头

中指明了分隔符的内容。

一定是post提交,如果换成get提交,则浏览器默认仅仅把文件名作为属性值来上传,不会上传文件内容,如下

apache fileupload源码分析文件上传格式文件上传注意点:apache fileupload的解析

form表单中一定不要忘了添加

否则的话,浏览器则不是按照上述的格式来来传递数据的。

上述两点才能保证浏览器正常的进行文件上传。

有了上述文件上传的组织格式,我们就需要合理的设计后台的解析方式,下面来看下apache fileupload的使用。先来看下整体的流程图 

apache fileupload源码分析文件上传格式文件上传注意点:apache fileupload的解析

apache fileupload分servlets and portlets两种情形来处理。servlet我们很熟悉,而portlets我也没用过,可自行去搜索。

对于httpservletrequest来说,另一个不再说明,自行查看源码,判断规则如下:

是否是post请求

contenttype是否以multipart/开头

见源码:

servlet的输入参数为httpservletrequest,portlets的输入参数为actionrequest,数据来源不同,为了统一方便后面的数据处理,引入了requestcontext接口,来统一一下目标数据的获取。

接口requestcontext的实现类:

servletrequestcontext

portletrequestcontext

此时requestcontext就作为了数据源,不再与httpservletrequest和actionrequest打交道。

上述的实现过程是由fileupload的子类servletfileupload和portletfileupload分别完成包装的。

父类fileupload的子类:

servletfileupload

portletfileupload

源码展示如下:

servletfileupload类

portletfileupload类

上述的parserequest便完成了整个request的解析过程,内容如下:

分以下两个大步骤:

根据requestcontext数据源得到解析后的数据集合 fileitemiterator

遍历fileitemiterator中的每个item,类型为fileitemstreamimpl,使用fileitemfactory工厂类来将每个fileitemstreamimpl转化成最终的fileitem

fileitemiterator内容如下:

这就是一个轮询器,可以假想成fileitemstream的集合,实际上不是,后面会进行介绍

fileitemstream则是之前上传文件格式内容

或者

的封装,代码如下

然后我们来具体看下由requestcontext如何解析成一个fileitemiterator的:

new了一个fileitemiteratorimpl,来看下具体的过程:

要点:

contenttype进行判断,是否以multipart开头

判断整个请求流的数据大小是否超过sizemax最大设置

获取重要的分隔符boundary信息

封装了request请求流的数据,包装为multipartstream类型

也可以设置通知器,来通知流的读取进度

这里可以看到fileitemiteratorimpl并不是fileitemstreamimpl的集合,其实是fileitemiteratorimpl内部包含了一个fileitemstreamimpl属性。fileitemiteratorimpl的一些重要属性和方法如下:

findnextitem()方法就是创建新的fileitemstreamimpl来替代当前的fileitemstreamimpl,并更新起始位置。

每次调用fileitemiteratorimpl的hasnext()方法,会创建一个新的fileitemstreamimpl赋值给fileitemstreamimpl属性

每次调用fileitemiteratorimpl的next()方法,就会返回当前fileitemstreamimpl属性的值

创建的每个fileitemstreamimpl都会共享fileitemiteratorimpl的multipartstream总流,仅仅更新了要读取的起始位置

其他应用其实就可以遍历fileitemiteratorimpl拿到每一项fileitemstreamimpl的解析数据了。只是这时候数据

存储在内存中的

每个fileitemstreamimpl都是共享一个总的流,不能被重复读取

我们想把这些文件数据存在临时文件中,就需要使用使用fileitemfactory来进行下转化成fileitem。每个fileitem才是相互独立的,而fileitemstreamimpl则不是,每个fileitem也是对应上传文件格式中的每一项,如下

fileitemfactory的实现类diskfileitemfactory即将数据存储在硬盘上,代码如下:

我们从上面可以看到,其实fileitemfactory的createitem方法,并没有为fileitem的流赋值。再回顾下上文parserequest方法的源代码,赋值发生在这里

上述fileitem的openstream()方法如下:

gettempfile()会根据fileitemfactory的临时文件目录配置repository,创建一个临时文件,用于上传文件。 

这里又用到了commons-io包中的deferredfileoutputstream类。

当数据数量小于sizethreshold阈值时,存储在内存中

当数据数量大于sizethreshold阈值时,存储到传入的临时文件中

至此,fileitem都被创建出来了,整个过程就结束了。