要約 (TL;DR)
通知やライブフィードのような一方向のサーバーからクライアントへの更新には、Server-Sent Events (SSE) を使用します。チャットやゲームのような双方向通信には WebSocket を使用します。SSE はよりシンプルで HTTP 上で動作します。WebSocket はより複雑ですが、双方向メッセージングをサポートします。Modern PetstoreAPI は、異なるリアルタイムのユースケースのために両方を実装しています。
はじめに
API でリアルタイムの更新が必要だとします。ペットのステータスが「利用可能」から「引き取られました」に変わったとき、クライアントはすぐに知る必要があります。このとき、WebSocket と Server-Sent Events (SSE) のどちらを使うべきでしょうか?
ほとんどの開発者は「より強力な」WebSocket をデフォルトで選びがちです。しかし、SSE の方が多くの場合、より良い選択肢です。SSE はよりシンプルで、標準の HTTP 上で動作し、自動再接続を処理します。WebSocket は、必要ないかもしれない複雑さを追加します。
Modern PetstoreAPI は両方のプロトコルを実装しています。ペットのステータス更新や注文通知には SSE を、ライブオークションの入札やリアルタイムチャットには WebSocket を使用しています。それぞれのプロトコルが異なるユースケースに対応しています。
このガイドでは、SSE と WebSocket の違いを学び、Modern PetstoreAPI の実際の例を見て、それぞれのプロトコルをいつ使用すべきかを発見します。
Server-Sent Events (SSE) とは?
SSE は、サーバーからクライアントへイベントをストリーミングするための HTTP ベースのプロトコルです。
SSE の仕組み
クライアントは接続を開き、発生したイベントを受け取ります。
const eventSource = new EventSource('https://petstoreapi.com/v1/pets/notifications');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Pet update:', data);
};
eventSource.addEventListener('adoption', (event) => {
const data = JSON.parse(event.data);
console.log('Pet adopted:', data.petId);
});
サーバーはイベントを送信します。
GET /v1/pets/notifications
Accept: text/event-stream
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
event: adoption
data: {"petId":"019b4132","userId":"user-456"}
event: status-change
data: {"petId":"019b4127","status":"AVAILABLE"}
SSE の特徴
1. 一方向通信
サーバーがクライアントにプッシュします。クライアントは SSE 接続を通じてメッセージを送信することはできません(ただし、通常の HTTP リクエストを使用することはできます)。
2. HTTP 上に構築
標準 HTTP を使用します。プロキシ、ファイアウォール、CDN を経由して動作します。
3. 自動再接続
接続が切断された場合、ブラウザは自動的に再接続します。
4. 再開のためのイベント ID
サーバーはイベント ID を送信できます。クライアントは最後に受信したイベントから再開します。
id: 123
event: adoption
data: {"petId":"019b4132"}
id: 124
event: status-change
data: {"petId":"019b4127"}
切断された場合、クライアントは Last-Event-ID: 124 ヘッダーを送信して再開します。
5. シンプルなプロトコル
テキストベースの形式。curl で簡単にデバッグできます。
curl -N -H "Accept: text/event-stream" \
https://petstoreapi.com/v1/pets/notifications
SSE の制限
1. 一方向のみ
クライアントは SSE 経由でメッセージを送信できません。クライアントからサーバーへの通信には別の HTTP リクエストが必要です。
2. テキストのみ
SSE はテキストを送信します。バイナリデータは Base64 エンコードする必要があります。
3. ブラウザの接続制限
ブラウザはドメインごとの SSE 接続を制限しています(通常 6 個)。ほとんどのアプリでは問題になりません。
4. 組み込みの圧縮機能なし
HTTP 圧縮は機能しますが、WebSocket のようなプロトコルレベルの圧縮はありません。
WebSocket とは?
WebSocket は、永続的な接続を介した全二重双方向プロトコルです。
WebSocket の仕組み
クライアントとサーバーはいつでもメッセージを送受信できます。
const ws = new WebSocket('wss://petstoreapi.com/auctions/019b4132');
// サーバーへメッセージを送信
ws.send(JSON.stringify({
type: 'bid',
amount: 500
}));
// サーバーからメッセージを受信
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Auction update:', data);
};
ws.onclose = () => {
console.log('Connection closed');
// 手動での再接続ロジックが必要
};
サーバーはいつでも送信できます。
{"type":"bid","userId":"user-456","amount":550}
{"type":"outbid","newAmount":550}
クライアントはいつでも送信できます。
{"type":"bid","amount":600}
{"type":"watch","petId":"019b4132"}
WebSocket の特徴
1. 双方向
クライアントとサーバーの両方がいつでもメッセージを送信できます。真の双方向通信です。
2. 低遅延
永続的な接続。メッセージごとの HTTP オーバーヘッドはありません。ゲーム、チャット、ライブコラボレーションに最適です。
3. バイナリサポート
バイナリデータを直接送信できます。Base64 エンコードは不要です。
4. カスタムプロトコル
ws:// または wss://(セキュア)を使用します。初期ハンドシェイク後は HTTP ではありません。
5. フレームベース
メッセージはフレーム化されます。部分的なメッセージを送信し、再構成できます。
WebSocket の制限
1. 複雑なセットアップ
WebSocket サーバーが必要です。HTTP エンドポイントよりも複雑です。
2. 手動再接続
自動再接続機能はありません。リトライロジックを自分で実装する必要があります。
3. プロキシの問題
一部の企業プロキシは WebSocket をブロックします。HTTP プロキシは ws:// を理解しません。
4. ステートフル
サーバーは接続を追跡する必要があります。ステートレスな HTTP よりもスケーリングが困難です。
5. HTTP 機能なし
ハンドシェイク後は HTTP キャッシュ、ステータスコード、標準ヘッダーを使用できません。
比較表
| 機能 | SSE | WebSocket |
|---|---|---|
| 方向性 | サーバー → クライアント | 双方向 |
| プロトコル | HTTP | カスタム (ws://) |
| 再接続 | 自動 | 手動 |
| ブラウザサポート | すべてのモダンブラウザ | すべてのモダンブラウザ |
| プロキシフレンドリー | はい | 時々 |
| 複雑さ | シンプル | 複雑 |
| バイナリデータ | なし (テキストのみ) | あり |
| 遅延 | 低い | 非常に低い |
| スケーラビリティ | 高い (ステートレス) | 中程度 (ステートフル) |
| ユースケース | 通知、フィード | チャット、ゲーム、コラボレーション |
Modern PetstoreAPI が両方をどのように使用しているか
Modern PetstoreAPI は、異なるシナリオのために SSE と WebSocket の両方を実装しています。
ペットの更新のための SSE
エンドポイント: GET /v1/pets/notifications
const events = new EventSource(
'https://petstoreapi.com/v1/pets/notifications?userId=user-456'
);
events.addEventListener('adoption', (e) => {
const data = JSON.parse(e.data);
showNotification(`${data.petName} was adopted!`);
});
events.addEventListener('status-change', (e) => {
const data = JSON.parse(e.data);
updatePetStatus(data.petId, data.status);
});
サーバー実装:
app.get('/v1/pets/notifications', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const userId = req.query.userId;
// ペットの更新を購読
const subscription = petUpdates.subscribe(userId, (event) => {
res.write(`event: ${event.type}\n`);
res.write(`data: ${JSON.stringify(event.data)}\n\n`);
});
req.on('close', () => {
subscription.unsubscribe();
});
});
ユースケース:
- ペットのステータス変更(利用可能 → 引き取られました)
- 注文通知(注文済み、発送済み、配達済み)
- 在庫更新
- 価格変更
ライブオークションのための WebSocket
エンドポイント: wss://petstoreapi.com/auctions/{auctionId}
const ws = new WebSocket('wss://petstoreapi.com/auctions/019b4132');
// 入札
function placeBid(amount) {
ws.send(JSON.stringify({
type: 'bid',
amount
}));
}
// 更新の受信
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'bid':
updateCurrentBid(msg.amount, msg.userId);
break;
case 'outbid':
showOutbidNotification(msg.newAmount);
break;
case 'auction-end':
showAuctionResult(msg.winner);
break;
}
};
サーバー実装:
wss.on('connection', (ws, req) => {
const auctionId = req.params.auctionId;
const auction = auctions.get(auctionId);
ws.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'bid') {
const result = auction.placeBid(msg.userId, msg.amount);
// 全参加者にブロードキャスト
auction.participants.forEach(participant => {
participant.send(JSON.stringify({
type: 'bid',
userId: msg.userId,
amount: msg.amount
}));
});
}
});
});
ユースケース:
- ライブオークション入札
- サポートとのリアルタイムチャット
- 共同ペットケア計画
- セール中のライブ在庫更新
Apidog を使用したリアルタイム API のテスト
Apidog は、SSE と WebSocket API の両方のテストをサポートしています。
SSE のテスト
1. SSE リクエストを作成:
GET https://petstoreapi.com/v1/pets/notifications
Accept: text/event-stream
2. イベントを検証:
- イベントタイプを確認する
- JSON ペイロードを検証する
- 再接続動作をテストする
- イベント ID を確認する
3. テストシナリオ:
- 接続切断
- サーバー再起動
- イベントの順序付け
- 最後のイベントからの再開
WebSocket のテスト
1. WebSocket 接続を作成:
wss://petstoreapi.com/auctions/019b4132
2. テストメッセージを送信:
{"type":"bid","amount":500}
{"type":"watch","petId":"019b4132"}
3. レスポンスを検証:
- メッセージ形式を確認する
- 双方向フローをテストする
- 接続処理を確認する
- エラーシナリオをテストする
4. テストシナリオ:
- 複数の同時接続
- メッセージの順序付け
- 接続タイムアウト
- 再接続ロジック
それぞれの使い分け
SSE を使用する場合:
- 一方向の更新 - サーバーがクライアントにプッシュし、クライアントが返信する必要がない場合
- シンプルなセットアップ - 標準の HTTP インフラストラクチャを使用したい場合
- 自動再接続 - リトライロジックを実装したくない場合
- プロキシフレンドリー - 企業ファイアウォール経由で動作させる必要がある場合
- 通知 - ステータス更新、アラート、ライブフィード
例:
- ペットの引き取り通知
- 注文ステータスの更新
- 在庫変更
- 価格アラート
- ニュースフィード
WebSocket を使用する場合:
- 双方向 - クライアントとサーバーの両方が頻繁にメッセージを送受信する場合
- 低遅延が重要 - ゲーム、リアルタイムコラボレーション
- バイナリデータ - 画像、音声、動画を送信する場合
- カスタムプロトコル - メッセージ形式を完全に制御する必要がある場合
- メッセージ頻度が高い - 毎秒数百件のメッセージ
例:
- ライブオークション入札
- リアルタイムチャット
- マルチプレイヤーゲーム
- 共同編集
- ライブビデオストリーミング
次の理由だけで WebSocket を使用しないこと:
❌ 「より高度だから」 - メリットのない複雑さ
❌ 「みんな使っているから」 - SSE の方がシンプルな場合が多い
❌ 「速いから」 - ほとんどのユースケースでは SSE で十分な速さ
❌ 「双方向だから」 - 実際に双方向性が必要ですか?
結論
SSE と WebSocket は両方ともリアルタイム通信を可能にしますが、それぞれ異なるシナリオのために設計されています。SSE は、自動再接続と HTTP 互換性を備えた一方向のサーバーからクライアントへの更新に優れています。WebSocket は、双方向で低遅延の通信で輝きます。
Modern PetstoreAPI は、両方のプロトコルを効果的に使用する方法を示しています。通知やステータス更新には SSE を、ライブオークションやチャットには WebSocket を使用します。どちらのプロトコルが「より優れている」ように見えるかではなく、ユースケースに基づいて選択してください。
さまざまなシナリオで SSE と WebSocket の両方の実装が正しく機能することを確認するために、Apidog を使用してリアルタイム API をテストしてください。
よくある質問
SSE は企業ファイアウォールを通過できますか?
はい。SSE は標準 HTTP を使用するため、HTTP プロキシやファイアウォールを通過して動作します。WebSocket はカスタムプロトコルを使用するため、一部のプロキシではブロックされる可能性があります。
WebSocket は SSE よりも高速ですか?
WebSocket はわずかに低い遅延(メッセージごとの HTTP オーバーヘッドがないため)を持ちますが、ほとんどのアプリケーションではその差はごくわずかです。SSE は通知、フィード、ステータス更新には十分な速さです。
SSE の再接続はどのように処理しますか?
ブラウザが自動的に再接続を処理します。サーバーからイベント ID を送信すると、クライアントは Last-Event-ID ヘッダーを使用して最後に受信したイベントから再開します。
モバイルアプリで SSE を使用できますか?
はい。iOS および Android は、ネイティブの HTTP クライアントまたはライブラリを介して SSE をサポートしています。SSE は HTTP が動作する場所ならどこでも動作します。
SSE 接続の最大接続時間はどれくらいですか?
厳密な制限はありません。SSE 接続は無期限に開いたままにできます。一部のプロキシやロードバランサーはタイムアウト(通常 30~60 秒)を設定している場合がありますが、ブラウザは自動的に再接続します。
WebSocket はバイナリデータを送信できますか?
はい。WebSocket はテキストフレームとバイナリフレームの両方をサポートしています。画像、音声、またはあらゆるバイナリデータを Base64 エンコードなしで送信できます。
ブラウザはいくつの SSE 接続を持てますか?
ブラウザはドメインごとの SSE 接続を制限しています(通常 6 個)。これはめったに問題になりません。ほとんどのアプリでは 1~2 個の SSE 接続しか必要ありません。
SSE のために特別なサーバーが必要ですか?
いいえ。どの HTTP サーバーでも SSE を処理できます。正しいヘッダー(Content-Type: text/event-stream)を設定し、接続を開いたままにするだけです。
