使用 html5 分块上传文件

Posted

技术标签:

【中文标题】使用 html5 分块上传文件【英文标题】:uploading a file in chunks using html5 【发布时间】:2011-12-12 19:11:30 【问题描述】:

我正在尝试使用 html5 的文件 API 分块上传文件,然后在 php.ini 的服务器端重新组装它。我正在上传视频,但是当我在服务器端合并文件时,大小增加了,它变成了无效文件。请注意以下 html5 代码仅适用于浏览器 chrome 。在 Chrome 9 中测试,因为它使用了文件 API 的 slice 函数。 有人可以指导我吗? 谢谢

PHP 源码

<?php

$target_path = "uploads/";
$tmp_name = $_FILES['fileToUpload']['tmp_name'];
$size = $_FILES['fileToUpload']['size'];
$name = $_FILES['fileToUpload']['name'];


$target_file = $target_path . basename($name);


$complete = "complete.mov";
$com = fopen("uploads/".$complete, "ab");
error_log($target_path);

// Open temp file
$out = fopen($target_file, "wb");

if ( $out ) 
    // Read binary input stream and append it to temp file
    $in = fopen($tmp_name, "rb");
    if ( $in ) 
        while ( $buff = fread( $in, 1048576 ) ) 
            fwrite($out, $buff);
            fwrite($com, $buff);
           
    
    fclose($in);
    fclose($out);

fclose($com);

?>

HTML 源代码

<!DOCTYPE html>
<html>
    <head>
        <title>Upload Files using XMLHttpRequest</title>
        <script type="text/javascript">

            window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

            function sendRequest() 
                var blob = document.getElementById('fileToUpload').files[0];
                const BYTES_PER_CHUNK = 1048576; // 1MB chunk sizes.
                const SIZE = blob.size;
                var start = 0;
                var end = BYTES_PER_CHUNK;
                while( start < SIZE ) 
                    var chunk = blob.slice(start, end);
                    uploadFile(chunk);
                    start = end;
                    end = start + BYTES_PER_CHUNK;
                
            

            function fileSelected() 
                var file = document.getElementById('fileToUpload').files[0];
                if (file) 
                    var fileSize = 0;
                    if (file.size > 1024 * 1024)
                        fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
                    else
                        fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';

                    document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
                    document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
                    document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
                
            

            function uploadFile(blobFile) 
                //var file = document.getElementById('fileToUpload').files[0];  
                var fd = new FormData();
                fd.append("fileToUpload", blobFile);

                var xhr = new XMLHttpRequest();
                xhr.upload.addEventListener("progress", uploadProgress, false);
                xhr.addEventListener("load", uploadComplete, false);
                xhr.addEventListener("error", uploadFailed, false);
                xhr.addEventListener("abort", uploadCanceled, false);
                xhr.open("POST", "upload.php");
                xhr.onload = function(e) 
                  alert("loaded!");
                  ;

                xhr.send(fd);
                //alert("oen over");
            

            function uploadProgress(evt) 
                if (evt.lengthComputable) 
                    var percentComplete = Math.round(evt.loaded * 100 / evt.total);
                    document.getElementById('progressNumber').innerHTML = percentComplete.toString() + '%';
                
                else 
                    document.getElementById('progressNumber').innerHTML = 'unable to compute';
                
            

            function uploadComplete(evt) 
                /* This event is raised when the server send back a response */
                alert(evt.target.responseText);
            

            function uploadFailed(evt) 
                alert("There was an error attempting to upload the file.");
            

            function uploadCanceled(evt) 
                xhr.abort();
                xhr = null;
                //alert("The upload has been canceled by the user or the browser dropped the connection.");
            
        </script>
    </head>
    <body>
        <form id="form1" enctype="multipart/form-data" method="post" action="upload.php">
            <div class="row">
                <label for="fileToUpload">Select a File to Upload</label><br />
                <input type="file" name="fileToUpload" id="fileToUpload" onchange="fileSelected();"/>
                <input type="button" value="cancel"  onClick="uploadCanceled();"/>
            </div>
            <div id="fileName"></div>
            <div id="fileSize"></div>
            <div id="fileType"></div>
            <div class="row">
                <input type="button" onclick="sendRequest();" value="Upload" />
            </div>
            <div id="progressNumber"></div>
        </form>
    </body>
</html>

【问题讨论】:

文件大小增加了多少? 嗨,马特,文件大小增加了 2 倍以上 所以你达到了服务器端的大小限制,对吗?更改该限制不是一种选择吗? (虽然我发现现在可以进行分块上传很有趣,很酷!) 没有服务器端文件增加,因此成为无效文件 不,我的意思是为什么你首先需要分块上传? 【参考方案1】:

我试图解决这个问题,发现了以下几点,希望对你有用:

1) 您用于切片文件的 JS 函数已弃用。我正在运行 Chrome v14,但控制台无法识别它。在我做任何事情之前我必须把它改成这个:

var chunk = blob.webkitSlice(start, end);

2) 我尝试了小得多的文件(大约 10MB),但遇到了类似的问题 - 我的视频在上传后总是损坏。当我比较原件和“副本”时,我注意到一件特别的事情:文件的各个部分似乎只是混淆了——它们都在那里,只是顺序错误。

我怀疑您当前的程序遇到的一个问题是没有采取措施确保文件以正确的顺序组装。我相信发生的事情是您的 JS 多次运行 uploadFile,而无需等待先前的上传完成,并且服务器尝试按接收到的顺序组装文件,但这与发送文件的顺序不同.

我能够通过让您的代码正常工作来证明这一点(进行了一些修改,作为概念验证),通过为每个文件分配一个收到的序列号,然后在收到所有部件后组装它们为了。完成此操作后,我可以在上传视频文件后播放它。

我认为您将不得不采取类似的措施。接收所有文件块,然后组装它们,或者至少确保您正在采取必要的措施来按顺序组装它们。我不确定为什么你的文件会变大(我很早就观察到了这种现象),所以我怀疑这只是一些奇怪的副作用,否则不会同步文件块。

您将立即遇到的一个困难是 Javasacript 中的 Blob 对象不支持更改或设置文件名,因此您不能在客户端以这种方式为文件提供唯一标识符。我所做的,作为一个简单的解决方法如下:

            var i = 1;
            while( start < SIZE ) 
                var chunk = blob.webkitSlice(start, end);
                uploadFile(chunk, i);
                i++;

                start = end;
                end = start + BYTES_PER_CHUNK;
            


function uploadFile(blobFile, part) 
     ....
     xhr.open("POST", "test.php?num=" + part);
     ....

正如您可能在服务器端猜到的那样,我刚才使用该 GET 变量来分配一个标识符,并将其用作需要在服务器上完成的任何其他处理的基础。

无论如何,这并不能直接解决文件大小增长的问题,所以我只能希望这对您有所帮助;我很想看看你还发现了什么!

【讨论】:

感谢您的详尽回答。我发现了问题。 slice 参数将第二个参数作为长度而不是字节的结尾。 哈!很高兴你想出来了。这是我第一次尝试赏金,答案很简单=)【参考方案2】:

您好,我已经检查了您的 php 文件。我添加了一些安全代码。并更改了文件名属性并删除了dubbel文件创建。在这里。

<?php
session_start();
if ($_SESSION['newsession'] == false and $_SESSION['TypeUser'] == 'Admin' )
$target_path = "../uploads/";
$tmp_name = $_FILES['fileToUpload']['tmp_name'];
$size = $_FILES['fileToUpload']['size'];
$name = $_FILES['fileToUpload']['name'];
$name2 = $_GET['filename'];

$target_file = $target_path.$name;


$complete =$target_path.$name2;
$com = fopen($complete, "ab");
error_log($target_path);

// Open temp file
//$out = fopen($target_file, "wb");

//if ( $out ) 
    // Read binary input stream and append it to temp file
    $in = fopen($tmp_name, "rb");
    if ( $in ) 
        while ( $buff = fread( $in, 1048576 ) ) 
           // fwrite($out, $buff);
            fwrite($com, $buff);
           
    
    fclose($in);

//
//fclose($out);
fclose($com);
else
    echo'you are not logged in.';

?>

对于 html 部分,我更改了多部分文件的上传方式。我将主题放入列表中,并一个一个上传。这是代码。

<script type="text/javascript" > 
function uploadchange() 
            var input = document.getElementById("file");
            var ul = document.getElementById("uploadlist");
            while (ul.hasChildNodes()) 
                ul.removeChild(ul.firstChild);
            
            for (var i = 0; i < input.files.length; i++) 
                var li = document.createElement("li");
                thefilesize = input.files[i].fileSize||input.files[i].size;
                if (thefilesize > 1024 * 1024)
                             thefilesize = (Math.round(thefilesize  * 100 / (1024 * 1024)) / 100).toString() + 'MB';
                         else
                                thefilesize = (Math.round(thefilesize  * 100 / 1024) / 100).toString() + 'KB';
                

                li.innerHTML = input.files[i].name + " " + thefilesize ;
                ul.appendChild(li);             
            
            if(!ul.hasChildNodes()) 
                var li = document.createElement("li");
                li.innerHTML = 'No Files Selected';
                ul.appendChild(li);
            
            sendRequest();
        

window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder;

            function sendRequest() 
        var blob = document.getElementById('file').files[0];
                var BYTES_PER_CHUNK = 1048576; // 1MB chunk sizes.
                var SIZE = blob.size;
                var start = 0;
                var end = BYTES_PER_CHUNK;
        window.uploadcounter=0;
        window.uploadfilearray = [];
        document.getElementById('progressNumber').innerHTML = "Upload: 0 % ";
                while( start < SIZE ) 

                    var chunk = blob.slice(start, end);
            window.uploadfilearray[window.uploadcounter]=chunk;
                        window.uploadcounter=window.uploadcounter+1;
                    start = end;
                    end = start + BYTES_PER_CHUNK;
                
        window.uploadcounter=0;
        uploadFile(window.uploadfilearray[window.uploadcounter],document.getElementById('file').files[0].name);
            

            function fileSelected() 
                var file = document.getElementById('fileToUpload').files[0];
                if (file) 
                    var fileSize = 0;
                    if (file.size > 1024 * 1024)
                        fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
                    else
                        fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';

                    document.getElementById('fileName').innerHTML = 'Name: ' + file.name;
                    document.getElementById('fileSize').innerHTML = 'Size: ' + fileSize;
                    document.getElementById('fileType').innerHTML = 'Type: ' + file.type;
                
            

            function uploadFile(blobFile,filename) 
                var fd = new FormData();
                fd.append("fileToUpload", blobFile);

                var xhr = new XMLHttpRequest();


                xhr.addEventListener("load", uploadComplete, false);
                xhr.addEventListener("error", uploadFailed, false);
                xhr.addEventListener("abort", uploadCanceled, false);

                xhr.open("POST", "./system/upload2.php?filename="+filename);

                xhr.onload = function(e) 
            window.uploadcounter=window.uploadcounter+1;
            if (window.uploadfilearray.length > window.uploadcounter )
                uploadFile(window.uploadfilearray[window.uploadcounter],document.getElementById('file').files[0].name); 
                var percentloaded2 = parseInt((window.uploadcounter/window.uploadfilearray.length)*100);
                document.getElementById('progressNumber').innerHTML = 'Upload: '+percentloaded2+' % ';                              
            else
                document.getElementById('progressNumber').innerHTML = "File uploaded";
                loadXMLDoc('./system/loaddir.php?url='+ window.currentuploaddir);

            
                  ;

                xhr.send(fd);

            

           function uploadComplete(evt) 
                /* This event is raised when the server send back a response */
        if (evt.target.responseText != "")
                    alert(evt.target.responseText);
        
            

            function uploadFailed(evt) 
                alert("There was an error attempting to upload the file.");
            

            function uploadCanceled(evt) 
                xhr.abort();
                xhr = null;
                //alert("The upload has been canceled by the user or the browser dropped the connection.");
            


</script>
<LINK HREF="./system/link.css" REL="stylesheet" TYPE="text/css">

</head>
<body>
<div id="fileselector">
<div id="containerback">

</div>
<div id="dirlijst">

</div>


<div id="container">
    <h1>Upload file</h1>
    <br />
    <form name="form1" onSubmit="return uploadFile();" method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" enctype="multipart/form-data">

<div id="progressNumber"></div>


<input type="file" id="file" multiple name="uploads[]" style="visibility:hidden" onChange="uploadchange();">
<a href="#" onClick="document.getElementById('file').click();return false"><img src="system/iconfilemanager/upload.png" ></a>
 <div id="uploadlist">

</div>

 </form>
</div>

【讨论】:

【参考方案3】:

更新答案

在 chrome ==> 切片函数接受第二个参数作为长度。

在 FF ==> 切片函数接受第二个参数作为结束。

代码示例

fileorblob.slice(startingPosition, length) //for chrome
fileorblob.slice(startingPosition, end)//for FF

webkitslice and mozslice 已弃用,请改用本机 "slice()"

BlobBuilder 也已弃用 Blob constructor

资源:

http://updates.html5rocks.com/2012/06/Don-t-Build-Blobs-Construct-Them

https://developer.mozilla.org/en-US/docs/Web/API/Blob

Reading files as chunks and uploading

【讨论】:

【参考方案4】:

切片函数接受第二个参数作为长度。 mozSlice 接受第二个参数作为 end

【讨论】:

【参考方案5】:

根据 PHP 文档,fread 以字节为单位,而不是位。您是否尝试使用 1000000 而不是 1048576 ?

【讨论】:

以上是关于使用 html5 分块上传文件的主要内容,如果未能解决你的问题,请参考以下文章

在循环中使用 xhr 对象进行 html5 分块上传

如何使用 Dropzone.js 进行分块文件上传(仅限 PHP)?

哪个javascript框架支持ajax方式的文件上传

分块上传验证:“无法上传文件。”

WebUploader实现浏览器端大文件分块上传

vue实现浏览器端大文件分块上传