JAX-RSのサーバー送信イベント(SSE)

JAX-RSのサーバー送信イベント(SSE)

1. 概要

Server-Sent Events(SSE)は、サーバーからクライアントへの長時間実行のモノチャネル接続を確立する方法を提供するHTTPベースの仕様です。

クライアントは、Acceptヘッダーのメディアタイプtext/event-streamを使用してSSE接続を開始します。

後で、サーバーを要求せずに自動的に更新されます。

the official specで仕様の詳細を確認できます。

このチュートリアルでは、SSEの新しいJAX-RS2.1実装を紹介します。

したがって、JAX-RSサーバーAPIを使用してイベントを公開する方法を見ていきます。 また、JAX-RSクライアントAPIによって、またはcurlツールなどのHTTPクライアントによってそれらを消費する方法についても説明します。

2. SSEイベントを理解する

SSEイベントは、次のフィールドで構成されるテキストのブロックです。

  • Event:イベントのタイプ。 サーバーはさまざまなタイプの多くのメッセージを送信でき、クライアントは特定のタイプのみをリッスンするか、各イベントタイプを別々に処理できます。

  • Data:サーバーから送信されたメッセージ。 同じイベントに対して多くのデータ行を使用できます

  • Id:は、接続の再試行後にLast-Event-IDヘッダーを送信するために使用されるイベントのIDです。 サーバーが既に送信されたイベントを送信するのを防ぐことができるので便利です

  • Retry:電流が失われたときに、クライアントが新しい接続を確立する時間(ミリ秒単位)。 最後に受信したIDは、Last-Event-ID headerを介して自動的に送信されます

  • :‘:これはコメントであり、クライアントによって無視されます

また、2つの連続するイベントは、二重改行 ‘ ‘で区切られます。

さらに、次の例に見られるように、同じイベントのデータを多くの行に書き込むことができます。

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}

JAX RSでは、SSEイベントはSseEvent interface _、 or more precisely, by the two subinterfaces _OutboundSseEvent、およびInboundSseEvent.によって抽象化されます

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. SSEイベントの公開

SSEイベントとは何かについて説明したので、それを作成してHTTPクライアントに送信する方法を見てみましょう。

3.1. プロジェクトのセットアップ

JAX RSベースのMavenプロジェクトのセットアップについてはすでにa tutorialがあります。 依存関係を設定し、JAX RSを使い始める方法については、お気軽にご覧ください。

3.2. SSEリソースメソッド

SSE Resourceメソッドは、次のJAX RSメソッドです。

  • text/event-streamメディアタイプを生成できます

  • イベントが送信されるSseEventSinkパラメータが挿入されています

  • イベントビルダーを作成するためのエントリポイントとして使用されるSseパラメータが挿入されている場合もあります

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

そのため、クライアントは次のHTTPヘッダーを使用して最初のHTTP要求を作成する必要があります。

Accept: text/event-stream

3.3. SSEインスタンス

SSEインスタンスは、JAX RSランタイムがインジェクションに使用できるようにするコンテキストBeanです。

これをファクトリとして使用して、次のものを作成できます。

  • OutboundSseEvent.Builder – を使用すると、イベントを作成できます。

  • SseBroadcaster – を使用すると、複数のサブスクライバーにイベントをブロードキャストできます

その仕組みを見てみましょう。

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

それでは、イベントビルダーに焦点を当てましょう。 OutboundSseEvent.Builderは、OutboundSseEventの作成を担当します。

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

ご覧のとおり、the builder has methods to set values for all event fields shown aboveです。 さらに、mediaType() メソッドは、データフィールドのJavaオブジェクトを適切なテキスト形式にシリアル化するために使用されます。

デフォルトでは、データフィールドのメディアタイプはtext/plainです。 したがって、Stringデータ型を処理するときに明示的に指定する必要はありません。

それ以外の場合、カスタムオブジェクトを処理する場合は、メディアタイプを指定するか、カスタムMessageBodyWriter.The JAX RS Runtime provides MessageBodyWriters for the most known media typesを提供する必要があります。

Sseインスタンスには、データフィールドのみ、またはタイプとデータフィールドのみでイベントを作成するための2つのビルダーショートカットもあります。

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

3.4. 簡単なイベントの送信

これで、イベントの作成方法がわかり、SSEリソースの仕組みがわかりました。 簡単なイベントを送信しましょう。

SseEventSinkインターフェースは、単一のHTTP接続を抽象化します。 JAX-RSランタイムは、SSEリソースメソッドへの注入によってのみ使用可能にできます。

イベントの送信は、SseEventSink.send(). を呼び出すのと同じくらい簡単です。

次の例では、大量の株価更新を送信し、最終的にイベントストリームを閉じます。

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

すべてのイベントを送信した後、サーバーはclose()メソッドを明示的に呼び出すか、できればSseEventSinkAutoClosableインターフェースを拡張するときにtry-with-resource,を使用して接続を閉じます。

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

サンプルアプリでは、次のページにアクセスすると、この実行を確認できます。

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

3.5. 放送イベント

ブロードキャストは、イベントを複数のクライアントに同時に送信するプロセスです。 これはSseBroadcaster APIによって実現され、次の3つの簡単なステップで実行されます。

まず、前に示したように、挿入されたSseコンテキストからSseBroadcasterオブジェクトを作成します。

SseBroadcaster sseBroadcaster = sse.newBroadcaster();

次に、クライアントはSseイベントを受信できるようにサブスクライブする必要があります。 これは通常、SseEventSinkコンテキストインスタンスが挿入されるSSEリソースメソッドで実行されます。

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

そして最後に、we can trigger the event publishing by invoking the broadcast() method

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

これにより、登録されている各SseEventSink.に同じイベントが送信されます

放送を紹介するために、次のURLにアクセスできます。

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

そして、broadcast()リソースメソッドを呼び出すことで、ブロードキャストをトリガーできます。

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

4. SSEイベントの消費

サーバーから送信されたSSEイベントを利用するには、任意のHTTPクライアントを使用できますが、このチュートリアルでは、JAXRSクライアントAPIを使用します。

4.1. SSE用のJAXRSクライアントAPI

SSEのクライアントAPIを使用するには、JAX RSクライアントの実装に依存関係を提供する必要があります。

ここでは、ApacheCXFクライアントの実装を使用します。


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


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

SseEventSourceはこのAPIの中心であり、WebTarget.から構築されます

まず、InboundSseEventインターフェイスによって抽象化された着信イベントをリッスンします。

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

接続が確立されると、登録されたイベントコンシューマーが受信ごとに呼び出されます * InboundSseEvent *。

次に、readData()メソッドを使用して、元のデータString:を読み取ることができます。

String data = inboundSseEvent.readData();

または、オーバーロードされたバージョンを使用して、適切なメディアタイプを使用してデシリアライズされたJavaオブジェクトを取得できます。

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

ここでは、コンソールに着信イベントを出力する単純なイベントコンシューマーを提供しました。

5. 結論

このチュートリアルでは、JAX RS 2.1でサーバー送信イベントを使用する方法に焦点を当てました。 単一のクライアントにイベントを送信する方法と、複数のクライアントにイベントをブロードキャストする方法を示す例を提供しました。

最後に、JAX-RSクライアントAPIを使用してこれらのイベントを使用しました。

いつものように、このチュートリアルのコードはover on Githubにあります。