مقدمة حول Server-Sent Events (SSE)

Server-Sent Events (SSE)، هي تكنولوجيا لدفع الخادم تسمح للعملاء بتلقي تحديثات تلقائية من الخادم عبر اتصال HTTP. تصف كيف يقوم الخادم ببدء نقل البيانات إلى العميل بعد تأسيس اتصال العميل الأولي. وتُستخدم عادة لإرسال تحديثات الرسائل أو تدفقات البيانات المستمرة إلى عملاء المتصفح، بهدف تعزيز تدفقات العميل المحلية عبر واجهة برمجة التطبيقات JavaScript المسماة EventSource. يمكن للعملاء استخدام هذه الواجهة لطلب عنوان URL مُحدد لتلقي تدفقات الأحداث. كجزء من HTML5، تم توحيد واجهة EventSource من قبل WHATWG. نوع الوسائط لـ SSE هو text/event-stream.

ملاحظة: أكبر الفروق بين SSE و Websocket هو أن SSE هو نقل رسائل من الخادم إلى العميل في اتجاه واحد، بينما Websocket هو نقل رسائل في اتجاهين. في بعض الأحيان، عندما تكون متطلبات العمل غير معقدة، قد يكون نقل الرسائل في اتجاه واحد الخاص بـ SSE كافيًا. هذا مشابه للتكنولوجيا المستخدمة في محادثات الذكاء الاصطناعي لـ ChatGPT.

مثال على 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، وفي ال-scenarios عملي، سيقوم الجبهة بتنفيذ ذلك.
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 = "أسف، متصفحك لا يدعم حدثات الخادم...";
}
</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، انتبه لنوع المحتوى
		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("الكاتب")
			var i int
			// يحاكي دفع رسائل بشكل مستمر إلى العميل
			for {
				i++
				msg := fmt.Sprintf("%d - الوقت الآن هو %v", i, time.Now())
				// استخدام الكاتب ووظيفة Fprintf لدفع الرسائل إلى العميل، في ال-scenarios عملي، يمكن إرسال نص 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"))
}