サーバー送信イベントで最新情報をストリーミングする

サーバー送信イベント (SSE)は、HTTP を使用してサーバーからクライアントに自動更新を送信します。 接続します接続が確立されると、サーバーは できます。

SSE を使用して、ウェブアプリからプッシュ通知を送信することをおすすめします。 SSE は一方向に情報を送信するため、SSE からの更新は受信できません。 できます。

SSE の概念はおなじみかもしれません。ウェブアプリが「定期購入」するイベントのストリームに 生成され、新しいイベントが発生するたびに 送信されます。ただし、サーバーから送信されたイベントを真に理解するには、次のことを行う必要があります。 AJAX 前任者の限界について学びました。これには以下が該当します。

  • ポーリング: アプリケーションはサーバーに対してデータを繰り返しポーリングします。この手法は、 大部分の AJAX アプリケーションで使用されています。HTTP プロトコルでは、 データはリクエストとレスポンスの形式を中心に構成されます。クライアントがリクエストを行う サーバーがデータを返すのを待ちます。使用できるものがない場合は、 レスポンスが返されます。追加のポーリングにより、HTTP オーバーヘッドが増加します。

  • ロング ポーリング(Hanging GET / COMET): サーバーにデータがない場合 サーバーは、新しいデータが使用可能になるまでリクエストを開いたままにします。 したがって、この手法は「Hanging GET」と呼ばれます。日時 サーバーが応答し、接続を閉じます。 このプロセスが繰り返されますしたがって、サーバーは常に 1 対 1 の 作成しますこれを設定するために、デベロッパーは通常、 スクリプトタグを「infinite」使用できます。

サーバーに送信されるイベントは、効率を考慮して一から設計されています。 SSE と通信する際、サーバーは SSE と通信する際に いつでもアプリを使用できます。最初のリクエストを行う必要はありません。つまり 更新が発生したらサーバーからクライアントにストリーミングできます。SSE サーバーとクライアントの間で単一の単方向チャネルを開く。

サーバー送信イベントとロング ポーリングの主な違いは、SSE が ブラウザによって直接処理され、ユーザーはメッセージをリッスンするだけで済みます。

サーバー送信イベントと WebSocket

WebSocket ではなくサーバー送信イベントを選択する理由は何ですか。その問いが重要です。

WebSockets には、 双方向、全二重通信をサポートします。双方向のチャネルは ゲーム、メッセージ アプリなど、ほぼリアルタイムのアップデートが必要なユースケースでは、 あります。

ただし、サーバーからの一方向の通信のみが必要な場合もあります。 たとえば、友だちが自分のステータス、株価情報、ニュース フィード、 他の自動データプッシュ メカニズムを使用します。つまり クライアントサイドの Web SQL Database または IndexedDB オブジェクト ストアの更新。 サーバーにデータを送信する必要がある場合、XMLHttpRequest は頼りになります。

SSE は HTTP 経由で送信されます。特別なプロトコルやサーバーはなく 実装する必要があります。WebSocket には全二重通信と プロトコルを処理する新しい WebSocket サーバーを接続します。

さらに、サーバー送信イベントには、WebSocket にはないさまざまな機能があります。 設計上、たとえば自動再接続、イベント ID、 できます。

JavaScript を使用して EventSource を作成する

イベント ストリームに登録するには、EventSource オブジェクトを作成して、 ストリームの URL:

const source = new EventSource('stream.php');

次に、message イベントのハンドラを設定します。必要に応じて openerror をリッスンします。

source.addEventListener('message', (e) => {
  console.log(e.data);
});

source.addEventListener('open', (e) => {
  // Connection was opened.
});

source.addEventListener('error', (e) => {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
});

更新がサーバーから push されると、onmessage ハンドラが起動します。 新しいデータは e.data プロパティで取得できます。すばらしい点は、 ブラウザは接続を閉じるたびに、自動的に 表示されます。サーバー実装では、サーバーとサーバーを この再接続タイムアウト」を使用します

以上です。クライアントが stream.php のイベントを処理できるようになりました。

イベント ストリームの形式

ソースからのイベント ストリームの送信は、 text/event-stream Content-Type で提供される書式なしテキストのレスポンス。 必要があります。 基本的な形式では、レスポンスには data: 行が含まれ、その後に メッセージに続けて「\n」が 2 つ続きますストリームの末尾の文字列:

data: My message\n\n

複数行のデータ

メッセージが長い場合は、複数の data: 行を使用して分割できます。 data: で始まる 2 つ以上の連続した行は、 つまり、1 つの message イベントのみが発生します。

各行は 1 つの「\n」で終わる必要があります。( あります。message ハンドラに渡される結果は単一の文字列 改行文字で連結されます。例:

data: first line\n
data: second line\n\n</pre>

これにより、「1 行目\n2 行目」が生成されます。(e.data)次に e.data.split('\n').join(''): 「\n」ではなくメッセージを再構築します。あります。

JSON データを送信する

複数行を使用すると、構文を損なうことなく JSON を送信できます。

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n

このストリームを処理するクライアントサイドのコードは次のようになります。

source.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  console.log(data.id, data.msg);
});

ID をイベントに関連付ける

ストリーム イベントに一意の ID を送信するには、 id::

id: 12345\n
data: GOOG\n
data: 556\n\n

ID を設定すると、ブラウザは最後に呼び出されたイベントをトラッキングできるため、 接続がドロップされると、特別な HTTP ヘッダー(Last-Event-ID)が 新しいリクエストとともに設定されます。これにより、どのイベントを発生させるのかをブラウザが判断できます。 message イベントには e.lastEventId プロパティが含まれています。

再接続のタイムアウトを制御する

ブラウザは約 3 秒でソースへの再接続を試みます。 各接続が閉じられるたびに発生します。このタイムアウトを変更するには、 retry: で始まり、その後にミリ秒単位が続く行です。 待機してから再接続を試行します。

次の例では、10 秒後に再接続を試みます。

retry: 10000\n
data: hello world\n\n

イベント名を指定する

単一のイベントソースに なります。event: で始まる行が存在する場合、 その後にイベントの一意の名前を追加すると、イベントはその名前に関連付けられます。 クライアントでは、特定のイベントをリッスンするようにイベント リスナーを設定できます。

たとえば、次のサーバー出力では、3 種類のイベントが送信されます。 一般的な「メッセージ」event、userlogon、update です。event:

data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n

クライアントにイベント リスナーを設定すると、次のようになります。

source.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  console.log(data.msg);
});

source.addEventListener('userlogon', (e) => {
  const data = JSON.parse(e.data);
  console.log(`User login: ${data.username}`);
});

source.addEventListener('update', (e) => {
  const data = JSON.parse(e.data);
  console.log(`${data.username} is now ${data.emotion}`);
};

サーバーの例

PHP での基本的なサーバー実装は次のとおりです。

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.

/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/

function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

$serverTime = time();

sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>

Node JS 上での同様の実装を、 Express ハンドラ:

app.get('/events', (req, res) => {
    // Send the SSE header.
    res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
    });

    // Sends an event to the client where the data is the current date,
    // then schedules the event to happen again after 5 seconds.
    const sendEvent = () => {
        const data = (new Date()).toLocaleTimeString();
        res.write("data: " + data + '\n\n');
        setTimeout(sendEvent, 5000);
    };

    // Send the initial event immediately.
    sendEvent();
});

sse-node.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <script>
    const source = new EventSource('/events');
    source.onmessage = (e) => {
        const content = document.createElement('div');
        content.textContent = e.data;
        document.body.append(content);
    };
    </script>
  </body>
</html>

イベント ストリームをキャンセルする

通常、ブラウザはイベントソースに接続したときに、イベントソースに自動再接続します。 その動作はクライアントまたはサーバーからキャンセルできます。

クライアントからストリームをキャンセルするには、次の呼び出しを行います。

source.close();

サーバーからストリームをキャンセルするには、text/event-stream 以外を返します。 Content-Type を返すか、200 OK 以外の HTTP ステータスを返す (404 Not Found など)。

どちらの方法でも、ブラウザは接続を再確立できません。

セキュリティについて

EventSource によって生成されたリクエストには、 取得する場合もありますサーバー上の SSE エンドポイントを 有効にする方法については、 クロスオリジン リソース シェアリング(CORS)