Server Sent Events
Эта страница переведена с английского автоматически. Читать оригинал
Sisk поддерживает отправку сообщений через Server Sent Events «из коробки». Вы можете создавать одноразовые и постоянные соединения, получать соединения во время выполнения и использовать их.
Эта возможность имеет некоторые ограничения, налагаемые браузерами, такие как отправка только текстовых сообщений и невозможность полностью закрыть соединение. Соединение, закрытое на стороне сервера, будет периодически пытаться переподключиться клиенту каждые 5 секунд (3 секунды в некоторых браузерах).
Эти соединения полезны для отправки событий с сервера клиенту без необходимости клиенту запрашивать информацию каждый раз.
Creating an SSE connection #
SSE‑соединение работает как обычный HTTP‑запрос, но вместо того, чтобы отправить ответ и сразу закрыть соединение, соединение остаётся открытым для отправки сообщений.
Вызвав метод HttpRequest.GetEventSource(), запрос переводится в состояние ожидания, пока создаётся экземпляр SSE.
r.MapGet("/", (req) =>
{
using var sse = req.GetEventSource();
sse.Send("Hello, world!");
return sse.Close();
});В приведённом выше коде мы создаём SSE‑соединение и отправляем сообщение «Hello, world!», затем закрываем SSE‑соединение со стороны сервера.
Примечание
При закрытии соединения на стороне сервера по умолчанию клиент будет пытаться подключиться снова, и соединение будет перезапущено, вызывая метод заново, бесконечно.
Обычно отправляют сообщение о завершении со стороны сервера, когда соединение закрывается, чтобы предотвратить повторные попытки переподключения клиента.
Appending headers #
Если необходимо отправить заголовки, вы можете использовать метод HttpRequestEventSource.AppendHeader до отправки любых сообщений.
r.MapGet("/", (req) =>
{
using var sse = req.GetEventSource();
sse.AppendHeader("Header-Key", "Header-value");
sse.Send("Hello!");
return sse.Close();
});Обратите внимание, что заголовки нужно отправлять до отправки любых сообщений.
Wait-For-Fail connections #
Соединения обычно завершаются, когда сервер больше не может отправлять сообщения из‑за возможного отключения клиента. При этом соединение автоматически закрывается, а экземпляр класса удаляется.
Даже при переподключении экземпляр класса не будет работать, так как он привязан к предыдущему соединению. В некоторых ситуациях вам может понадобиться это соединение позже, и вы не хотите управлять им через метод‑обратный вызов маршрута.
Для этого мы можем идентифицировать SSE‑соединения с помощью идентификатора и получать их позже, даже вне обратного вызова маршрута. Кроме того, мы помечаем соединение атрибутом WaitForFail, чтобы не завершать маршрут и не закрывать соединение автоматически.
SSE‑соединение в WaitForFail ждёт ошибку отправки, вызванную разрывом, или истечения настроенного времени простоя, прежде чем маршрут возобновится и закроет соединение.
r.MapGet("/", (req) =>
{
using var sse = req.GetEventSource("my-index-connection");
sse.WaitForFail(TimeSpan.FromSeconds(15)); // ждать 15 секунд без сообщений перед завершением соединения
return sse.Close();
});Вышеприведённый метод создаст соединение, обработает его и будет ждать разъединения или ошибки.
HttpRequestEventSource? evs = server.EventSources.GetByIdentifier("my-index-connection");
if (evs != null)
{
// соединение всё ещё активно
evs.Send("Hello again!");
}И приведённый выше фрагмент попытается найти только что созданное соединение и, если оно существует, отправит в него сообщение.
Все активные серверные соединения, которые идентифицированы, будут доступны в коллекции HttpServer.EventSources. Эта коллекция хранит только активные и идентифицированные соединения. Закрытые соединения удаляются из коллекции.
Примечание
Важно отметить, что keep alive имеет ограничение, установленное компонентами, которые могут быть подключены к Sisk неконтролируемым образом, например веб‑прокси, HTTP‑ядром или сетевым драйвером, и они закрывают простоящие соединения после определённого периода времени.
Поэтому важно поддерживать соединение открытым, отправляя периодические пинги или увеличивая максимальное время до закрытия соединения. Читайте следующий раздел, чтобы лучше понять отправку периодических пингов.
Setup connections ping policy #
Ping Policy — это автоматический способ отправки периодических сообщений вашему клиенту. Эта функция позволяет серверу понять, что клиент отключился от соединения, без необходимости держать соединение открытым бесконечно.
[RouteGet("/sse")]
public async Task<HttpResponse> Events(HttpRequest request)
{
using var sse = await request.GetEventSourceAsync("user-events");
sse.WithPing(ping =>
{
ping.DataMessage = "ping-message";
ping.Interval = TimeSpan.FromSeconds(5);
ping.Start();
});
await sse.WaitForFailAsync(TimeSpan.FromMinutes(10));
return await sse.CloseAsync();
}В приведённом коде каждые 5 секунд клиенту будет отправляться новое ping‑сообщение. Это поддерживает TCP‑соединение живым и предотвращает его закрытие из‑за бездействия. Кроме того, когда сообщение не удаётся отправить, соединение автоматически закрывается, освобождая ресурсы, использованные соединением.
Используйте SendAsync и CloseAsync в асинхронных маршрутах. Если нужно отбросить накопленные события перед закрытием, вызовите Cancel.
Querying connections #
Вы можете искать активные соединения, используя предикат по идентификатору соединения, чтобы, например, выполнить широковещательную рассылку.
HttpRequestEventSource[] evs = server.EventSources.Find(es => es.StartsWith("my-connection-"));
foreach (HttpRequestEventSource e in evs)
{
e.Send("Broadcasting to all event sources that starts with 'my-connection-'");
}Также можно использовать метод All для получения всех активных SSE‑соединений.