Bài viết về Server-sent event (SSE), là một kỹ thuật trong HTML5 tạo ra kết nối chuyển dữ liệu một chiều từ server đến client.

Tại sao dùng Server-sent Events (SSE)

SSE tạo ra kết nối một chiều từ server đến client sử dụng giao thức HTTP truyền thống với định nghĩa dữ liệu đơn giản cho phía server (event stream format) và API gọn nhẹ phía client.

So với phương pháp polling từ client lên server để kiểm tra dữ liệu thì SSE hiệu quả hơn rất nhiều do client chỉ cần tạo kết nối HTTP lên server một lần, và server giữ kết nối đó để liên tục gửi data cho client.

WebSockets thì phức tạp và hoành tráng hơn hẳn SSE. Tuy nhiên WebSockets là kết nối hai chiều mà đôi khi ta lại không cần chiều từ client lên server. Ngoài ra WebSockets là một giao thức hoàn toàn khác và đòi hỏi server phải hỗ trợ (Điểm này thì Node.js là số 1, PHP thì cần thêm thư viện ví dụ Ratchet PHP WebSockets).

Định dạng dữ liệu của Server-sent Events

Phía server phải tuân thủ in dữ liệu theo format của text/event-stream như sau:

PrefixKiểuMô tả
data:stringDữ liệu là một chuỗi ký tự kết thúc bằng hai ký tự xuống dòng \n\n.
Ví dụ: data: This is a message\n\n

data luôn là chuỗi cuối cùng được gửi từ server.

Nếu muốn chuỗi dữ liệu có nhiều dòng thì ta có thể dùng nhiều dòng data: nhưng kết thúc chỉ với một ký tự \n, trừ dòng cuối cùng vẫn phải là hai ký tự \n.

Ví dụ:
data: First message\n
data: Second message\n
data: Last message\n\n


Phía client sẽ nhận được chuỗi First message\nSecond message\nLast message.

SSE không có cấu trúc object nhưng bạn có thể thay bằng chuỗi JSON và phía client dùng JSON.parse() để chuyển thành object.
id:stringThông báo cho phía client EventSource biết được sự kiện cuối cùng trong trường hợp mất kết nối.

Khi EventSource thiết lập lại kết nối sẽ gửi lên server một HTTP header là Last-Event-ID để server biết mà trả lại dữ liệu phù hợp.

Ví dụ:
id: some_id\n
data: this is a line\n\n


Phía client EventSource cũng có thể lấy giá trị id này qua event.lastEventId.
event:stringChỉ định tên sự kiện sẽ được EventSource kích hoạt bằng EventSource.addEventListener(eventName, callback).

Ví dụ:
event: bigdata\n
data: Big data is arriving\n\n


Ta đón sự kiện bigdata như sau:

source.addEventListener("bigdata", function(e) {
console.log(e.data); // Big data is arriving
}, false);


Chú ý dữ liệu của data: khi event: diễn ra sẽ không bắt được trong EventSource.onmessage.
retry:integerKhi bị ngắt kết nối do timeout (không có dữ liệu mới sau một thời gian) hoặc máy chủ trả về HTTP code 200, EventSource sẽ đợi một khoảng thời gian retry (milliseconds) trước khi kết nối lại từ đầu.

Nếu không chỉ định retry, mặc định trình duyệt sẽ kết nối lại sau 3 giây.

Mã nguồn tạo kết nối và nhận dữ liệu Server-sent Events phía client (JavaScript)

Bộ khung khai báo cơ bản phía client browser bằng JavaScript như sau:

if ( !!window.EventSource ) {
  // Pass the URL of the event stream to subscribe,
  // it might be relative or absolute
  var source = new EventSource( "sse.php" );

  source.onopen = function(e) {
    console.log("SSE connection was established.");
  };

  source.onerror = function(e) {
    console.error("SSE error:", e);
  }

  // handle incoming data
  source.onmessage = function(e) {
    console.log("SSE data received:", e.id, e.data);
  };

  // custom events
  source.addEventListener("bigdata", function(e) {
    // TODO some specific processing goes here
    console.log("SSE event received:", e.data);
  }, false);
} else {
  console.error( "Your web browser does not support SSE" );
}

Lưu ý khi dùng Server-sent Events

CORS

EvenSource cũng như các HTTP request thông thường từ client đều nhạy cảm với CORS. Server phải cùng domain với script hoặc gửi header Access-Control-Allow-Origin để cho phép domain chứa script.

Server phải giữ kết nối

Phía server nếu không giữ kết nối (ví dụ bằng event loop) mà chỉ đơn thuần trả ít dữ liệu rồi exit (Code 200) thì Server-sent Events sẽ tìm cách kết nối lại liên tục và hành xử chẳng khác gì polling – hoàn toàn không đúng với tinh thần thiết kế của SSE.

Đóng kết nối Server-sent Events từ phía client

Để đóng kết nối từ phía JavaScript client, chúng ta sử dụng hàm source.close().

Nếu bạn không thực hiện đóng kết nối EventSource, trình duyệt khi không thấy có dữ liệu mới sau một thời gian sẽ tự hiểu là bị connection timeout và kết nối lại với server từ đầu.

Yêu cầu đóng kết nối Server-sent Events từ phía server

Phía server có thể chủ động yêu cầu ngừng kết nối này bằng cách gửi một dấu hiệu, ví dụ dùng id: hoặc tạo một sự kiện tùy chỉnh với event: để client biết mà gọi source.close().

Ví dụ server gửi chuỗi:

id: close\n
data: \n\n

Đoạn mã phía client:

source.onmessage = function(e) {
  if (e.lastEventId === 'close')
    source.close();
  else
    console.log(e.data);
};