web-dev-qa-db-ja.com

大きなファイルをチャンクにスライスし、ajaxとhtml5FileReaderを使用してアップロードします

私が実装したいのは:

フロントエンドでは、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);
            }
    }

問題は、ファイルのチャンクがすべてサーバーにアップロードされて新しいファイルにマージされた後、ファイルの合計サイズが元のサイズよりも小さくなり、マージされたファイルが壊れてしまうことです。問題はどこにあり、どのように修正するのですか?

11
user3035467
  • ReadAsBinaryStringfnの使用は悪い習慣です
  • SendAsBinaryも非推奨です
  • チャンクの内容を読むことは純粋なダムです。それらをスライスするだけで十分です。 xhr.send(blob.slice(0,10))
  • サーバーがそのような大きなファイルを受け入れない限り、スライスも不要です(dropbox Limited REST API)など)
  • したがって、大きなファイルをアップロードするためにワーカースレッド、base64、またはFileReaderを賢く使用しようとしている人は、そうしないでください。すべて不要です。

ファイルをサーバーに送信する前にファイルを暗号化/復号化/圧縮することを決定した場合にのみ、ファイルの読み取り/スライスを行うことができます。
ただし、すべてのブラウザがストリームのサポートを開始するまでの限られた時間のみ。
次に、fetchとReadableStreamを確認する必要があります

_fetch(url, {method: 'post', body: new ReadableStream({...})})
_

blobをサーバーに転送する必要がある場合は、次のようにします。xhr.send(blob_or_file)すると、ブラウザーがblobの読み取りを(正しく)処理し、メモリを消費しません。また、ファイルはどのように大きくてもかまいません。

5
Endless

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); }
3
Alessio Carello

この問題は主に、共有ホストのグローバルな制限が原因で発生します。多くの場合、データの量を制御し、制限が上書きされると接続をドロップします。私はこれを数回、いくつかの方法で試しました。常に同じ位置でスタックします。ターゲットファイルが小さく、破損しています。このサイズよりも小さいファイルをアップロードする場合でも、マージされたデータが制限に収まるように、結果はOKでした。チャンスは1つだけです。ファイルを開くために、最大サイズを増やしてください。ターゲットファイルを開いてチャンクコンテンツをそのファイルに書き込むたびに、サイズが制限を上書きすると、ホスティング業者は接続を切断します。ホスティング業者の例:Stratoここでは、制限はグローバルに16MBに設定されています。最大マージ結果サイズがこのサイズの2倍になります。それを上書きするチャンスはありません。

1
BigMac121263