用webuploader能跨域吗怎么解决跨域上传文件的问题

前段时间在中分享了,由于ppt携带的信息非常有限,故在此整理成文章分享出来,供感兴趣的同学阅读。
HTML VS FLASH
对于文件上传,相信还有不少同学还停留在FLASH时代,其实现在 HTML5 不仅可以实现文件上传,而且可以做得更好。
以下是对 HTML5 与 FLASH 就文件上传方面的功能调研测试得出的结果。
拖拽(文件 & 文件夹)
Cookie & Session
文件内容读取
图片预览&裁剪
? 更加精准
PS: 截屏粘贴是指,如果剪切板里面存在图片数据,是可以通过 CTRL + V 将此图片作为文件添加到文件上传组件中的。让剪切板中有图片数据有很多方式:截屏软件(如QQ截屏),浏览器中右击图片点击复制,QQ聊天软件中复制图片…
可以看出,采用 HTML5 技术与传统的 FLASH 技术相比,能实现的功能更多且性能优势特别明显。
更多调研细节请移步到。
虽然 HTML5 优势非常明显,但是如果目前支持 HTML5 的浏览器占比情况不理想,采用 HTML5 技术的文件上传还是不能带来足够的收益!
让我们先来看看由
提供的2014年3月份全球浏览器占比图。
通过浏览器占比可以推算出,目前支持 HTML5 的浏览器占比高达64.5%,加上 HTML5 有如此大的优势,看来完全没有理由拒绝采用 HTML5 来实现文件上传了。
但是还有35%的浏览器不支持 HTML5,怎么办?
为了考虑最大的兼容性,目前同时实现了 HTML5 和 FLASH 两套运行时,在不支持 HTML5 的浏览器里面自动切换成 FLASH 方式上传,这样的好处是,既能在条件允许的情况保证 HTML5 发挥出其高效的优势,又能在不支持 HTML5 的浏览器里面保证能正常运行。
如何优化文件上传性能?
对于文件上传性能优化,可以从两个方面来着手,即上传前的优化和上传过程中的优化。
上传前的优化
主要有两个思路。
思路一:通过减少文件体积,减少上传流量来优化。
思路二:通过合并小文件,减少请求数来优化。
基于这两个思路,我们尝试了以下几种方案。
如果上传的是图片类文件,存在一部分用户喜欢直接选择相机或者手机拍摄的原始文件进行上传,对于这类图片,图片的分辨极高,高达5000多。这就注定了此图片的文件体积不会太小。其实如果只是在电脑上查看,1千多的分辨率就已经足够。于是我们尝试通过js将此类图片进行缩小,得出的结果是:1张()大小为5M的jpg图片,缩小到()后,体积变成了407kb, 直接节省了4.5M的流量。
ZIP 合并小文件
类似于拷贝文件夹到U盘,如果小文件比较多,拷贝过程非常慢,通常我们的做法是将文件夹打包成一个文件再拷贝,速度往往要快出许多。其实文件上传也一样,如果能把体积比较小的文件合并成一个文件,请求数就会少了很多,这样是不是就提高了整体文件上传速度呢?
但是,通过测试得出的结果是不尽如意的。ZIP的运算效率太低,以至于只有在2G网络下才有速度提升,3G和wifi网络下反而变慢了。更多细节请移步到。
SPRITE 合并小图片
类似于css sprite, 将多个小图片画到一张大图片上,通过这种方式来进行合并,思路和zip合并差不多,但是采用的技术不太一样,此方案是直接用canvas来画,经过测试发现速度比zip快出来10倍,总体能带来20%左右的速度提升。
但是此方案只满足于图片类文件,且服务端需要通过位置信息还原出小文件,带来一定的服务端开发成本,目前并没有采用此方案。更多细节请移步到。
直接合并文件内容
其实,并不需要采用ZIP或者SPRIT方式合并文件,把文件读取出 arraybuffer 后是直接可以连接在一起的,之后还可以再次转成 blob 发送到服务端,或者直接发送 arraybuffer,理论上性能应该比SPRITE方案靠谱。
但是这块没有进行详细的调研,在此就不多说了。
上传过程中优化
主要采用并发与分块,以下将细说这两个方案。
采用此方案主要是源于单一请求无法让网络达到饱和,于是我们来尝试采用并发方式看能否提高总体文件上传速度。
以下是通过测试20个1M的文件在不同的并发数下得到的总体上传时间对比图。
很明显,并发数越多速度越快!
但是,并发数越多,给服务端带来的压力也越大,如何去选择一个合适的并发数呢?
主要可以从三方面去考虑。
并发数越多,服务端压力越大,所以选择并发数不能太大!
同时每个浏览器都有固定的最大并发数限制,所以选择并发数不能超出这个值。
从上面的图标可以看出,并发数到了3以后,收益开始渐渐不明显。
如是,最佳的并发数应该是3。
PS: 以下是常用浏览器的最大并发数信息。根据这个表可以说明为什么前面的测试结果,并发数只测试到了6,原因是chrome的最大并发数是6,当并发设置到7的时候,第7个请求是处于等待状态,直到前面某个请求完成,才会开始有进度。
Safari 3, 4
Chrome 1, 2
为什么并发会更快?
这里列出了我个人觉得可能的原因。
多请求能抢占更多的带宽。
服务器端可能针对单一请求限速。
跨域时,并发可以共用options验证请求,浏览器有个特点是,对于跨域请求,如果在一定的时间内,有多个请求,只会发送一个options请求验证的。
左边是非并发的情况,右边为采用并发的情况。可以看出,当不采用并发的时候,每个文件上传请求前都会进行options请求验证,而并发的时候,三个文件上传请求共用了一个opions请求。
为什么要采用分块上传?
试想一下,如果上传的文件是一个大文件,本来上传时间就相对较久,再加上网络不稳定各种因素影响,容易导致传输中断,用户除了重传整个文件外没有更好的选择。采用分片上传可以很好地解决这个问题。
什么是分片上传?
分块上传,就是把一个大的文件分成若干块,一块一块的传输。如上面的case, 如果传输中断,仅需重传出错的分片,而不需要重传整个文件,大大减少了重传开销。
那么,采用分块上传还有哪些优势?
更强容错能力
如以上的case, 出错重传,仅需重传一小块。
可以模拟暂停与继续
对于一个http请求,其实并没有暂停功能,要不就是已完成,要不就是已中断。如果不分块,是没法做暂停功能。但是如果采用分块上传,在某个分块上传完了后不自动开始下个分块上传,是不是就实现了暂停功能?
利用此功能,就可以通过网络检测,在网络断开的时候自动暂停传输,网络恢复后,继续传输,给用户带来更好的体验效果。
利用并发提速
如果单独的上传一个大文件,只有采用了分块上传才能利用并发请求来提速。
更精准的速度跟踪
关于客户端监控上传进度,其实监控的都是客户端的发送速度,服务端有没有接收,有没有存储,是不知道的,只有在整个请求完毕,收到服务端反馈后才能确定数据已经成功接收。这样也就解释了进度显示的时候,长长出现,进度条瞬间到100%,要过一段时间才全部完成。如果采用分块上传,每个分块上传完成,可以确定这个分块服务端已经成功接收。
当然,分块也会有一定的副作用,本来是一个请求,分块后变成了多个请求,自然会带来网络开销。那么具体是个什么的情况呢?
以下是通过测试3个30M的文件在3个并发下调整不同的分片大小得出的总体时间消耗表。
可以看出来,分块越小,时间消耗越大,尤其是分块大小小到256K的时候,时间花费增长特别明显。
那么,如何选择一个合适的分块大小?
主要有三个方面的考虑。
分块越小,请求越多,开销越大。所以不能设置得太小。
分块越大,灵活度越小,前面所说的那些优势就会相对越不明显。故不能太小。
服务端一般都会有个固定大小的接受buffer(client_body_buffer_size), 分块的大小最好是这个值的整数倍。
综合这些考虑,推荐的分块大小是2M-5M,具体size根据产品中文件上传的大小分布来定。如果上传的文件大部分是500M以上,很大的文件,建议是5M, 如果相对较小,推荐2M。
有了分块上传,其实我们可以实现更多的功能。试想,如果服务端能够把每个已成功上传的分块都记住,客户端可以快速验证,条件选择跳过某个分块,是不是就实现了断点续传?
那么,断点续传能带来哪些好处?
节省流量,避免上传重复的分块。
减少用户等待时间。
可恢复的上传。出现中断,就算浏览器刷新或者是换了台电脑也能恢复到上次中断的位置。
那么现在最关键的问题是如何标识一个分块了。怎样标识让服务端好入库,同时客户端可以快速的计算出来与服务端验证,换句话说就是,如何去找出分块的唯一ID。
之前尝试过文件名+分块编号,或者再加文件大小,文件最后修改时间什么的,都无法保证分块的唯一性。于是还是直接采用 MD5 的方式来序列化文件内容吧,这样就算是文件不同名,只要内容是一致的也会正确地识别出是同一个分块。
那么现在的逻辑就是,每次分块上传前,根据内容 MD5 序列化,得到一个唯一ID,与服务端验证,如果此分块已经存在于服务端,则直接跳过此分块上传,否则上传此分块,成功后,服务端记下此分块ID。
如是,分块上传是不是具有了秒传的功能?既然分块能秒传,那么整个文件是否也可以秒传?
分块能秒传,整个文件当然也能秒传,关键还是看 MD5 的性能。
通过以上测试结果可以看出,如果文件大小在 10M 以内,是可以真正的秒级内完成的,大于这个值时间花费就大于1秒了,比如一个200M的文件 MD5 时间花费需要13秒左右。
但是,即便如此,相比于文件传输时间花费,MD5 的时间花费根本就不算什么。对于类似于百度云,文库这类的产品,在上传一个文件的时候很可能服务端已经存在了此文件,如果多等个几秒钟,能跳过整个文件上传,我觉得是非常划算的。
另外,如果是一次上传多个文件是可以在算法上去优化这个过程的。
验证过程提前到当前文件的传输期
如果当前文件已经在传输了,这个时候,用户是处于等待状态,机器也处于等待期,如果把下一个文件的验证过程移至此过程,那么用户的等待 MD5 的时间和等待当前文件传输完成的时间就重合了。这样用户就只需要等待第一个文件的验证过程。
小文件优先处理,减少用户等待时间
因为第一个文件的验证等待无法避免,如果第一个文件处理的文件越小,是不是等待的时间就越短?所以把队列中最小的一个文件放到第一个优先处理可以进一步减少用户等待时间
更换序列化算法,取段MD5
其实对于某些二进制文件,如JPEG,前面一段数据记录了很多此图片的信息,比如:拍摄时间,相机名称,图片尺寸,图片旋转度等等,直接 MD5 这一段数据基本上就可以保证此文件的唯一性了。只要取段的总大小小于10M,再大的文件也能在1秒内完成序列号工作。
作者: - 念我昵称的时候请用英文发音,谢谢!Posts - 225,
Articles - 1,
Comments - 2458
19:16 by chenkai, ... 阅读,
最近在处理后台数据时需要实现文件上传.考虑到对浏览器适配上采用. Fine Uploader 采用ajax方式实现对文件上传.同时在浏览器中直接支持文件拖拽[对浏览器版本有要求类似IE版本必须是9或是更高的IE10].在不同浏览器中提供统一用户体验.该组件基本覆盖目前所有主流浏览器.同时没有任何第三方组件依赖.相当Clear.在服务器端已经覆盖支持了ASP.NET/ColdFusion/Java/Node.js/Perl/PHP/Python. 对上传细节类似限制文件大小,文件类型,文件上传的数量等通过统一接口以暴露选项方式操作.
看到 按照官方的说法. Fine Uploader前身是Ajax Upload. 新版本Fine Uploader主要添加一些新特性.从1.0版本发布的Realse Note来看.二者最大的区别在于.Fine Uploder不在基于Jquery组件.而某些细节处理也更加统一严格.类似返回值全部统一为Json格式.对后台服务器操作和前端Dom对象一些操作Code全部集中Js Script脚本文件中.这样集成使Fine Uploader组件使用非常简单.只需要添加一个CSS+JavaScript文件即可实现文件上传.大大简化用户引用和操作组件难度.
Fine Uploader特点如下:
Fine Uploader Features:
A:支持文件上传进度显示.
B:文件拖拽浏览器上传方式
C:Ajax页面无刷新.
D:多文件上传.
F:跨浏览器.
E:跨后台服务器端语言.
在上下载打包源码,在Php Designer 8中打开其源码可以看到其源码结构如下:
在根目录中可以看到Client客户端调用需要使用文件.Server目录则是对应不同语言Perl/Php/Asp.net[VB]等版本实现.test目录则有包含一个完整本地Sample Demo.可供参考.
如何快速构建一个简单Demo? 其实官方在上已经给出一个简单的演示.这里基于方式构建.
首先新建一个Html空白页面.命名FineUploderDemo.html.添加如下CSS引用如下:
&link href="static/css/fineuploader.css" rel="stylesheet"&
&link href="static/css/bootstrap.min.css" rel="stylesheet"&
这两个文件时必须引用的.fineuploader.css则是对应下载Fine Uploder源码Client目录下.fineuploder.css 提供JS脚本中所需的CSS样式,主要包括按钮的样式、进度显示的样式以及上传结果的样式.添加JavaScript文件引用如下:
&script src="static/script/fineupload/header.js"&&/script&
&script src="static/script/fineupload/util.js"&&/script&
&script src="static/script/fineupload/button.js"&&/script&
&script src="static/script/fineupload/handler.base.js"&&/script&
&script src="static/script/fineupload/handler.form.js"&&/script&
&script src="static/script/fineupload/handler.xhr.js"&&/script&
&script src="static/script/fineupload/uploader.basic.js"&&/script&
&script src="static/script/fineupload/dnd.js"&&/script&
&script src="static/script/fineupload/uploader.js"&&/script&
其中uploder.js和uploder.basic.js则是前端的所有上传功能都在该脚本中实现.必须引用.
同时添加client目录下processing和loading两张进度显示所需要的动态图片.该图片都在fineuploder.css文件调用.
在body添加如下Code:
&div id="bootstrapped-fine-uploader"&&/div&
function createUploader() {
var uploader = new qq.FineUploader({
element: document.getElementById('bootstrapped-fine-uploader'),
request: {
endpoint: 'server/handlerfunction'
uploadButton: '&i class="icon-upload icon-white"&&/i& Click me now and upload a product image'
'&div class="qq-uploader span12"&' +
'&pre class="qq-upload-drop-area span12"&&span&{dragZoneText}&/span&&/pre&' +
'&div class="qq-upload-button btn btn-success" style="width:"&{uploadButtonText}&/div&' +
'&span class="qq-drop-processing"&&span&{dropProcessingText}&/span&'+
'&span class="qq-drop-processing-spinner"&&/span&&/span&' +
'&ul class="qq-upload-list" style="margin-top: 10 text-align:"&&/ul&' +
classes: {
success: 'alert alert-success',
fail: 'alert alert-error'
debug: true
window.onload = createU
这是基于Bootstrap实现对Fine Uploader最简单的前端调用.前端一般需要做两件事A:添加Css+Js文件引用.B:在Js中实例化qq.FineUploder对象.运行效果如下:
查看JS构建qq.Fineuploader对象创建过程.首先指定Fine Uploader插件的Dom元素.通过Dom获取操作.request则是对应服务器端实现文件路径.在这建议不要自己构建服务器端处理.而是直接采用官方提供的实现文件修改即可.template则是对应上传文件添加内容模版也可以自己修改.debug是一个布尔值.用来控制是否使用浏览器的控制台打印Fine Uploader的调试信息,默认为false.
qq.FineUploader对象还有如下控制参数:
validation:该参数一般用来在执行上传文件操作前.在客户端做一些验证.验证操作包含文件格式.文件大小.等添加格式如下:
validation:
allowedExtensions: ['jpeg', 'jpg', 'gif', 'png'],
sizeLimit: 204800 // 200 kB = 200 * 1024 bytes
allowedExtensions控制上传文件的后缀格式数组.
sizeLimit上传文件大小的上限,单位为byte的数值.浏览器不一定支持本设置.也可以在服务器端里设置.
minSizeLimit:上传文件大小的下限,单位为byte的数值.同上有些浏览器存在适配问题.建议统一在服务端设置.
另外针对qq.FineUploder对象在执行上传操作整个过程.定义了五个客户端可控做额外操作的事件.可以再callback参数下设置定义:
callbacks:
onSubmit: function(id, fileName) {
$messages.append('&div id="file-' + id + '" class="alert" style="margin: 20px 0 0"&&/div&');
onUpload: function(id, fileName) {
$('#file-' + id).addClass('alert-info')
.html('&img src="client/loading.gif" alt="Initializing. Please hold."& ' +
'Initializing ' +
'&' + fileName + '&');
onSubmit事件:文件开始提交.调用参数格式如下:onSubmit:& function(id,& fileName)& {}.
onUpload事件: 文件开始上传.调用参数格式如下:onUpload: function(id, fileName) {}.
onProgress事件: 文件正在上传.调用参数格式如下:onProgress:& function(id,& fileName,& loaded,& total)& {}.
onComplete事件: 文件上传成功. 调用参数格式如下:onComplete:& function(id,& fileName,& responseJSON)& {}.
onCancel事件: 取消文件上传.调用参数格式如下:onCancel:& function(id,& fileName)& {}.
如上五个事件基本覆盖整个上传文件操作中所有过程.完全以开放的形式可以再客户端操作.关于调用如上事件参数说明如下:
Id:表示第几个开始上传的文件.Fine Uploder定义是默认从0开始计数.
fileName:上传文件的文件名.
loaded:表示已经上传到服务器端数据的大小[byte].
total: 需要上传文件的大小.
responseJSON: 用来在上传操作完成后返回的Json格式的数据.通过Jquery反序列化出来对象.其中包含一个IsSuccess属性用来判断此次上传是否成功.
如果你想在上传过程向服务器端传递数据.可以通过如下参数控制:
params:用来向服务器端传递数据.注意params采用key-value的数组存储.采用Post方式发送给服务器端.一般传递参数格式如下:
argument1: "value1",
argument2: "value2"
ok.这时基本关于Fine Uploader客户端初始化和控制操作选项基本完成.当我们需要上传操作时.如果IsAuto=false时可以通过已经定义qq.FineUploader对象的uploadStoreFiles()方式手工触发上传操作:
$('#triggerUpload').click(function() {
uploader2.uploadStoredFiles();
如果我们此时点击上传会发现.则提示上传失败. 因为还没有对上传服务器端做任何处理:
endpoint: 'server/handlerfunction'
这时我们需要在EndPoint指定处理文件上传的Php文件[这里是phpdemo].关于服务器端如果你没有已经成熟处理模块.还是推荐你使用官方Server目录上.这里我采用php环境则选中时php.php文件.对应客户端修改如下:
endpoint: 'controller/php.php'
打开php.php发现在文件头部说明该文件使用同时在文件定义三个类用来分别处理XMLHttpRequest、FormPost、BasicPost方式文件服务器端处理.在文件顶部注释中:
/****************************************
Example of how to use this uploader class...
You can uncomment the following lines (minus the require) to use
hese as your defaults.
// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array();
// max file size in bytes
$sizeLimit = 10 * 1024 * 1024;
//the input name set in the javascript
$inputName = 'qqfile'
require('valums-file-uploader/server/php.php');
$uploader = new qqFileUploader($allowedExtensions, $sizeLimit, $inputName);
// Call handleUpload() with the name of the folder, relative to PHP's getcwd()
$result = $uploader-&handleUpload('uploads/');
// to pass data through iframe you will need to encode all html tags
header("Content-Type: text/plain");
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
/******************************************/
已经详细说明如下Class调用方式.添加如下Php代码即可简单完成服务器端处理:
$allowedExtensions = array("jpeg", "jpg", "bmp", "png");
$sizeLimit = 10 * 1024 * 1024;
$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
$result = $uploader-&handleUpload('uploads/'); //folder for uploaded files
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
allowExtensions则定义了允许上传文件的格式.
sizeLimit上限定义为10M.注意首先采用Phpinfo();方法输出当前php环境配置.一般默认情况默认上传文件最大大小为2M.如果你需要上传更大则修改php.ini文件配置参数 这里不再赘述.
uploder则是初始化qq.Fileuploder对象.并加载配置.
fineuploder调用处理上传函数.并传递服务器端存储上传文件存储路径.
echo想服务器端输出上传结果.必须.不然客户端接受不到指定responseJason参数用来判断上传后状态.
在进一步看看服务器端如何处理上传的找到handleUpload函数定义.
* Handle the uploaded file
* @param string $uploadDirectory
* @param string $replaceOldFile=true
* @returns array('success'=&true) or array('error'=&'error message')
function handleUpload($uploadDirectory, $replaceOldFile = FALSE){
if (!is_writable($uploadDirectory)){
return array('error' =& "Server error. Upload directory isn't writable.");
if (!$this-&file){
return array('error' =& 'No files were uploaded.');
$size = $this-&file-&getSize();
if ($size == 0) {
return array('error' =& 'File is empty');
if ($size & $this-&sizeLimit) {
return array('error' =& 'File is too large');
$pathinfo = pathinfo($this-&file-&getName());
$filename = $pathinfo['filename'];
//$filename = md5(uniqid());
$ext = @$pathinfo['extension'];
// hide notices if extension is empty
if($this-&allowedExtensions && !in_array(strtolower($ext), $this-&allowedExtensions)){
$these = implode(', ', $this-&allowedExtensions);
return array('error' =& 'File has an invalid extension, it should be one of '. $these . '.');
$ext = ($ext == '') ? $ext : '.' . $
if(!$replaceOldFile){
/// don't overwrite previous files that were uploaded
while (file_exists($uploadDirectory . DIRECTORY_SEPARATOR . $filename . $ext)) {
$filename .= rand(10, 99);
$this-&uploadName = $filename . $
if ($this-&file-&save($uploadDirectory . DIRECTORY_SEPARATOR . $filename . $ext)){
return array('success'=&true);
return array('error'=& 'Could not save uploaded file.' .
'The upload was cancelled, or server error encountered');
在调用这个处理函数时.需要注意的是.传递的URL存储路径需要时绝对的.所以需要对传入路劲做一下格式化处理:
$uploadDirectory = $_SERVER['DOCUMENT_ROOT']."DS".$uploadD
对于is_writeable文件是否可写的判断.我个人认为还不够详细.is_writeable主要判断文件或目录是否存在.并可写才会返回true. 所以个人建议在is_writable前添加一个文件是否存在.这样更易于在客户端判断服务器端文件出错具体的情况:
if (!file_exists($uploadDirectory)) {
return array('error' =& "Server error. Upload directory dones't exist.");
在保存文件操作前.可以看到.处理函数分别做了四次判断.分别判断了 上传文件的数量、文件上传的大小、文件上传大小是否超过上限、另外在上传过程.如果我们多次想服务器端上传同一个文件.发现Fine Uploder处理方式是.并非是重写.而是从10-99随机一个数字重写命名该文件.并保存到目录下.当保存文件成功后.则想服务器端返回一个Json数据其中包含IsSuccess来指定此次上传操作是否操作成功. IsSuccess参数作为客户端判断此时操作唯一参数.
在上传操作过程发信很多出现&increase post_max_size and upload_max_filesize to 10M&错误,其实针对这个问题.主要是上传文件配置超过php环境默认的2M.需要在php.ini文件中把post_max_size和upload_max_filesize两项的值改到10M以上,然后重启Apache即可.或是参考Php官网针对 修改php.ini配置文件.
至此整个Fine Uploader配置流程已经全部完成.点击选择文件时.会如下效果:
提示上传成功.当然更多的请参考.如上从Fine Uploader源码角度分析其实现原理.
参考链接如下:

我要回帖

更多关于 webuploader 跨域 的文章

 

随机推荐