สรุปสั้น ๆ
ปกติแล้วเวลาที่เราสร้างเว็บขึ้นมาเว็บนึง เวลา Client ต้องการข้อมูลอะไรก็ทำการเรียกไปที่ Server ผ่าน API เป็นเรื่องปกติที่ทุกคนน่าจะคุ้นเคยกันอยู่แล้ว แต่ถ้าเราอยากจะให้ Server เป็นคนเรียกไปหา Client บ้าง เชื่อว่าจุดนี้หลาย ๆ คนอาจจะต้องเปิดกูเกิลกันบ้างแล้วว่าจะใช้ท่าไหนดี ใช่แล้วครับ วันนี้เราจะมาดูหนึ่งในท่าที่เราสามารถใช้ได้นั่นคือ Server Sent Event นั่นเอง
เขียนโดย
Sutthinai Boonyingyongchai
MidLevel Software Developer
บทความนี้ตีพิมพ์ และ เผยแพร่เมื่อ 07 เมษายน 2566
Server Sent Event คืออะไร ?
Server Sent Event (SSE) เป็นวิธีการที่จะทำให้ Server สามารถเป็นคนส่งข้อมูลไปหา Client ได้ โดยเป็นการสื่อสารจากทางเดียว และหลังจากที่ Client กับ Server เชื่อมต่อกันเรียบร้อยแล้ว Server ก็จะสามารถเริ่มส่งข้อมูลไปหา Client ได้เรื่อย ๆ ตามที่ต้องการ
แนวคิดของ SSE
การสร้าง Connection ระหว่าง Client และ Server นั้นทำผ่าน HTTP Protocol แบบเดียวกันกับเวลาที่ Client เรียก API เพียงแต่ใน Header จะระบุ Content-Type เป็น text/event-stream เพื่อให้รู้ว่าเป็นเส้นสำหรับ SSE
หลังจากนั้น Server ก็จะส่งข้อมูลไปหา Client ได้เรื่อย ๆ ผ่าน Connection เดิมที่เชื่อมต่อกันค้างไว้ จนกว่าฝั่งใดฝั่งหนึ่งจะปิดการชื่อมต่อ ลำดับการติดต่อกันก็จะหน้าตาประมาณนี้
การใช้ SSE
สำหรับงานที่เหมาะจะนำ SSE มาใช้จะเป็นงานที่ต้องการการอัปเดตอย่างต่อเนื่องจากฝั่ง Server ไปยัง Client ตัวอย่างเช่น
- จำนวนสินค้าคงเหลือ
- ราคาสินค้า
- สถานะพัสดุ
- แชท
- Social media feeds
เพื่อให้เข้าใจ SSE มากขึ้นเราลองมาดูตัวอย่างการเขียนจริง ๆ กันดีกว่า โดยที่เราจะสร้าง SSE ที่ส่งข้อมูลราคาหุ้น (แบบสมมติ) ไปให้ Client เรื่อย ๆ
เริ่มที่ฝั่ง Server ที่เราจะใช้ Express.js สำหรับเป็น API Server โดยที่มี API ชื่อว่า ‘/stock-price’ ที่เอาไว้สำหรับเชื่อมต่อ SSE โดยการทำงานข้างในก็จะใช้คำสั่ง setInterval ในการสุ่มเลขทุกวินาทีเพื่อเป็นราคาหุ้นแบบปลอม ๆ ของเรา โค้ดในไฟล์ server.js ก็จะมีหน้าตาแบบด้านล่างนี้
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// API สำหรับเชื่อมต่อรับการอัพเดตราคาหุ้น
app.get('/stock-price', (req, res) => {
// Set header ให้รู้ว่าเป็น SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// สมมติว่าตรงนี้เป็นการอัพเดตราคาหุ้นทุก ๆ วินาที
const intervalId = setInterval(() => {
const randomNum = Math.floor(Math.random() * 100) + 1;
res.write(`data: ${randomNum}\n\n`);
}, 1000);
// ถ้า Client ปิด Connection ไปแล้วก็ clearInterval ทิ้งด้วย
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
// Start server
const port = 4000;
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// API สำหรับเชื่อมต่อรับการอัพเดตราคาหุ้น
app.get('/stock-price', (req, res) => {
// Set header ให้รู้ว่าเป็น SSE
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
// สมมติว่าตรงนี้เป็นการอัพเดตราคาหุ้นทุก ๆ วินาที
const intervalId = setInterval(() => {
const randomNum = Math.floor(Math.random() * 100) + 1;
res.write(`data: ${randomNum}\n\n`);
}, 1000);
// ถ้า Client ปิด Connection ไปแล้วก็ clearInterval ทิ้งด้วย
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
});
// Start server
const port = 4000;
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
ส่วนในฝั่ง Client ก็จะทำการเชื่อมต่อ SSE ผ่านการประกาศ EventSource และใส่ Path ลงไป โดยที่จะมีการ Handle 2 อีเวนท์คือ ‘onopen’ ตอนที่เชื่อมต่อสำเร็จ แล้วก็ ‘onmessage’ ตอนที่ได้รับข้อมูลมาจาก Server กับอีกหนึ่งปุ่มสำหรับปิดการเชื่อมต่อ เราจะได้ไฟล์ index.html หน้าตาดังต่อไปนี้
<!DOCTYPE html>
<html>
<head>
<title>Stock</title>
</head>
<body>
<p>status: <span id="connect-status">Connecting</span></p>
<button id="stop-sse" onclick="stopSSE()">Stop</button>
<h1>Stock Price:</h1>
<ul id="stocks"></ul>
<script>
const stockElem = document.getElementById('stocks');
const statusElem = document.getElementById('connect-status');
// เชื่อมต่อ API ที่ใช้เป็น SSE
const eventSource = new EventSource('http://localhost:4000/stock-price');
// Handle ตอนที่เปิดการเชื่อมต่อสำเร็จแล้ว
eventSource.onopen = (event) => {
statusElem.textContent = 'Connected'
}
// Handle ตอนที่มีข้อความใหม่ส่งมา
eventSource.onmessage = (event) => {
const number = JSON.parse(event.data);
const listItem = document.createElement('li');
listItem.innerText = number;
stockElem.appendChild(listItem);
};
function stopSSE() {
eventSource.close();
statusElem.textContent = 'Closed'
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Stock</title>
</head>
<body>
<p>status: <span id="connect-status">Connecting</span></p>
<button id="stop-sse" onclick="stopSSE()">Stop</button>
<h1>Stock Price:</h1>
<ul id="stocks"></ul>
<script>
const stockElem = document.getElementById('stocks');
const statusElem = document.getElementById('connect-status');
// เชื่อมต่อ API ที่ใช้เป็น SSE
const eventSource = new EventSource('http://localhost:4000/stock-price');
// Handle ตอนที่เปิดการเชื่อมต่อสำเร็จแล้ว
eventSource.onopen = (event) => {
statusElem.textContent = 'Connected'
}
// Handle ตอนที่มีข้อความใหม่ส่งมา
eventSource.onmessage = (event) => {
const number = JSON.parse(event.data);
const listItem = document.createElement('li');
listItem.innerText = number;
stockElem.appendChild(listItem);
};
function stopSSE() {
eventSource.close();
statusElem.textContent = 'Closed'
}
</script>
</body>
</html>
ผลลัพธ์ที่ได้จะเห็นว่าตอนแรก status จะเป็น Connecting แล้วเปลี่ยนเป็น Connected ตอนที่เชื่อมต่อสำเร็จและมีการแสดงตัวเลขที่ได้รับจาก Server โดยการใส่ <li> ใหม่เพิ่มลงไปเรื่อย ๆ สุดท้ายพอกดปุ่มเพื่อปิดการเชื่อมต่อก็จะไม่มีการรับข้อมูลมาใส่เพิ่มแล้วนั่นเอง
ข้อจำกัด
ถึงแม้ว่าการใช้ SSE จะทำได้ง่าย ตั้งแต่การเขียนโค้ดไปจนถึงโปรโตคอลที่เชื่อมต่อก็ใช้ HTTP ธรรมดา ๆ ทำให้ไม่ต้องกังวลเรื่องปัญหาการเชื่อมต่อต่าง ๆ แต่ว่าทุก ๆ เครื่องมือก็มีข้อจำกัดของมัน และสำหรับ SSE ก็จะมีเรื่องที่ควรจะรู้เอาไว้ก่อนเลือกใช้งาน เช่น
- Unidirectional – ถูกออกแบบมาให้ใช้เพื่อสื่อสารเพียงทางเดียว ถ้าหากต้องการสื่อสารสองทางอาจจะต้องเลือกใช้ WebSocket แทน
- Text Base – SSE นั้นรองรับการส่งข้อมูลในรูปแบบของข้อความเท่านั้น ไม่สามารถส่ง Binary ได้
Concurrent Connections – การใช้ SSE นั้นถูกจำกัดจำนวนการเชื่อมต่อสูงสุดเอาไว้โดยเบราเซอร์ โดยนับเป็นรายโดเมน ทำให้การเปิดเว็บหลาย ๆ แท็บ อาจจะเกินจำนวนสูงสุดได้ (javascript – Server sent events and browser limits – Stack Overflow)
“SSE นั้นเป็นเครื่องมือหรือวิธีการนึงที่น่าจะมีประโยชน์กับการทำงานของเราได้เยอะมาก ๆ ถ้าหากเราเลือกใช้อย่างเหมาะสม หวังว่า SSE จะถูกรับไปเป็นหนึ่งในตัวเลือกเครื่องมือในอนาคตของทุกคนกันนะครับ”
ระบบฝึกทักษะ การเขียนโปรแกรม
ที่พร้อมตรวจผลงานคุณ 24 ชั่วโมง
- โจทย์ปัญหากว่า 200 ข้อ ที่รอท้าทายคุณอยู่
- รองรับ 9 ภาษาโปรแกรมหลัก ไม่ว่าจะ Java, Python, C ก็เขียนได้
- ใช้งานได้ฟรี ! ครบ 20 ข้อขึ้นไป รับ Certificate ไปเลย !!