【PHP】大きいサイズファイルのHTTPアップロード

##1.通常のやり方
1. php設定値を大きく設定して
  - upload_max_filesize = 500M
  - post_max_size = 500M
  - max_input_time = 300
  - max_execution_time = 300

2.Client-Side HTML. 
  enctype="multipart/form-data"とする。

    <!doctype html>
    <html lang="ja">
    <head>

       <meta charset="utf-8">
       <title>test upload</title>
    </head>
    <body>
    <form method="post" action="upload.php" enctype="multipart/form-data">
    ファイル:<input type="file" name="myfile"><br>
    <input type="submit" value="upload">
    </form>
    </body>
    </html>

3.Server-Side PHP

    if(is_uploaded_file($_FILES['myfile']['tmp_name'])){
        if(move_uploaded_file($_FILES['myfile']['tmp_name'],"./".$_FILES['myfile']['name'])){
            echo "upload successed.";

        }else{
            echo "error save file.";
        }

    }else{
        echo "error upload file.";
    }

    //laravel
    $dir_path = storage_path('app/debug');
    $file = Request::file('myfile');
    $name = $file->getClientOriginalName();
    $move = $file->move(dir_path,$name);

通常やり方の問題点

- PHPの色んな設定値を気にしないといけない。
- 場合によってHTTPサーバーの設定値も調整する必要がある。
  - Nginx:  client_max_body_size 100m;
  - Tomcat: maxPostSize="10485760"
  - Apache: LimitRequestBody=0
- ファイルサイズが数GBを超えると何れ何処の壁とぶつかる。

##2.ファイルを分割して複数回リクエストでアップロードする方法
###TUS resumable upload protocol
  https://tus.io/
  ファイルアップロード途中でブラウザを再読み込みするなどして、強制中断した後に、同じファイルをアップロードすると、中断時点からアップロード処理を再開することができます。
###Demo
https://tus.io/demo.html
###Tusとは
  tusは、再開可能なファイルのアップロードを簡単に使用でき、広く利用できるようにすることを目的としたプロジェクトです。
このプロジェクトの最も興味深い部分は、プロトコル仕様と多くの自由に利用可能なクライアントとサーバーの実装です。
###主な特徴

- HTTP-based 
  HTTPプロトコルの上にレイヤーとして構築されたtusは、既存のライブラリ、プロキシ、ファイアウォールを使用してアプリケーションに簡単に統合でき、任意のWebサイトから直接使用できます。

- Production-ready
    tusはプロダクション環境にも安心的に利用可能です。何度も改善され、Vimeo、Google、その他の複数の有名企業で働く人々から貴重なフィードバックを受けています。
- Open source
    MITライセンスでの利用が可能。https://github.com/tus
- Minimalistic design...
    ClientとServerに少ないコードを実装をするだけで利用可能。tusは、すべての人にシンプルさ、迅速な開発、イテレーション速度を提供します。
- ... yet still extensible
    tusには、並列アップロードやチェックサムと有効期限などの追加機能を導入する拡張機能のかなりのリストがあります。これらはすべて、好みに応じて実装できます。
- Community-owned
    このプロトコルのオープンでアクセス可能な進化を信じています。 tusの開始以降のどの段階でも、フィードバックは高く評価され、統合されています。あなたの貢献も楽しみにしています。

###Server-Side PHP
1.Installation 
  `$ composer require ankitpokhrel/tus-php`

2.Laravel Integration
新しいserviceProviderを作成する。
    
    namespace App\Providers;  
    use TusPhp\Tus\Server as TusServer; 
    use Illuminate\Support\ServiceProvider;  
    class TusServiceProvider extends ServiceProvider {     
      // ...      
      /**      
       * Register the application services.
       *       
       * @return void      
       */     
       public function register()
       {
         $this->app->singleton('tus-server', function ($app) {
             $server = new TusServer('redis');
             $server->setApiPath('/tus') // tus server endpoint.
                 ->setUploadDir(storage_path('app/public/uploads')); // uploads dir.
              return $server;
         });
       }
       // ...
     }
     
Routing設定
     
    Route::any('/tus/{any?}', function () {
      $response = app('tus-server')->serve();
      return $response->send(); 
    })->where('any', '.*');
     
CSRF validationを除外する

    // ... 
    class VerifyCsrfToken extends BaseVerifier {
      // ...
      protected $except = [
         '/tus',
         '/tus/*',
      ];
    }
     

###Client-Side
1.Vue 版
`$ npm install tus-js-client`

      <template>
      ... ...
        <input type="file" name="largefile" @change="fileSelected">
        <button-submit class="btn btn-adn" :onclick="tusUpload"><span>ファイルアップロード</span></button-submit>
      ... ...
      </template>
      <script>
        import * as Tus from 'tus-js-client'
        export default {
          data: function () {
              return {
                  fileInfo: '',
              }
          },
          methods: {
              fileSelected: function (e) {
                  this.fileInfo = e.target.files[0];
              },
              tusUpload: function (e) {
                e.preventDefault();
                  return new Promise((resolve) => {
                      // Get the selected file from the input element
                      var file = this.fileInfo;
                       // Create a new tus upload
                      var upload = new Tus.Upload(file, {
                          endpoint: "/tus",
                          retryDelays: [0, 3000, 5000, 10000, 20000],
                          chunkSize: 1024 ** 1024 ** 1,
                          metadata: {
                              filename: file.name,
                              filetype: file.type
                          },
                          onError: function(error) {
                              console.log("Failed because: " + error);
                          },
                          onProgress: function(bytesUploaded, bytesTotal) {
                              var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
                              console.log(bytesUploaded, bytesTotal, percentage + "%");
                          },
                          onSuccess: function() {
                              console.log("Download %s from %s", upload.file.name, upload.url);
                          }
                      })
                       // Start the upload
                      upload.start();
                      resolve();
                  });
              }
          }
      }
  </script>
     
2.JS版

      input.addEventListener("change", function(e) {
        // Get the selected file from the input element
        var file = e.target.files[0]
        // Create a new tus upload
        var upload = new tus.Upload(file, {
          endpoint: "/tus",
          retryDelays: [0, 3000, 5000, 10000, 20000],
          metadata: {
              filename: file.name,
              filetype: file.type
          },
          onError: function(error) {
              console.log("Failed because: " + error)
          },
          onProgress: function(bytesUploaded, bytesTotal) {
              var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2)
              console.log(bytesUploaded, bytesTotal, percentage + "%")
          },
          onSuccess: function() {
              console.log("Download %s from %s", upload.file.name, upload.url)
          }
        })
        // Start the upload
        upload.start()
      })
     
3.進捗を見える化
  onProgressのeventを利用して画面側にプログレスバーの表示は可能です。
     

##参考URL
  https://tus.io/
  https://github.com/ankitpokhrel/tus-php
  https://github.com/tus/tus-js-client

以上