cgiで時間のかかる処理をjavascriptのfetch非同期で受ける。〜プログレスバーの実装の準備〜

# 目的
- 時間のかかるサーバの処理の状態をWebブラウザでプログレスバーとして表示したいです。
- fetchで非同期で受けられそうなので、この記事では時間のかかるcgi処理を模擬してそれをjavascriptで受けられるかの実験です。

# 環境
- macOS Mojave
- apache version

```
$  httpd -v
Server version: Apache/2.4.46 (Unix)
Server built:   Aug  7 2020 12:58:18
```

- web browser : Firfox (macOS)

# 参考
参考にさせていただきました資料です。いつも先人たちの知恵に感謝です。ありがとうございます。

- [まだXMLHttpRequestを使ってるの？　fetchのすすめ](https://qiita.com/uhyo/items/91649e260165b35fecd7)←（ほぼこの使い方です）
- [JavaScript、Node.js で文字列とバイト列の相互変換](https://qiita.com/masakielastic/items/8eb4bf4efc2310ee7baf)
- [サーバーでの重い処理の経過をリアルタイムに通知する](https://blog.mudatobunka.org/entry/2015/08/30/195802) （←これは使っていません）


# 時間のかかるcgiの準備
- sleepで仮想的に時間を遅らせてそれを単純にカウントアップさせます
- バックグランド処理でも可能か試します。

### cgiの準備
```{test_fetch_stream.cgi}
#!/usr/bin/env bash
# 結果の出力
echo "Content-type: text/plain; charset=UTF-8;"
echo ""
echo "counter kicked."
for i in {1..5}; do 
	sleep 1; 
	echo $i; 
done
/var/www/cgi/test/count.bash &
exit 0
```

- まずHTTPにそって`Content-type:`〜を記述します。
- このcgiはメッセージとそれに続く1秒ごとの１から５のテキストの数字を返します。
- その後、重い処理を模した子プロセスをバックグランドで起動します。

### 子プロセスの準備

```{/var/www/cgi/test/count.bash}
#!/usr/bin/env bash
for i in {100..105}; do 
	sleep 20; 
	echo $i; 
done
exit 0
```
- 表示はテキストで100から105まで表示します。
- 重い処理の時間を`sleep`で模擬します。

### htmlの準備
```{/var/www/html/test/test_fetch_stream.html}
<!DOCTYPE html>
<html>
    <head>
     <meta charset="UTF-8" />
     <title>JavaScript test fetch</title>
    </head>
    <body>
        <h1>
            test fetch steam
        </h1>
        <button id="btn">
            fetch
        </button>
        server responce = 
         <span id="result"></span>
         <script>
            let str = new TextDecoder;
             document.addEventListener('DOMContentLoaded', function () {
                 document.getElementById('btn').addEventListener('click', function () {
                    const url = "/cgi/test/test_fetch_stream.cgi";
                     fetch(url)
                        .then(async response => {
                            console.log(response.status);
                          // response.body にレスポンス本文のストリーム（ReadableStream）が入っている
                          const reader = response.body.getReader();
                          while (true) {
                                // ストリームからデータを読む
                                const {done, value} = await reader.read();
                                const res = str.decode(new Uint8Array(value));
                                console.log(done, res);
                                document.getElementById('result').textContent =  res;
                                if (done) {
                                  // doneがtrueならストリームのデータを全部読み終わった
                                    document.getElementById('result').textContent =  "done";
                                  break;
                                }
                          }
                    })
                }, false);
             }, false);
        </script>
    </body>
</html>
```
- fetch(url)でcgiを呼び出します。
- response.body にレスポンス本文のストリーム（ReadableStream）が入っています。
- 終了したかの判定は変数`done`に`await`で簡略表現して`read()`します。この辺りは参考文献の通りです。
- 例外処理は`try{} catch{}`で[async await の使い方](https://qiita.com/niusounds/items/37c1f9b021b62194e077)など参照してみてください。
- ボタン`fetch`を2回押すと２つのcgiが起動され、それぞれからの結果を受けるようです。cgiで複数の起動を禁止するとか処理が必要そうです。
- それでブラウザはどのくらい待てるのか（タイムアウト）が問題になりますが、30秒（`sleep 30`）はOKでした。`sleep 60`ではNGでした。子コマンドも10秒ぐらいで何か返せば小手先の対応はできそうです。
- 

### 画面

![スクリーンショット 2020-12-30 10.24.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178143/6c403055-c115-0229-6f3d-0d445196cbc4.png)