LoginSignup
2
14

More than 3 years have passed since last update.

大きなファイルをJavaScriptで分割アップロードする

Posted at

FileReader関数を利用して分割アップロードする

アップロード前に画像のプレビューを表示するくらいしか使い道が思いつかなかったFileReader関数ですが、実用的な使途がありました。

サーバーの制限を回避する

php.iniなど設定を変更できないサーバーを使っている場合、タイムアウトやメモリ-不足などでアップロードが失敗してしまいます。
<?php
ini_set('upload_max_filesize', '128M');
ini_set('max_execution_time', '120');
?>

などの記述で回避できる場合もありますが、これらを書いても効かない場合もあります。ならば分割して少しずつ送信すれば良いわけです。

アップロードがメインになるようなサービスを作ろうというわけではないので、極力シンプルに書いてみたのが、以下のコードです。

ブラウザーからの分割アップロード(html部分)

FileReader.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>dividing_upload</title>
<script src="./dividing_upload.js" type="text/javascript"></script>
</head>
<body>
    <form>
        <input id="file" name="file" type="file" />
    </form>
    <span id="msg"></span>
    <form>
        <input type="text" id="from" name="from" readonly  />
        <textarea id="data" name="data" readonly></textarea>
    </form>
</body>
</html>

実用上は、2つめのformは非表示にした方が良いでしょうね。

ブラウザーからの分割アップロード(js部分)

dividing_upload.js
var cnt=0;
var total=0;

function post(result,from,size)
{
    plus=0;
    data=result.substr(from,size);
    if(data.substr(size-1,1)=="\r"){    // \r\nを分断すると\nを補ってくれちゃうので
        plus=1;
        data=result.substr(from,size+plus);
    }
    document.querySelector('#data').value=data;
    document.querySelector('#from').value=from;
    postForm('FileReaderPOST.php',document.forms[1],function(responseText){
        if(responseText==''){
            cnt+=data.length;
            if(cnt<total){  // 2回め以降の送信
                document.querySelector('#msg').innerHTML =""+cnt+"/"+total;
                post(result,from+size+plus,size);
            }else{          // 完了時のリセット
                document.forms[0].reset();
                document.forms[1].reset();
                cnt=0;
                total=0;
                document.querySelector('#msg').innerHTML ='completed => <a target="_blank" href="FileReaderPOST.txt">download</a><br />';
            }
        }else{
            alert(responseText);
        }
    });
}
function postForm(uri,formNode,callback1)
{
    form = new FormData(formNode);
    var xhr = new XMLHttpRequest();
    xhr.open('POST', uri);
    xhr.onreadystatechange=function(){
        if(xhr.readyState == 4 && xhr.status == 200){
            callback1(xhr.responseText);
        }
    };
    xhr.send(form);
}
window.addEventListener('DOMContentLoaded', function() {
    document.querySelector("#file").addEventListener('change', function(e) {
        if (window.File) {
            var input = document.querySelector('#file').files[0];
            var reader = new FileReader();
            reader.addEventListener('load', function(e) {
                base64=btoa(reader.result); // 文字バケ防止のエンコード
                post(base64,0,32768);       // 初回送信、分割サイズ指定
                total=base64.length;
                document.querySelector('#msg').innerHTML ="送信中";
            }, true);
            reader.readAsBinaryString(input);
        }
    }, true);
});

FileReaderを使う部分はコチラをベースに使いました

分割をまとめるため、サーバー側で再結合する処理が必要です。

FileReaderPOST.php
<?php
    $filename='FileReaderPOST.txt'; // とりあえず固定値
    if(is_file($filename)){
        if($_POST['from']==0){      // 初回送信なら前回のファイルを削除
            $data='';
            unlink($filename);
        }else{
            $data=file_get_contents($filename);
        }
    }else{
        $data='';
    }
    file_put_contents(  // デコードしてから追加書き込み
        $filename,
        $data.base64_decode ($_POST['data']));
?>

file_put_contentsじゃなくてfopen("……","a")にしろ、ってところでしょうか。要するにデコードして追加書き込みをするだけなので、phpでなくても構いません。
"$filename"がハードコーディングで".txt"になっていますが、エンコードされるのでバイナリーデータも送信できます。その場合はform内にファイルタイプを追加して、拡張子を変更させれば良いでしょう。

2
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
14