SSE(Server-Sent Events)を実装したけど、なぜかリアルタイム反映されてないときの解決法
過去に自分がはまってしまった、SSE(Server-Sent Events)を実装してしっかりとCORSも考慮したのに、なぜかリアルタイム反映されない罠について解説します。
はじめに
Server-Sent Events (SSE)
は、サーバーからクライアントに対してリアルタイムでデータを送信するための技術です。しかし、実装したものの、リアルタイムで反映されないという問題に遭遇することがあります。これは、さまざまな原因が考えられます。
この記事では、SSEを実装した際にリアルタイム更新が反映されない原因と、それを解決するための方法を解説します。
1. HTTPレスポンスのバッファリング
これが一番の沼りポイントだと思います。
SSEでは、サーバーからクライアントにデータをリアルタイムで送信することが目的ですが、HTTPレスポンスがバッファリングされると、レスポンスがクライアントに即座に送信されず、更新が遅れることがあります。これは、Node.jsのHTTPレスポンスがデフォルトでバッファにデータをため込み、一定のタイミングで一度に送信するためです。
解決策
Node.jsのレスポンスはデフォルトでバッファリングされるため、SSEでリアルタイムにデータを送信したい場合、res.flush()
を使ってバッファを即座に送信することが必要です。これにより、データがブラウザに即時に反映され、リアルタイム更新が実現できます。
コード例
以下は、MongoDBのChange Streamsを使ってリアルタイムでデータをクライアントに送信するコードの一部です。このコードでは、SSEを使用しての変更をクライアントに通知していまするためのsendData
の作成例です。
const sendData = (data) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
// 即座にレスポンスを返す
res.flush();
};
ChangeStream.on('change', async (change) => {
const updatedData = change.fullDocument; // 変更後の完全なドキュメント
sendData(updatedData); // クライアントに送信
console.log('Change detected in Data:', JSON.stringify(change, null, 2));
});
2. CORS設定
異なるオリジンからデータを送信する場合、CORS(クロスオリジンリソースシェアリング)設定が正しく行われていないと、クライアントがSSE接続を拒否することがあります。特に、異なるドメインやポートでサーバーとクライアントが通信している場合は、CORS設定を確認することが重要です。
解決策
CORS設定を行い、SSEを許可するようにします。以下のように、Corsミドルウェアを使用して、オリジンを許可します。methodsやoriginを適切に設定することで、セキュリティリスクを軽減し、外部アプリケーションとの連携を可能にすることができる。
コード例
mothods
は、許可するHTTPメソッドを指定しています。ここでは、GETとHEADメソッドのみが許可。
origin
の*
はワイルドカードで、すべてのオリジンからのアクセスを許可することを意味します。つまり、どのドメインからでもリクエストが許可される設定です。
セキュリティを考慮する場合は、'*'
を使用するのではなく、特定のオリジン
(例:origin: 'https://example.com') を指定することが推奨されます。
import Cors from 'cors';
// CORSミドルウェアの初期化
const cors = Cors({
methods: ['GET', 'HEAD'],
origin: '*', // 必要に応じて特定のオリジンを指定
});
3. ヘッダーの設定
デフォルトでは、HTTPレスポンスが完了した後にサーバーとクライアントの接続は閉じられます。しかし、リアルタイム通信を行うSSE(Server-Sent Events)などでは、接続を維持し、サーバーがクライアントにデータを送り続ける必要があります。
解決策
Connection: keep-alive
ヘッダーを設定して、接続を維持します。
Connection: keep-alive
を設定することで、クライアントとサーバーの接続を維持するために使います。この設定により、クライアントとサーバーの間でTCP接続が切断されることなく、長時間接続が維持されます。
コード例
SSEのヘッダー設定の場所にres.setHeader('Connection', 'keep-alive');
を加えてあげましょう。
// SSEのヘッダー設定
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
最後に
リアルタイムのデータ更新を実現するためには、SSEやWebSocketのような技術を活用することが重要ですが、その際に発生するバッファリングの問題には注意が必要です。
自分の場合、プロンプトに問題があったのかChat-GPTもこのバッファリング問題(res.flush()追加)について教えてくれなかったので、プロンプトの重要性を実感しました。