Eventi inviati dal server in primavera

1. Panoramica

In questo tutorial vedremo come implementare API basate su eventi inviati dal server con Spring.

In poche parole, Server-Sent-Events, o SSE in breve, è uno standard HTTP che consente a un'applicazione Web di gestire un flusso di eventi unidirezionale e ricevere aggiornamenti ogni volta che il server emette dati.

La versione Spring 4.2 lo supportava già, ma a partire dalla Spring 5, ora abbiamo un modo più idiomatico e conveniente per gestirlo.

2. SSE con Spring 5 Webflux

Per ottenere ciò, possiamo fare uso di implementazioni come la classe Flux fornita dalla libreria Reactor , o potenzialmente l' entità ServerSentEvent , che ci dà il controllo sui metadati degli eventi.

2.1. Eventi in streaming utilizzando Flux

Flux è una rappresentazione reattiva di un flusso di eventi: viene gestito in modo diverso in base al tipo di supporto di richiesta o risposta specificato.

Per creare un endpoint di streaming SSE, dovremo seguire le specifiche W3C e designare il suo tipo MIME come flusso di testo / evento :

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

Il metodo intervallo crea un flusso che emette valori lunghi in modo incrementale. Quindi mappiamo quei valori al nostro output desiderato.

Avviamo la nostra applicazione e proviamola esplorando l'endpoint.

Vedremo come reagisce il browser agli eventi inviati secondo per secondo dal server. Per ulteriori informazioni su Flux e Reactor Core , possiamo controllare questo post.

2.2. Utilizzo dell'elemento ServerSentEvent

Ora avvolgeremo la nostra stringa di output in un oggetto ServerSentSevent ed esamineremo i vantaggi di fare questo:

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

Come possiamo apprezzare, ci sono un paio di vantaggi nell'usare l' entità ServerSentEvent :

  1. possiamo gestire i metadati degli eventi, di cui avremmo bisogno in uno scenario reale
  2. possiamo ignorare la dichiarazione del tipo di media " text / event-stream "

In questo caso, abbiamo specificato un ID , un nome di evento e, soprattutto, i dati effettivi dell'evento.

Inoltre, avremmo potuto aggiungere un attributo commenti e un valore di nuovo tentativo , che specificherà il tempo di riconnessione da utilizzare quando si tenta di inviare l'evento.

2.3. Consumare gli eventi inviati dal server con un WebClient

Ora consumiamo il nostro flusso di eventi con un WebClient .:

public void consumeServerSentEvent() { WebClient client = WebClient.create("//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!!!")); }
      
     
    

Il metodo di sottoscrizione ci consente di indicare come procederemo quando riceveremo un evento con successo, quando si verificherà un errore e quando lo streaming sarà completato.

Nel nostro esempio, abbiamo utilizzato il metodo retrieve , che è un modo semplice e diretto per ottenere il corpo della risposta.

Questo metodo genera automaticamente un'eccezione WebClientResponseException se riceviamo una risposta 4xx o 5xx a meno che non gestiamo gli scenari aggiungendo un'istruzione onStatus .

D'altra parte, avremmo potuto utilizzare anche il metodo di scambio , che fornisce l'accesso a ClientResponse e inoltre non segnala errori in caso di risposte non riuscite.

Dobbiamo tenere in considerazione che possiamo bypassare il wrapper ServerSentEvent se non abbiamo bisogno dei metadati dell'evento.

3. Streaming SSE in Spring MVC

Come abbiamo detto, la specifica SSE è stata supportata dalla primavera 4.2, quando è stata introdotta la classe SseEmitter .

In termini semplici, definiremo un ExecutorService , un thread in cui SseEmitter farà il suo lavoro spingendo i dati e restituirà l'istanza dell'emettitore, mantenendo la connessione aperta in questo modo:

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

Assicurati sempre di scegliere il giusto ExecutorService per il tuo caso d'uso.

Possiamo saperne di più su SSE in Spring MVC e dare un'occhiata ad altri esempi leggendo questo interessante tutorial.

4. Comprensione degli eventi inviati dal server

Ora che sappiamo come implementare gli endpoint SSE, proviamo ad andare un po 'più in profondità comprendendo alcuni concetti sottostanti.

Un SSE è una specifica adottata dalla maggior parte dei browser per consentire lo streaming di eventi in modo unidirezionale in qualsiasi momento.

Gli "eventi" sono solo un flusso di dati di testo codificati UTF-8 che seguono il formato definito dalla specifica.

Questo formato è costituito da una serie di elementi chiave-valore (id, riprova, dati ed evento, che indica il nome) separati da interruzioni di riga.

Sono supportati anche i commenti.

La specifica non limita in alcun modo il formato del payload dei dati; possiamo usare una semplice stringa o una struttura JSON o XML più complessa.

Un ultimo punto che dobbiamo prendere in considerazione è la differenza tra l'utilizzo di streaming SSE e WebSocket .

Mentre i WebSocket offrono una comunicazione full duplex (bidirezionale) tra il server e il client, mentre SSE utilizza la comunicazione unidirezionale.

Inoltre, WebSocket non è un protocollo HTTP e, contrariamente a SSE, non offre standard di gestione degli errori.

5. conclusione

Per riassumere, in questo articolo abbiamo imparato i concetti principali dello streaming SSE, che è senza dubbio una grande risorsa che ci consentirà di creare sistemi di nuova generazione.

Ora siamo in una posizione eccellente per capire cosa sta succedendo sotto il cofano quando usiamo questo protocollo.

Inoltre, abbiamo integrato la teoria con alcuni semplici esempi, che possono essere trovati nel nostro repository Github.