เทคโนโลยี Server-Sent Events (SSE) ที่ถูกประเมินค่าต่ำเกินไป
(igorstechnoclub.com)Server-Sent Events (SSE) ถูกประเมินค่าต่ำเกินไป
- นักพัฒนาส่วนใหญ่รู้จัก WebSockets แต่ SSE เป็นทางเลือกที่เรียบง่ายกว่าและมักถูกมองข้าม
- SSE สร้างช่องทางสื่อสารแบบทางเดียวจากเซิร์ฟเวอร์ไปยังไคลเอนต์ผ่าน HTTP
- ต่างจากการเชื่อมต่อแบบสองทางของ WebSockets, SSE จะคงการเชื่อมต่อ HTTP แบบเปิดไว้เพื่อส่งอัปเดตจากเซิร์ฟเวอร์ไปยังไคลเอนต์
ทำไม SSE จึงถูกประเมินค่าต่ำเกินไป
- ความนิยมของ WebSocket: ความสามารถในการสื่อสารแบบ full-duplex ของ WebSockets ทำให้แนวทางที่เรียบง่ายของ SSE ถูกกลบไป
- มุมมองต่อข้อจำกัด: แม้ลักษณะแบบทางเดียวอาจดูจำกัด แต่ก็เพียงพอสำหรับหลายกรณีการใช้งาน
จุดแข็งสำคัญของ SSE
-
ความเรียบง่ายในการติดตั้งใช้งาน
- ใช้ประโยชน์จากโปรโตคอล HTTP มาตรฐาน จึงตัดความซับซ้อนของการจัดการการเชื่อมต่อ WebSocket ออกไป
-
ความเข้ากันได้กับโครงสร้างพื้นฐาน
- ทำงานร่วมกับโครงสร้างพื้นฐาน HTTP ที่มีอยู่ได้อย่างราบรื่น:
- load balancer
- proxy
- firewall
- เซิร์ฟเวอร์ HTTP มาตรฐาน
- ทำงานร่วมกับโครงสร้างพื้นฐาน HTTP ที่มีอยู่ได้อย่างราบรื่น:
-
ประสิทธิภาพด้านทรัพยากร
- ใช้ทรัพยากรน้อยกว่า WebSockets:
- ธรรมชาติแบบทางเดียว
- ใช้การเชื่อมต่อ HTTP มาตรฐาน
- ไม่ต้องคอยดูแล socket แบบต่อเนื่อง
- ใช้ทรัพยากรน้อยกว่า WebSockets:
-
การเชื่อมต่อใหม่อัตโนมัติ
- มีการรองรับในตัวจากเบราว์เซอร์:
- จัดการการหลุดของการเชื่อมต่อ
- พยายามเชื่อมต่อใหม่อัตโนมัติ
- มอบประสบการณ์เรียลไทม์ที่ยืดหยุ่น
- มีการรองรับในตัวจากเบราว์เซอร์:
-
ความหมายเชิงโครงสร้างที่ชัดเจน
- รูปแบบการสื่อสารทางเดียวช่วยให้เกิดสิ่งต่อไปนี้:
- การแยกขอบเขตความรับผิดชอบที่ชัดเจน
- การไหลของข้อมูลที่เข้าใจง่าย
- ตรรกะของแอปพลิเคชันที่เรียบง่ายขึ้น
- รูปแบบการสื่อสารทางเดียวช่วยให้เกิดสิ่งต่อไปนี้:
การใช้งานจริง
- ฟีดข่าวแบบเรียลไทม์และอัปเดตโซเชียล
- ราคาหุ้นและข้อมูลการเงิน
- แถบความคืบหน้าและการติดตามงาน
- การสตรีม log จากเซิร์ฟเวอร์
- การแก้ไขร่วมกัน (สำหรับการอัปเดต)
- ลีดเดอร์บอร์ดของเกม
- ระบบติดตามตำแหน่ง
ตัวอย่างการติดตั้งใช้งาน
ฝั่งเซิร์ฟเวอร์ (Flask)
- เส้นทาง
/streamจัดการการเชื่อมต่อ SSE generate_random_data()สร้างอีเวนต์ที่จัดรูปแบบแล้วอย่างต่อเนื่อง- MIME type
text/event-streamใช้ระบุโปรโตคอล SSE stream_with_contextใช้คง application context ของ Flask ไว้
ฝั่งไคลเอนต์ (JavaScript)
- อ็อบเจ็กต์
EventSourceใช้จัดการการเชื่อมต่อ SSE - handler
onmessageใช้ประมวลผลอีเวนต์ที่ได้รับ onerrorใช้จัดการปัญหาการเชื่อมต่อ- เบราว์เซอร์จะจัดการการเชื่อมต่อใหม่อัตโนมัติ
ข้อจำกัดและสิ่งที่ต้องพิจารณา
-
การสื่อสารทางเดียว
- ทำได้เฉพาะจากเซิร์ฟเวอร์ไปยังไคลเอนต์
- การสื่อสารจากไคลเอนต์ไปยังเซิร์ฟเวอร์ต้องใช้ HTTP request แยกต่างหาก
-
การรองรับของเบราว์เซอร์
- รองรับได้ดีในเบราว์เซอร์สมัยใหม่
- เบราว์เซอร์รุ่นเก่าอาจต้องใช้ polyfill
-
รูปแบบข้อมูล
- รองรับข้อมูลแบบข้อความเป็นหลัก
- ข้อมูลแบบไบนารีต้องมีการเข้ารหัส (เช่น Base64)
แนวทางปฏิบัติที่ดี
-
การจัดการข้อผิดพลาด
- จัดการข้อผิดพลาดของการเชื่อมต่อด้วย
eventSource.onerror
- จัดการข้อผิดพลาดของการเชื่อมต่อด้วย
-
การจัดการการเชื่อมต่อ
- ปิดและเก็บกวาดการเชื่อมต่อเมื่อเสร็จสิ้น
-
กลยุทธ์การเชื่อมต่อใหม่
- กำหนดจำนวนครั้งสูงสุดในการลองใหม่และติดตั้งตรรกะการเชื่อมต่อใหม่
ตัวอย่างจริง: การติดตั้งใช้งานของ ChatGPT
- โมเดลภาษาขนาดใหญ่สมัยใหม่ (LLM) ใช้ SSE เพื่อส่งคำตอบแบบสตรีม
- รูปแบบหลัก:
- ส่งคืนเฮดเดอร์
content-type: text/event-stream - สตรีมบล็อกข้อมูลที่คั่นด้วย
\r\n\r\n
- ส่งคืนเฮดเดอร์
บทสรุป
- SSE มอบโซลูชันที่สวยงามสำหรับการสื่อสารแบบเรียลไทม์ระหว่างเซิร์ฟเวอร์กับไคลเอนต์
- ความเรียบง่าย ประสิทธิภาพ และการผสานเข้ากับโครงสร้างพื้นฐานเดิม ทำให้มันเป็นตัวเลือกที่เหมาะสมสำหรับหลายแอปพลิเคชัน
- แม้ WebSockets จะยังมีประโยชน์สำหรับการสื่อสารสองทาง แต่ SSE มอบโซลูชันที่เหมาะสมและตรงจุดกว่าในสถานการณ์การสตรีมข้อมูลแบบทางเดียว
5 ความคิดเห็น
ผมได้นำ OpenAI ไปใช้งานผ่าน REST และได้ใช้ SSE จริง ๆ
ถ้าเป็นสถานการณ์ที่ต้องการการสื่อสารทางเดียว ผมคิดว่าจะเลือกใช้มันอย่างแน่นอน
SSE มักไม่ถูกบล็อกโดยอุปกรณ์ความปลอดภัย (เช่น web application firewall หรือระบบความปลอดภัยอัจฉริยะ) แต่ก็มักเจอกรณีที่สตรีมเป็นหน่วยตามอักขระขึ้นบรรทัดใหม่ไม่ได้ โดยเฉพาะในสภาพแวดล้อม (on-premises) ที่ตัวกลางจะรับ response ทั้งหมดไว้ก่อนแล้วค่อยส่งรวดเดียว
น่าเสียดายจริง ๆ ที่ OpenAPI ไม่รองรับ SSE
เป็นวิธีที่ดีมากในการสร้างการสื่อสารแบบสองทางในสภาพแวดล้อม NAT ครับ
ความคิดเห็นจาก Hacker News
Mercure เป็นโปรโตคอลเปิดที่อิงกับ SSE และใช้เป็นทางเลือกแทนโซลูชันที่อิงกับ WebSockets ได้ Mercure ทำงานโดยมีฮับแยกอิสระเป็นศูนย์กลางซึ่งคงการเชื่อมต่อ SSE แบบต่อเนื่องกับไคลเอนต์ไว้ และมี HTTP API แบบเรียบง่ายให้ทั้งแอปฝั่งเซิร์ฟเวอร์และไคลเอนต์ใช้งาน Mercure เพิ่มความสามารถอย่างกลไกยืนยันตัวตนแบบ JWT, การสมัครรับข้อมูลหลายหัวข้อผ่านการเชื่อมต่อเดียว, การบันทึกเหตุการณ์ และการปรับสถานะอัตโนมัติเมื่อเกิดปัญหาเครือข่าย
ข้อเสียใหญ่ของ SSE คือหากไม่ใช่ HTTP/2 จะมีข้อจำกัดเรื่องจำนวนการเชื่อมต่อสูงสุด ซึ่งอาจเป็นปัญหาได้เมื่อเปิดหลายแท็บ เพราะเบราว์เซอร์จำกัดจำนวนไว้ค่อนข้างต่ำ
มีการใช้ SSE ใน CLI ของ Doppler เพื่อทำฟีเจอร์รีสตาร์ตอัตโนมัติ โดยรับอีเวนต์จากเซิร์ฟเวอร์ผ่าน SSE แล้วดึงข้อมูลซีเคร็ตล่าสุดมาใส่ให้กับโปรเซสของแอปพลิเคชัน เหตุผลที่เลือก SSE แทน WebSockets คือเพื่อไม่ให้เพิ่ม dependency ในแอปพลิเคชัน Golang และต้องส่งอีเวนต์ "ping" เป็นระยะเพื่อแก้ปัญหา HTTP timeout
ลักษณะทางเดียวของ SSE อาจดูเหมือนมีข้อจำกัด แต่ในหลายกรณีก็เพียงพอแล้ว ข้อจำกัดหลักของ SSE คือรองรับเฉพาะข้อความเท่านั้น และมีข้อจำกัดการเชื่อมต่อของเบราว์เซอร์บน HTTP/1.1 หากใช้ HTTP/2 ขึ้นไป ข้อจำกัดเรื่องการเชื่อมต่อก็จะไม่เป็นปัญหา หากให้ความสำคัญกับประสิทธิภาพ สามารถใช้ fetch และ ReadableStream เพื่อเลือกโซลูชันที่ยืดหยุ่นกว่าและมี overhead น้อยกว่าได้
เพราะ SSE เรียบง่าย นักพัฒนาหลายคนจึงมักไม่ได้ใช้ implementation ที่เหมาะสม และหันไป parse data chunk ด้วย regular expression ซึ่งอาจเป็นปัญหาได้ เพราะ SSE รองรับคอมเมนต์ในสตรีม
Data-star.dev เป็นไลบรารีฝั่งฟรอนต์เอนด์ที่มุ่งเน้นการสตรีม hypermedia response ผ่าน SSE พัฒนาด้วยการใช้ Go และ NATS เป็นเทคโนโลยีแบ็กเอนด์ และเข้ากันได้กับทุก implementation ของ SSE
SSE ไม่ได้ถูกประเมินค่าต่ำเกินไป ที่จริง Open AI ก็ใช้มันกับการสตรีมผลลัพธ์ที่เสร็จสมบูรณ์ การนำ SSE ไปใช้ในโค้ดเบส ReactJS เป็นเรื่องยาก และในตอนนั้น Axios ยังไม่รองรับ จึงต้องใช้ native fetch
เมื่อมีการนำ SSE ไปใช้ในโปรเจ็กต์เว็บ พอเปิดเกิน 6 แท็บ เว็บไซต์ก็หยุดทำงาน Firefox นับการเชื่อมต่อ SSE รวมอยู่ในข้อจำกัดการเชื่อมต่อสูงสุด 6 รายการต่อโฮสต์ ทำให้คำขอเพิ่มเติมถูกบล็อก
SSE มักถูกประเมินค่าต่ำเกินไปตอนที่มันทำงานได้ดี แต่ในโปรเจ็กต์ที่กำลังทำอยู่ตอนนี้กลับเจอปัญหาเรื่องการยืนยันตัวตนและปัญหา keep-alive ของ tunnel ซึ่งไม่ใช่ปัญหาของโปรโตคอลเอง แต่การหาวิธีแก้ทำได้ยาก