Eventos enviados pelo servidor no Spring

Eventos enviados pelo servidor no Spring

1. Visão geral

Neste tutorial, veremos como podemos implementar APIs baseadas em eventos enviados por servidor com Spring.

Simplificando, Server-Sent-Events, ou SSE para breve, é um padrão HTTP que permite a um aplicativo da web lidar com um fluxo de eventos unidirecional e receber atualizações sempre que o servidor emite dados.

A versão do Spring 4.2 já o suportava, mas a partir do Spring 5,we now have a more idiomatic and convenient way to handle it.

2. SSE com Webflux do Spring 5

Para conseguir isso,we can make use of implementations such as the Flux class provided by the Reactor library, or potentially the ServerSentEvent entity, que nos dá controle sobre os metadados dos eventos.

2.1. Eventos de fluxo usandoFlux

Flux é uma representação reativa de um fluxo de eventos - é tratada de forma diferente com base na solicitação especificada ou tipo de mídia de resposta.

Para criar um endpoint de streaming SSE, teremos que seguir oW3C specificationse designar seu tipo MIME comotext/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());
}

O métodointerval cria umFlux que emite valoreslong incrementalmente. Em seguida, mapeamos esses valores para a saída desejada.

Vamos iniciar nosso aplicativo e testá-lo navegando no endpoint.

Veremos como o navegador reage aos eventos sendo empurrados segundo a segundo pelo servidor. Para obter mais informações sobreFluxeReactor Core, podemos verificarthis post.

2.2. Fazendo uso do elementoServerSentEvent

Agora, envolveremos nossa saídaString em um objetoServerSentSevent e examinaremos os benefícios de fazer isso:

@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());
}

Como podemos apreciar,there’re a couple of benefits of using the ServerSentEvent entity:

  1. podemos lidar com os metadados de eventos, que precisaríamos em um cenário de caso real

  2. podemos ignorar a declaração de tipo de mídia “text/event-stream

Nesse caso, especificamosid,event name e, o mais importante,data reais do evento.

Além disso, poderíamos ter adicionado um atributocomments e um valorretry, que irá especificar o tempo de reconexão a ser usado ao tentar enviar o evento.

2.3. Consumindo os eventos enviados pelo servidor com um cliente da Web

Agora vamos consumir nosso fluxo de eventos com umWebClient .:

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!!!"));
}

O métodosubscribe nos permite indicar como procederemos quando recebermos um evento com sucesso, quando ocorrer um erro e quando o streaming for concluído.

Em nosso exemplo, usamos o métodoretrieve, que é uma maneira simples e direta de obter o corpo da resposta.

Este método lança automaticamente umWebClientResponseException se recebermos uma resposta 4xx ou 5xx, a menos que tratemos dos cenários adicionando uma instruçãoonStatus.

Por outro lado, poderíamos ter usado o métodoexchange também, que fornece acesso aoClientResponsee também não sinaliza erro em respostas com falha.

Devemos levar em consideração que podemos ignorar o wrapperServerSentEvent se não precisarmos dos metadados do evento.

3. Streaming SSE no Spring MVC

Como dissemos, a especificação SSE era suportada desde o Spring 4.2, quando a classeSseEmitter foi introduzida.

Em termos simples, definiremos umExecutorService, um encadeamento ondeSseEmitter fará seu trabalho enviando dados e retornará a instância do emissor, mantendo a conexão aberta desta maneira:

@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;
}

Sempre certifique-se de escolher oExecutorService correto para seu cenário de caso de uso.

Podemos aprender mais sobre SSE no Spring MVC e dar uma olhada em outros exemplos lendothis interesting tutorial.

4. Noções básicas sobre eventos enviados pelo servidor

Agora que sabemos como implementar pontos de extremidade SSE, vamos tentar ir um pouco mais fundo, entendendo alguns conceitos subjacentes.

Um SSE é uma especificação adotada pela maioria dos navegadores para permitir a transmissão de eventos unidirecionalmente a qualquer momento.

Os ‘eventos 'são apenas um fluxo de dados de texto codificado em UTF-8 que seguem o formato definido pela especificação.

Esse formato consiste em uma série de elementos de valor-chave (identificação, nova tentativa, dados e evento, que indicam o nome) separados por quebras de linha.

Comentários também são suportados.

A especificação não restringe o formato da carga de dados de forma alguma; podemos usar umString simples ou uma estrutura JSON ou XML mais complexa.

Um último ponto que devemos levar em consideração é a diferença entre usar streaming SSE eWebSockets.

EnquantoWebSockets oferece comunicação full-duplex (bidirecional) entre o servidor e o cliente, enquanto SSE usa comunicação unidirecional.

Além disso,WebSockets não é um protocolo HTTP e, ao contrário do SSE, não oferece padrões de tratamento de erros.

5. Conclusão

Resumindo, neste artigo aprendemos os principais conceitos de streaming de SSE, que é, sem dúvida, um grande recurso que nos permitirá criar sistemas de próxima geração.

Agora estamos em uma excelente posição para entender o que está acontecendo sob o capô quando usamos este protocolo.

Além disso, complementamos a teoria com alguns exemplos simples, que podem ser encontrados emour Github repository.