Server gesendete Ereignisse (SSE) in JAX-RS

Server-Sent Events (SSE) In JAX-RS

1. Überblick

Server-Sent Events (SSE) ist eine HTTP-basierte Spezifikation, mit der eine lang andauernde Monokanalverbindung vom Server zum Client hergestellt werden kann.

Der Client initiiert die SSE-Verbindung mithilfe des Medientypstext/event-stream im HeaderAccept.

Später wird es automatisch aktualisiert, ohne den Server anzufordern.

Wir können weitere Details zur Spezifikation fürthe official spec überprüfen.

In diesem Tutorial stellen wir die neue JAX-RS 2.1-Implementierung von SSE vor.

Daher werden wir uns ansehen, wie wir Ereignisse mit der JAX-RS Server-API veröffentlichen können. Außerdem werden wir untersuchen, wie wir sie entweder von der JAX-RS-Client-API oder nur von einem HTTP-Client wie dem Toolcurlverwenden können.

2. Grundlegendes zu SSE-Ereignissen

Ein SSE-Ereignis ist ein Textblock, der aus den folgenden Feldern besteht:

  • Event:ist der Ereignistyp. Der Server kann viele Nachrichten unterschiedlichen Typs senden, und der Client kann möglicherweise nur auf einen bestimmten Typ warten oder jeden Ereignistyp unterschiedlich verarbeiten

  • Data:ist die vom Server gesendete Nachricht. Wir können viele Datenleitungen für dasselbe Ereignis haben

  • Id:ist die ID des Ereignisses, mit der der Header vonLast-Event-IDnach einem erneuten Verbindungsversuch gesendet wird. Dies ist nützlich, da der Server möglicherweise keine bereits gesendeten Ereignisse sendet

  • Retry:ist die Zeit in Millisekunden, die der Client benötigt, um eine neue Verbindung herzustellen, wenn der Strom verloren geht. Die zuletzt empfangene ID wird automatisch über denLast-Event-ID header gesendet

  • ':': Dies ist ein Kommentar und wird vom Client ignoriert

Außerdem werden zwei aufeinanderfolgende Ereignisse durch einen doppelten Zeilenumbruch „ “ getrennt.

Darüber hinaus können die Daten im selben Ereignis in mehrere Zeilen geschrieben werden, wie im folgenden Beispiel dargestellt:

event: stock
id: 1
: price change
retry: 4000
data: {"dateTime":"2018-07-14T18:06:00.285","id":1,
data: "name":"GOOG","price":75.7119}

event: stock
id: 2
: price change
retry: 4000
data: {"dateTime":"2018-07-14T18:06:00.285","id":2,"name":"IBM","price":83.4611}

InJAX RS wird ein SSE-Ereignis durch dieSseEvent-Schnittstelle _, or more precisely, by the two subinterfaces _OutboundSseEvent undInboundSseEvent. abstrahiert

While the OutboundSseEvent is used on the Server API and designs a sent event, the InboundSseEvent is used by the Client API and abstracts a received event.

3. Veröffentlichen von SSE-Ereignissen

Nachdem wir uns nun mit einem SSE-Ereignis befasst haben, wollen wir sehen, wie wir es erstellen und an einen HTTP-Client senden können.

3.1. Projektaufbau

Wir haben bereitsa tutorial zum Einrichten eines JAX RS-basierten Maven-Projekts. Schauen Sie sich dort um, um zu erfahren, wie Sie Abhängigkeiten festlegen und mit JAX RS beginnen können.

3.2. SSE-Ressourcenmethode

Eine SSE-Ressourcenmethode ist eine JAX RS-Methode, die:

  • Kann einen Medientyp vontext/event-streamerzeugen

  • Hat einen injiziertenSseEventSink-Parameter, an den Ereignisse gesendet werden

  • Kann auch einen injiziertenSse-Parameter haben, der als Einstiegspunkt zum Erstellen eines Event Builders verwendet wird

@GET
@Path("prices")
@Produces("text/event-stream")
public void getStockPrices(@Context SseEventSink sseEventSink, @Context Sse sse) {
    //...
}

Folglich sollte der Client die erste HTTP-Anforderung mit dem folgenden HTTP-Header senden:

Accept: text/event-stream

3.3. Die SSE-Instanz

Eine SSE-Instanz ist eine Kontext-Bean, die von JAX RS Runtime zur Injektion bereitgestellt wird.

Wir könnten es als Fabrik benutzen, um folgendes zu erschaffen:

  • OutboundSseEvent.Builder – erlaubt uns dann, Ereignisse zu erstellen

  • SseBroadcaster – ermöglicht es uns, Ereignisse an mehrere Abonnenten zu senden

Mal sehen, wie das funktioniert:

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

Konzentrieren wir uns nun auf den Event Builder. OutboundSseEvent.Builder ist für die Erstellung derOutboundSseEvent verantwortlich:

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

Wie wir sehen können,the builder has methods to set values for all event fields shown above. Zusätzlich wird diemediaType() -Smethod verwendet, um das Datenfeld-Java-Objekt in ein geeignetes Textformat zu serialisieren.

Standardmäßig ist der Medientyp des Datenfeldstext/plain. Daher muss es beim Umgang mit dem DatentypStringnicht explizit angegeben werden.

Andernfalls müssen wir, wenn wir ein benutzerdefiniertes Objekt verarbeiten möchten, den Medientyp angeben oder ein benutzerdefiniertesMessageBodyWriter.The JAX RS Runtime provides MessageBodyWriters for the most known media types angeben.

Die Sse-Instanz verfügt auch über zwei Builder-Verknüpfungen zum Erstellen eines Ereignisses nur mit dem Datenfeld oder dem Typ und den Datenfeldern:

OutboundSseEvent sseEvent = sse.newEvent("cool Event");
OutboundSseEvent sseEvent = sse.newEvent("typed event", "data Event");

3.4. Einfaches Ereignis senden

Jetzt wissen wir, wie Ereignisse erstellt werden und wie eine SSE-Ressource funktioniert. Senden wir ein einfaches Ereignis.

DieSseEventSink-Schnittstelle abstrahiert eine einzelne HTTP-Verbindung. Die JAX-RS Runtime kann es nur durch Injection in der SSE-Ressourcenmethode verfügbar machen.

Das Senden eines Ereignisses ist dann so einfach wie das Aufrufen vonSseEventSink.send(). 

Im nächsten Beispiel werden eine Reihe von Bestandsaktualisierungen gesendet und der Ereignisstrom wird schließlich geschlossen:

@GET
@Path("prices")
@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("price change")
              .build();
            sseEventSink.send(sseEvent);
            lastEventId++;
        }
     //..
    }
    sseEventSink.close();
}

Nach dem Senden aller Ereignisse schließt der Server die Verbindung entweder durch explizites Aufrufen derclose()-Methode oder vorzugsweise durch Verwenden vontry-with-resource,, währendSseEventSink dieAutoClosable-Schnittstelle erweitert:

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

In unserer Beispiel-App können wir dies sehen, wenn wir besuchen:

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

3.5. Rundfunkveranstaltungen

Broadcasting ist der Vorgang, bei dem Ereignisse gleichzeitig an mehrere Clients gesendet werden. Dies wird durch dieSseBroadcaster-API erreicht und erfolgt in drei einfachen Schritten:

Zuerst erstellen wir dasSseBroadcaster-Objekt aus einem injizierten Sse-Kontext, wie zuvor gezeigt:

SseBroadcaster sseBroadcaster = sse.newBroadcaster();

Anschließend sollten Clients ein Abonnement abschließen, um Sse-Ereignisse empfangen zu können. Dies erfolgt im Allgemeinen in einer SSE-Ressourcenmethode, in die eineSseEventSink-Kontextinstanz eingefügt wird:

@GET
@Path("subscribe")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void listen(@Context SseEventSink sseEventSink) {
    this.sseBroadcaster.register(sseEventSink);
}

Und schließlichwe can trigger the event publishing by invoking the broadcast() method:

@GET
@Path("publish")
public void broadcast() {
    OutboundSseEvent sseEvent = //...;
    this.sseBroadcaster.broadcast(sseEvent);
}

Dadurch wird das gleiche Ereignis an alle registriertenSseEventSink.gesendet

Um die Sendung zu präsentieren, können wir auf diese URL zugreifen:

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

Und dann können wir das Broadcasting auslösen, indem wir die broadcast () - Ressourcenmethode aufrufen:

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

4. Konsumieren von SSE-Ereignissen

Um ein vom Server gesendetes SSE-Ereignis zu nutzen, können wir jeden HTTP-Client verwenden. Für dieses Lernprogramm verwenden wir jedoch die JAX RS-Client-API.

4.1. JAX RS Client API für SSE

Um mit der Client-API für SSE zu beginnen, müssen Abhängigkeiten für die JAX RS-Client-Implementierung bereitgestellt werden.

Hier verwenden wir die Apache CXF-Client-Implementierung:


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


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

SseEventSource ist das Herzstück dieser API und besteht ausWebTarget.

Wir warten zunächst auf eingehende Ereignisse, die von derInboundSseEvent-Schnittstelle abstrahiert werden:

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

Sobald die Verbindung hergestellt ist, wird der registrierte Ereigniskonsument für jeden Empfang aufgerufen * InboundSseEvent *.

Wir können dann die MethodereadData() verwenden, um die OriginaldatenString: zu lesen

String data = inboundSseEvent.readData();

Oder wir können die überladene Version verwenden, um das deserialisierte Java-Objekt mit dem geeigneten Medientyp abzurufen:

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

Hier haben wir nur einen einfachen Ereigniskonsumenten bereitgestellt, der das eingehende Ereignis in der Konsole druckt.

5. Fazit

In diesem Tutorial haben wir uns auf die Verwendung der von Servern gesendeten Ereignisse in JAX RS 2.1 konzentriert. Wir haben ein Beispiel bereitgestellt, das zeigt, wie Ereignisse an einen einzelnen Client gesendet werden und wie Ereignisse an mehrere Clients gesendet werden.

Schließlich haben wir diese Ereignisse mit der JAX-RS-Client-API verarbeitet.

Wie üblich befindet sich der Code dieses Tutorials inover on Github.