刘昆 李富涛 舒亚非
摘要:该文列举了当前几种常见的文件上传方式,该文基于云服务研究了文件跨域上传的解决方案,并指出了每一种文件上传技术,在当前的技术背景下各自存在的优缺点。通过对每一种技术优缺点的权衡,找出一种合适的方案来解决项目中文件跨域上传的问题。
关键词:上传;跨域;同源策略
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2016)18-0071-02
1 背景
笔者最近在开发一个基于 HTML5 的社区活动应用商店的项目,该项目主要是面向活动组织者并以 Web API 的方式提供一系列的在线服务,以方便活动发起者进行活动的筹备和组织。其中,有一项云存储功能,方便第三方活动发起者在他自己的网站或 APP中通过调用上传API,将活动相关文件譬如活动照片、视频等存储到活动云服务器。该功能涉及文件异步上传、跨域上传等技术难点,即本文的研究重点。
2 研究
2.1 传统上传方式
浏览器一般都是通过 HTTP 的 POST 方法来实现基本的上传操作。最常见的数据提交方式就是浏览器的原生表单,使用其默认的enctype属性设置application/x-www-form-urlencoded。但是这仅限于上传基本的文本数据,如果要上传文件,可以设置enctype属性为 multipart/form-data。具体代码如下:
但是使用传统方式上传存在许多的弊端。首先,该上传方式属于“同步上传”,就是在上传过程中会把页面“锁死”,用户只能够等待文件上传结束页面刷新完,才能进行下一步的操作。不仅如此,使用传统上传文件方式,上传文件的当前进度以及上传的速度都不得而知,没有一个很好的显示交互过程,就用户角度来说体验很差。而且对于有大量上传任务的用户来说,这种“同步上传”的方式是不能对其进行批量处理的。
2.2 异步上传方式
由于“同步上传”存在的弊端,寻找更优的方式来解决上传时页面“锁死”和交互信息不足的问题,成为了开发人员当前急需解决的事。
显然同步上传已经无法解决上述问题,自然就提出异步的解决方式。异步上传文件的方式有很多,本文主要介绍一下异步上传文件中使用 Flash、Ajax 以及基于 HTML5 的文件上传方式。
2.2.1 Flash上传
Flash 是比较常见的上传方法之一。在 Flash 中上传文件只要对 FileReference 进行简单的封装就可以实现。通过 FileReference类的方法可使应用程序本地加载和保存数据文件,并与远程服务器之间传输文件数据。另外,Flash 的优势在于有较好的文件上传队列,能够自动逐个上传文件,将上传的进度很好的反馈。但是第三方插件毕竟还是需要浏览器的支持,比如 Safari 不支持 Flash,也就造成 Flash 会存在兼容性的问题,并且就目前 Flash 的发展来看,Flash 也因为资源的消耗严重而不断被放弃使用。也因为如此,其他的上传技术将得到进一步发展。
2.2.2 iframe上传
因为 Flash 这些第三方插件存在兼容性的问题,我们需要一种能够在浏览器上直接使用 Web 技术来解决的方法。因此想到使用 iframe 来模拟文件的异步上传操作。实现这种方法很简单,只需要设置 form 表单的 target 属性到 iframe 的 name 。总体来说,这种基于 iframe 的表单上传,因为异步、简单,适用于一些简单的上传应用场景。代码如下:
2.2.3 HTML5 新接口
随着 HTML5 的发布,添加了一些文件读取的新接口,浏览器处理用户电脑里的文件可以使用以下三个方法:FormData Interface、FileList Interface、FileReader API。这三个方法分别实现了浏览器从文件打开、选择、读取三个功能。而且由于XMLHttpRequest Level 2很好地兼容了这三个方法,从而允许我们可以使用 FormData 将表单中的对象状态转换成可保持或传输的格式,从而实现异步上传一个二进制文件。
document.getElementById("myform").onsubmit = function(e) {
e.preventDefault();
var f = e.target,
formData = new FormData(f),
xhr = new XMLHttpRequest();
xhr.open("POST", f.action);
xhr.send(formData);
}
完成将 FormData 对象通过send()方法传递后,对应的 HTTP 头信息会自动帮你设置好。不需要再纠结编码的问题。而且就兼容性来说,目前主流的最新版本的浏览器都已经兼容了 HTML5,IE 也在 10 以后的版本也开始兼容 HTML5。
虽然 HTML5 已经得到主流最新版本的浏览器的支持,但还是有很多旧版本的浏览器是无法使用 HTML5 的新特性。如果还是要考虑到这方面用户的上传需求,那么就必须结合 Flash 或者 iframe 的上传方式。这样子就可以全方位地兼容市面上的浏览器了。
3实践
针对于以上的研究,笔者了解到目前很多公司的解决方法都是通过服务器代理上传,这种方法很消耗服务器的资源,但是好处在于它不受到限制,技术上实现起来比较容易。为了提高用户的体验,本文试图通过客户端跨域直传的方式,将本地的文件直接上传的云存储平台上,这样可以使用云平台提供的 CDN 加速,大大提高了用户上传文件的速度,然而对于服务器代理上传,客户端直传在技术上实现起来比较复杂,主要是因为浏览器做了很多的限制。
首先,先来了解一下两种方式的上传流程。对于服务器代理上传,很多开发者采用的做法是先上传到服务器,然后由服务器把文件上传到云存储平台。流程如图所示:
客户端(浏览器)=>服务器 =>云存储服务,这种上传方式存在文件上传速度受限、扩展性不佳、费用高等问题。针对以上问题,本地浏览器直传技术将文件直接上传到云存储服务上,将会为开发者节省大量的维护成本并提高上传的速度。流程如图所示:
由于浏览器在安全方面的考虑,实施了同源策略来限制浏览器的跨域访问。因此实现直传首先需要解决客户端浏览器的跨域问题,在 iframe 中可以选择使用片段标识符技术监听新消息。该技术通过每隔 N 毫秒轮询 URL,来获取到当前需要上传的信息。但是使用这种 URL 拼接的方式,对于大文件的上传会造成限制,主要原因是浏览器对 URL 的限制。
如果页面在主域名相同而子域名不同的情况下,可以设置 document.domain 让它们同域。而在当前的业务场景之下,项目本身服务器提供的域名和云服务提供商所提供的域名,它们之间的主域名是不同的,本身就不满足使用 document.domain 的前提条件,所以通过设置 document.domain 来实现跨域不是很好的选择。
当然在 HTML5 规范中引入了 window.postMessage 新特性,可以用于安全跨域通信。window.postMessage 基于事件设计的机制解决了跨域的问题。不再是直接访问一个文档的属性和方法,而是向文档发送消息而后等待响应。这种方式要求双方建立一种双向的通信通道,从而消除对文档的恶意访问攻击。最后我们考虑使用 window.postMessage 来实现跨域的通信。
解决完跨域问题,紧接着需要云存储服务商提供有效的的签名授权,初始化服务器的配置之后,我们的服务器所要做的就是向客户端提供上传的凭证(token),通过凭证来达到客户端直传文件到云存储上。通过 DNS 的智能解析,云存储可以连接到最近的 ISP 服务商节点,对于原本的上传流程来说,这样的方式速度上获得了很大的提升,也减轻了服务器的压力。
4 结束语
其实全文所提到的跨域问题,都是浏览器开发商出于安全性的考虑实行“同源策略”,对浏览器进行了限制。但随着HTML5 的普及,主流的浏览器更新速度加快,越来越多的接口会被添加进来,可能困扰许多人的跨域问题在未来会很容易的得到解决。当然,就目前来看,其实很多的插件也都已经很好地实现跨域的问题。而且很多云服务平台的提供商,如七牛云等,都有提供相应的 SDK,开发人员只需要依照相应的文档就可以很方便地实现跨域直传技术。而且使用第三方的云存储平台,可以大大提高用户上传和下载的速度。
参考文献:
[1] Ben Vinegar,AntonKovalyov.第三方JavaScript编程[M]. 郭凯,译.北京:人民邮电出版社,2015.
[2] Mozilla Developer Network[EB/OL].https://developer.mozilla.org/zh-CN/.
[3] 阮一峰. 文件上传的渐进式增强[EB/OL]. (2012-08-10)[2016-05-10]. http://www.ruanyifeng.com/blog/2012/08/file_upload.html.
[4] Dimitar Ivanov. AJAX File Upload[EB/OL]. (2015-10-03)[2016-05-10]. http://zinoui.com/blog/ajax-file-upload.
[5] 廖学芝. HTML5文件上传组件的深度剖析[EB/OL]. (2014-03-29)[2016-05-10]. https://segmentfault.com/a/1190000000491128.