Door de server verzonden gebeurtenissen (SSE) in JAX-RS

1. Overzicht

Server-Sent Events (SSE) is een op HTTP gebaseerde specificatie die een manier biedt om een ​​langdurige en monokanaalverbinding tot stand te brengen van de server naar de client.

De client brengt de SSE-verbinding tot stand door het mediatype te gebruiken tekst / event-stream in de Aanvaarden koptekst.

Later wordt het automatisch bijgewerkt zonder de server te vragen.

We kunnen meer details over de specificatie bekijken op de officiële specificatie.

In deze tutorial introduceren we de nieuwe JAX-RS 2.1-implementatie van SSE.

Daarom zullen we bekijken hoe we gebeurtenissen kunnen publiceren met de JAX-RS Server API. We zullen ook onderzoeken hoe we ze kunnen consumeren door de JAX-RS Client API of gewoon door een HTTP-client zoals de krullen gereedschap.

2. SSE-gebeurtenissen begrijpen

Een SSE-gebeurtenis is een tekstblok dat bestaat uit de volgende velden:

  • Evenement: het type evenement. De server kan veel verschillende soorten berichten verzenden en de klant kan alleen naar een bepaald type luisteren of kan elk gebeurtenistype anders verwerken
  • Gegevens: het bericht dat door de server is verzonden. We kunnen veel datalijnen hebben voor dezelfde gebeurtenis
  • ID kaart: de id van de gebeurtenis, gebruikt om het Last-Event-ID header, na een nieuwe poging tot verbinding. Het is handig omdat het kan voorkomen dat de server reeds verzonden gebeurtenissen verzendt
  • Opnieuw proberen: de tijd, in milliseconden, die de client nodig heeft om een ​​nieuwe verbinding tot stand te brengen wanneer de stroom wegvalt. De laatst ontvangen ID wordt automatisch verzonden via het Last-Event-ID koptekst
  • :‘: Dit is een opmerking en wordt genegeerd door de klant

Ook worden twee opeenvolgende evenementen gescheiden door een dubbele nieuwe regel ‘\ n \ n‘.

Bovendien kunnen de gegevens in dezelfde gebeurtenis in veel regels worden geschreven, zoals te zien is in het volgende voorbeeld:

event: stock id: 1: prijswijziging opnieuw proberen: 4000 data: {"dateTime": "2018-07-14T18: 06: 00.285", "id": 1, data: "name": "GOOG", "price" : 75.7119} event: stock id: 2: prijswijziging opnieuw proberen: 4000 data: {"dateTime": "2018-07-14T18: 06: 00.285", "id": 2, "name": "IBM", "prijs ": 83.4611}

In JAX RS, wordt een SSE-gebeurtenis geabstraheerd door de SseEvent koppel, of beter gezegd, door de twee subinterfaces OutboundSseEvent en InboundSseEvent.

Terwijl de OutboundSseEvent wordt gebruikt op de Server API en ontwerpt een verzonden gebeurtenis, de InboundSseEvent wordt gebruikt door de Client API en abstraheert een ontvangen gebeurtenis.

3. SSE-evenementen publiceren

Nu we hebben besproken wat een SSE-evenement is, laten we eens kijken hoe we het kunnen bouwen en naar een HTTP-client kunnen verzenden.

3.1. Project instellen

We hebben al een tutorial over het opzetten van een op JAX RS gebaseerd Maven-project. Neem daar gerust een kijkje om te zien hoe u afhankelijkheden instelt en aan de slag gaat met JAX RS.

3.2. SSE-bronmethode

Een SSE-bronmethode is een JAX RS-methode die:

  • Kan een tekst / event-stream mediatype
  • Heeft een geïnjecteerd SseEventSink parameter, waar gebeurtenissen naartoe worden gestuurd
  • Kan ook een injectie krijgen Sse parameter die wordt gebruikt als een startpunt om een ​​gebeurtenisbouwer te maken
@GET @Path ("prijzen") @Produces ("text / event-stream") public void getStockPrices (@Context SseEventSink sseEventSink, @Context Sse sse) {// ...}

Bijgevolg moet de klant het eerste HTTP-verzoek doen, met de volgende HTTP-header:

Accepteren: tekst / event-stream 

3.3. De SSE-instantie

Een SSE-instantie is een contextbean die de JAX RS Runtime beschikbaar stelt voor injectie.

We zouden het als fabriek kunnen gebruiken om te creëren:

  • OutboundSseEvent.Builder - stelt ons in staat om evenementen te creëren
  • SseBroadcaster - stelt ons in staat om evenementen uit te zenden naar meerdere abonnees

Laten we eens kijken hoe dat werkt:

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

Laten we ons nu concentreren op de evenementenbouwer. OutboundSseEvent.Builder is verantwoordelijk voor het maken van het OutboundSseEvent:

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

Zoals we kunnen zien, de bouwer heeft methoden om waarden in te stellen voor alle hierboven getoonde gebeurtenisvelden. Bovendien is het mediatype() methode wordt gebruikt om het dataveld Java-object te serialiseren naar een geschikt tekstformaat.

Standaard is het mediatype van het gegevensveld tekst / gewoon. Daarom hoeft het niet expliciet te worden gespecificeerd bij het omgaan met het Draad data type.

Anders, als we een aangepast object willen afhandelen, moeten we het mediatype specificeren of een aangepast MessageBodyWriter.De JAX RS Runtime biedt MessageBodyWriters voor de meest bekende mediatypen.

De Sse-instantie heeft ook twee builders-snelkoppelingen voor het maken van een evenement met alleen het dataveld of de type- en datavelden:

OutboundSseEvent sseEvent = sse.newEvent ("coole gebeurtenis"); OutboundSseEvent sseEvent = sse.newEvent ("getypte gebeurtenis", "gegevensgebeurtenis");

3.4. Eenvoudig evenement verzenden

Nu we weten hoe we evenementen moeten bouwen en we begrijpen hoe een SSE-bron werkt. Laten we een eenvoudig evenement sturen.

De SseEventSink interface abstraheert een enkele HTTP-verbinding. De JAX-RS Runtime kan het alleen beschikbaar maken via injectie in de SSE-resourcemethode.

Het verzenden van een evenement is dan net zo eenvoudig als het aanroepen SseEventSink.sturen().

In het volgende voorbeeld worden een aantal voorraadupdates verzonden en wordt uiteindelijk de evenementenstroom gesloten:

@GET @Path ("prijzen") @Produces ("text / event-stream") public void getStockPrices (@Context SseEventSink sseEventSink /*..*/) {int lastEventId = // ..; while (running) {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) .comment ("prijswijziging") .build (); sseEventSink.send (sseEvent); lastEventId ++; } // ..} sseEventSink.close (); }

Nadat alle gebeurtenissen zijn verzonden, verbreekt de server de verbinding door expliciet de dichtbij() methode of, bij voorkeur, met behulp van de proberen met bron, als de SseEventSink breidt de AutoClosable koppel:

probeer (SseEventSink sink = sseEventSink) {OutboundSseEvent sseEvent = // .. sink.send (sseEvent); }

In onze voorbeeld-app kunnen we dit zien draaien als we een bezoek brengen aan:

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

3.5. Uitzenden van evenementen

Uitzenden is het proces waarbij gebeurtenissen tegelijkertijd naar meerdere clients worden verzonden. Dit wordt bereikt door de SseBroadcaster API, en het is gedaan in drie eenvoudige stappen:

Eerst maken we het SseBroadcaster object uit een geïnjecteerde Sse-context zoals eerder getoond:

SseBroadcaster sseBroadcaster = sse.newBroadcaster ();

Vervolgens moeten klanten zich abonneren om Sse-evenementen te kunnen ontvangen. Dit wordt over het algemeen gedaan in een SSE-resourcemethode waarbij een SseEventSink context instantie wordt geïnjecteerd:

@GET @Path ("abonneren") @Produces (MediaType.SERVER_SENT_EVENTS) openbare leegte luisteren (@Context SseEventSink sseEventSink) {this.sseBroadcaster.register (sseEventSink); }

En tenslotte, we kunnen het publiceren van de gebeurtenis activeren door de uitzending() methode:

@GET @Path ("publish") openbare ongeldige uitzending () {OutboundSseEvent sseEvent = // ...; this.sseBroadcaster.broadcast (sseEvent); }

Dit zal hetzelfde evenement naar elke geregistreerde sturen SseEventSink.

Om de uitzending onder de aandacht te brengen, hebben we toegang tot deze URL:

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

En dan kunnen we de uitzending activeren door de bronmethode broadcast () aan te roepen:

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

4. SSE-evenementen gebruiken

Om een ​​SSE-gebeurtenis te consumeren die door de server is verzonden, kunnen we elke HTTP-client gebruiken, maar voor deze zelfstudie gebruiken we de JAX RS-client-API.

4.1. JAX RS Client API voor SSE

Om aan de slag te gaan met de client-API voor SSE, moeten we afhankelijkheden bieden voor de implementatie van JAX RS Client.

Hier gebruiken we Apache CXF-clientimplementatie:

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

De SseEventSource is het hart van deze API en is opgebouwd uit The WebTarget.

We beginnen met te luisteren naar inkomende evenementen die worden geabstraheerd door de InboundSseEvent koppel:

Client client = ClientBuilder.newClient (); WebTarget-doel = client.target (url); probeer (SseEventSource source = SseEventSource.target (target) .build ()) {source.register ((inboundSseEvent) -> System.out.println (inboundSseEvent)); source.open (); }

Zodra de verbinding tot stand is gebracht, wordt de geregistreerde gebeurtenisgebruiker opgeroepen voor elke ontvangen gebeurtenis InboundSseEvent.

We kunnen dan de readData () methode om de originele gegevens te lezen Draad:

String data = inboundSseEvent.readData ();

Of we kunnen de overbelaste versie gebruiken om het gedeserialiseerde Java-object te krijgen met het geschikte mediatype:

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

Hier hebben we zojuist een eenvoudige gebeurtenisgebruiker gegeven die de inkomende gebeurtenis in de console afdrukt.

5. Conclusie

In deze zelfstudie hebben we ons gericht op het gebruik van door de server verzonden gebeurtenissen in JAX RS 2.1. We hebben een voorbeeld gegeven dat laat zien hoe evenementen naar een enkele klant kunnen worden verzonden en hoe evenementen naar meerdere klanten kunnen worden uitgezonden.

Ten slotte hebben we deze gebeurtenissen gebruikt met behulp van de JAX-RS-client-API.

Zoals gewoonlijk is de code van deze tutorial te vinden op Github.