1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今の今まで知らなかった Server-Sent Events を使ってみた

Posted at

先日、積読になったまま放置していた技術書に目を通していたら Server-Sent Events(以下、SSE) に関する記事を見かけたので、実際にコードを書いて挙動を確認してみました。

SSE を使うと、サーバからクライアントへのメッセージプッシュが可能になります。

恥ずかしながら、筆者は SSE について今の今まで知らなかったのですが、「W3C Working Draft 23 April 2009」というページを見つけましたので、15年前には登場していたようです。

SSE の特徴(や欠点)については、こちらの記事の SSE の項に、とても分かりやすくまとめてありましたので、一読されることをお勧めします。

環境

  • Ubuntu 22.04 ※WSL2

  • Nodejs 18.18.0

  • npm 9.8.1

  • express 4.19.2

ディレクトリ構成

sse-sample という作業用ディレクトリを作成して、その中に必要ファイルを置きました。
npm i express で Express をインストールしておきます。

sse-sample
├── index.js
├── node_modules
├── package-lock.json
├── package.json
└── public
    └── index.html

プログラム

・サーバ側(index.js)

ポイントとなりそうな箇所にコメントを入れています。

const express = require('express');

const app = express();

app.use(express.static('public'));

app.get('/subscribe', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream', // SSE を使うために `text/event-stream` を指定する
    'Connection': 'keep-alive', // 接続を開いたままにする
    'Cache-Control': 'no-cache' // キャッシュ処理をしない
  });

  const intervalId = setInterval(() => {
    // 送信したいメッセージ(データ)は「data:」の後に書く
    // 「\n\n」がメッセージの区切りになる
    // 今回はデバイスから何らかの値(計測値)をプッシュしているイメージ
    const device = { name: 'device-A-1234', value: Math.floor(Math.random() * 100) };
    res.write(`data: ${JSON.stringify(device)}\n\n`);
  }, 3000);

  req.on('close', () => {
    clearInterval(intervalId);
    res.end();
  });
});

app.listen(3000, () => console.log('Server listening: http://localhost:3000'));

・クライアント側(public/index.html)

ポイントとなりそうな箇所にはコメントを入れています。
※今回は、HTML ファイルの <script> タグ内に直接書いています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Server-Sent Events Sample</title>
  </head>
  <body>
    <h1>SSE Sample</h1>
    <ul id="list"></ul>

    <script>
      // SSEを実装したサーバに接続する
      const eventSource = new EventSource('/subscribe'); 

      // open/error/message が既定イベント
      eventSource.onopen = () => {
        // 接続の確立
      };
      eventSource.onerror = () => {
        // 接続の切断、など
      };
      eventSource.onmessage = (event) => {
		// サーバからのメッセージを受信(データは event.data にある)
        const eventList = document.getElementById('list');
        const newElement = document.createElement('li');

        const parsedData = JSON.parse(event.data);
        newElement.textContent = `${parsedData.name} / ${parsedData.value}`;

        eventList.appendChild(newElement);
      };
    </script>
  </body>
</html>

動作確認

node index.js を実行した後、ブラウザで http://localhost:3000 にアクセスしてみます。

サーバ側では3秒に1度 data: ... \n\n でメッセージを送っているため、クライアント側では3秒に1度、受信したデータが表示されます。

Animation.gif

ちなみに、サーバ側で event: フィールドを持たせると、クライアント側で addEventListener() を使ってイベントを待ち受けることができるようです。

サーバ側(index.js)

const device = { name: 'device-A-1234', value: 50 };
res.write(`event: hoge\n`);
res.write(`data: ${JSON.stringify(device)}\n\n`);

クライアント側(public/index.html)

eventSource.addEventListener('hoge', (event) => {
  // ...
});

まだ試せていませんが、他には id: でイベントの固有ID(識別子)を指定したり、retry: で再接続時間(サーバへの接続が切れると、ブラウザは指定した時間だけ待ってから再接続を試みる)の指定もできるようです。

手っ取り早く動作確認する場合は、サーバ側プログラムだけ準備して、ターミナルを開いて curl コマンドを実行すると良いです。ターミナル上で、受信したメッセージが表示されます。

curl -H Accept:text/event-stream http://localhost:3000/subscribe

参考URL

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?