jQuery + FileReader APIでファイルを分割し、バイナリでアップロード
HTML 5 + jQueryで複数ファイルのアップロードを試してみました。CodeIgniter 3 + HTML5 FileAPI + jQueryで複数ファイルのアップロード
HTML5 + jQueryで複数ファイルのアップロード時、プログレスバーを表示
今度は、1つの大きなファイルを分割し、バイナリ形式でアップロード。
サーバー側で復元してみます。
FileReader API
JavaScriptでファイルを読み込み。
指定バイトでスライスする方法は、こちらがとても参考になりました。
Reading files in JavaScript using the File APIs
File API W3C Working Draft 21 April 2015
FileReader.readAsArrayBuffer()
XMLHttpRequest の利用
分割してアップロードする都合上、識別子としてユニークなIDが振りたい。
UUIDの生成は、こちらのJavaScript実装を使用しました。
UUID v4 generator in JavaScript (RFC4122 compliant)
jQueryを使用して、ヘッダー情報をつけたり、バイナリデータをアップロードする方法はこちら。
JSON Post with Customized HTTPHeader Field
Sending binary data in javascript over HTTP
web側の実装はPHP + CodeIgniter 3で行っています。
viewのソースはこんな感じになりました。
・application/views/fileupload.php
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>ファイルアップロード</title>
- <script src="//code.jquery.com/jquery-2.2.3.min.js" integrity="sha256-a23g1Nt4dtEYOj7bR+vTu7+T8VP13humZFBJNIYoEJo=" crossorigin="anonymous"></script>
- <script>
- <!--
- $(function(){
- // フォームデータのアップロード処理
- var uploadBlobData = function(fileNmae, fileKey, totalBytes, binaryData, tasks, chunkCount) {
- // アップロードの進捗表示
- var xhr_func = function(){
- var XHR = $.ajaxSettings.xhr();
- XHR.upload.addEventListener('progress',function(e){
- tasks[chunkCount] = e.loaded;
- var upload = 0;
- tasks.forEach(function(bytes) {
- upload += bytes;
- });
- var progre = parseInt(upload/totalBytes * 100);
- $('#prog').val(progre);
- $('#pv').html(progre);
- });
- return XHR;
- };
- // Ajaxでアップロード処理をするファイルへ内容渡す
- $.ajax({
- url: 'fileupload/upload',
- type: 'POST',
- data: binaryData,
- processData: false,
- contentType: 'application/octet-stream',
- headers: {
- 'File-Name': fileNmae,
- 'File-Key': fileKey,
- 'Chunk-Index': chunkCount,
- 'Chunk-Total': tasks.length
- },
- xhr : xhr_func
- }).done(function(data) {
- console.log(data);
- }).fail(function(data) {
- console.log(data.responseText);
- });
- };
- // https://gist.github.com/jcxplorer/823878
- var createUuid = function() {
- var uuid = "", i, random;
- for (i = 0; i < 32; i++) {
- random = Math.random() * 16 | 0;
- if (i == 8 || i == 12 || i == 16 || i == 20) {
- uuid += "-"
- }
- uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
- }
- return uuid;
- };
- // ファイルのアップロード処理
- var uploadFile = function(file) {
- $('#prog').val(0);
- $('#pv').html('0');
- // 分割するサイズ(byte)
- var chunkSize = 8 * 1024 * 1024;
- // 選択されたファイルの総容量を取得
- var totalBytes = file.size;
- // ファイル名
- var fileNmae = file.name;
- // チャンク分割数
- var chunkCount = Math.ceil(totalBytes / chunkSize);
- // 識別キー
- var fileKey = createUuid();
- var readBytes = 0;
- var tasks = [];
- for (var i = 0; i < chunkCount; i++) {
- tasks.push(0);
- }
- // チャンクサイズごとにスライスしながら読み込み
- $.each(tasks, function(index) {
- // stopをオーバーして指定した場合は自動的に切り詰められる
- var blob = file.slice(readBytes, readBytes + chunkSize);
- readBytes += chunkSize
- var reader = new FileReader();
- reader.onloadend = function(evt) {
- // 読み取り完了のイベントだけキャッチ
- if (evt.target.readyState != FileReader.DONE) {
- return;
- }
- // 読み取ったデータを取り出し
- var binaryData = evt.target.result;
- uploadBlobData(fileNmae, fileKey, totalBytes, binaryData, tasks, index);
- };
- reader.readAsArrayBuffer(blob);
- });
- };
- // ファイルドロップ時の処理
- $('#drag-area').on('drop', function(e){
- // デフォルトの挙動を停止
- e.preventDefault();
- // ファイル情報を取得
- var files = e.originalEvent.dataTransfer.files;
- uploadFile(files[0]);
- // デフォルトの挙動を停止 これがないと、ブラウザーによりファイルが開かれる
- }).on('dragenter', function(){
- return false;
- }).on('dragover', function(){
- return false;
- });
- // ボタンを押した時の処理
- $('#btn').on('click', function() {
- // ダミーボタンとinput[type="file"]を連動
- $('#file_selecter').click();
- });
- $('#file_selecter').on('change', function(){
- // ファイル情報を取得
- uploadFile(this.files[0]);
- });
- });
- -->
- </script>
- </head>
- <body>
- <div id="drag-area" style="border-style: dashed;background-color: #042943; color: #ffffff;">
- <p>アップロードするファイルをドロップ</p>
- <p>または</p>
- <div class="btn-group">
- <input id="file_selecter" type="file" style="display:none;" name="files"/>
- <button id="btn">ファイルを選択</button>
- </div>
- </div>
- <progress value="0" id="prog" max=100></progress>(<span id="pv" style="color:#00b200">0</span>%)
- </body>
- </html>
コントローラー側は、分割されたチャンクデータを受け取り保存。
すべてのチャンクが揃ったことを確認して復元します。
POSTされたデータの読み取りはこちら。
CodeIgniter3 JSONを返すAPIサーバーとして使用する
分割ファイルの結合はこちら。
PHP 指定バイト数でファイルを分割&結合する
・application/controllers/Fileupload.php
- <?php
- class Fileupload extends CI_Controller {
- // アップロード用の画面を表示
- public function index() {
- $this->load->view('fileupload');
- }
- // 画像アップロード
- public function upload() {
- $headers = $this->input->request_headers();
- // 一時領域に[UUID].[チャンクの番号(0詰め10桁)]というファイル名で保存
- // ※globで取得した時、ファイル名が順番に取れるようにしておく
- $tmp_file = sprintf("%stmp/%s-%010d", FCPATH, $headers['File-Key'], $headers['Chunk-Index']);
- file_put_contents($tmp_file, $this->input->raw_input_stream);
- // ファイルが揃っているかチェック
- $chunk_list = glob( FCPATH.'tmp/'.$headers['File-Key'].'*');
- $chunk_count = count($chunk_list);
- // 他のチャンクがアップロードされるのを待つ
- if ($chunk_count < $headers['Chunk-Total']) {
- $this->output
- ->set_content_type('application/json')
- ->set_output(json_encode(['result' => 'other chank waiting']));
- return;
- }
- // チャンクが揃ったら結合する
- // 結合用の同名ファイルが存在したら消しておく
- $file_path = FCPATH.'tmp/'.$headers['File-Name'];
- if (file_exists($file_path)) {
- unlink($file_path);
- }
- // 順番に結合
- foreach($chunk_list as $chunk_path) {
- $chunk = file_get_contents($chunk_path);
- file_put_contents($file_path, $chunk, FILE_APPEND);
- unlink($chunk_path);
- }
- // 保存結果を返信
- $this->output
- ->set_content_type('application/json')
- ->set_output(json_encode(['result' => $file_path]));
- }
- }
画面表示はこんな感じ。
プログレスバーの動きが進まない場合があるのは今後の課題です。
これで100MBを超えるファイルもアップロードできるようになりました。
【参考URL】
Reading files in JavaScript using the File APIs
File API W3C Working Draft 21 April 2015
FileReader.readAsArrayBuffer()
XMLHttpRequest の利用
UUID v4 generator in JavaScript (RFC4122 compliant)
CodeIgniter3 JSONを返すAPIサーバーとして使用する
PHP 指定バイト数でファイルを分割&結合する
- 関連記事
-
- Debian + PHP 5.6からSQL Server 2016へ接続する
- PHP zipファイルを解凍せずにファイルの内容を読み取る
- jQuery + FileReader APIでファイルを分割し、バイナリでアップロード
- HTML5 + jQueryで複数ファイルのアップロード時、プログレスバーを表示
- CodeIgniter 3 + HTML5 FileAPI + jQueryで複数ファイルのアップロード
コメント