diff --git a/files/ru/web/api/server-sent_events/using_server-sent_events/index.md b/files/ru/web/api/server-sent_events/using_server-sent_events/index.md index c99102215e401b..7026de607b56ca 100644 --- a/files/ru/web/api/server-sent_events/using_server-sent_events/index.md +++ b/files/ru/web/api/server-sent_events/using_server-sent_events/index.md @@ -1,109 +1,139 @@ --- -title: Using server-sent events +title: Использование отправляемых сервером событий slug: Web/API/Server-sent_events/Using_server-sent_events +l10n: + sourceCommit: 00f46adb5616d826821d63b11eac285faf1cf4a5 --- {{DefaultAPISidebar("Server Sent Events")}} -Разрабатывать web-приложения, использующие [Server-Sent Events](/ru/docs/Web/API/Server-sent_events) намного проще, чем с использованием websockets. Нужно всего лишь немного кода на стороне сервера, чтобы переправлять события web-приложению, но клиентская часть кода для обработки этих событий работает почти точно так же, как и для любых других событий. +Разрабатывать веб-приложения, использующие [отправляемые сервером события](/ru/docs/Web/API/Server-sent_events) (англ. _Server-sent events_, _SSE_) не сложно. +Требуется немного кода на стороне сервера для передачи событий веб-приложению, а клиентская часть кода для обработки этих событий работает почти идентично [веб-сокетам](/ru/docs/Web/API/WebSockets_API). +Это одностороннее соединение, поэтому нельзя отправлять события от клиента на сервер. ## Получение событий от сервера -Server-Sent Event API содержится внутри интерфейса {{domxref("EventSource")}}. Чтобы открыть соединение с сервером для начала записи событий, которые он присылает, необходимо создать новый объект `EventSource`, который будет указывать на URI скрипта, который создаёт события. Например: +API отправляемые сервером событий содержит в себе интерфейс {{domxref("EventSource")}}. + +### Создание экземпляра `EventSource` + +Чтобы открыть соединение с сервером и начать получать от него события, необходимо создать новый объект `EventSource` с URL-адресом скрипта, который генерирует события. +Например: ```js -const evtSource = new EventSource("ssedemo.php"); +const evtSource = new EventSource("sse-demo.php"); ``` -Если файл с генератором событий размещён на другом домене, то должен быть создан новый объект `EventSource` в который следует передать помимо URI ещё и словарь опций. Например, если предположить, что клиентский скрипт находится на `example.com`: +Если скрипт генератора событий размещён на другом домене, необходимо создать новый объект `EventSource` с URL и словарем параметров. +Предположим, что скрипт клиента находится на `example.com`: ```js -const evtSource = new EventSource("//api.example.com/ssedemo.php", { +const evtSource = new EventSource("//api.example.com/sse-demo.php", { withCredentials: true, }); ``` -Как только вы создали экземпляр `EventSource`, вы можете начать получать сообщения с сервера, добавив обработчик события [`message`](/ru/docs/Web/Events/message) : +### Подписка на события `message` + +Сообщения, отправленные с сервера и не имеющие поля [`event`](#event), принимаются как события `message`. +Чтобы получать сообщения из событий, необходимо установить обработчик для события {{domxref("EventSource.message_event", "message")}}: ```js -evtSource.onmessage = function (event) { +evtSource.onmessage = (event) => { const newElement = document.createElement("li"); const eventList = document.getElementById("list"); - newElement.innerHTML = "message: " + event.data; + newElement.textContent = `Сообщение: ${event.data}`; eventList.appendChild(newElement); }; ``` -Этот код обрабатывает входящие сообщения (то есть уведомления от сервера, на которых нет поля `event`) и добавляет текст сообщения в список в HTML-документе. +Этот код обрабатывает входящие события и добавляет текст сообщения в список в HTML-документе. + +### Подписка на пользовательские события -Вы также можете обрабатывать события, используя `addEventListener()`: +Сообщения от сервера, у которых определено поле `event`, принимаются как события с именем, указанным в `event`. +Например: ```js -evtSource.addEventListener("ping", function (event) { +evtSource.addEventListener("ping", (event) => { const newElement = document.createElement("li"); + const eventList = document.getElementById("list"); const time = JSON.parse(event.data).time; - - newElement.innerHTML = "ping at " + time; + newElement.textContent = `ping в ${time}`; eventList.appendChild(newElement); }); ``` -Этот код аналогичен коду выше, за исключением того, что он будет вызываться автоматически всякий раз, когда сервер отправляет сообщение с полем `event`, установленным в «ping»; затем он парсит JSON в поле `data` и выводит эту информацию. +Этот код будет вызываться каждый раз, когда сервер отправляет сообщение с полем `event`, установленным в значение `ping`. +После этого он анализирует JSON в поле `data` и выводит эту информацию. + +> [!WARNING] **Без использования HTTP/2**, максимальное количество открытых SSE-соединений может быть ограничено, что может быть особенно заметным при открытии нескольких вкладок, поскольку ограничение действует _на браузер_ и установлено в очень низкое значение (6). Эта проблема отмечена как «Не будет исправлена» в [Chrome](https://crbug.com/275955) и [Firefox](https://bugzil.la/906896). Ограничение действует на связку «браузер + домен», то есть можно открыть только 6 SSE-соединений к `www.example1.com` для всех вкладок и ещё 6 SSE-соединений к `www.example2.com` (согласно [StackOverflow](https://stackoverflow.com/questions/5195452/websockets-vs-server-sent-events-eventsource/5326159)). При использовании HTTP/2, максимальное количество одновременных _HTTP-потоков_ согласовывается между сервером и клиентом (по умолчанию оно равно 100). ## Отправка событий с сервера -Код на стороне сервера, который отправляет события, должен отвечать, используя MIME-тип `text/event-stream`. Каждое уведомление отправляется в виде блока текста, оканчивающегося парой новых строк (`\n`) . Подробнее о формате потока событий см. [Event stream format](#event_stream_format). +Скрипт на стороне сервера, который отправляет события, должен отвечать с использованием MIME-типа `text/event-stream`. +Каждое сообщение отправляется как блок текста, завершающийся парой пустых строк. +Подробнее о формате потока событий смотрите в [Формат потока событий](#event_stream_format). -{{Glossary("PHP")}} код, который мы используем для примера приведён ниже: +Ниже приведен код на языке {{Glossary("PHP")}} для примера, который мы использовали выше: ```php -date_default_timezone_set("America/New_York"); -header('Cache-Control: no-cache'); -header("Content-Type: text/event-stream\n\n"); +date_default_timezone_set("Europe/Moscow"); +header("X-Accel-Buffering: no"); +header("Content-Type: text/event-stream"); +header("Cache-Control: no-cache"); $counter = rand(1, 10); -while (1) { - // Every second, send a "ping" event. +while (true) { + // Отправляем событие "ping" каждую секунду echo "event: ping\n"; $curDate = date(DATE_ISO8601); echo 'data: {"time": "' . $curDate . '"}'; echo "\n\n"; - // Send a simple message at random intervals. + // Отправляем простые сообщения со случайным интервалом $counter--; if (!$counter) { - echo 'data: This is a message at time ' . $curDate . "\n\n"; + echo 'Информация: это сообщение было отправлено в ' . $curDate . "\n\n"; $counter = rand(1, 10); } - ob_end_flush(); + if (ob_get_contents()) { + ob_end_flush(); + } flush(); + + // Прерываем цикл если клиент закрыл соединение (закрытие страницы) + + if (connection_aborted()) break; + sleep(1); } ``` -Приведённый выше код генерирует событие каждую секунду с типом события «ping». Данные каждого события - это объект JSON, содержащий метку времени ISO 8601, соответствующую дате, когда было сгенерировано событие. Через случайные интервалы отправляется простое сообщение (без типа `event`). +Приведенный выше код генерирует событие с типом «ping» каждую секунду. Данные каждого события представляют собой объект JSON, содержащий временную метку ISO 8601, соответствующую времени, в которое было сгенерировано событие. Через случайные интервалы времени отправляется простое сообщение (без типа события). +Цикл будет продолжать работать независимо от состояния соединения, поэтому добавлена проверка, чтобы прервать цикл, если соединение было закрыто (например, при закрытии страницы). > [!NOTE] -> Вы можете найти полный пример, который использует код, показанный в этой статье на GitHub - см. [Simple SSE demo using PHP.](https://github.com/mdn/dom-examples/tree/master/server-sent-events) +> Полный код этого примера можно найти на GitHub, смотрите [Simple SSE demo using PHP](https://github.com/mdn/dom-examples/tree/main/server-sent-events). ## Обработка ошибок -Когда возникают проблемы (такие как тайм-аут ответа сети или проблемы, связанные с [контролем доступа](/ru/docs/HTTP/Access_control_CORS)), тогда генерируется событие `error`. Вы можете обработать это событие программно, реализовав метод `onerror` для объекта `EventSource`: +При возникновении ошибок (например, проблемы сети или [доступа](/ru/docs/Web/HTTP/CORS)), генерируется сообщение об ошибке. Его можно обработать программно, установив метод `onerror` в объекте `EventSource`: ```js -evtSource.onerror = function (error) { - console.error("⛔ EventSource failed: ", error); +evtSource.onerror = (err) => { + console.error("В EventSource произошла ошибка:", err); }; ``` -## Закрытие потоков событий +## Закрытие потока событий -По умолчанию, если соединение между клиентом и сервером закрывается, то соединение сбрасывается. Прервать соединение можно с помощью метода `.close()`. +По умолчанию, если соединение между клиентом и сервером закрывается, оно будет перезапущено. Для завершения соединения необходимо вызывать метод `.close()`. ```js evtSource.close(); @@ -111,83 +141,85 @@ evtSource.close(); ## Формат потока событий -Поток событий представляет из себя простой поток текста, который должен иметь кодировку [UTF-8](/ru/docs/Glossary/UTF-8). Сообщения в потоке событий разделяются парой символов новой строки. Двоеточие, как первый символ строки считается комментарием и игнорируется. +Поток событий — это поток текстовых данных, которые должны быть закодированы с использованием [UTF-8](/ru/docs/Glossary/UTF-8). +Сообщения в потоке событий разделяются парой символов новой строки. Двоеточие в качестве первого символа строки по сути является комментарием и игнорируется. > [!NOTE] -> Строка комментария может использоваться, чтобы предотвратить тайм-аут соединений; сервер может периодически отправлять комментарий, чтобы поддерживать соединение. +> Строку комментариев можно использовать для предотвращения закрытия соединения. Сервер может периодически отправлять комментарии, чтобы поддерживать соединение активным. -Каждое сообщение содержит одну или более строчек текста, которые перечисляют поля этого сообщения. Каждое имеет своё имя, за которым следует двоеточие, после которого идут текстовые данные для значения этого поля. +Каждое сообщение состоит из одной или нескольких строк текста, содержащих поля этого сообщения. +Каждое поле представлено именем, за которым следует двоеточие и текстовые данные для значения этого поля. ### Поля -Каждое полученное сообщение имеет некоторую комбинацию следующих полей, по одному на строку: +Каждое полученное сообщение содержит комбинацию следующих полей, по одному в каждой строке: - `event` - - : Строка, идентифицирующая тип описанного события. Если `event` указан, то событие будет отправлено в браузер обработчику указанного имени события. Исходный код сайта должен использовать `addEventListener()` для обработки именованных событий. Обработчик `onmessage` вызывается, если для сообщения не указано имя события. + - : Строка, определяющая тип события. Если это поле указано, то событие будет передано браузером в обработчик события такого типа. Код на клиенте должен использовать `addEventListener()` для подписки на именованные события. Если имя события не было указано, то оно попадёт в обработчик `onmessage`. - `data` - - : Поле данных для сообщения. Когда `EventSource` получает несколько последовательных строк, начинающихся с `data:`, [он объединяет их](http://www.w3.org/TR/eventsource/#dispatchMessage), вставляя символ новой строки между каждой из них. Последние переводы строки удаляются. + - : Поле данных сообщения. Когда `EventSource` получает несколько строк подряд, которые начинаются с `data:`, [он объединяет их](https://html.spec.whatwg.org/multipage/#dispatchMessage), вставляя между ними символ переноса строка. Завершающие символы новой строки удаляются. - `id` - - : Идентификатор события для установки значения последнего ID события для объекта [`EventSource`](/en/Server-sent_events/EventSource). + - : Идентификатор события для установки значения последнего события в объекте [`EventSource`](/ru/docs/Web/API/EventSource). - `retry` - - : Время переподключения, используемое при попытке отправить событие. Это должно быть целое число, указывающее время переподключения в миллисекундах. Если указано нецелое значение, поле игнорируется. + - : Время переподключения. Если соединение с сервером потеряно, браузер будет ждать указанное время перед попыткой переподключения. Это должно быть целое число, указывающее время переподключения в миллисекундах. Если указано нецелое значение, поле игнорируется. -Другие названия полей игнорируются. +Другие имена полей игнорируются. > [!NOTE] -> If a line doesn't contain a colon, the entire line is treated as the field name with an empty value string. +> Если строка не содержит двоеточия, вся строка рассматривается как имя поля с пустым значением. ### Примеры -#### Сообщения с данными +#### Сообщения, содержащие только данные + +В следующем примере отправляется три сообщения. Первое — просто комментарий, так как начинается с двоеточия. Как упоминалось ранее, это может быть полезно для реализации механизма поддержания активности, если сообщения могут отправляться нерегулярно. -В следующем примере отправлено три сообщения. Первый - это просто комментарий, так как он начинается с символа двоеточия. Как упоминалось ранее, это может быть полезно в качестве подтверждения активности, если сообщения могут отправляться не регулярно. +Второе сообщение содержит поле данных со значением `some text`. -Второе сообщение содержит поле данных со значением «some text». Третье сообщение содержит поле данных со значением «another message \n with two lines». Обратите внимание на специальный символ новой строки в значении. +Третье сообщение содержит поле данных со значением `another message\nwith two lines`. Обратите внимание на специальный символ новой строки в значении. -```json +```bash : this is a test stream data: some text data: another message -data: +data: with two lines ``` #### Именованные события -Данный пример отправляет именованные события. У каждого из них есть имя события, указанное в поле `event`, и поле `data`, значением которого является соответствующая строка JSON с данными, необходимыми для клиента, чтобы реагировать на событие. Поле `data` может, конечно, содержать любые строковые данные; это не обязательно должен быть JSON. +В этом примере отправляются именованные события. Каждое из них имеет имя, указанное в поле `event`, и поле `data`, содержащее строку JSON с данными, необходимыми клиенту для выполнения действия по этому событию. Поле `data` может содержать любые строковые данные, это не обязательно должен быть JSON. -```json +```bash event: userconnect -data: {"username": "bobby", "time": "02:33:48"} +data: {"username": "vasya", "time": "02:33:48"} event: usermessage -data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."} +data: {"username": "vasya", "time": "02:34:11", "text": "Всем привет!"} event: userdisconnect -data: {"username": "bobby", "time": "02:34:23"} +data: {"username": "vasya", "time": "02:34:23"} event: usermessage -data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."} +data: {"username": "masha", "time": "02:34:36", "text": "Пока, vasya!"} ``` #### Смешивание и сопоставление -Вам не нужно использовать только неназванные сообщения или именованные события. Вы можете смешать их вместе в одном потоке событий. +Не обязательно использовать только именованные или неименованные сообщения, можно объединить их в один поток событий. -```json +```bash event: userconnect -data: {"username": "bobby", "time": "02:33:48"} +data: {"username": "vasya", "time": "02:33:48"} -data: Here's a system message of some kind that will get used -data: to accomplish some task. +data: Системное сообщение, которое будет использоваться +data: для выполнения какой-то задачи. event: usermessage -data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."} +data: {"username": "vasya", "time": "02:34:11", "text": "Всем привет!"} ``` ## Совместимость с браузерами -### `EventSource` - {{Compat}}