前因:因为web开发用到文件上传功能,搜索到了Valums,感觉很不错,特别是它在非IE浏览器下通过HTML5技术来支持多文件上传,支持拖拽功能等,不过在具体用中并不顺利,Valums在FF下罢工,追究原因,原来跟我使用Aapche FIleUpload有关,再追究,对文件上传的原理有了新的认识。
1. 文件上传时的抓包
不说理论,先看下对一个基于WEB的文件上传的抓包。在IE7/8下(不要用firefox或者chrome),在http://valums.com/ajax-upload/页面上,上传一个文件,用wireshark抓包,结果如下:
众所周知,一个HTTP报文主要请求行、请求头部、空行和请求数据四部分组成,而对于文件上传来说,对应的HTTP报文的关键设置有:
- 请求行设置为POST方式
- 请求头部设置Content-Length,设置为要传输的文件长度
- 请求头部设置Content-Type: multipart/form-data; boundary=---------------------------88739631214394723612117964652, 这个boundary是用来作为多个上传文件的分割的。
- 请求数据部分,每个部分(包括文件和form内的html标签等)的结构大致如下:
之所以在这儿提到抓包,是因为Apache FileUpload库就是为这种格式的报文设计的,它的ServletFileUpload.isMultipartContent()方法,还有它常规的fileItem的遍历操作都是建立在报文合乎上面格式的前提下才能正常使用。之前我并不清楚,后来在客户端使用Valums库出错的时候才慢慢注意到这些问题的。
2. 传统的解决方案
传统的web页面上的文件上传是通过html标签来实现的:
当你在页面上提交这样的form时,浏览器器会为你自动生成1.中那样格式的报文。本方式最大的缺点是提交就要刷新页面,于是,见下面。。。
3. 异步提交的文件上传
因为ajax技术的引进,人们也希望文件上传做成异步的,在页面后台进行,而不用刷新整个页面。gmail,QQ等纷纷采用flash技术,不过我还是想用纯粹的js来解决这个问题。根据http://valums.com/ajax-upload/的思路,方法有两种,一是在IE中使用隐藏的ifram+加异步form提交,二是在FF或者Chrome中利用xhr + html5特性。
iframe+form异步提交很简单,在网页动态增加一个iframe,在iframe里动态增加一个带有属性enctype="multipart/form-data"的form,然后异步提交这个form即可。
第二种方法,主要是利用XmlHttpRequest(简称XHR)技术来完成,具体实现上,可以利用Chrome等提供的FormData或者FileAPI,也可以只简单地使用XHR来完成(这种方法有缺陷,当后台使用Apache的Commons upload库时,就会发现)。
4. Valums插件的问题与解决方案
XHR是Ajax的核心类。我们可以用它来进行异步通信,包括异步发送文件。Valums在Firefox和Chrome上关于文件上传的实现很简单,核心代码如下:
不过,这种作法有个问题,当后台使用Apache FileUpload库的时候出现。在1.中已经提到过Valums在IE下通过IFRAME异步上传文件时的抓包;现在对Valums在Chrome下进行抓包,发现包的数据部分不一样,
很明显,这个包与前面在IE下的抓包有两处的关键不同,一是Content-Type没有关于文件上传的设置;二是请求数据部分的组成不同。这样的结果导致在后台用Apache FileUpload处理文件的时候无法识别(FileUpload的用法见参考4)。解决办法有两种,一种是在客户端设置异步请求的Content-Type,并在后台避开FileUpload的常规用法,而直接用操作request.getInputStream,这样的话,既要修改客户端,又要在后台判断不同浏览器然后作出不同应对,不合算;我用另一种方法解决,在客户端对Valums修改,使之与IE下通过IFRAME的请求报文完全相同,这样就不用再改后台了。
具体的作法是利用Chrome和FF所支持的HTML5新特性——FormData。FormData可以生成如1.中提到那样传统的文件上传请求格式,并且你可以自由地在FormData中通过append方法来增加各种key/value对作为参数,附加到请求中。
不过,这种解决方案的前提是浏览器支持FormData,IE不要指望了,FF和chrome比较新的应该都支持,具体的版本号我没测试过,反正我机子上的浏览器都可以。
5. 参考:
1. http://hi.baidu.com/netpet/blog/item/423007faa0867f9058ee90c4.html
2. valums文件上传js插件:http://valums.com/ajax-upload/
3. 关于File object: https://developer.mozilla.org/en/Using_files_from_web_applications
4. Apache Fileupload文档:http://commons.apache.org/fileupload/using.html