Ajax から PHP を呼び出すとき、
普通の方法だと1回ぽっきりのレスポンスしか受け取れません。
でも次みたいな場合、リアルタイムにレスポンスを受け取りたいです。
- データを常に画面上で更新したい場合
- Twitterタイムライン的なのを作る場合
- とにかくリアルタイム通信したい場合
僕もあるWebアプリを作っていて、リアルタイムレスポンスが必要でした。
ここでは自分の記憶の整理も兼ね、
PHPでストリーム出力して Ajax でリアルタイム更新する方法 を紹介します。
ストリーム出力の基本さえ知っていれば、意外と簡単にできます。
手順0.PHP + Ajax でリアルタイム更新する基本の流れ
リアルタイム更新をする処理、これは2つの部分に分けられます。
- サーバー側(データを更新する)
- ブラウザ側(データを受け取る)
サーバー側では PHP を使ってストリーム出力、
ブラウザ側では Ajax を使ってそれを受け取るみたい流れ
ここではリアルタイム更新の例として、 1.PHPから現在時刻をレスポンスとして返す ⇒ 2.ブラウザ側でデータを受け取る ⇒ 3.コンソールに表示 みたいなコードを書いてみます。
手順1.PHP側でストリーム出力するコードを書く
まずはリアルタイム更新するPHPコードの書き方について
大事なポイントは次の3つ
1.
Transfer-Encoding: chuncked をヘッダーに指定
2. データをそれぞれのチャンク(塊)に分けて出力
3. データ出力後に
ob_flush と
flush 実行
言葉だと分かりにくいので、実際のコードを見た方が分かりやすいです。
例えば次がそのコード例、
1秒おきにレスポンスを返すコードを書いてみました。
▼ stream.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 |
/// ストリーム出力するためのヘッダー設定 header("Content-type: application/octet-stream"); header("Transfer-encoding: chunked"); ob_flush(); flush(); /// 最大実行時間の設定(仮で1時間) set_time_limit(60*60); $counter = 0; /// 通信終了まで延々とレスポンスを返す while ( !connection_aborted() ) { /// レスポンスするデータ $date = date_create(); $streamed_on = date_format( $date, 'Y-m-d-H-i-s'); /// データをJSON化する $json = json_encode([ 'streamed_on' => $streamed_on, 'count' => ++$counter ]); /// データをブラウザ側に掃き出し output_chunk( $json."\n" ); ob_flush(); flush(); /// 1秒だけ待つ(この部分はなくてもOK) sleep(1); } /// ストリーム出力終了 output_chunk( '' ); /// チャンク化されたデータを掃き出す function output_chunk($chunk) { echo sprintf("%x\r\n", strlen($chunk)); echo $chunk."\r\n"; } |
最初にも書いたように、最重要なのが3つのポイント
- ヘッダーの指定
データを塊で出力できるように Transfer-encoding: chunked を指定してる。
あと Content-type に対して application/octet-stream を指定 - データをチャンクに分けて出力
コード内の output_chunk() という関数。ここでチャンクの長さとチャンクデータ自体を出力してる(必ず改行が必要)
- データ出力後に ob_flush と flush 実行
これをしないとブラウザ側でリアルタイムに受け取れないので注意!!
あと途中でタイムアウトしないように、
set_time_limit も設定してます。
ここでは1時間にしてますが、必要ならもっと長く設定しておけばOK( 0 で無限)
手順2.Ajax側からリアルタイム出力を受け取る
次に Ajax から PHP を呼び出し、リアルタイムでレスポンス受け取り
ここでは次の3ステップで受け取ることにします。
- まず Ajax から PHP を呼び出す
- 定期的にレスポンスを監視
- レスポンスに変化があれば表示する
上の手順でAjaxからリクエストを送るのは1回だけ。
それ以降は
setInterval を使って変化がないかどうか監視するだけです。
実際にリアルタイム更新するコード例は次の通りです。
▼ ストリーム出力を受け取り、表示するコード例
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 42 43 44 45 |
var timer = null; $.ajax({ url: '/stream.php', type: 'POST', cache: false, xhrFields: { onloadstart: function(){ var xhr = this; var resTextLen = 0; timer = setInterval(function(){ /// レスポンス全体のテキスト var resText = xhr.responseText; /// 新たに追加されたレスポンスだけ切り取り var newResText = resText.substring(resTextLen); /// 更新部分があるならデータ表示 if(resText.length > resTextLen){ newResText.split("\n").forEach(function(line){ var json = []; try { json = JSON.parse(line); }catch(e){ //console.error('Failed to parse JSON'); return; } resTextLen = resText.length; /// 更新部分だけをコンソール表示 console.log('json : ', json); }, 100); } }); } }, success: function(){ /// 全部終わったらタイマー解除 setTimeout(function(){ clearInterval(timer); }, 1000); }, error: function(){ /// エラーが起きてもタイマー解除 clearInterval(timer); } }); |
何をしてるかはコード内のコメント参照
あと大事な所だけ箇条書きすると、こんな感じです。
- Ajax通信開始したかを監視する
この監視には xhrFields の onloadstart イベントを使っている。Ajax通信が始まったらリアルタイム処理を連続で受け取る処理を開始する
- タイマーでレスポンス開始
ここでは1秒ごとにレスポンスに変化がないか監視している。もし変化があれば、
差分を切り出してコンソール表示している
他にも大事なところはあるけど、最重要なのがこの2点ですね。
Ajax通信を行うのは1回だけで、それ以降はレスポンスが延々と受け取れます。
ここまでのまとめ
以上、PHP+Ajaxでリアルタイム更新する方法でした。
Twitter的なタイムラインを作ったり、リアルタイム更新の掲示板を作ったり・・・
色々な場面で役立ちそうなテクニックです。
でもやり方が特殊なので、忘れないようにメモしておきます。ではまた(^^)/~~~バイバイ