将大文件切成块并使用 ajax 和 html5 FileReader 上传

Posted

技术标签:

【中文标题】将大文件切成块并使用 ajax 和 html5 FileReader 上传【英文标题】:slice large file into chunks and upload using ajax and html5 FileReader 【发布时间】:2013-12-11 08:19:08 【问题描述】:

我要实现的是:

我在前端使用html5文件api读取文件,然后使用ajax将文件内容上传到php后端,如果文件小也可以。但是,如果文件足够大,它会导致 chrome 崩溃。所以我使用file.slice将大文件分割成块,当所有块都上传到php时,将块合并成一个完整的块。

代码如下:

前端:

<style>
#container 
     min-width:300px;
     min-height:200px;
     border:3px dashed #000;

</style>
<div id='container'>

</div>
<script>
function addDNDListener(obj)
    obj.addEventListener('dragover',function(e)
            e.preventDefault();
            e.stopPropagation();
    ,false);
    obj.addEventListener('dragenter',function(e)
            e.preventDefault();
            e.stopPropagation();
    ,false);
    obj.addEventListener('drop',function(e)
            e.preventDefault();
            e.stopPropagation();
            var ul = document.createElement("ul");
            var filelist = e.dataTransfer.files;
            for(var i=0;i<filelist.length;i++)
                    var file = filelist[i];
                    var li = document.createElement('li');
                    li.innerHTML = '<label id="'+file.name+'">'+file.name+':</label>  <progress value="0" max="100"></progress>';
                    ul.appendChild(li);
            
            document.getElementById('container').appendChild(ul);
            for(var i=0;i<filelist.length;i++)
                    var file = filelist[i];
                    uploadFile(file);
            
    ,false);


function uploadFile(file)
    var loaded = 0;
    var step = 1024*1024;
    var total = file.size;
    var start = 0;
    var progress = document.getElementById(file.name).nextSibling;

    var reader = new FileReader();

    reader.onprogress = function(e)
            loaded += e.loaded;
            progress.value = (loaded/total) * 100;
    ;

    reader.onload = function(e)
            var xhr = new XMLHttpRequest();
            var upload = xhr.upload;
            upload.addEventListener('load',function()
                    if(loaded <= total)
                            blob = file.slice(loaded,loaded+step+1);
                            reader.readAsBinaryString(blob);
                    else
                            loaded = total;
                    
            ,false);
            xhr.open("POST", "upload.php?fileName="+file.name+"&nocache="+new Date().getTime());
            xhr.overrideMimeType("application/octet-stream");
            xhr.sendAsBinary(e.target.result);
    ;
    var blob = file.slice(start,start+step+1);
    reader.readAsBinaryString(blob);


window.onload = function()

    addDNDListener(document.getElementById('container'));
    if(!XMLHttpRequest.prototype.sendAsBinary) 
              XMLHttpRequest.prototype.sendAsBinary = function(datastr)   
                        function byteValue(x)   
                            return x.charCodeAt(0) & 0xff;  
                          
                        var ords = Array.prototype.map.call(datastr, byteValue);  
                        var ui8a = new Uint8Array(ords);  
                        try
                            this.send(ui8a);
                        catch(e)
                            this.send(ui8a.buffer);
                          
              ;  
    
;
</script>

php 代码:

<?php
     $filename = "upload/".$_GET['fileName'];
     //$filename = "upload/".$_GET['fileName']."_".$_GET['nocache'];
     $xmlstr = $GLOBALS['HTTP_RAW_POST_DATA'];
     if(empty($xmlstr))
             $xmlstr = file_get_contents('php://input');
     
     $is_ok = false;
     while(!$is_ok)
            $file = fopen($filename,"ab");

            if(flock($file,LOCK_EX))
                    fwrite($file,$xmlstr);
                    flock($file,LOCK_UN);
                    fclose($file);
                    $is_ok = true;
            else
                    fclose($file);
                    sleep(3);
            
    

问题是,文件的chunk全部上传到服务器并合并为一个新文件后,文件总大小比原来的要小,合并后的文件坏了。问题出在哪里以及如何解决?

【问题讨论】:

也许寻找最终文件中缺少哪些块?重复多次并寻找出现的任何模式。 kongaraju.blogspot.in/2012/07/… 如果问题仍然存在,请尝试此操作 【参考方案1】: 使用 readAsBinaryString fn 是不好的做法 SendAsBinary 也被贬低了 读取块内容只是纯粹的愚蠢。切片就足够了。 xhr.send(blob.slice(0,10)) 切片也是不必要的,除非服务器不接受如此大的文件(例如 Dropbox 受限的 REST API) 因此,如果有人试图巧妙地使用工作线程、base64 或 FileReader 上传大文件 - 不要这样做,这完全没有必要。

只有当您决定在将文件发送到服务器之前对其进行加密/解密/压缩时,才可以读取/切片文件。 但仅在有限的时间内,直到所有浏览器开始支持流。 那你应该看看 fetch 和 ReadableStream

fetch(url, method: 'post', body: new ReadableStream(...))

如果您只需要将 blob 转发到服务器,只需执行以下操作: xhr.send(blob_or_file) 并且浏览器将负责(正确地)读取它并且不消耗任何内存。并且文件可以是文件/blob 的大小

【讨论】:

不是dum先生。看,互联网连接不好的人将很难将大文件上传回服务器。此方法允许他们在中断后恢复上传。 我并不是说切片是愚蠢的,而是读取块的内容是 如果互联网连接中断向 api 发出请求,请询问上传了多少并从那里继续。 也可以通过 xhr 的进度事件之一监听您上传了多少,向 api 发送请求并告诉它将文件附加到现有文件并从偏移量 x 开始写入/跨度> 【参考方案2】:

你的js脚本有一个小错误 我注意到 reader.onprogress 事件比 xhr load 事件触发的次数更多。在这种情况下,会跳过一些块。尝试在 load 函数中增加 loaded 变量。

function uploadFile(file)
var loaded = 0;
var step = 1024*1024;
var total = file.size;
var start = 0;
var progress = document.getElementById(file.name).nextSibling.nextSibling;

var reader = new FileReader();

reader.onload = function(e)
        var xhr = new XMLHttpRequest();
        var upload = xhr.upload;
        upload.addEventListener('load',function()
        loaded += step;
        progress.value = (loaded/total) * 100;
                if(loaded <= total)
                        blob = file.slice(loaded,loaded+step);

                        reader.readAsBinaryString(blob);
                else
                        loaded = total;
                
        ,false);
        xhr.open("POST", "upload.php?fileName="+file.name+"&nocache="+new Date().getTime());
        xhr.overrideMimeType("application/octet-stream");
        xhr.sendAsBinary(e.target.result);
;
var blob = file.slice(start,step);
reader.readAsBinaryString(blob); 

【讨论】:

这会向后端服务器发出多个http请求吗?【参考方案3】:

这个问题主要是由共享主机的全局限制引起的。他们通常控制数据量并在限制被覆盖时丢弃连接。 我尝试了几次和几种方法。总是停留在同一个位置。目标文件较小且已损坏。即使上传比这个大小更小的文件,使合并的数据符合限制,结果也可以。 你只有一次机会:增加文件的最大尺寸,以便打开文件。 每次打开目标文件,将chunk Content写入其中,且大小超过Limit时,hoster都会断开Connection。示例主机:Strato 这里的 Limit 全局设置为 16MB。我得到最大合并结果大小是这个大小的两倍。没有机会覆盖它。

【讨论】:

以上是关于将大文件切成块并使用 ajax 和 html5 FileReader 上传的主要内容,如果未能解决你的问题,请参考以下文章

有没有更快的方法将大文件从十六进制转换为二进制,二进制转换为int?

Javascript大文件上传器

HTML5 使用 AJAX 上传多个文件?

split文件切片

基于HTML5和JSP实现的图片Ajax上传和预览

HTML5多文件上传:通过AJAX一一上传