Отправленные сервером события весной

Отправленные сервером события весной

1. обзор

В этом руководстве мы увидим, как реализовать API на основе Server-Sent-Events с помощью Spring.

Проще говоря, Server-Sent-Events или сокращенно SSE - это стандарт HTTP, который позволяет веб-приложению обрабатывать однонаправленный поток событий и получать обновления всякий раз, когда сервер передает данные.

Версия Spring 4.2 уже поддерживала его, но начиная с Spring 5,we now have a more idiomatic and convenient way to handle it.

2. SSE с Spring 5 Webflux

Для этого используетсяwe can make use of implementations such as the Flux class provided by the Reactor library, or potentially the ServerSentEvent entity, который дает нам контроль над метаданными событий.

2.1. Потоковые события с использованиемFlux

Flux - это реактивное представление потока событий - оно обрабатывается по-разному в зависимости от указанного типа медиа-запроса или ответа.

Чтобы создать конечную точку потоковой передачи SSE, мы должны следоватьW3C specifications и обозначить ее тип MIME какtext/event-stream:

@GetMapping(path = "/stream-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux streamFlux() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> "Flux - " + LocalTime.now().toString());
}

Методinterval создаетFlux, который постепенно выдает значенияlong. Затем мы сопоставляем эти значения с желаемым результатом.

Тогда давайте запустим наше приложение и опробуем его, просмотрев конечную точку.

Мы увидим, как браузер реагирует на события, посекундно отправляемые сервером. Для получения дополнительной информации оFlux иReactor Core, мы можем проверитьthis post.

2.2. Использование элементаServerSentEvent

Теперь мы обернем наш выводString в объектServerSentSevent и рассмотрим преимущества этого:

@GetMapping("/stream-sse")
public Flux> streamEvents() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> ServerSentEvent. builder()
        .id(String.valueOf(sequence))
          .event("periodic-event")
          .data("SSE - " + LocalTime.now().toString())
          .build());
}

Как мы понимаем,there’re a couple of benefits of using the ServerSentEvent entity:

  1. мы можем обрабатывать метаданные событий, которые нам понадобятся в реальном сценарии

  2. мы можем игнорировать объявление типа носителя «text/event-stream»

В этом случае мы указалиid, anevent name и, что наиболее важно, фактическийdata события.

Кроме того, мы могли бы добавить атрибутcomments и значениеretry, которые будут указывать время повторного подключения, которое будет использоваться при попытке отправить событие.

2.3. Использование отправленных сервером событий с веб-клиентом

Теперь давайте воспользуемся нашим потоком событий сWebClient:

public void consumeServerSentEvent() {
    WebClient client = WebClient.create("http://localhost:8080/sse-server");
    ParameterizedTypeReference> type
     = new ParameterizedTypeReference>() {};

    Flux> eventStream = client.get()
      .uri("/stream-sse")
      .retrieve()
      .bodyToFlux(type);

    eventStream.subscribe(
      content -> logger.info("Time: {} - event: name[{}], id [{}], content[{}] ",
        LocalTime.now(), content.event(), content.id(), content.data()),
      error -> logger.error("Error receiving SSE: {}", error),
      () -> logger.info("Completed!!!"));
}

Методsubscribe позволяет нам указать, как мы будем действовать после успешного получения события, возникновения ошибки и завершения потоковой передачи.

В нашем примере мы использовали методretrieve, который представляет собой простой и понятный способ получения тела ответа.

Этот метод автоматически выдаетWebClientResponseException, если мы получаем ответ 4xx или 5xx, если мы не обрабатываем сценарии, добавляя операторonStatus.

С другой стороны, мы могли бы также использовать методexchange, который обеспечивает доступ кClientResponse, а также не сигнализирует об ошибке при неудачных ответах.

Мы должны учитывать, что мы можем обойти оболочкуServerSentEvent, если нам не нужны метаданные события.

3. SSE Streaming в Spring MVC

Как мы уже говорили, спецификация SSE поддерживалась с Spring 4.2, когда был представлен классSseEmitter.

Проще говоря, мы определимExecutorService, поток, в которомSseEmitter будет выполнять свою работу по отправке данных и возвращать экземпляр эмиттера, сохраняя соединение открытым следующим образом:

@GetMapping("/stream-sse-mvc")
public SseEmitter streamSseMvc() {
    SseEmitter emitter = new SseEmitter();
    ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
    sseMvcExecutor.execute(() -> {
        try {
            for (int i = 0; true; i++) {
                SseEventBuilder event = SseEmitter.event()
                  .data("SSE MVC - " + LocalTime.now().toString())
                  .id(String.valueOf(i))
                  .name("sse event - mvc");
                emitter.send(event);
                Thread.sleep(1000);
            }
        } catch (Exception ex) {
            emitter.completeWithError(ex);
        }
    });
    return emitter;
}

Всегда выбирайте правильныйExecutorService для вашего сценария использования.

Мы можем узнать больше о SSE в Spring MVC и взглянуть на другие примеры, прочитавthis interesting tutorial.

4. Понимание событий, отправленных сервером

Теперь, когда мы знаем, как реализовать конечные точки SSE, давайте попробуем пойти немного глубже и понять некоторые основные концепции.

SSE - это спецификация, принятая большинством браузеров, чтобы разрешить потоковую передачу событий в любое время.

«События» - это просто поток текстовых данных в кодировке UTF-8, которые соответствуют формату, определенному в спецификации.

Этот формат состоит из серии элементов значения ключа (id, retry, data и event, которая указывает имя), разделенных переносами строк.

Комментарии также поддерживаются.

Спецификация никоим образом не ограничивает формат полезной нагрузки данных; мы можем использовать простуюString или более сложную структуру JSON или XML.

Последний момент, который мы должны принять во внимание, - это разница между использованием потоковой передачи SSE иWebSockets.

В то время какWebSockets предлагает полнодуплексную (двунаправленную) связь между сервером и клиентом, в то время как SSE использует однонаправленную связь.

Кроме того,WebSockets не является протоколом HTTP и, в отличие от SSE, не предлагает стандартов обработки ошибок.

5. Заключение

Подводя итог, в этой статье мы изучили основные концепции потоковой передачи SSE, которая, несомненно, является отличным ресурсом, который позволит нам создавать системы следующего поколения.

Теперь мы находимся в прекрасном положении, чтобы понять, что происходит под капотом, когда мы используем этот протокол.

Кроме того, мы дополнили теорию несколькими простыми примерами, которые можно найти вour Github repository.