Server-Sent Events (SSE) I JAX-RS

1. Oversigt

Server-Sent Events (SSE) er en HTTP-baseret specifikation, der giver en måde at etablere en langvarig og monokanalforbindelse fra serveren til klienten.

Klienten initierer SSE-forbindelsen ved hjælp af medietypen tekst / event-stream i Acceptere header.

Senere opdateres den automatisk uden at anmode om serveren.

Vi kan tjekke flere detaljer om specifikationen på den officielle specifikation.

I denne vejledning introducerer vi den nye JAX-RS 2.1-implementering af SSE.

Derfor ser vi på, hvordan vi kan offentliggøre begivenheder med JAX-RS Server API. Vi undersøger også, hvordan vi kan forbruge dem enten ved hjælp af JAX-RS Client API eller bare af en HTTP-klient som den krølle værktøj.

2. Forståelse af SSE-begivenheder

En SSE-begivenhed er en tekstblok, der består af følgende felter:

  • Begivenhed: begivenhedens type. Serveren kan sende mange beskeder af forskellige typer, og klienten lytter muligvis kun efter en bestemt type eller kan behandle forskelligt for hver begivenhedstype
  • Data: meddelelsen sendt af serveren. Vi kan have mange datalinjer til den samme begivenhed
  • Id: id for begivenheden, der bruges til at sende Sidste begivenheds-ID header, efter en forbindelse forsøger igen. Det er nyttigt, da det kan forhindre serveren i at sende allerede sendte begivenheder
  • Prøve igen: tiden i millisekunder, hvor klienten opretter en ny forbindelse, når strømmen går tabt. Den sidst modtagne id vil automatisk blive sendt via Sidste begivenheds-ID header
  • :‘: Dette er en kommentar og ignoreres af klienten

To på hinanden følgende begivenheder er også adskilt af en dobbelt ny linje '\ n \ n‘.

Derudover kan dataene i samme begivenhed skrives i mange linjer som det kan ses i følgende eksempel:

begivenhed: lager-id: 1: prisændring forsøg igen: 4000 data: {"dateTime": "2018-07-14T18: 06: 00.285", "id": 1, data: "navn": "GOOG", "pris" : 75.7119} begivenhed: lager-id: 2: prisændring forsøg: 4000 data: {"dateTime": "2018-07-14T18: 06: 00.285", "id": 2, "name": "IBM", "price ": 83.4611}

I JAX RS, er en SSE-begivenhed abstraheret af SseEvent interface, eller mere præcist ved de to undergrænseflader OutboundSseEvent og InboundSseEvent.

Mens OutboundSseEvent bruges på Server API og designer en sendt begivenhed, den InboundSseEvent bruges af Client API og opsummerer en modtaget begivenhed.

3. Udgivelse af SSE-begivenheder

Nu da vi diskuterede, hvad en SSE-begivenhed er, skal vi se, hvordan vi kan bygge og sende den til en HTTP-klient.

3.1. Projektopsætning

Vi har allerede en tutorial om opsætning af et JAX RS-baseret Maven-projekt. Du er velkommen til at kigge der for at se, hvordan du indstiller afhængigheder og kommer i gang med JAX RS.

3.2. SSE-ressource metode

En SSE Resource-metode er en JAX RS-metode, der:

  • Kan producere en tekst / event-stream medietype
  • Har en injiceret SseEventSink parameter, hvor begivenheder sendes
  • Kan også få en indsprøjtning Sse parameter, der bruges som et startpunkt til at oprette en begivenhedsbygger
@GET @Path ("priser") @Produces ("tekst / event-stream") offentlig ugyldighed getStockPrices (@Context SseEventSink sseEventSink, @Context Sse sse) {// ...}

Som følge heraf skal klienten foretage den første HTTP-anmodning med følgende HTTP-header:

Accepter: tekst / event-stream 

3.3. SSE-forekomsten

En SSE-forekomst er en kontekstbønne, som JAX RS Runtime stiller til rådighed til injektion.

Vi kunne bruge det som en fabrik til at skabe:

  • OutboundSseEvent.Builder - giver os mulighed for at oprette begivenheder dengang
  • SseBroadcaster - giver os mulighed for at sende begivenheder til flere abonnenter

Lad os se, hvordan det fungerer:

@Context public void setSse (Sse sse) {this.sse = sse; this.eventBuilder = sse.newEventBuilder (); this.sseBroadcaster = sse.newBroadcaster (); }

Lad os nu fokusere på begivenhedsbyggeren. OutboundSseEvent.Builder er ansvarlig for at skabe OutboundSseEvent:

OutboundSseEvent sseEvent = this.eventBuilder .name ("stock") .id (String.valueOf (lastEventId)) .mediaType (MediaType.APPLICATION_JSON_TYPE) .data (Stock.class, stock) .reconnectDelay (4000) .kommentar ("prisændring) ") .build ();

Som vi kan se, bygherren har metoder til at indstille værdier for alle hændelsesfelter vist ovenfor. Derudover er mediaType () metoden bruges til at serialisere datafeltet Java-objekt til et passende tekstformat.

Datafeltets medietype er som standard tekst / almindelig. Derfor behøver det ikke specifikt at specificeres, når der beskæftiger sig med Snor datatype.

Ellers, hvis vi ønsker at håndtere et brugerdefineret objekt, skal vi angive medietypen eller give en brugerdefineret MessageBodyWriter.JAX RS Runtime giver MessageBodyWriters til de mest kendte medietyper.

Sse-forekomsten har også to genveje til bygherrer til oprettelse af en begivenhed med kun datafeltet eller type- og datafelterne:

OutboundSseEvent sseEvent = sse.newEvent ("cool begivenhed"); OutboundSseEvent sseEvent = sse.newEvent ("indtastet begivenhed", "datahændelse");

3.4. Afsendelse af enkel begivenhed

Nu hvor vi ved, hvordan man bygger begivenheder, og vi forstår, hvordan en SSE-ressource fungerer. Lad os sende en simpel begivenhed.

Det SseEventSink interface trækker en enkelt HTTP-forbindelse ud. JAX-RS Runtime kan kun gøre det tilgængeligt via injektion i SSE-ressourcemetoden.

At sende en begivenhed er så lige så let som at påberåbe sig SseEventSink.sende().

I det næste eksempel vil der sendes en masse lageropdateringer og til sidst lukker begivenhedsstrømmen:

@GET @Path ("priser") @Produces ("tekst / event-stream") offentlig ugyldighed getStockPrices (@Context SseEventSink sseEventSink /*..*/) {int lastEventId = // ..; mens (kører) {Stock stock = stockService.getNextTransaction (lastEventId); if (stock! = null) {OutboundSseEvent sseEvent = this.eventBuilder .name ("stock") .id (String.valueOf (lastEventId)) .mediaType (MediaType.APPLICATION_JSON_TYPE) .data (Stock.class, stock) .reconnectDelay ( 3000) .kommentar ("prisændring") .build (); sseEventSink.send (sseEvent); lastEventId ++; } // ..} sseEventSink.close (); }

Efter at have sendt alle begivenheder lukker serveren forbindelsen enten ved eksplicit at påkalde tæt() fremgangsmåde eller fortrinsvis ved anvendelse af prøv med ressource, som den SseEventSink udvider Kan lukkes automatisk grænseflade:

prøv (SseEventSink sink = sseEventSink) {OutboundSseEvent sseEvent = // .. sink.send (sseEvent); }

I vores eksempelapp kan vi se, at dette kører, hvis vi besøger:

//localhost:9080/sse-jaxrs-server/sse.html

3.5. Broadcasting begivenheder

Broadcasting er den proces, hvor begivenheder sendes til flere klienter samtidigt. Dette opnås af SseBroadcaster API, og det gøres i tre enkle trin:

Først opretter vi SseBroadcaster objekt fra en injiceret Sse-kontekst som vist tidligere:

SseBroadcaster sseBroadcaster = sse.newBroadcaster ();

Derefter skal klienter abonnere for at kunne modtage Sse-begivenheder. Dette gøres normalt i en SSE-ressource metode, hvor en SseEventSink kontekstinstans indsprøjtes:

@GET @Path ("abonner") @Produces (MediaType.SERVER_SENT_EVENTS) offentlig ugyldig lytte (@Context SseEventSink sseEventSink) {this.sseBroadcaster.register (sseEventSink); }

Og endelig, vi kan udløse begivenhedsudgivelsen ved at påberåbe sig udsende() metode:

@GET @Path ("publicer") offentlig ugyldig udsendelse () {OutboundSseEvent sseEvent = // ...; this.sseBroadcaster.broadcast (sseEvent); }

Dette sender den samme begivenhed til hver registreret SseEventSink.

For at fremvise udsendelsen kan vi få adgang til denne URL:

//localhost:9080/sse-jaxrs-server/sse-broadcast.html

Og så kan vi udløse udsendelsen ved at påberåbe sig udsendelsesmetoden ():

curl -X GET // localhost: 9080 / sse-jaxrs-server / sse / stock / publish

4. Forbruger SSE-begivenheder

For at forbruge en SSE-begivenhed sendt af serveren kan vi bruge enhver HTTP-klient, men til denne vejledning bruger vi JAX RS-klient-API.

4.1. JAX RS Client API til SSE

For at komme i gang med klient-API til SSE er vi nødt til at levere afhængigheder til JAX RS-klientimplementering.

Her bruger vi implementering af Apache CXF-klient:

 org.apache.cxf cxf-rt-rs-client $ {cxf-version} org.apache.cxf cxf-rt-rs-sse $ {cxf-version} 

Det SseEventSource er hjertet i denne API, og den er konstrueret fra The WebTarget.

Vi starter med at lytte efter indkommende begivenheder, hvis abstrakte af InboundSseEvent grænseflade:

Klientklient = ClientBuilder.newClient (); WebTarget target = client.target (url); prøv (SseEventSource kilde = SseEventSource.target (target) .build ()) {source.register ((inboundSseEvent) -> System.out.println (inboundSseEvent)); source.open (); }

Når forbindelsen er oprettet, vil den registrerede begivenhedsforbruger blive påberåbt for hver modtaget InboundSseEvent.

Vi kan derefter bruge readData () metode til at læse de originale data Snor:

Strengdata = inboundSseEvent.readData ();

Eller vi kan bruge den overbelastede version til at få det deserialiserede Java-objekt ved hjælp af den passende medietype:

Lagerbeholdning = inboundSseEvent.readData (Stock.class, MediaType.Application_Json);

Her leverede vi netop en simpel begivenhedsforbruger, der udskriver den indkommende begivenhed i konsollen.

5. Konklusion

I denne vejledning fokuserede vi på, hvordan man bruger de sendte hændelser i JAX RS 2.1. Vi gav et eksempel, der viser, hvordan man sender begivenheder til en enkelt klient, samt hvordan man sender begivenheder til flere klienter.

Endelig forbrugte vi disse begivenheder ved hjælp af JAX-RS-klient-API.

Som sædvanlig kan koden til denne vejledning findes på Github.