Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】SSE in Go: Your Timeouts Do Not Apply Where You Think

2026年08月24日に「Dev.to」が公開したITニュース「SSE in Go: Your Timeouts Do Not Apply Where You Think」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

GoでSSE(サーバーからデータを受け取り続ける技術)を実装すると、デフォルト設定が原因で問題が起きやすい。HTTP/2ではConnectionヘッダーが不正で、Goのタイムアウト設定がストリームを30秒で切断する。またHTTP/1.1では接続数制限に注意。適切なヘッダー設定、タイムアウト無効化、HTTP/2環境での利用が重要だ。

ITニュース解説

サーバー・セント・イベント(SSE)は、ウェブページにリアルタイムな情報を提供する技術の一つだ。これは、ウェブサーバーが一方的にクライアント(ブラウザなど)にメッセージを送り続ける仕組みで、ブラウザはそのメッセージを受け取って表示を更新する。例えば、SNSの通知や株価のリアルタイム表示、チャットなどがこれにあたる。技術的には、SSEはHTTPリクエストの一種だが、通常のHTTPリクエストのように一度応答を返したらすぐに終わるのではなく、サーバーがメッセージを送り続ける限り接続が維持される「決して終わらない」リクエストだという点が非常に重要だ。

この「終わらないリクエスト」という特性が、サーバーの仕組みと衝突し、多くの問題を引き起こすことがある。一般的なHTTPサーバーには、応答が遅いリクエストや、いつまでも終わらないリクエストを自動的に切断するための「番人」のような機能が備わっている。これをタイムアウト機能と呼ぶ。サーバーから見ると、SSEストリームは、まさにこの「番人」が切断しようとする「いつまでも終わらない遅いリクエスト」に見えてしまうため、意図せず接続が切断されてしまうのだ。

実際に遭遇する問題の第一は、HTTP/2という新しい通信プロトコルと、Connectionヘッダーという昔ながらのヘッダーの組み合わせで発生する。ウェブブラウザとサーバー、またはサーバーの前に位置するリバースプロキシ(外部からの通信を受け付ける入り口のシステム)との間では、現在HTTP/2というプロトコルが広く使われている。HTTP/2は、一つの接続で複数の通信を同時に行える「多重化」や、接続を永続的に維持する機能を標準で備えている。そのため、かつてHTTP/1.1で接続を維持するために使われていたConnection: keep-aliveのようなヘッダーは、HTTP/2では不要どころか、プロトコルのルール上「不正」なものとされている。しかし、古いSSEのチュートリアルなどには、このConnectionヘッダーを設定するよう指示されていることが多く、これをそのまま使ってしまうと問題が起こる。ブラウザがHTTP/2でリクエストを送り、サーバーが不正なConnectionヘッダーを含む応答を返すと、リバースプロキシなどがそのヘッダーを見てHTTP/2のストリームをエラーとしてリセットしてしまう。その結果、ブラウザには通信エラーが表示され、何度も再接続を試みるループに陥ってしまう。SSEで必要なヘッダーは、Content-Type: text/event-stream、キャッシュを防ぐためのCache-Control: no-cache、そして特定のプロキシ(Nginxなど)でのバッファリングを防ぐためのX-Accel-Buffering: noの3つだけであり、これ以外は設定すべきではない。

第二の問題は、Go言語で書かれたサーバーのデフォルトのタイムアウト設定が、SSEストリームを予期せず切断してしまうことだ。GoのHTTPサーバーには、リクエストの読み込み時間(ReadTimeout)、応答の書き込み時間(WriteTimeout)、接続が使われずに待機する時間(IdleTimeout)といった、いくつかのタイムアウト設定がある。SSEストリームはサーバーがデータを「書き込み続ける」ため、特にWriteTimeoutが設定されていると、一定時間書き込みがなければ接続が切断されてしまう。多くのサーバーではこのWriteTimeoutが30秒などに設定されていることが多く、その結果、30秒ごとにSSEストリームが途切れてしまう事態が発生する。また、ミドルウェア(リクエスト処理の途中で追加の処理を行う仕組み)でリクエスト全体のコンテキスト(リクエストの処理全体を管理する仕組み)を一定時間でキャンセルする設定がある場合も、同様にストリームが切断される原因となる。この問題を解決するには、SSEのエンドポイント(特定のURLパス)に対して、WriteTimeoutを「無期限」(Goでは0に設定することで実現される)にするか、または非常に長い時間に設定し、さらにタイムアウトを適用するミドルウェアがあれば、そのパスでは適用をバイパスする設定が必要となる。ReadTimeoutについては、SSEクライアントは最初の要求を送るだけで、その後はデータを送らないため、通常は問題にならない。IdleTimeoutも、サーバーが定期的に「ハートビート」と呼ばれる空のメッセージを送信していれば、接続がアイドル状態と見なされて切断されることはない。

第三に、HTTP/1.1という古いプロトコルでSSEを使う場合に起こりうる問題がある。ウェブブラウザは、HTTP/1.1環境下では、同じウェブサイト(オリジン)に対して同時に開ける接続の数に制限を設けている。一般的には約6つだ。SSEストリームは、サーバーからデータが送られ続ける間、この接続のうちの一つを永久に占有してしまう。その結果、他のウェブページ要素(画像、スクリプト、その他のAPIリクエストなど)が読み込まれるための接続スロットが減り、ページ全体の読み込みが遅くなったり、ボタンがいつまでも反応しなかったり、他のリクエストが詰まってしまったりすることがある。複数のタブで同じサイトを開けば、すぐに接続の上限に達してしまうだろう。この問題は、HTTP/2を使えば解決する。HTTP/2では、一つの物理的な接続の上で複数の論理的な通信を並行して行えるため、SSEストリームが接続を占有してしまうという問題がなくなるのだ。そのため、SSEを実装する際には、リクエストがHTTP/2経由で来ているかを確認し、HTTP/1.1からのリクエストにはSSEを提供しないようにするのが賢明な対策となる。

Goの標準ライブラリ自体にも、タイムアウト設定が特定の状況で正しく適用されないという過去の事例がある。例えば、最近修正されたセキュリティ上の脆弱性(CVE-2026-56853)では、HTTP/2の接続検出時にリクエストヘッダーの読み込みタイムアウトが適用されないという問題があった。これは、悪意のあるクライアントが接続を長時間保持し、サーバーのリソースを使い果たすことでサービス停止を引き起こす可能性があった。この事例は、「タイムアウト設定が存在していても、それが実際に意図した範囲をカバーしているか」という点を常に確認する必要があることを示している。

SSEエンドポイントを本番環境にデプロイする際には、以下の点を確認することが非常に重要だ。まず、ハンドラー(サーバーでのリクエスト処理部分)で不要なConnectionKeep-AliveTransfer-EncodingUpgradeといったヘッダーを設定していないかを確認する。次に、そのSSEパスにおいて、サーバーのWriteTimeoutが無効になっているか、または非常に長く設定されているか、そしてタイムアウトを適用するミドルウェアがある場合は、そのパスをバイパスしているかを確認する。また、サーバーのIdleTimeoutよりも頻繁にハートビートメッセージを送信し、接続が切断されないようにする必要がある。SSEストリームは、必ずHTTP/2のフロント(リバースプロキシなど)を通して提供し、プロキシがレスポンスをバッファリングしないように設定することも忘れてはならない。クライアント側では、ストリームが予期せず切断された場合に適切に再接続を行い、その再接続回数を監視する仕組みも重要だ。最後に、使用しているGoのバージョンが最新であり、標準ライブラリのタイムアウト関連の修正が適用されているかを確認する。

これらの問題から学ぶべき重要な教訓は、「タイムアウトはそれがカバーする範囲しか保護しない」ということだ。サーバーの構成、ミドルウェアの動作、そしてブラウザとサーバー間のプロトコルまで、全体の流れを理解することが不可欠となる。SSEストリームが期待通りに動作しない場合、すぐに自分のビジネスロジック(アプリケーション固有の処理)を疑うのではなく、まず通信の基盤であるヘッダーやタイムアウト設定、そしてHTTPプロトコルに問題がないかを徹底的に調査することが、トラブル解決の近道となるだろう。そして、もし特定の機能がシステム全体に悪影響を与えるのであれば、一時的にその機能を取り除く勇気も時には必要となる。動作しないSSEは、ウェブページ全体をフリーズさせるよりもはるかに良い状態だ。

関連コンテンツ

関連IT用語

関連ITニュース