サーバー送信イベント(SSE)の紹介

サーバー送信イベント(SSE)は、クライアントがHTTP接続を介して自動的にサーバーから更新を受け取ることを可能にするサーバープッシュ技術です。クライアント接続を確立した後にサーバーがデータ転送をイニシエイトする方法を記述しています。これは、メッセージの更新やブラウザクライアントへの連続データストリームの送信に一般的に使用され、JavaScript APIのEventSourceを通じてローカルのクロスブラウザストリームを強化することを目的としています。クライアントはこのAPIを使用して特定のURLを要求し、イベントストリームを受信することができます。HTML5の一部として、EventSource APIはWHATWGによって標準化されています。SSEのメディアタイプはtext/event-streamです。

注意:SSEとWebsocketの最大の違いは、SSEが一方向のサーバーからクライアントへのメッセージプッシュであるのに対し、Websocketは双方向のメッセージプッシュであることです。時には、ビジネス要件がそれほど複雑でない場合、SSEの一方向のメッセージプッシュが十分であることがあります。これは、ChatGPT AI会話で使用される技術に類似しています。

Fiber SSEの例

package main

import (
	"bufio"
	"fmt"
	"log"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/valyala/fasthttp"
)

// HTMLクライアントをシミュレートします。ここでは、実際のビジネスシナリオでは、フロントエンドがこれを実装します。
var index = []byte(`<!DOCTYPE html>
<html>
<body>

<h1>SSEメッセージ</h1>
<div id="result"></div>

<script>
if(typeof(EventSource) !== "undefined") {
  var source = new EventSource("http://127.0.0.1:3000/sse");
  source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br>";
  };
} else {
  document.getElementById("result").innerHTML = "申し訳ありませんが、お使いのブラウザはサーバー送信イベントをサポートしていません...";
}
</script>

</body>
</html>
`)

func main() {
	// Fiberインスタンス
	app := fiber.New()

	// 任意のドメインからのアクセスを許可するCORS制約
	app.Use(cors.New(cors.Config{
		AllowOrigins:     "*",
		AllowHeaders:     "Cache-Control",
		AllowCredentials: true,
	}))

	// /パスにアクセスすると、まずフロントエンドページを返し、フロントエンドがバックエンドにSSEメッセージプッシュを開始するようにします。
	app.Get("/", func(c *fiber.Ctx) error {
		c.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)

		return c.Status(fiber.StatusOK).Send(index)
	})

	// SSEメッセージプッシュアドレス
	app.Get("/sse", func(c *fiber.Ctx) error {
		// sse httpヘッダーを設定します。Content-Typeに注意してください。
		c.Set("Content-Type", "text/event-stream")
		c.Set("Cache-Control", "no-cache")
		c.Set("Connection", "keep-alive")
		c.Set("Transfer-Encoding", "chunked")

		// メッセージのプッシュを開始します
		c.Context().SetBodyStreamWriter(fasthttp.StreamWriter(func(w *bufio.Writer) {
			fmt.Println("WRITER")
			var i int
			// クライアントにメッセージを継続的にプッシュすることをシミュレートします
			for {
				i++
				msg := fmt.Sprintf("%d - 現在の時刻は %v", i, time.Now())
				// writerとFprintf関数を使用してメッセージをクライアントにプッシュします。実際のビジネスシナリオでは、フロントエンドの処理の便宜のためにJSONテキストを送信することができます
				fmt.Fprintf(w, "data: メッセージ: %s\n\n", msg)
				fmt.Println(msg)

				// 出力データをクライアントにフラッシュします
				err := w.Flush()
				if err != nil {
					// ウェブブラウザでページを更新すると、新しいSSE接続が確立されますが、最後の接続のみが有効です。したがって、ここで不要な接続を閉じる必要があります
					fmt.Printf("フラッシュ中にエラーが発生しました: %v. http接続を閉じています。\n", err)
					break
				}
				time.Sleep(2 * time.Second)
			}
		}))

		return nil
	})

	// サーバーを起動します
	log.Fatal(app.Listen(":3000"))
}