การแนะนำเกี่ยวกับ Server-Sent Events (SSE)

Server-Sent Events (SSE) เป็นเทคโนโลยีการดึงข้อมูลจากเซิร์ฟเวอร์ที่ช่วยให้ไคลเอนต์ได้รับอัปเดตโดยอัตโนมัติผ่านการเชื่อมต่อ HTTP โดยอธิบายถึงวิธีที่เซิร์ฟเวอร์เริ่มการส่งข้อมูลไปยังไคลเอนต์หลังจากการเชื่อมต่อเริ่มต้นของไคลเอนต์ ซึ่งถูกนำมาใช้เป็นที่นิยมในการส่งอัปเดตข้อความหรือการสตรีมข้อมูลต่อไปให้กับไคลเอนต์ผ่าน API ของ JavaScript ที่เรียกว่า EventSource ไคลเอนต์สามารถใช้ API นี้เพื่อขอ URL ที่เซิร์ฟเวอร์เพื่อรับการสตรีมเหตุการณ์ ใน HTML5 การใช้งานของ EventSource API ได้มีการประมาณระดับมาตรฐานโดย WHATWG ประเภทสื่อสำหรับ SSE คือ text/event-stream

หมายเหตุ: ความแตกต่างที่สำคัญที่สุดระหว่าง SSE และ Websocket คือ SSE เป็นการจัดส่งข้อความจากเซิร์ฟเวอร์ไปยังไคลเอนต์แบบ one-way ในขณะที่ Websocket เป็นการจัดส่งข้อความทางสองทาง บางครั้งเมื่อความต้องการของธุรกิจไม่ซับซ้อนมาก SSE มีการจัดส่งข้อความแบบ one-way เพียงพอ ซึ่งมีความคล้ายคลึงกับเทคโนโลยีที่ใช้ในการสนทนา 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, นี่คือตัวอย่างการไคลเอนต์รับข้อความจากแบ็กเอนด์ผ่าน JS, ในสถานการณ์ธุรกิจจริง ๆ ไคลเอนต์จะดำเนินการนี้
var index = []byte(`<!DOCTYPE html>
<html>
<body>

<h1>SSE Messages</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 = "Sorry, your browser does not support server-sent events...";
}
</script>

</body>
</html>
`)

func main() {
	// Fiber instance
	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 {
		// ตั้งค่าส่วนหัว http สำหรับ sse โปรดทราบถึง 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 text สามารถส่งไปเพื่อความสะดวกในการประมวลผลของไคลเอนต์
				fmt.Fprintf(w, "data: ข้อความ: %s\n\n", msg)
				fmt.Println(msg)

				// Flush ข้อมูลออกไปยังไคลเอนต์
				err := w.Flush()
				if err != nil {
					// การรีเฟรชหน้าเว็บบราวเซอร์จะอยู่การเชื่อมต่อ SSE ใหม่ ๆ แต่เพียงแค่ล่าสุดเท่านั้นที่ยังมีอยู่ ดังนั้นการปิดการเชื่อมต่อที่เสีย โดยเฉพาะึก
					fmt.Printf("เกิดข้อผิดพลาดขณะทำ Flush: %v กำลังปิดการเชื่อมต่อ http\n", err)
					break
				}
				time.Sleep(2 * time.Second)
			}
		}))

		return nil
	})

	// เริ่มเซิร์ฟเวอร์
	log.Fatal(app.Listen(":3000"))
}