--- title: Server-Sent Events(SSE)をPHPで使うときの自分用まとめ tags: SSE Server-Sent-Events PHP nginx JavaScript author: okumurakengo slide: false --- SSEを自分なりに調べました、間違いなどありましたらご指摘お願いいたします:bow: # Server-Sent Events とは - [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html) Google翻訳↓ > この仕様は、DOMイベントの形式でサーバーからプッシュ通知を受信するためのHTTP接続を開くためのAPIを定義します。 このAPIは、Push SMSなどの他のプッシュ通知スキームと連携するように拡張できるように設計されています。 よくwebsocketと比較される。websocketはブラウザとサーバーで双方向通信できるが、SSEはサーバーからブラウザへの一方向の通信。 # SSEのすぐ動かせるサンプル - [Using server-sent events - Web APIs | MDN](https://developer.mozilla.org/ja/docs/Server-sent_events/Using_server-sent_events) ↑のページのコードを参考にさせていただきました ```php:events.php (new DateTime('now', new DateTimeZone('Asia/Tokyo')))->format('H:i:s'), 'word' => 'abcあいう😀😁😂', ])); ob_end_flush(); flush(); sleep(1); } ``` ```html:index.html Document ``` ![jP6D7HvW9e.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/15627b1b-f394-0b4d-3b1d-4132af71155e.gif) このとき、events.phpは以下のように文字列を返しています ![Screen Shot 2019-02-03 at 6.05.35.png](https://qiita-image-store.s3.amazonaws.com/0/142910/91c675b7-3389-c75d-a1ac-045b35ddc0c3.png) # フィールドごとに調べたこと `data`、`event`、`id`、`retry` のそれぞれのフィールドについて調べました ## data 基本的に送信するデータは、`'data: ホゲホゲ'`という文字列を`echo`、`print`などで出力すると良い、javascriptで`message`イベントで`e.data`から`ホゲホゲ`という文字列を取得できる ```php { console.log(e.data); // ホゲホゲ }); ``` --- `events.php`の出力内容が、空白行ごとに区切られてjsのmessageイベントが実行されていきます。送信したい単位で1行空白行を入れることを忘れないようにしましょう。 例1 ```:events.phpのレスポンス内容 data: ホゲホゲ data: フガフガ data: ピヨピヨ ``` ↑だと `message` イベントは3回実行されることになります --- 例2 空白行を入れずに複数行データを送信することもできます ```:events.phpのレスポンス内容 data: ホゲホゲ data: フガフガ data: ピヨピヨ ``` ↑だと`ホゲホゲ`と`フガフガ\nピヨピヨ`で `message` イベントは2回実行されます ### NG例 - 大文字小文字は区別される ```php:php 1, 'fuga' => 'A', 'piyo' => false, ])); ``` ```javascript:javascript const es = new EventSource('./events.php'); es.addEventListener('message', e => { ({hoge, fuga, piyo} = JSON.parse(e.data)); console.log(hoge, fuga, piyo); // 1 "A" false }); ``` ### JSONをプリティプリントする 送信するJSONの文字列が長くなってしまうとデバッグがしずらくなってしまいます ```php { console.log('message -> '+e.data); // ホゲ }); es.addEventListener('add', e => { console.log('add -> '+e.data); // 追加 }); es.addEventListener('update', e => { console.log('update -> '+e.data); // 更新 }); ``` ![Screen Shot 2019-02-03 at 7.22.37.png](https://qiita-image-store.s3.amazonaws.com/0/142910/192bbcf7-d3d5-f0cf-7680-9a928502d456.png) ## id `data`と一緒に一意のIDの文字列を送ることができます。 `e.lastEventId`で取得できるようでした。 ```js:js const es = new EventSource('./events.php'); es.addEventListener('message', e => { console.log(e.lastEventId, e.data); }); ``` ```php:php { console.log('open'); }); ``` # エラー処理 `error`イベントで、接続できなかった場合、接続が途切れた場合などの処理を書ける ```js:js const es = new EventSource('./events.php'); es.addEventListener('error', e => { console.log('error'); es.close(); // エラーが起きても再接続する必要がない場合はclose()を実行する }) ``` --- phpの方でも接続が切れた場合の処理を作成しみる ```php '+es.readyState); es.addEventListener('open', e => { console.log('open -> '+es.readyState); }); es.addEventListener('message', e => { console.log('message -> '+es.readyState); }); es.addEventListener('error', e => { console.log('error -> '+es.readyState); es.close(); console.log('error -> '+es.readyState); }) ``` 以下は一度接続し、接続中にサーバをシャットダウンした時の動作です。`es.readyState`で正しく状態が取得できるのが確認できました。 ![Screen Shot 2019-02-03 at 9.59.58.png](https://qiita-image-store.s3.amazonaws.com/0/142910/bc79ae29-d54c-5035-75d6-eb3d09cd1b39.png) # クロスオリジン ajaxと同じです。 urlを別のドメインに設定すると以下のようにエラーになります ![Screen Shot 2019-02-03 at 10.47.40.png](https://qiita-image-store.s3.amazonaws.com/0/142910/eaef6a4c-9052-89c4-2016-a6b4e58847d1.png) あまりやったことがないので、以下を参考にしつつ行いました。 間違っていたらご指摘お願いいたしますm(_ _)m - [CORSリクエストでクレデンシャル(≒クッキー)を必要とする場合の注意点 - Qiita](https://qiita.com/kawaz/items/1e51c374b7a13c21b7e2) - [jquery - CORS not working php - Stack Overflow](https://stackoverflow.com/questions/18382740/cors-not-working-php) - [php - While loops for server-sent events are causing page to freeze - Stack Overflow](https://stackoverflow.com/questions/29480791/while-loops-for-server-sent-events-are-causing-page-to-freeze/) 別ドメインのphpを読み込む ```js:js // http://192.168.33.10 から http://localhost:8888 を読み込みに行く const es = new EventSource('http://localhost:8888/events.php'); es.addEventListener('message', e => { console.log(e.data); }); ``` ```php:http://localhost:8888/events.php { console.log(e.data); }); ``` ```php:http://localhost:8888/events.php (new DateTime())->format(DateTime::RFC3339), ])); ob_end_flush(); flush(); sleep(1); } ``` `X-Accel-Buffering: no`を追加するとうまく行きました ```diff (new DateTime())->format(DateTime::RFC3339), ])); ob_end_flush(); flush(); sleep(1); } ``` ![c4zW9j3qGT.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/c2724607-ef35-92b3-fc18-c4a606143590.gif) --- nginxの設定ファイルをいじるという方法でも大丈夫なようです - [How do I enable PHP’s flush() with nginx+PHP-FPM? - Server Fault](https://serverfault.com/questions/488767/how-do-i-enable-php-s-flush-with-nginxphp-fpm) # ブラウザ対応状況 https://caniuse.com/#search=server-sent%20events 2019/2/3現在 ![Screen Shot 2019-02-03 at 18.16.07.png](https://qiita-image-store.s3.amazonaws.com/0/142910/018885ad-ce3e-b4ca-66eb-e3bc74efe5a3.png) IE、Edgeでは使えないようなのでpolifillを使うと良いようです - [Yaffle/EventSource: a polyfill for http://www.w3.org/TR/eventsource/](https://github.com/Yaffle/EventSource) # その他自分が疑問に思った部分 ### `ob_end_flush()`と`flush()`の両方を実行しないといけないのはなぜ? @7of9 さんから情報いただきました、ありがとうございますm(_ _)m - [PHP buffer ob_flush() vs. flush() - Stack Overflow](https://stackoverflow.com/questions/4191385/php-buffer-ob-flush-vs-flush) ### `ob_end_flush()`と`ob_flush()`はどちらが良い? sseで使うのにどちらを使ったほうがいいのかはわかりませんでした。 解説ページによって、`ob_flush()`だったり`ob_end_flush()`だったりしていて、どちらでも動きとしては問題なさそうでした。 情報お持ちの方おりましたらよろしくお願いいたします --- 参考 - [Server-sent events - Wikipedia](https://en.wikipedia.org/wiki/Server-sent_events) - [Server-Sent Events](https://www.w3.org/TR/eventsource/) - [Stream Updates with Server-Sent Events - HTML5 Rocks](https://www.html5rocks.com/en/tutorials/eventsource/basics/) - [キャッシュについて整理 - Qiita](https://qiita.com/anchoor/items/2dc6ab8347c940ea4648) - [ExpressでServer Sent Event (SSE) を簡単に扱ってみる - Qiita](https://qiita.com/taqm/items/881abcf1df226fb17a15) - [WebSocketとServer-Sent Eventの違いとリアルタイムWebアプリの作り方 - WPJ](https://www.webprofessional.jp/real-time-apps-websockets-server-sent-events/) - [十三章第二回 Server-Sent Events — JavaScript初級者から中級者になろう — uhyohyo.net](https://uhyohyo.net/javascript/13_2.html) - [サーバPUSHざっくりまとめ](https://www.slideshare.net/mawarimichi/push-37869433) - [Server Sent Events(SSE)の使いどころと使い方 | GREE Engineers' Blog](https://labs.gree.jp/blog/2014/08/11070/) 最後まで読んでいただいてありがとうございましたm(_ _)m