cover

Server-Side Events: シンプルで効率的なサーバーサイドプッシュ

sorcererxw

紹介

通常、バックエンドのプッシュ方式を選択する際に、WebSocketを使用しない場合、どのような方法を選ぶでしょうか?

まず基本的なポーリング Pollingから説明しましょう。クライアントは一定の時間ごとにサーバーにリクエストを送り、更新データがあるかどうかを確認しますが、これではデータのリアルタイム性を保証することができず、また多くのトラフィックを消費する必要があります。

それではさらに進んで、データ更新がない場合、サーバーはクライアントに応答せず、コネクションを保持し、データが更新されたら、最新のデータをクライアントに返し、その後クライアントは新しい接続を再構築します。これがロングポーリング Long Pollingのメカニズムです。単純なポーリングに比べて、よりリアルタイムで、ネットワークの負荷も小さいですが、非常にエレガントとは言えず、断続的に再接続が必要です。

では、サーバーがデータを応答する際にHTTP接続を切断しないようにすることは可能でしょうか?そうすれば、すべての応答をストリームとして送信し、クライアントが継続的に送信されるイベントを処理するだけでよくなります。これがServer-Side Events(以下、SSEと略します)の基本原理となります。

WebSocket と比較して、SSE は完全に HTTP ベースであり、サーバー側の実装がよりシンプルで軽量です。

サーバーサイドの実装

以下は、Golang+echo を使用して実装したシンプルなSSEサーバーです:

e:=echo.New()
e.GET("/sub", func(c echo.Context) error {
	c.Response().Header().Set(echo.HeaderContentType, "text/event-stream")
	c.Response().WriteHeader(http.StatusOK)
	
	for {
			select {
			case <-c.Request().Context().Done():
				return nil
			default:
			}
			fmt.Fprint(c.Response(), "data: hi\n\n")
			c.Response().Flush()
			time.Sleep(1 * time.Second)
	}
	return nil
})

フロントエンドの実装

EventSource はW3C標準の正式なメンバーであり、ブラウザも完全な実装を提供しています。直接 <code>EventSource</code> を作成するだけで、バックエンドのデータを直接購読することができます。

const evtSrc = new EventSource("http://example.com/eventsource");
evtSrc.onmessage = function (event) {
	console.log(event.data)
}

多くの場合、バックエンドサービスやゲートウェイはHTTPリクエストに最長接続時間を設定し、タイムアウトするとクライアントの接続を自動的に切断します。これにより、クライアントは自動再接続メカニズムを実装する必要があり、データのリアルタイム性を保証する必要があります。しかし、EventSourceは自動再接続メカニズムをネイティブに実装しており、接続が切断されると、ブラウザは接続が回復するまで断続的に再試行します。

適用シーン

上述の紹介から、SSEはフロントエンドにもバックエンドにも非常にフレンドリーであり、実装が簡単で、イベントの配信ニーズを非常によく満たすことができます。フロントエンドのメッセージサブスクリプションには、SSEを使用することが非常に適しています。

Consuming streaming data
Tips for consuming streaming data.
https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data

これはTwitterの開発者ドキュメントで、TwitterのイベントプッシュもHTTPストリームに基づいて実装されていることがわかります。