サーバー送信イベント

導入

サーバーがクライアントにデータをプッシュするためのソリューションは数多くあります。 HTML 5 では、「ポーリング」と WebSocket に加えて、Server-Sent Events (以下、SSE) も提供されています。

一般に、HTTP プロトコルはクライアントがサーバーへのリクエストを開始できることのみを許可し、サーバーはクライアントにアクティブにプッシュすることはできません。ただし、サーバーが次に送信されるのはストリーミング情報であるとクライアントに宣言する特殊なケースがあります。つまり、送信されるのは 1 回限りのデータ パケットではなく、継続的に送信されるデータ ストリームです。現時点では、クライアントは接続を閉じず、常にサーバーからの新しいデータ ストリームを待ちます。本質的に、この種の通信は、ストリーミング情報の形式で長いダウンロードを完了することです。

SSE は、このメカニズムを使用して、ストリーム情報を使用してブラウザーに情報をプッシュします。これは HTTP プロトコルに基づいており、現在 IE/Edge を除く他のブラウザでサポートされています。

WebSocketとの比較

SSE と WebSocket には同様の機能があり、どちらもブラウザとサーバーの間に通信チャネルを確立し、サーバーがブラウザに情報をプッシュします。

全体的に、WebSocket はより強力で柔軟です。これは全二重チャネルであるため、双方向で通信できます。SSE は一方向チャネルであり、ストリーミングは基本的にダウンロードであるため、サーバーからブラウザにのみ送信できます。ブラウザがサーバーに情報を送信すると、それは別の HTTP リクエストになります。

ただし、SSE には利点もあります。

  • SSE は、既存のサーバー ソフトウェアでサポートされている HTTP プロトコルを使用します。 WebSocket は独立したプロトコルです。
  • SSE は軽量で使いやすいですが、WebSocket プロトコルは比較的複雑です。
  • SSE はデフォルトで切断と再接続をサポートしますが、WebSocket は独自に切断と再接続を実装する必要があります。
  • SSE は通常、テキストの送信にのみ使用されます。WebSocket は、デフォルトでバイナリ データの送信をサポートします。
  • SSE は、送信されるカスタム メッセージ タイプをサポートします。

したがって、両方には独自の特徴があり、さまざまなシーンに適しています。

クライアント API

EventSource オブジェクト

SSE のクライアント API は EventSource オブジェクトにデプロイされます。次のコードは、ブラウザが SSE をサポートしているかどうかを検出します。

if (ウィンドウ内の「EventSource」) {
  // ...
}

SSE を使用する場合、ブラウザはまず「EventSource」インスタンスを生成し、サーバーへの接続を開始します。

var ソース = 新しい EventSource(url);

上記の「url」は、現在の URL と同じドメイン内にあることも、クロスドメイン内にあることもできます。クロスドメインの場合、2 番目のパラメーターを指定して withCredentials 属性をオンにして、Cookie を一緒に送信するかどうかを指定できます。

var source = new EventSource(url, { withCredentials: true });

###readyState プロパティ

EventSource インスタンスの readyState プロパティは、接続の現在の状態を示します。このプロパティは読み取り専用であり、次の値を取ることができます。

  • 0: 定数 EventSource.CONNECTING と同等で、接続が確立されていないか、接続が切断された後に再接続中であることを示します。
  • 1: 定数 EventSource.OPEN と同等で、接続が確立されており、データを受け入れることができることを示します。
  • 2: 定数 EventSource.CLOSED と同等で、接続が切断され、再接続されないことを示します。
var ソース = 新しい EventSource(url);
console.log(source.readyState);

URL 属性

EventSource インスタンスの url プロパティは接続の URL を返します。このプロパティは読み取り専用です。

withCredentials 属性

EventSource インスタンスの withCredentials プロパティは、現在のインスタンスで CORS の withCredentials が有効になっているかどうかを示すブール値を返します。このプロパティは読み取り専用で、デフォルトは「false」です。

onopen 属性

接続が確立されると、「open」イベントがトリガーされ、コールバック関数を「onopen」属性で定義できます。

source.onopen = 関数 (イベント) {
  // ...
};

// 別の書き方
source.addEventListener('open', 関数 (イベント) {
  // ...
}、 間違い);

onmessage 属性

クライアントがサーバーからデータを受信すると、「message」イベントがトリガーされます。「onmessage」属性でコールバック関数を定義できます。

source.onmessage = 関数 (イベント) {
  var データ = イベント.データ;
  var 原点 = イベント.原点;
  var lastEventId = イベント.lastEventId;
  // メッセージを処理します
};

// 別の書き方
source.addEventListener('メッセージ', 関数 (イベント) {
  var データ = イベント.データ;
  var 原点 = イベント.原点;
  var lastEventId = イベント.lastEventId;
  // メッセージを処理します
}、 間違い);

上記のコードでは、パラメーター オブジェクト event には次の属性があります。

  • data: サーバーから返されたデータ (テキスト形式)。
  • origin: サーバー URL のドメイン名部分、つまりプロトコル、ドメイン名、およびポートは、メッセージの送信元を示します。
  • lastEventId: サーバーによって送信されたデータの番号。数値がない場合、この属性は空です。

onerror 属性

通信エラー (接続の中断など) が発生した場合、error イベントがトリガーされ、onerror 属性でコールバック関数を定義できます。

source.onerror = 関数 (イベント) {
  // エラーイベントを処理します
};

// 別の書き方
source.addEventListener('error', function (event) {
  // エラーイベントを処理します
}、 間違い);

カスタムイベント

デフォルトでは、サーバーから送信されたデータは常にブラウザの EventSource インスタンスの message イベントをトリガーします。開発者は SSE イベントをカスタマイズすることもできます。この場合、返送されたデータは「message」イベントをトリガーしません。

source.addEventListener('foo', 関数 (イベント) {
  var データ = イベント.データ;
  var 原点 = イベント.原点;
  var lastEventId = イベント.lastEventId;
  // メッセージを処理します
}、 間違い);

上記のコードでは、ブラウザは SSE の foo イベントをリッスンします。 foo イベントを送信するサーバーの実装方法は以下を参照してください。

close() メソッド

SSE 接続を閉じるには、「close」メソッドが使用されます。

ソース.close();

サーバーの実装

データ形式

サーバーからブラウザに送信される SSE データは、次の HTTP ヘッダー情報を含む UTF-8 でエンコードされたテキストである必要があります。

コンテンツ タイプ: テキスト/イベント ストリーム
キャッシュ制御: キャッシュなし
接続: キープアライブ

上記 3 行のうち、1 行目の Content-Type では MIME タイプを event-steam として指定する必要があります。

送信される各メッセージは複数の「メッセージ」で構成され、各「メッセージ」は「\n\n」で区切られます。各「メッセージ」は複数行で構成されており、各行は次の形式になっています。

[フィールド]: 値\n

上記の「フィールド」には 4 つの値を指定できます。

  • データ
  • イベント -id -リトライ

さらに、コメントを示すコロンで始まる行も存在します。通常、サーバーは接続を維持するためにブラウザに時々コメントを送信します。

: コメントです

以下に例を示します。

: これはテスト ストリームです\n\n

データ: テキスト\n\n

データ: 別のメッセージ\n
データ: 2 行 \n\n

データフィールド

データの内容は「data」フィールドで表されます。

データ: メッセージ\n\n

データが非常に長い場合、最後の行は \n\n で終わり、前の行は \n で終わることがあります。

データ: メッセージの開始\n
データ: メッセージを続行します\n\n

以下はJSONデータを送信する例です。

データ: {\n
データ: "foo": "バー",\n
データ: "バズ"、555\n
データ: }\n\n

ID フィールド

データの識別子は「id」フィールドで表され、各データの番号に相当します。

ID: msg1\n
データ: メッセージ\n\n

ブラウザは、「lastEventId」プロパティを使用してこの値を読み取ります。接続が切断されると、ブラウザは特別な「Last-Event-ID」ヘッダー情報を含む HTTP ヘッダーを送信し、サーバーが接続を再確立できるようにこの値を送り返します。したがって、このヘッダーは同期メカニズムとみなすことができます。

イベントフィールド

「event」フィールドはカスタム イベント タイプを表し、デフォルトは「message」イベントです。ブラウザは addEventListener() を使用してこのイベントをリッスンできます。

イベント: foo\n
データ: foo イベント\n\n

データ: 名前のないイベント\n\n

イベント: バー\n
データ: バー イベント\n\n

上記のコードは 3 つのメッセージを作成します。最初の項目の名前は foo で、ブラウザの foo イベントをトリガーします。2 番目の項目は名前がなく、デフォルトのタイプを示し、ブラウザの message イベントをトリガーします。ブラウザの「message」イベント。

別の例を示します。

イベント:ユーザー接続
データ: {"ユーザー名": "ボビー", "時刻": "02:33:48"}

イベント: ユーザーメッセージ
データ: {"ユーザー名": "ボビー", "時間": "02:34:11", "テキスト": "皆さん、こんにちは。"}

イベント: ユーザー切断
データ: {"ユーザー名": "ボビー", "時刻": "02:34:23"}

イベント: ユーザーメッセージ
データ: {"ユーザー名": "ショーン"、"時間": "02:34:36"、"テキスト": "さようなら、ボビー。"}

再試行フィールド

サーバーは「retry」フィールドを使用して、ブラウザが接続を再開始する時間間隔を指定できます。

再試行: 10000\n

ブラウザは 2 つの状況で接続を再開始します。1 つは時間間隔が経過した場合、もう 1 つはネットワーク エラーやその他の理由により接続エラーが発生した場合です。

ノードサーバーインスタンス

SSE では、サーバーがブラウザとの接続を維持する必要があります。サーバー ソフトウェアが異なれば、消費されるリソースも異なります。 Apache サーバーでは、各接続はスレッドです。多数の接続を維持すると、大量のリソースが消費されます。ノードはすべての接続で同じスレッドを使用するため、消費されるリソースは大幅に少なくなりますが、これには、各接続にディスク IO の読み取りや書き込みなどの時間のかかる操作が含まれていないことが必要です。

以下はNodeのSSEサーバーです。

var http = require("http");

http.createServer(function (req, res) {
  var ファイル名 = "." + 要求.url;

  if (ファイル名 === "./ストリーム") {
    res.writeHead(200, {
      "コンテンツタイプ":"テキスト/イベントストリーム",
      "キャッシュ制御":"キャッシュなし",
      "接続":"キープアライブ",
      "アクセス制御許可オリジン": '*',
    });
    res.write("再試行: 10000\n");
    res.write("イベント: 接続時間\n");
    res.write("データ: " + (new Date()) + "\n\n");
    res.write("データ: " + (new Date()) + "\n\n");

    間隔 = setInterval(関数() {
      res.write("データ: " + (new Date()) + "\n\n");
    }, 1000);

    req.connection.addListener("閉じる", function () {
      クリアインターバル(間隔);
    }、 間違い);
  }
}).listen(8844, "127.0.0.1");

参考リンク


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0