Sử dụng server-sent events
Baseline
Widely available
*
This feature is well established and works across many devices and browser versions. It’s been available across browsers since January 2020.
* Some parts of this feature may have varying levels of support.
Phát triển ứng dụng web sử dụng server-sent events khá đơn giản. Bạn cần một chút mã ở phía server để truyền phát sự kiện đến front-end, nhưng mã phía client hoạt động gần như giống hệt websockets trong phần xử lý các sự kiện đến. Đây là kết nối một chiều, vì vậy bạn không thể gửi sự kiện từ client đến server.
Nhận sự kiện từ server
API server-sent event được chứa trong giao diện EventSource.
Tạo phiên bản EventSource
Để mở kết nối đến server và bắt đầu nhận sự kiện từ nó, hãy tạo đối tượng EventSource mới với URL của script tạo sự kiện. Ví dụ:
const evtSource = new EventSource("sse-demo.php");
Nếu script tạo sự kiện được lưu trữ trên nguồn gốc khác, đối tượng EventSource mới nên được tạo với cả URL và từ điển tùy chọn. Ví dụ, giả sử script client là trên example.com:
const evtSource = new EventSource("//api.example.com/sse-demo.php", {
withCredentials: true,
});
Lắng nghe sự kiện message
Các thông điệp được gửi từ server không có trường event được nhận dưới dạng sự kiện message. Để nhận sự kiện message, gắn handler cho sự kiện message:
evtSource.onmessage = (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
};
Mã này lắng nghe các sự kiện message đến và thêm văn bản thông điệp vào danh sách trong HTML của tài liệu.
Lắng nghe sự kiện tùy chỉnh
Các thông điệp từ server có trường event được định nghĩa sẽ được nhận dưới dạng sự kiện với tên được cung cấp trong event. Ví dụ:
evtSource.addEventListener("ping", (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
const time = JSON.parse(event.data).time;
newElement.textContent = `ping at ${time}`;
eventList.appendChild(newElement);
});
Mã này sẽ được gọi bất cứ khi nào server gửi thông điệp với trường event được đặt thành ping; sau đó nó phân tích JSON trong trường data và xuất thông tin đó.
Warning: Khi không sử dụng qua HTTP/2, SSE gặp giới hạn về số lượng kết nối mở tối đa, điều này có thể đặc biệt khó chịu khi mở nhiều tab, vì giới hạn theo trình duyệt và được đặt ở số rất thấp (6). Khi sử dụng HTTP/2, số lượng HTTP streams đồng thời tối đa được thương lượng giữa server và client (mặc định là 100).
Gửi sự kiện từ server
Script phía server gửi sự kiện cần phản hồi bằng MIME type text/event-stream. Mỗi thông báo được gửi dưới dạng khối văn bản kết thúc bởi một cặp dòng mới. Để biết chi tiết về định dạng của event stream, xem Định dạng event stream.
Mã PHP cho ví dụ chúng ta đang sử dụng ở đây như sau:
date_default_timezone_set("America/New_York");
header("X-Accel-Buffering: no");
header("Content-Type: text/event-stream");
header("Cache-Control: no-cache");
$counter = rand(1, 10);
while (true) {
// Mỗi giây, gửi sự kiện "ping".
echo "event: ping\n";
$curDate = date(DATE_ISO8601);
echo 'data: {"time": "' . $curDate . '"}';
echo "\n\n";
// Gửi thông điệp đơn giản theo khoảng thời gian ngẫu nhiên.
$counter--;
if (!$counter) {
echo 'data: This is a message at time ' . $curDate . "\n\n";
$counter = rand(1, 10);
}
if (ob_get_contents()) {
ob_end_flush();
}
flush();
// Thoát vòng lặp nếu client đã hủy kết nối (đóng trang)
if (connection_aborted()) break;
sleep(1);
}
Mã trên tạo một sự kiện mỗi giây, với loại sự kiện "ping". Dữ liệu của mỗi sự kiện là đối tượng JSON chứa timestamp ISO 8601 tương ứng với thời gian sự kiện được tạo.
Note: Bạn có thể tìm ví dụ đầy đủ sử dụng mã hiển thị trong bài viết này trên GitHub, xem Simple SSE demo using PHP.
Xử lý lỗi
Khi xảy ra lỗi (như timeout mạng), một sự kiện lỗi được tạo ra. Bạn có thể xử lý lỗi lập trình bằng cách triển khai callback onerror trên đối tượng EventSource:
evtSource.onerror = (err) => {
console.error("EventSource failed:", err);
};
Đóng event stream
Theo mặc định, nếu kết nối giữa client và server đóng, kết nối sẽ được khởi động lại. Kết nối được kết thúc bằng phương thức .close().
evtSource.close();
Định dạng event stream
Event stream là luồng dữ liệu văn bản đơn giản phải được mã hóa bằng UTF-8. Các thông điệp trong event stream được phân tách bởi một cặp ký tự dòng mới. Dấu hai chấm ở đầu dòng về cơ bản là bình luận và bị bỏ qua.
Note: Dòng bình luận có thể được dùng để ngăn kết nối bị timeout; server có thể gửi bình luận định kỳ để giữ kết nối sống.
Mỗi thông điệp gồm một hoặc nhiều dòng văn bản liệt kê các trường của thông điệp đó. Mỗi trường được đại diện bởi tên trường, theo sau là dấu hai chấm, theo sau là dữ liệu văn bản cho giá trị của trường đó.
Các trường
Mỗi thông điệp nhận được có một số kết hợp các trường sau, mỗi trường trên một dòng:
event-
Chuỗi xác định loại sự kiện được mô tả. Nếu được chỉ định, một sự kiện sẽ được gửi trong trình duyệt đến listener cho tên sự kiện được chỉ định; mã nguồn trang web nên sử dụng
addEventListener()để lắng nghe các sự kiện được đặt tên. Handleronmessageđược gọi nếu không có tên sự kiện nào được chỉ định cho thông điệp. data-
Trường dữ liệu cho thông điệp. Khi
EventSourcenhận nhiều dòng liên tiếp bắt đầu bằngdata:, nó sẽ nối chúng lại, chèn ký tự dòng mới giữa mỗi dòng. id-
ID sự kiện để đặt giá trị ID sự kiện cuối cùng của đối tượng
EventSource. retry-
Thời gian kết nối lại. Nếu kết nối đến server bị mất, trình duyệt sẽ đợi thời gian được chỉ định trước khi cố gắng kết nối lại. Đây phải là số nguyên, chỉ định thời gian kết nối lại tính bằng mili giây.
Tất cả các tên trường khác bị bỏ qua.
Ví dụ
Thông điệp chỉ dữ liệu
Trong ví dụ sau, có ba thông điệp được gửi. Thông điệp đầu tiên chỉ là bình luận, vì nó bắt đầu bằng dấu hai chấm. Thông điệp thứ hai chứa trường dữ liệu với giá trị "some text". Thông điệp thứ ba chứa trường dữ liệu với giá trị "another message\nwith two lines".
: this is a test stream
data: some text
data: another message
data: with two lines
Sự kiện được đặt tên
Ví dụ này gửi các sự kiện được đặt tên. Mỗi sự kiện có tên được chỉ định bởi trường event và trường data có giá trị là chuỗi JSON phù hợp.
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}
event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
Kết hợp
Bạn không phải chỉ sử dụng thông điệp không tên hoặc sự kiện có loại; bạn có thể trộn chúng trong một event stream duy nhất.
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
data: Here's a system message of some kind that will get used
data: to accomplish some task.
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}