1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
---
title: Using server-sent events
slug: Web/API/Server-sent_events/Using_server-sent_events
translation_of: Web/API/Server-sent_events/Using_server-sent_events
---
<p>{{DefaultAPISidebar("Server Sent Events")}}</p>
<div class="summary">
<p>Разрабатывать web-приложения, использующие <a href="/en-US/docs/Web/API/Server-sent_events">Server-Sent Events</a> намного проще, чем с использованием websockets. Нужно всего лишь немного кода на стороне сервере, чтобы переправлять события web-приложению, но клиентская часть кода для обработки этих событий работает почти точно так же, как и для любых других событий.</p>
</div>
<h2 id="Получение_событий_от_сервера">Получение событий от сервера</h2>
<p>Server-Sent Event API содержится внутри интерфейса {{domxref("EventSource")}}. Чтобы открыть соединение с сервером для начала записи событий, которые он присылает, необходимо создать новый объект <code>EventSource</code>, который будет указывать на URI скрипта, который создает события. Например:</p>
<pre class="brush: js">const evtSource = new EventSource("ssedemo.php");</pre>
<p>Если файл с генератором событий размещен на другом домене, то должен быть создан новый объект <code>EventSource</code> в который следует передать помимо URI еще и словарь опций. Например, если предположить, что клиентский скрипт находится на <code>example.com</code>:</p>
<pre class="brush: js"><code>const evtSource = new EventSource("//api.example.com/ssedemo.php", { withCredentials: true } ); </code>
</pre>
<p>Как только вы создали экземпляр <code>EventSource</code>, вы можете начать прослушивание сообщений с сервера, добавив обработчик для события {{event("message")}} :</p>
<pre class="brush: js">evtSource.onmessage = function(event) {
const newElement = document.createElement("li");
const eventList = document.getElementById('list');
newElement.innerHTML = "message: " + event.data;
eventList.appendChild(newElement);
}
</pre>
<p>Этот код прослушивает входящие сообщения (то есть уведомления от сервера, на которых нет поля <code>event</code>) и добавляет текст сообщения в список в HTML-документе.</p>
<p>Вы также можете прослушивать события, используя <code>addEventListener()</code>:</p>
<pre class="brush: js"><code>evtSource.addEventListener("ping", function(event) {
const newElement = document.createElement("li");
const time = JSON.parse(event.data).time;
newElement.innerHTML = "ping at " + time;
eventList.appendChild(newElement);
});</code></pre>
<p>Этот код аналогичен коду выше, за исключением того, что он будет вызываться автоматически всякий раз, когда сервер отправляет сообщение с полем <code>event</code>, установленным в «ping»; затем он парсит JSON в поле <code>data</code> и выводит эту информацию.</p>
<h2 id="Отправка_событий_с_сервера">Отправка событий с сервера</h2>
<p>Код на стороне сервера, который отправляет события, должен отвечать, используя MIME-тип <code>text/event-stream</code>. Каждое уведомление отправляется в виде блока текста, оканчивающегося парой новых строк (<code>\n</code>) . Подробнее о формате потока событий см. {{ anch("Event stream format") }}.</p>
<p>{{Glossary("PHP")}} код, который мы используем для примера приведен ниже:</p>
<pre class="brush: php">date_default_timezone_set("America/New_York");
header('Cache-Control: no-cache');
header("Content-Type: text/event-stream\n\n");
$counter = rand(1, 10);
while (1) {
// Every second, send a "ping" event.
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";
$counter = rand(1, 10);
}
ob_end_flush();
flush();
sleep(1);
}
</pre>
<p>Приведенный выше код генерирует событие каждую секунду с типом события «ping». Данные каждого события - это объект JSON, содержащий метку времени ISO 8601, соответствующую дате, когда было сгенерировано событие. Через случайные интервалы отправляется простое сообщение (без типа <code>event</code>).</p>
<div class="note">
<p><strong>Примечание</strong>: Вы можете найти полный пример, который использует код, показанный в этой статье на GitHub - см. <a href="https://github.com/mdn/dom-examples/tree/master/server-sent-events">Simple SSE demo using PHP.</a></p>
</div>
<h2 id="Обработка_ошибок">Обработка ошибок</h2>
<p>Когда возникают проблемы (такие как тайм-аут ответа сети или проблемы, связанные с <a href="/en-US/docs/HTTP/Access_control_CORS" title="/en-US/docs/HTTP/Access_control_CORS">контролем доступа</a>), тогда генерируется событие <code>error</code>. Вы можете обработать это событие программно, реализовав метод <code>onerror</code> для объекта <code>EventSource</code>:</p>
<pre class="brush: js">evtSource.onerror = function(error) {
console.error("⛔ EventSource failed: ", error);
};
</pre>
<h2 id="Закрытие_потоков_событий">Закрытие потоков событий</h2>
<p>По умолчанию, если соединение между клиентом и сервером закрывается, то соединение сбрасывается. Прервать соединение можно с помощью метода <code>.close()</code>.</p>
<pre class="brush: js">evtSource.close();</pre>
<h2 id="Формат_потока_событий">Формат потока событий</h2>
<p>Поток событий представляет из себя простой поток текста, который должен иметь кодировку <a href="https://developer.mozilla.org/en-US/docs/Glossary/UTF-8">UTF-8</a>. Сообщения в потоке событий разделяются парой символов новой строки. Двоеточие, как первый символ строки считается комментарием и игнорируется.</p>
<div class="note"><strong>Примечание:</strong> Строка комментария может использоваться, чтобы предотвратить тайм-аут соединений; сервер может периодически отправлять комментарий, чтобы поддерживать соединение.</div>
<p>Каждое сообщение содержит одну или более строчек текста, которые перечисляют поля этого сообщения. Каждое имеет свое имя, за которым следует двоеточие, после которого идут текстовые данные для значения этого поля. </p>
<h3 id="Поля">Поля</h3>
<p>Каждое полученное сообщение имеет некоторую комбинацию следующих полей, по одному на строку:</p>
<dl>
<dt><code>event</code></dt>
<dd>Строка, идентифицирующая тип описанного события. Если <code>event</code> указан, то событие будет отправлено в браузер слушателю для указанного имени события. Исходный код сайта должен использовать <code>addEventListener()</code> для прослушивания именованных событий. Обработчик <code>onmessage</code> вызывается, если для сообщения не указано имя события.</dd>
<dt><code>data</code></dt>
<dd>Поле данных для сообщения. Когда <code>EventSource</code> получает несколько последовательных строк, начинающихся с <code>data:</code>, <a href="http://www.w3.org/TR/eventsource/#dispatchMessage">он объединяет их</a>, вставляя символ новой строки между каждой из них. Последние переводы строки удаляются.</dd>
<dt><code>id</code></dt>
<dd>Идентификатор события для установки значения последнего ID события для объекта <code><a href="/en/Server-sent_events/EventSource" title="en/Server-sent events/EventSource">EventSource</a></code>.</dd>
<dt><code>retry</code></dt>
<dd>Время переподключения, используемое при попытке отправить событие. Это должно быть целое число, указывающее время переподключения в миллисекундах. Если указано нецелое значение, поле игнорируется.</dd>
</dl>
<p>Другие названия полей игнорируются.</p>
<div class="note"><strong>Примечание:</strong> If a line doesn't contain a colon, the entire line is treated as the field name with an empty value string.</div>
<h3 id="Примеры">Примеры</h3>
<h4 id="Сообщения_с_данными">Сообщения с данными</h4>
<p>В следующем примере отправлено три сообщения. Первый - это просто комментарий, так как он начинается с символа двоеточия. Как упоминалось ранее, это может быть полезно в качестве подтверждения активности, если сообщения могут отправляться не регулярно.</p>
<p>Второе сообщение содержит поле данных со значением «some text». Третье сообщение содержит поле данных со значением «another message \n with two lines». Обратите внимание на специальный символ новой строки в значении.</p>
<pre class="brush: json">: this is a test stream
data: some text
data: another message
data:
</pre>
<h4 id="Именованные_события">Именованные события</h4>
<p>Данный пример отправляет именованные события. У каждого из них есть имя события, указанное в поле <code>event</code>, и поле <code>data</code>, значением которого является соответствующая строка JSON с данными, необходимыми для клиента, чтобы реагировать на событие. Поле <code>data</code> может, конечно, содержать любые строковые данные; это не обязательно должен быть JSON.</p>
<pre class="brush: json">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."}
</pre>
<h4 id="Смешивание_и_сопоставление">Смешивание и сопоставление</h4>
<p>Вам не нужно использовать только неназванные сообщения или именованные события. Вы можете смешать их вместе в одном потоке событий.</p>
<pre class="brush: json">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."}</pre>
<h2 id="Совместимость_с_браузерами">Совместимость с браузерами</h2>
<div>
<h3 id="EventSource"><code>EventSource</code></h3>
<div>
<p>{{Compat("api.EventSource")}}</p>
</div>
</div>
|