大きなファイルもChunked uploadできるFlow.jsを実装してみた（サーバ側はPHP）

# HTML5のFileAPIを使ったファイルアップロード

[Flow.js](https://github.com/flowjs)
A JavaScript library providing multiple simultaneous, stable, fault-tolerant and resumable/restartable file uploads via the HTML5 File API.


Flow.jsというJavaScriptライブラリを使って、簡単ですが、ファイルアップローダーを作成してみました。

- 大きなサイズのファイルを小分けにしてアップロードできるので
- アップロードの進捗を表示したり、
- 途中で停止、再開したり
- また、途中でブラウザを再読込するなどしても、途中からアップロードを再開できるレジューム機能などもできます。

今回は色々いじっていく前段階として、とにかくシンプルに実装して動きを見たかったので、バリデーションやセキュリティ面、またインストールに必要なPHPのComposerなど、他の要素を一切排したカタチで作ってみました。


## 前準備

### Flow.jsをダウンロード（※1）
Flow.jsをダウンロードします。https://github.com/flowjs/flow.js にソースがあるのでダウンロードします。本当はBowerでインストール推奨ですが、手動でZipをダウンロードして、src/の中にあるflow.jsだけあれば動きます。

### サーバ側のライブラリ（PHP）をダウンロード(※2)

サーバ側のPHPファイル(flow-php-server)をダウンロードします。
https://github.com/flowjs/flow-php-server
上記からダウンロードします。Composerで簡単にインストールできます。また、単体で動くようなので上記からZipファイルをダウンロードして、srcの中の「Flowディレクトリ」をそっくり取り出せば使えます。


### ファイル構成

とりあえず、BowerとかComposerを使わない手動の場合のファイル構成です。

**├　form.html** アップロード側のHTMLファイル
**├　upload.php** サーバ側のPHPファイル（アップロードされたファイルの結合や保存）
**├　js/**
**│　└　flow.js**  Flow.jsの本体　JSファイル
**├　Flow/** サーバ側のPHPファイル（上記※２）でダウンロードしたディレクトリ一式
**├　up/** アップロードされたファイルの保存場所
**└　chunks_temp_folder/**　アップロードされた小分けファイルの一時保存場所

とりあえずこんな構成で作っていきます。


## ローカル側のプログラム

アップロードする側の画面と、Flow.jsを動かすためのコードを書きます。

```html:form.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
  <script src="./js/flow.js"></script>
  <title>Flow.jsのテスト</title>
<style>
/*プログレスバーのスタイル（仮）*/
.progress{
  width:100%;
  border:1px solid #000;
  box-sizing: border-box;
  margin:30px 0;
}
.bar{
  height:30px;
  width:0;
  background:#ccc;
}
</style>
</head>
<body>

  <button id="browseButton">upload</button>
  <div class="progress"><div class="bar"></div></div>
  <div class="img-box"></div>

<script>
var flow = new Flow({
  target:'upload.php',
  chunkSize:1*1028*1028, //チャンクサイズ（小分けにするサイズです）
});
var flowfile= flow.files;

flow.assignBrowse(document.getElementById('browseButton'));
flow.on('fileSuccess', function(file,message){
  // アップロード完了したときの処理
  alert("アップロード完了");//今回はメッセージを表示します。
});
flow.on('filesSubmitted', function(file) {
  // アップロード実行
  flow.upload();
});
flow.on('progress',function(){
  //プログレスバーの実行
  //flow.progress() で進捗が取得できるのでそれを利用してプログレスバーを設定
  $('.bar').css({width:Math.floor(flow.progress()*100) + '%'});
});
flow.on('fileSuccess',function(file){
  // アップロードが完了したときの処理
  // ....
});
</script>


  <button onclick="flow.resume(); return(false);">再開</a>
  <button onclick="flow.pause(); return(false);">停止</a>

</body>
</html>
```

## サーバ側のプログラム

ファイルの送信先のプログラムです。PHPで書きます。
小分けで送られてくるファイルを受け取ってチェックして結合して、保存します。

```php:upload.php
<?php
require_once './Flow/Autoloader.php';
\Flow\Autoloader::register(); // Composerでインストールした場合は必要ありません。

$config = new \Flow\Config();
$config->setTempDir( dirname(__FILE__) . '/chunks_temp_folder'); //小分けファイルの一時保存先指定

$file = new \Flow\File($config);
$request = new \Flow\Request();

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    if ($file->checkChunk()) {
        header("HTTP/1.1 200 Ok");
    } else {
        header("HTTP/1.1 204 No Content");
        return ;
    }
} else {
  if ($file->validateChunk()) {
      $file->saveChunk();
  } else {
      // error, invalid chunk upload request, retry
      header("HTTP/1.1 400 Bad Request");
      return ;
  }
}
//ファイルが揃ったら結合して保存
if ($file->validateFile() && $file->save( dirname(__FILE__) . '/up/' . $request->getIdentifier() ) ) {
  // ファイルが全部アップロードされた後の処理

}else{
  // ファイルアップロード途中のときの処理

}
```

## 使ってみる

form.htmlを開いて、大きなファイルをアップロードしてみてください。
プログレスバーが動いて最後にアップロード完了のメッセージが表示されたら成功です。
up/ディレクトリにファイルがあるか確認してください。

環境によっては、数MB程度のファイルはあっという間にアップロードされてしまうのであまり動きが見れません。
そこで、form.htmlのchunkSizeを、10000とかの小さい数値にすると、ファイルの小分けサイズが細かくなるので、小さいファイルが順番にアップロードされていく過程を確認することができます。


### チャンク（小分け）の確認
大きなファイルをアップロードしたら、アップロード中に、chunks_temp_folderディレクトリを覗いてみると、小分けにしたファイルが保存されているのが見えると思います。

### レジュームの確認
ファイルのアップロード途中に、ブラウザを再読込するなどして中断させてみてください。
その後、同じファイルをアップロードすると、プログレスバーが先程進んでいた部分までササっと進み、そこからアップロード作業が再開されるのがわかると思います。



## 注意事項等

使い方とかを簡単に把握するために、非常にシンプルなコードで実装していますので、バリデーションとかセキュリティ関連とかは考慮していません。
必ず、ローカルなクローズドな環境で試してください。
公開環境でテストしたい場合は、公式のデモがありますので、そちらをおすすめします。
（公式のデモが結構つくりこんであってコードを追いにくかったので、あえて単純なものをつくってみたのが今回の経緯です）
また、PHPのライブラリも本当はComposerでインストールするのが正しいやり方ですが、とにかくシンプルに実装したかったので手動で実装しています。

※Flow.jsで検索すると、静的型付関連の記事がいっぱいでてくるので、ライブラリの名前変えたほうがいいよね。

## 参考サイト
[Flow.js公式サイト](https://github.com/flowjs)
[公式のデモ](http://flowjs.github.io/ng-flow/)
[Flow.js - 巨大なファイルもリジューム付きで簡単アップロード - moongift](http://www.moongift.jp/2014/03/flow-js-%E5%B7%A8%E5%A4%A7%E3%81%AA%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%82%E3%83%AA%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%A0%E4%BB%98%E3%81%8D%E3%81%A7%E7%B0%A1%E5%8D%98%E3%82%A2%E3%83%83%E3%83%97/)