ブラウザでサーバー側処理進捗のリアルタイム表示 (プログレスバーの実装)

#概要
##説明
タイトルが怪しく、分かりにくいかもしれませんが、簡単に言うとサーバー側で行うバッチ処理の進捗をプログレスバーの形でリアルタイムで表示するといった機能の実装です。　やり方については、ググッてみると色々と方法があるらしいですが、そのうち、今回の僕のような実装方法はなかったので一応ポスティングして置きます。

##処理の流れ
- ブラウザ: **パラメータ入力** (使用言語: php)
- サーバー:
    - **管理スクリプトの起動** (使用言語: python)
        - バッチ処理スクリプトとモニタリングスクリプトを起動
        - モニタリング結果をプログレスバーに渡す
    -  **バッチ・スクリプト** (使用言語: shell)
        -  ブラウザからのパラメータにより、バッチ処理を行う
    -  **モニタリング・スクリプト** (使用言語: shell)
        -  バッチ処理で生じたログファイルをリアルタイムで監視し、進捗状況を出力
    - **プログレスバーの生成、表示** (使用言語: php, javascript)
        - モニタリング・スクリプトの結果をリアルタイムで取得し、プログレスバーで表示。まだ、処理結果を管理人にメールでお知らせする。  
     
#実装
##ブラウザ: パラメータ入力部分
今回は商品のISBN入力が必要だったので、ISBN番号を入力し、postの形式でサーバーに送ります。

```php:convert.php
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Convert WAV File!</title>
</head>
<body>
<form action="result.php" method="post">
  <p><label>ISBN (Numeric Only) : <input type="number" name="isbn"></label></p>
  <p><input type="submit" value="Submit"></p>
</form>
</body>
</html>
```

##サーバー:管理スクリプト部分
サーバー側では、ブラウザ側から得たパラメータにより、バッチ処理を行うと同時に、その状況を把握するためのモニタリングスクリプトが同時に動く必要があります。　そのため、バッチ処理とモニタリング二つのスクリプトを並列に起動させることになります。

注意点：　バッチ処理が終わった時に、モニタリング・スクリプトを終了させるために、　バッチ処理プロセスのIDをモニタリング・スクリプトに渡すことで、バッチ処理の状態を知らせます。

```python:process.py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
 
import os, sys, subprocess, shlex
from multiprocessing import Pool, Process, Queue

def f(logPID, q, scriptName, isbn):
	q.put([os.getpid()])
	os.system("sh "+scriptName+".sh "+logPID + " " + isbn)

def main():
	jobs = []
	q = Queue()

	isbn = sys.argv[1] 
	
	#バッチ処理部分
	mainJob = Process(target=f, args=("",q,"test", isbn, ))
	jobs.append(mainJob)
	mainJob.start()

	logPID = str(q.get()[0])
        
	#モニタリング部分
	monitorJob = Process(target=f, args=(logPID,q,"monitor", "", ))
	jobs.append(monitorJob)
	monitorJob.start()
		
	for job in jobs:
		job.join()

main()
```

##サーバー: バッチ処理
元々このプログラムの目的は、ISBN情報により、バッチ処理を行うことですが、　今回はテストケースとして、一定時間間隔で処理進捗を数値として出力するスクリプトで代替します。

```shell:test.sh
#!/bin/sh

isbn=$1

touch log.txt
for i in 1 2 3 4 5 6 7 8 9 10; do
   sleep 2
  echo $i >> log.txt
done
```

##サーバー:　　モニタリング処理
モニタリングスクリプトでは、```tail```コマンドを使って、ログファイルに追加された行をリアルタイムで取得し、分析します。　今回は、バッチ処理で進捗状況を直接数字として出力するので、何の処理もしていませんが、実際にはログファイルに進捗状況はもちろん様々な情報が混ざっているため、モニタリングスクリプトで進捗状況のみ抽出する処理が必要になります。

注意点： ```tail```コマンドにバッチ処理プロセスIDを加えることで、バッチ処理が終わった時点で、モニタリングスクリプトを終了させることが可能になります。

```shell:monitor.sh
#!/bin/sh

pID=$1 #バッチ処理プロセスID
tail -f -n 1 --pid=$pID log.txt | while read line
do 
  if [ -n "$line" ] ; then 
     echo $line
  fi
done
```

##サーバー:　 プログレスバーの表示
モニタリング結果をリアルタイムで取得し、プログレスバーで表示します。　最後に、　処理済みのことを管理者にメールでおしらせします。

注意点: phpでpythonの処理結果をリアルタイムで取得するために、```popen```コマンドを使う。

```php:result.php
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>Progress Bar</title>
</head>
<body>
<!-- Progress bar holder -->
<div id="progress" style="width:500px;border:1px solid #ccc;"></div>
<!-- Progress information -->
<div id="information" style="width"></div>
<?php
$isbn = $_POST['isbn']; 
echo "ISBN: ".$isbn;
// Total processes
$total = 10;
// Loop through process
$cmd = "python /var/www/html/service/progressbar/process.py ".$isbn;
$proc = popen($cmd, 'r');
//rea one line of the last oparation, and do nothing
$i = (int) fread($proc, 4096);
while (!feof($proc)) {
    // Calculate the percentation
	$i = (int) fread($proc, 4096);
    $percent = intval($i/$total * 100)."%";    
	// Javascript for updating the progress bar and information
    echo '<script language="javascript">
    document.getElementById("progress").innerHTML="<div style=\"width:'.$percent.';background-color:#ddd;\">&nbsp;</div>";
    document.getElementById("information").innerHTML="'.$percent.' processed.";
    </script>';
	// Send output to browser immediately
    flush();
	// Sleep one second so we can see the delay
    sleep(1);
	
	if($percent=="100%")
		break;
}
// Tell user that the process is completed
    echo '<script language="javascript">
    document.getElementById("information").innerHTML="Process completed!";
    </script>';

//send mail when the process is over
    $to = "admin@hoge.co.jp";
    $subject = "バッチ処理済み";
    $message = "Adminさん、\n    商品番号: 「".$isbn."」処理済みです。 \n    宜しくお願い致します。 ";
    $headers = 'From: from@hoge.co.jp' . "\r\n";
    if(mail($to, $subject, $message, $headers))
        echo "通知メール送信済み！";
    else
        echo "通知メール送信できませんでした。\n 直接Adminさんに商品番号をお知らせください。"
?>
</body>
</html>
```

#備考
このプログラムのソースコードは以下のリンクからも取得可能です。
[プログレスバーの実装](https://github.com/donaldchi/progressbar)