Server-Sent Events

Последнее обновление: 09.11.2023

Server-Sent Events или сокращенно SSE представляет еще одну технологией взаимодействия клиента и сервера, которая позволяет серверу отправлять сообщению клиенту. Стоит отметить, что в отличие от WebSockets, коммуникация через Server-Sent Events является однонаправленной: сообщения доставляются в одном направлении - от сервера к клиенту (например, веб-браузеру пользователя). Это делает их отличным выбором, когда нет необходимости отправлять данные от клиента на сервер. Например, Server-Sent Events можно применять для обработки таких вещей, как обновление статуса в социальных сетях, ленты новостей или отправка данных для хранения на стороне клиента.

На веб-странице в коде JavaScript для взаимодействия с сервером применяется интерфейс EventSource. Объкт EventSource по сути представляет собой сервер, который генерирует события или отправляет сообщения. Для создания объекта EventSource применяется конструктор:

new EventSource(url, options)

В качестве первого обязательного параметра в конструктор EventSource передается URL-адрес ресурса на сервере:

const evtSource = new EventSource("/events");

Также опционально можно передать необязательный параметр, который настраивает объект EventSource. Этот параметр представляет объект с одним свойством withCredentials. Это свойство указывает, следует ли включать заголовки CORS для кроссдоменного взаимодействия. По умолчвнию оно равно false

События EventSource

Для управления состояния подключением в EventSource определено ряд событий:

  • open: генерируется при установке соединения. Для установки обработчика события можно применять свойство onopen

  • error: генерируется при возникновении ошибки при установке соединения. Для установки обработчика события можно применять свойство onerror

  • message: генерируется при получении данных с сервера. Для установки обработчика события можно применять свойство onmessage

В качестве параметра обработчики этих событий принимают стандартный объект Event. Пример установки обработчиков событий:

const evtSource = new EventSource("/events");

// с помощью addEventListener
evtSource.addEventListener("open", () => {
  console.log("connection opened");
});
evtSource.addEventListener("error", () => {
  console.log("Error");
});

// с помощью свойств
evtSource.onopen = () => {
  console.log("connection opened");
};
evtSource.onerror = () => {
  console.log("Error");
};

Прием данных

Когда приходят данные с сервера, у объекта WebSocket срабатывает событие message, для установки обработчика которого можно использовать свойство onmessage, либо метод addEventListener().

В обработчик события message передается объект типа MessageEvent. Этот объект предоставляет ряд свойств, которые позволяют извлечь данные ответа сервера:

  • data: возвращает полученные данные

  • origin: хранит адрес отправителя

  • lastEventId: хранит уникальный идентификатор последнего события в виде строки.

  • source: возвращает объект MessageEventSource, который может быть объектом WindowProxy, MessagePort или ServiceWorker и который представляет отправителя полученных данных.

  • ports: возвращает массив объектов MessagePort, которые хранят использованные для отправки порты

Пример получения данных:

const evtSource = new EventSource("/events");
evtSource.onmessage = (event) => { 
    console.log(event.data);    // выводим отправленные данные на консоль
};

Закрытие соединения

Для закрытия соединения применяется метод close():

evtSource.close();

Пример взаимодействия между клиентов и сервером с Server-Sent Events

Рассмотрим небольшой пример взаимодействия между клиентом и сервером с помощью Server-Sent Events. В качестве клиента будет выступать код JavaScript на веб-странице. А в качестве сервера будем использовать Node.js.

Сначала определим код сервере. Для этого создадим файл server.js со следующим кодом:

const http = require("http");
const fs = require("fs");

// данные для отправки клиенту
const messages = ["Привет", "Как дела?", "Что делаешь?", "Ты че спишь?", "Ну пока"];
http.createServer(function(request, response){
     
	if(request.url == "/events"){   // если запрос SSE
        if (request.headers.accept && request.headers.accept === "text/event-stream") {
            sendEvent(response);
        }
        else{
			response.writeHead(400);
			response.end("Bad Request");
		}
    }
    else{   // в остальных случаях отправляем страницу index.html
		fs.readFile("index.html", (_, data) => response.end(data));
    }
}).listen(3000, ()=>console.log("Сервер запущен по адресу http://localhost:3000"));

// отправляем сообщение клиенту
function sendEvent(response) { 
    // формируем заголовки
    response.writeHead(200, {    
        "Content-Type": "text/event-stream",    
        "Cache-Control": "no-cache",    
        "Connection": "keep-alive"  
    });
    const id = (new Date()).toLocaleTimeString();  // определяем идентификатор последнего события
    // раз в 5 секунд отправляем одно сообщение
    setInterval(() => { createServerSendEvent(response, id); }, 5000);
}
// отправляем данные клиенту
function createServerSendEvent(response, id) {
    // генерируем случайное число - индекс для массива messages
    const index = Math.floor(Math.random() * messages.length);
    const message = messages[index];
    response.write("id: " + id + "\n");  
    response.write("data: " + message + "\n\n");
}

Вкратце пройдемся по коду. Сначала подключаются пакеты с функциональностью, которую мы собираемся использовать:

const http = require("http");	// для обработки входящих запросов
const fs = require("fs");		// для чтения с жесткого диска файла index.html

Далее идент определение набора данных, которые будут отправляться клиенту - набор строк с важными сообщениями для клиента:

const messages = ["Привет", "Как дела?", "Что делаешь?", "Ты че спишь?", "Ну пока"];

Для создания сервера применяется функция http.createServer(). В эту функцию передается функция-обработчик, которая вызывается каждый раз, когда к серверу приходит запрос. Эта функция имеет два параметра: request (содержит данные запроса) и response (управляет отправкой ответа).

В функции-обработчике с помощью свойства request.url мы можем узнать, к какому ресурсу на сервере пришел запрос. Так, в данном случае, если пришел запрос по пути "/events", то мы будем взаимодействовать с клиентом с помощью Server-Sent Events:

iif(request.url == "/events"){   // если запрос SSE
    if (request.headers.accept && request.headers.accept === "text/event-stream") {
        sendEvent(response);
    }
    else{
		response.writeHead(400);
		response.end("Bad Request");
	}
}

И тут важно, чтобы в запросе были установлен заголовок "Accept": он должен иметь значение "text/event-stream". Если это так, то для отправки данных клиенту выполняем функцию sendEvent(), в которую передаем объект ответа response. Если же заголовки не установлены, то отправляем в ответ ошибку 400.

В функции sendEvent формируем заголовки ответа, получаем текущее время, которое будет выступать в качестве идентификатора события, и запускаем таймер с отправкой клиенту данных каждые 5 секунд.

function sendEvent(response) { 
    // формируем заголовки
    response.writeHead(200, {    
        "Content-Type": "text/event-stream",    
        "Cache-Control": "no-cache",    
        "Connection": "keep-alive"  
    });
    const id = (new Date()).toLocaleTimeString();  // определяем идентификатор последнего события
    // раз в 5 секунд отправляем одно сообщение
    setInterval(() => { createServerSendEvent(response, id); }, 5000);
}

Собственно отправка данных производится в функции createServerSendEvent:

function createServerSendEvent(response, id) {
    // генерируем случайное число - индекс для массива messages
    const index = Math.floor(Math.random() * messages.length);
    const message = messages[index];
    response.write("id: " + id + "\n");  
    response.write("data: " + message + "\n\n");
}

Здесь получаем случайное число, которое находится в диапазоне от 0 до messages.length и которое будет служить в качестве индекса, и по этому индексу выбирает некоторое сообщение. Далее формируем ответ. Устанавливаем идентификатор последнего события

response.write("id: " + id + "\n"); 

И устанавливаем собственно данные:

response.write("data: " + message + "\n\n");

Если запрос пришел на сервер по какому-то другому пути, то отправляем файл index.html, который мы дальше определим:

else{
	fs.readFile("index.html", (_, data) => response.end(data));
}

Для считывания файлов применяется встроенная функция fs.readFile(). Первый параметр функции - адрес файла (в данном случае предполагается, что файл index.html находится в одной папке с файлом сервера server.js). Второй параметр - функция, которая вызывается после считывания файла и получет его содержимое через свой второй параметр data. Затем считанное содежимое также может быть отпавлено с помощью функции response.end(data).

В конце с помощью функции listen() запускаем веб-сервер на 3000 порту. То есть сервер будет запускаться по адресу http://localhost:3000/

Теперь в папке сервера определим простенький файл index.html со следующим кодом:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>METANIT.COM</title>
</head>
<body>
<ul id="list"></ul>
<script>
const source = new EventSource("/events");      
const list = document.getElementById("list")      
source.addEventListener("message", (e) => {
    const listItem = document.createElement("li");       
    listItem.textContent += e.data;
    list.appendChild(listItem);
}); 
</script>
</body>
</html>

Здесь при получении данных от сервера добавляем их в список на веб-странице.

Теперь в консоли перейдем к папке сервера с помощью команды cd и запустим сервер с помощью команды node server.js

C:\app>node server.js
Сервер запущен по адресу http://localhost:3000

После запуска сервера мы можем перейти в браузере по адресу http://localhost:3000, нам отобразится страница, в javascript-коде которой будет происходить получение данных от сервера и их вывод на веб-страницу:

Server-Sent Events и EventSource в javascript
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850