กิจกรรมที่เซิร์ฟเวอร์ส่ง (SSE) ส่งการอัปเดตอัตโนมัติไปยังไคลเอ็นต์จากเซิร์ฟเวอร์ด้วย HTTP การเชื่อมต่อ เมื่อทำการเชื่อมต่อแล้ว เซิร์ฟเวอร์จะเริ่มข้อมูลได้ การแพร่เชื้อ
คุณอาจต้องการใช้ SSE เพื่อส่งข้อความ Push จากเว็บแอป SSE จะส่งข้อมูลไปในทิศทางเดียว ดังนั้นคุณจะไม่ได้รับการอัปเดตจาก ของคุณ
แนวคิดของ SSE อาจมีความคุ้นเคย เว็บแอป "สมัครใช้บริการ" ไปยังสตรีมของ อัปเดตที่สร้างโดยเซิร์ฟเวอร์ และเมื่อใดก็ตามที่มีกิจกรรมใหม่เกิดขึ้น จะส่งไปยังไคลเอ็นต์ แต่เพื่อให้เข้าใจเหตุการณ์ที่ส่งโดยเซิร์ฟเวอร์ เราต้อง เข้าใจข้อจำกัดของรุ่นก่อนหน้า AJAX ซึ่งรวมถึงเนื้อหาต่อไปนี้
การหยั่งสัญญาณ: แอปพลิเคชันจะสำรวจข้อมูลในเซิร์ฟเวอร์ซ้ำหลายครั้ง เทคนิคนี้ ใช้โดยแอปพลิเคชัน AJAX ส่วนใหญ่ เมื่อใช้โปรโตคอล HTTP การดึงข้อมูล ข้อมูลจะเกี่ยวข้องกับคำขอและรูปแบบการตอบกลับ ลูกค้าส่งคำขอ และรอให้เซิร์ฟเวอร์ตอบกลับด้วยข้อมูล ถ้าไม่มีตัวเลือกใดๆ แท็ก แสดงการตอบสนอง การโพลเพิ่มเติมจะทำให้มีการใช้โอเวอร์เฮด HTTP มากขึ้น
การสำรวจแบบยาว (Hanging GET / COMET): หากเซิร์ฟเวอร์ไม่มีข้อมูล ว่าง เซิร์ฟเวอร์จะระงับคำขอไว้จนกว่าจะมีข้อมูลใหม่ ดังนั้น เทคนิคนี้มักเรียกว่า "Hanging GET" วันและเวลา ข้อมูลพร้อมใช้งาน เซิร์ฟเวอร์ตอบสนอง ปิดการเชื่อมต่อ แล้วกระบวนการก็จะต้องทำซ้ำๆ ดังนั้น เซิร์ฟเวอร์จึงตอบสนองด้วย ข้อมูลใหม่ ในการตั้งค่าขั้นตอนนี้ นักพัฒนาซอฟต์แวร์มักจะใช้การแฮ็ก เช่น การต่อท้าย กำหนดแท็กสคริปต์ให้กับ "ไม่สิ้นสุด" iframe
กิจกรรมที่ส่งโดยเซิร์ฟเวอร์ได้รับการออกแบบตั้งแต่ต้นให้มีประสิทธิภาพ เมื่อสื่อสารกับ SSE เซิร์ฟเวอร์สามารถพุชข้อมูลไปยัง ได้ทุกเมื่อที่ต้องการ โดยไม่ต้องส่งคำขอเริ่มต้น กล่าวคือ คุณสามารถสตรีมการอัปเดตจากเซิร์ฟเวอร์สู่ไคลเอ็นต์ได้แบบเรียลไทม์ SSE เปิดแชแนลแบบทิศทางเดียวระหว่างเซิร์ฟเวอร์และไคลเอ็นต์
ความแตกต่างหลักๆ ระหว่างเหตุการณ์ที่ส่งโดยเซิร์ฟเวอร์กับการจัดทำแบบสำรวจนานคือ SSE จะจัดการโดยเบราว์เซอร์โดยตรง และผู้ใช้เพียงแค่ต้องคอยฟังข้อความเท่านั้น
กิจกรรมที่เซิร์ฟเวอร์ส่งเมื่อเทียบกับ WebSockets
เหตุใดคุณจึงเลือกเหตุการณ์ที่ส่งโดยเซิร์ฟเวอร์มากกว่า WebSockets เป็นคำถามที่ดี
WebSockets มีโปรโตคอลที่สมบูรณ์พร้อม การสื่อสารแบบ 2 ทิศทางเต็มรูปแบบ ช่องทางแบบ 2 ทางเหมาะสำหรับ เกม แอปรับส่งข้อความ และกรณีการใช้งานที่คุณต้องการการอัปเดตแบบเกือบเรียลไทม์ ทั้ง 2 ทาง
อย่างไรก็ตาม บางครั้งคุณต้องการการสื่อสารทางเดียวจากเซิร์ฟเวอร์เท่านั้น
ตัวอย่างเช่น เมื่อเพื่อนอัปเดตสถานะ แถบราคาหุ้น ฟีดข่าวสาร หรือ
กลไกการพุชข้อมูลอัตโนมัติอื่นๆ กล่าวคือ
การอัปเดตฐานข้อมูล SQL ในเว็บหรือที่เก็บออบเจ็กต์ IndexedDB ฝั่งไคลเอ็นต์
หากต้องการส่งข้อมูลไปยังเซิร์ฟเวอร์ XMLHttpRequest
คือเพื่อนเสมอ
ระบบจะส่ง SSE ผ่าน HTTP ไม่มีโปรโตคอลหรือเซิร์ฟเวอร์พิเศษ ในการใช้งานจริง WebSockets ต้องใช้ Full-duplex และเซิร์ฟเวอร์ WebSocket ใหม่เพื่อจัดการโปรโตคอล
นอกจากนี้ เหตุการณ์ที่ส่งโดยเซิร์ฟเวอร์มีฟีเจอร์ที่หลากหลายที่ WebSockets ไม่มี ด้วยการออกแบบ ซึ่งรวมถึงการเชื่อมต่อใหม่โดยอัตโนมัติ รหัสเหตุการณ์ และความสามารถในการส่ง เหตุการณ์ที่กำหนดเอง
สร้าง EventSource ด้วย JavaScript
หากต้องการสมัครรับข้อมูลสตรีมเหตุการณ์ ให้สร้างออบเจ็กต์ EventSource
และส่งผ่านออบเจ็กต์ดังกล่าว
URL ของสตรีม:
const source = new EventSource('stream.php');
ถัดไป ให้ตั้งค่าเครื่องจัดการสำหรับเหตุการณ์ message
คุณสามารถเลือก
ฟัง open
และ error
:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
เมื่อพุชการอัปเดตจากเซิร์ฟเวอร์ ตัวแฮนเดิล onmessage
จะเริ่มทํางาน
และข้อมูลใหม่จะใช้ได้ในพร็อพเพอร์ตี้ e.data
ส่วนมหัศจรรย์ก็คือ
เมื่อใดก็ตามที่ปิดการเชื่อมต่อ เบราว์เซอร์จะเชื่อมต่อกับ
หลังผ่านไปประมาณ 3 วินาที การติดตั้งเซิร์ฟเวอร์ของคุณยังสามารถควบคุม
หมดเวลาการเชื่อมต่ออีกครั้งนี้
เท่านี้เอง ตอนนี้ลูกค้าประมวลผลเหตุการณ์จาก stream.php
ได้แล้ว
รูปแบบสตรีมกิจกรรม
การส่งสตรีมเหตุการณ์จากแหล่งที่มาคือการสร้าง
การตอบกลับด้วยข้อความธรรมดา ที่แสดงพร้อมประเภทเนื้อหา text/event-stream
ที่เป็นไปตามรูปแบบ SSE
ในรูปแบบพื้นฐาน คำตอบควรมีบรรทัด data:
ตามด้วย
แล้วตามด้วย "\n" สองตัว อักขระในการสิ้นสุดการสตรีม:
data: My message\n\n
ข้อมูลหลายบรรทัด
หากข้อความยาวขึ้น คุณสามารถแบ่งข้อความได้โดยใช้บรรทัด data:
หลายบรรทัด
บรรทัดติดกันสองบรรทัดขึ้นไปที่ขึ้นต้นด้วย data:
จะถือว่าเป็น
ข้อมูลชิ้นเดียว ซึ่งหมายความว่ามีเหตุการณ์ message
เพียง 1 เหตุการณ์เท่านั้นที่เริ่มทำงาน
แต่ละบรรทัดควรลงท้ายด้วย "\n" เดียว (ยกเว้นพารามิเตอร์สุดท้าย ซึ่งควรสิ้นสุด
2) ผลลัพธ์ที่ส่งไปยังเครื่องจัดการ message
คือสตริงเดียว
ที่คั่นด้วยอักขระขึ้นบรรทัดใหม่ เช่น
data: first line\n
data: second line\n\n</pre>
ซึ่งจะสร้าง "บรรทัดแรก\nบรรทัดที่สอง" ใน e.data
จากนั้น คนหนึ่งสามารถใช้
e.data.split('\n').join('')
เพื่อสร้างข้อความ San "\n" ใหม่ อักขระ
ส่งข้อมูล JSON
การใช้หลายบรรทัดจะช่วยให้คุณส่ง JSON ได้โดยไม่ส่งผลกระทบต่อไวยากรณ์
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
และอาจใช้โค้ดฝั่งไคลเอ็นต์เพื่อจัดการสตรีมนั้น ดังนี้
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
เชื่อมโยงรหัสกับเหตุการณ์
คุณสามารถส่งรหัสที่ไม่ซ้ำกันพร้อมเหตุการณ์สตรีมโดยใส่บรรทัดที่ขึ้นต้นด้วย
id:
:
id: 12345\n
data: GOOG\n
data: 556\n\n
การตั้งค่ารหัสช่วยให้เบราว์เซอร์สามารถติดตามเหตุการณ์ล่าสุดที่เริ่มทำงานได้
การเชื่อมต่อกับเซิร์ฟเวอร์ถูกตัด ส่วนหัว HTTP พิเศษ (Last-Event-ID
) คือ
ที่ตั้งไว้ด้วยคำขอใหม่ วิธีนี้จะช่วยให้เบราว์เซอร์ระบุได้ว่าเหตุการณ์ใดที่เหมาะสมที่จะเริ่มทำงาน
เหตุการณ์ message
มีพร็อพเพอร์ตี้ e.lastEventId
ควบคุมระยะหมดเวลาการเชื่อมต่อใหม่
เบราว์เซอร์พยายามเชื่อมต่อกับแหล่งที่มาอีกครั้งประมาณ 3 วินาที
หลังจากปิดการเชื่อมต่อแต่ละครั้ง คุณสามารถเปลี่ยนระยะหมดเวลาโดยใส่
บรรทัดที่เริ่มต้นด้วย retry:
ตามด้วยจำนวนมิลลิวินาที
ก่อนที่จะลองเชื่อมต่อใหม่
ตัวอย่างต่อไปนี้จะพยายามเชื่อมต่อใหม่หลังจากผ่านไป 10 วินาที
retry: 10000\n
data: hello world\n\n
ระบุชื่อเหตุการณ์
แหล่งที่มาของเหตุการณ์เดียวสามารถสร้างเหตุการณ์ประเภทต่างๆ ได้โดยการรวม
ชื่อเหตุการณ์ หากมีบรรทัดที่ขึ้นต้นด้วย event:
ตามด้วยชื่อที่ไม่ซ้ำกันของเหตุการณ์ เหตุการณ์จะเชื่อมโยงกับชื่อนั้น
ในไคลเอ็นต์ Listener เหตุการณ์สามารถตั้งค่าเพื่อฟังเหตุการณ์ที่เฉพาะเจาะจงนั้นได้
เช่น เอาต์พุตของเซิร์ฟเวอร์ต่อไปนี้จะส่งเหตุการณ์ 3 ประเภท "ข้อความ" ทั่วไป เหตุการณ์, 'userlogon' และ "update" กิจกรรม:
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
เมื่อตั้งค่า Listener เหตุการณ์ในไคลเอ็นต์แล้ว ให้ทำดังนี้
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
ตัวอย่างเซิร์ฟเวอร์
การใช้เซิร์ฟเวอร์เบื้องต้นใน PHP มีดังนี้
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
ต่อไปนี้เป็นการใช้งานที่คล้ายกันใน Node JS โดยใช้แท็ก เครื่องจัดการด่วน:
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
ยกเลิกสตรีมกิจกรรม
โดยปกติแล้ว เบราว์เซอร์จะเชื่อมต่อกับแหล่งที่มาของเหตุการณ์อีกครั้งโดยอัตโนมัติเมื่อทำการเชื่อมต่อ ปิด แต่ลักษณะการทำงานดังกล่าวสามารถยกเลิกได้จากไคลเอ็นต์หรือเซิร์ฟเวอร์
หากต้องการยกเลิกสตรีมจากไคลเอ็นต์ ให้โทรหาหมายเลขต่อไปนี้
source.close();
หากต้องการยกเลิกสตรีมจากเซิร์ฟเวอร์ ให้ตอบกลับด้วยอีเมลที่ไม่ใช่ text/event-stream
Content-Type
หรือแสดงผลสถานะ HTTP อื่นที่ไม่ใช่ 200 OK
(เช่น 404 Not Found
)
ทั้งสองวิธีจะป้องกันไม่ให้เบราว์เซอร์เริ่มต้นการเชื่อมต่ออีกครั้ง
คำเตือนเกี่ยวกับความปลอดภัย
คำขอที่สร้างโดย EventSource จะอยู่ภายใต้นโยบายต้นทางเดียวกันกับ API เครือข่ายอื่นๆ เช่น การดึงข้อมูล หากคุณต้องการให้ปลายทาง SSE ในเซิร์ฟเวอร์ เข้าถึงได้จากหลายต้นทาง โปรดอ่านวิธีเปิดใช้ด้วย กลไกการแชร์ทรัพยากรข้ามโดเมน (CORS)