リアルタイムWeb通信をシンプルに: SSEの基礎と使い方

サーバー送信イベント(Server-Sent Events、SSE)とは、Webブラウザーとサーバー間の一方向の非同期通信方法のことです。最近、サーバー送信イベントは、リアルタイム通信の実現によく利用されています。本文では、サーバー送信イベントを詳しく解説した上、それを実現する方法を皆さんに紹介します。

## サーバー送信イベント（Server-Sent Events）とは
サーバー送信イベント(Server-Sent Events、SSE)とは、Webブラウザーとサーバー間の一方向の非同期通信方法のことです。この通信方法では、サーバーがブラウザーに対してイベントをプッシュ送信でき、ブラウザーはサーバーから送信されたイベントを受信して処理できます。

SSEはHTTPプロトコルに基づいているため、サーバーとブラウザー間は長時間接続を維持でき、テキスト形式でデータ送受信を行います。長時間接続でもリソース消費が少ないため、リアルタイムな通信に向いているのが特徴です。

## Server-Sent Eventsの利用シーン
最近、リアルタイムの通信を実現するために、サーバー送信イベントがよく利用されるので、比較的に人気な通信技術になっています。実際には、サーバー送信イベントは、リアルタイムの通信の実現だけに機能しているわけではなく、他にも様々な役割を果たすこともできます。一般的には、サーバー送信イベント(SSE)の利用シーンは次のようになります。

- **リアルタイム通信の実現**: 従来のHTTP通信ではクライアントからのリクエストに対してのみサーバーがレスポンスを返すことで、[ステートレス](http://apidog.com/jp/blog/what-is-stateless/)になりますが、SSEを使うことでサーバーからクライアントへリアルタイムにデータをプッシュできます。これによりチャットやライブ更新などが実装できます

- **サーバー負荷の軽減**: 従来クライアントが周期的にポーリングしていた場合、頻繁なリクエスト送信でサーバー負荷が高くなります。SSEなら長時間接続を使い負荷を抑えられます

- **データ通信の効率化**: ポーリングではサーバー側に更新がない場合でも頻繁にリクエストを送信します。SSEなら更新時のみデータを送信するので通信効率が良いです

- **ストリーミング通信**: SSEではHTTPを利用してストリーミングでデータを送受信できます。ビデオや音声などのストリーミングにも利用できます

- **ブラウザーの対応**: 主要ブラウザーがSSEに対応しているため、特別なライブラリなしに利用できるのが強みです

このような理由から、リアルタイムWebアプリケーションを開発する際だけではなく、他の場面でもSSEが有用な通信手段になる可能性もあると言えます。

## JavaScriptでServer-Sent Events（サーバー送信イベント）の実装
ブラウザー側ではJavaScriptのEventSourceオブジェクトを使ってSSEを受信し、サーバー側はHTTPのレスポンスヘッダーを操作してストリーム形式でデータを送信します。

### Server-Sent Events (SSE) のContent-Type
Server-Sent Events (SSE) は、サーバーからクライアントへの一方向の通信を実現するストリーミングデータの伝送方式です。SSEのレスポンスヘッダのContent-Typeには以下のように指定します。
```
Content-Type: text/event-stream
```
この値はSSEの仕様で定められており、クライアントがSSEのストリームデータであることを認識するために必要となります。

### サーバー側の実装
- HTTPレスポンスヘッダーに以下を設定
- Content-Type: text/event-stream
- Cache-Control: no-cache
- Connection: keep-alive
- データはイベントストリーム形式で送信
- id: イベントの固有ID
- event: イベント名
- data: 送信データ
- flushメソッドでデータをフラッシュ


#### 【サーバー側のサンプルコード】

```
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); 

while(true){

  // データをJSON形式で作成
  $data = json_encode(['message' => 'Hello!', 'time' => date('r')]);
  
  // データを書き出し   
  echo "event: message\n";
  echo "data: {$data}\n\n";
  
  ob_flush();
  flush();
  
  // 一定間隔でデータを送信
  sleep(1);
}
```

### クライアント側の実装

- EventSourceオブジェクトを生成
- new EventSource(URL)
- addEventListenerでイベントを listens
- イベントハンドラで受信データを処理
- SSE接続が切れた時の処理

サーバー送信イベント(SSE)の簡単な実装例は以下のようになります。


#### 【クライアント側のサンプルコード】
```
// EventSourceオブジェクトを生成
const eventSource = new EventSource('/sse-endpoint');

// メッセージイベントのリスナーを設定
eventSource.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  console.log(data); 
});

// SSEが切断された時の処理  
eventSource.onerror = () => {
  console.log('SSE disconnected!');
  eventSource.close();
};
```

このようにサーバー側でHTTPレスポンスをストリーミング形式にし、クライアント側でEventSourceを使ってイベントリスナーを登録するのが基本的な実装パターンです。

## Apidogで簡単にサーバー送信イベントを実装
[Apidog](http://www.apidog.com/jp/?utm_source=opr&utm_medium=a2takuya)は、サーバー送信イベントに対応できるAPIクライアントです。Apidogを使うことで、非常に直感的な操作で、サーバー送信イベントを実装する事ができます。コードを書かずにサーバー送信イベントを実装したい場合は、Apidogは一番適切なソリューションになると思います。

### Server-Sent Events接続の開始
SSE 接続を開始するには、HTTP プロジェクトで新しい API を作成します。リクエストの送信後、レスポンスの `Content-Type` に `text/event-stream` が含まれている場合、Apidog は自動的に返されたデータを SSE イベントとして解析し、新しいタイムラインビューでレスポンスの内容をリアルタイムに更新します。

![sse-chatgpt-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877124/eec56850-eb15-267c-0b10-eae553343c8b.png)

### 後処理でメッセージ内容の抽出
現在のAPIの後処理でカスタムスクリプトを追加して、各 SSE イベントから特定のフィールド値を抽出し、完全な文字列に連結します。

上記の図に示した APIを例に取ると、この APIで返されるメッセージには JSONデータが含まれています。 `answer` フィールドからパラメーターの内容を抽出して、完全なテキストに連結します。

![sse-chatgpt-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877124/c2af1ebb-33d3-938d-1334-98573db4ea8a.png)

カスタム スクリプトに以下の例のコードを記述します。
```
// レスポンス テキストを取得
const text = pm.response.text() 
// テキストを行に分割
var lines = text.split('\n');   
// "content"パラメータを格納する空の配列を作成  
var contents = [];
// 各行を反復処理
for (var i = 0; i < lines.length; i++) {
    const line = lines[i];
    // "data:"で始まらない行はスキップ 
    if (!line.startsWith('data:')) {
        continue;
    }
    // JSON データを解析
    try {
        var data = JSON.parse(line.substring(5).trim()); // 先頭の "data: " を削除
        // "choices" 配列から "content" パラメーターを取得し、配列に追加
        contents.push(data.choices[0].delta.content); 
    } catch (e) {
        // 有効なJSONデータでない場合は現在の行を無視
    }
}
// "content" パラメーターを join() メソッドを使用して連結
var result = contents.join('');
// 結果を本文の "Visualize" タブに表示
pm.visualizer.set(result);
// 結果をコンソールに出力  
console.log(result);
```

リクエストの送信後、連結されたテキストは「**コンソール**」で確認できます。

![sse-chatgpt-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3877124/48ea5bca-af23-78fb-11f7-55c13896ec28.png)

## まとめ
本記事では、Server-Sent Events(SSE)について詳しく解説しました。

- SSEはHTTPプロトコルに基づく一方向の非同期通信方式
- サーバーからクライアントへのプッシュ通信が可能
- リアルタイム通信の実現やサーバー負荷軽減などに活用できる
- 主要ブラウザーに標準対応しており、JavaScriptのEventSourceで実装可能
- サーバー側ではレスポンスヘッダを設定し、イベントストリーム形式でデータを送信
- クライアント側ではEventSourceでストリームを受信し、イベントリスナーを登録

SSEはシンプルさと高パフォーマンスが特徴的な通信方式です。リアルタイムWebアプリケーションはもちろん、様々な用途に活用できるでしょう。一方で双方向通信が必要な場合はWebSocketsを利用する必要があります。

今後ますますリアルタイム性が求められるWebアプリケーション開発において、Server-Sent Eventsの理解と実装スキルは重要になってくると考えられます。

**最後まで読んでくださり、ありがとうございました！
この記事を読んで少しでも理解を深めていただければ幸いです！**