PHP出力バッファで進捗管理

∠( ゚Д゚)/イェェガァァ

どうもメガネです。

実行中のプログラムの進捗をクライアントで確認できるようにする仕組みをPHPで作成してみました。
とても簡易的なものなので汎用性とかあんま考えてないですw
最近ではフレームワークを使えば簡単に実装できる機能ではありますが、APCを使ってたり、細かいカスタマイズができそうにないので自作することにしました。
今回はリクエスト数を減らすという目的だけで ob_start() と ob_flush() を使って処理を書きました。

ob_start ob_flushってなんぞ?

出力されるはずのデータを一旦内部バッファにためこむことができる関数。

何が便利なの?

これを使うと、echoで出力したデータを変数に代入したり、後の条件で出力させなかったりできます

例えば、以下のファイルのバッファリングをコントロールしてみます。

出力バッファリング無

1
2
3
4
<?php
echo 'Hello ' . $test_text . '!';

$test_text = 'world'; // テンプレート内で使う変数

出力結果:

1
Hello !

出力バッファリング有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
ob_start(); // 自動出力無効

echo 'Hello ' . $test_text . '!';

$test_text = 'world'; // テンプレート内で使う変数

$body = ob_get_contents(); // 出力内容を変数にいれる

// 本来の出力内容(3行目のechoによる出力)を削除
// ※これやらないと二回出力されちゃう
ob_end_clean();

echo $body; // 出力

出力結果:

1
Hello world!

出力のタイミングをプログラムで制御することが可能になる。
今回はこの関数を使って出力バッファを断片的にクライアントに送信して、結果を逐一jsで処理して進捗状況をブラウザに表示してやろうぜってとこまでやってみる。

javascriptで進捗状況のイベントをハンドリングする

ファイル名: index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>

<!-- フォームの送信先をiframe宛てにし、iframe内のからwindow.ProgressBarが呼ばれる -->
<form action="progress.php" enctype="multipart/form-data" method="post" target="progress">
<input id="file1" type="file" name="file1" />
<input type="submit" value="upload" />
<span id="progress">0%</span>
</form>

<iframe style="display: none;" name="progress" height="240" width="320"></iframe>

<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script>
var progress = document.getElementById('progress');
window.ProgressBar = {
// 進捗が更新されるたびに実行したいメソッド
update: function (percent) {
progress.text(percent + '%');
},
// 完了時に実行したいメソッド
successe: function () {
},
// 失敗時に実行したいメソッド
error: function (error_message) {
alert(error_message);
},
// キャンセル時に実行したいメソッド
cancel: function () {
}
};
</script>
</body>
</html>

ファイル名: progress.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<?php
try {
if (!isset($_FILES["upfile"]) || !is_uploaded_file($_FILES["upfile"]["tmp_name"])) {
throw new Exception('アップロード失敗。');
}
$handle = fopen($_FILES["upfile"]["tmp_name"], "rb+");
$proc_length = 0;
while (!feof($handle)) {
// 通信が切断されていたら処理を中断
if (connection_aborted()) {
echo '<script>'
echo 'parent.ProgressBar.cancel();';
echo '</script>';
break;
}
// ファイルの読み込み
$contents = fread($handle, 8192);
$proc_length += strlen($contents);
$data = '<script>';
$data = 'parent.ProgressBar.update(\'' . $proc_length . '\');'
$data = '</script>';
// ブラウザに応答を出力させる
flush();
ob_flush();
sleep(1);
}
} catch (Exception $e) {
echo '<script type="text/javascript">';
echo 'parent.ProgressBar.error("' . $e->getMessage() . '");';
echo '</script>';
}
fclose($handle);
?>
</body>
</html>

考察
リクエスト数は減るが、コネクションが専有されてしまうのであまり使わないほうがいいかも。
プログラムの実行状況の管理にAPCを使わないのであれば、代わりにファイルもしくはDBで進捗理するようにして、定期的にクライアントから非同期で実行状況を確認しにいくような仕組みのほうがいいですね。

 

Please share  

開眼!JavaScript を読んでみた!

最近イメチェンをして襟足がなくなってしまったメガネです。

先日会社で隣に座っている社員さんから、JavaScriptの本を借りて読んでみたら、目から鱗だったのでざっくり感想を書いてみる。

最初に感想をいってしまうと、この本はJavaScriptの暗黙な部分を分かりやすく説明してくれてるなーって感じがしました。

1.javascriptは全てがオブジェクトではなかった!

javascriptで文字列を扱いたい場合は以下のような書き方があります。
Stringオブジェクト変数と文字列リテラル変数で動作の違いを検証したいと思います。

1
2
3
4
5
// Stringオブジェクト  
var str1 = new String("Hello World");

// 文字列リテラル
var str2 = "Hello World";

検証1. 変数の型を出力する

1
console.log(typeof str1);

出力結果:
object

1
console.log(typeof str2);

出力結果:
string

まぁ、ここまではなんとなく理解できる。

検証2. lengthメソッドを使って文字列の長さを取得する

1
console.log(str1.length);

出力結果:
11

これもオブジェクトのメソッドが使われてんだなーと、まぁわかる。

1
console.log(str2.length);

出力結果:
11

おや?あれ?str2はstringなのでプリミティブ型※1のはず。。。
length以外にもStringオブジェクトのメソッド全部使えちゃうけどなんでたー(?_?)

※1. プリミティブ型: 数字とか文字とかの基本的な値

以下サイトから引用

文字列リテラルのメソッド

文字列リテラルのメソッドを呼び出すと、一時的に文字列のラッパー オブジェクトに変換されます。 文字列リテラルは、その作成に new 演算子が使用されているかのように扱われます。

つまり、実行時にオブジェクトがつくられて、実行が終わったらオブジェクトは破棄されるってことなのかー。

javascriptは全てがオブジェクトではなく、オブジェクトのように振る舞うことができる言語なのね!!

2.プリミティブ型とオブジェクトの違い

比較
プリミティブ: 値を比較
オブジェクト: 参照を比較

1
2
3
4
var num1 = new Number(10);
var num2 = new Number(10);

console.log(num1 == num2);

出力結果:
false

1
2
3
4
5
6
var num1 = new Number(10);
var num2 = new Number(5);

num2 = num1;

console.log(num1 == num2);

出力結果:
true

値が違くてもオブジェクト型は参照を比較するので結果はtrueになる

3.所感

もっとjavascriptの本質に触れたい!という方は必読です。
本自体は薄いですが、かなり内容が濃くなっています。

apacheのKeepAliveをそれなりに考えてみた

5年以上apahce使ってるけどKeepAliveの設定ってあまり気にすることなかった。ていうか、そこまで巨大なサービスを運用する機会がなかったからとりえずonにしとけばいいんじゃね!?ぐらいなもんではい。。。

では、実際になぜKeepAliveが必要なのかhttpの仕様をみながらお話していきます。

注)KeepAliveはHTTP/1.1より実装された機能なので以下はHTTP/1.1を前提にお話いたします。 apacheをスレッドベースで動作させる方法もありますが、プロセスベースで検証します。

httpはステートレス・プロトコル

ステートレス・プロトコル(stateless protocol)とは状態を持たないプロトコルのことで、必要なレスポンスをかえしたら即時に切断される。サーバ側はどこの誰がリクエストしているかも知らないし、クライアントがどういう状態かもわからない。

つまり一人の人が100回アクセスしてこようがサーバー側では100回違う人がアクセスしてきてるという認識だ。

逆に状態を持つプロトコルでステートフル・プロトコル(stateful protocol)というものもある。
例えば、FTPは任意のホスト間のファイル転送を行いますが、クライアントからの接続要求によって通信が開始され、クライアントから明示的に切断要求がない限り通信状態が保持される。
一度接続を確立すると「あのファイルが欲しい」「このファイルをアップロードしたい」「ファイルを削除したい」等の処理を対話的に行う。
特定少数のユーザーしか使わないのに、通信のリクエストが入る度に認証処理をしてたら効率が悪いですね。

「えぇーーでもECサイトとかだとログイン状態保持してんじゃん!」

それは毎回クライアントから私こういうものですけどーっていうID(SESSION ID)を渡しつづけているからなのです。
そのIDをもとにサーバー側であー○○さんね!
てなかんじでリクエストの度にクライアントからIDを渡すことで、擬似的にステートフルなサービスを実現している。

なぜhttpはステートレスなのか?

ステートフルはクライアントが明示的に切断しないと接続が残りつづけるので、webページみたいな不特定多数からのアクセスに対しては不向きだ。 仮にhttpがステートフルだったら100万アクセスあったら100万個のコネクションを維持しないといけなくなる。。。 これではリクエストをさばききれないので、うけたらさっさと切断してしまったほうがいいわけです(笑)

実際のお店で例えてみる

webページってcssとかjsとか画像とか1ページ読み込むだけでたくさん通信してるし、ECサイトなんかだと購入途中でつながりにくくなるのも嫌だからやっぱりある程度接続しててもらったほうがいいんじゃない?

実際にお店で買い物をした場合で例えてみよう。
大行列の10人はいるお店で何かものを買おうとしたとき、商品を手にするたびに最後尾から並びなおすということがおきたらどうだろう?
買う気失せますね(笑)

そこで一時的にコネクションを維持しといてあげるよっていうのがKeepAliveだ。

○KeepAliveの設定項目
・KeepAlive On
接続を維持するかどうか

・MaxKeepAliveRequests 100
接続してから切断するまでに受け付けるリクエストの数
(商品100個とったら強制退出させるわ数)

・KeepAliveTimeout 15
接続しているセッションからのリクエストが来なくなってから切断するまでの待ち時間
(最後に商品を手にしてから15秒たったら強制退出させるわ秒数)

通信の状態を確認

実際にKeepAliveを設定した場合にサーバー側でコネクションが維持されているのかを確認したいと思う。

○検証内容

・画像(aa.jpg)を一つだけ埋め込んだindex.html にアクセスし、index.htmlの読み込みに1通信、画像の読み込みに1通信、計2通信を発生させKeepAliveがOnの時にコネクションが使い回されているかどうか確認する。

※KeepAliveの設定は上記であげているがここでは純粋にコネクションの動きだけを見たいので、KeepAliveのOnとOffだけで違いをみることにします。
※apacheの設定のみでKeepAliveを無効にできない(1つのコネクションでいくつリクエストを処理するかはブラウザ依存のため)ため、curlにて検証

■KeepAlive Off

【netstat実行結果】

1
2
3
Proto Recv-Q Send-Q Local Address Foreign Address State  
tcp 0 0 ::ffff:192.168.0.3:http XXXX.XXXX.XXXX.XXXX:62207 TIME_WAIT
tcp 0 0 ::ffff:192.168.0.3:http XXXX.XXXX.XXXX.XXXX:62209 TIME_WAIT

【パケットキャプチャ】

■KeepAlive On

【netstat実行結果】

1
2
Proto Recv-Q Send-Q Local Address Foreign Address State  
tcp 0 0 ::ffff:192.168.0.3:http XXXX.XXXX.XXXX.XXXX:62134 ESTABLISHED

【パケットキャプチャ】

上記の結果からKeepAlive Onの時はTCPコネクションが再利用され、KeepAlive Offの場合は通信ごとにTCPコネクションがはられていることがわかる。

所感

1ページあたりの読み込むファイル数が多いページは リクエスト可能回数(MaxKeepAliveRequests)、コネクションを維持する秒数(KeepAliveTimeout)を少し長目にとるとかするといいのかも。 設定はサイトによって柔軟に変更するといいと思います。