CLOVER🍀

That was when it all began.

WildFly 35(RESTEasy)のServer-Sent Events(SSE)のイベント送信時にメディアタイプを指定する

これは、なにをしたくて書いたもの?

前に、WildFly(RESTEasy)を使ってJakarta RESTful Web Services(以降JAX-RS)でServer-Sent Events(SSE)が
扱えることを試してみました。

WildFly 35(RESTEasy)でServer-Sent Events(SSE)を試す - CLOVER🍀

この時は基本的な動作を確認しましたが、イベント送信時にパイプライン(MessageBodyWriter)が使われるということ
だったのでこのあたりを確認しようかなと思いまして。

Jakarta RESTful Web ServicesでServer-Sent Eventsのイベント送信時にメディアタイプを指定する

今回、なんの話をしているかというと、この部分です。

The initial SSE response, which may only include the HTTP headers, is processed using the standard JAX-RS pipeline as described in Appendix Processing Pipeline. Each subsequent SSE event may include a different payload and thus require the use of a specific message body writer. Note that since this use case differs slightly from the normal JAX-RS pipeline, implementations SHOULD NOT call entity interceptors on each individual event.

Jakarta RESTful Web Services / Server-Sent Events / Processing Pipeline

HTTPヘッダーを含む可能性のある最初のレスポンスのみ、標準のJAX-RSのパイプラインが使われるとされています。

The initial SSE response, which may only include the HTTP headers, is processed using the standard JAX-RS pipeline as described in Appendix Processing Pipeline.

後続のSSEの各イベントでは、ペイロードに合わせたMessageBodyWriterを使われることが求められています。

Each subsequent SSE event may include a different payload and thus require the use of a specific message body writer.

またこのケースは通常のJAX-RSのパイプラインと少し異なるので、JAX-RS実装は個々のイベントごとにエンティティ
インターセプターを呼び出すべきではないとされています。

Note that since this use case differs slightly from the normal JAX-RS pipeline, implementations SHOULD NOT call entity interceptors on each individual event.

パイプラインというのはこちらですね。

Jakarta RESTful Web Services / Appendix C: Processing Pipeline

ここでOutboundSseEvent.Builderを見ると、イベントデータのメディアタイプを指定することができます。
これとServer-Sent EventsにおけるMessageBodyWriterが関係していそうですね。

OutboundSseEvent.Builder (Jakarta RESTful WS API 3.1.0 API)

ところで、Server-Sent Eventsの仕様自体を見ると、特にtext/event-stream以外のメディアタイプには触れられていません。
text/event-streamは最初のレスポンスで使うメディアタイプですね。

HTML Living Standard / Server-sent events

イベントにおけるメディアタイプの指定が、送信されるデータにどのように反映されるかが気になるところです。

というわけで、今回はこのあたりを確認してみたいと思います。

  • イベントの構築時にメディアタイプを指定する/しない時の挙動の変化
  • JAX-RSのフィルターやインターセプターを適用してみて、イベント送信時に動作するかどうか

あとはRESTEasyの実装面でも確認できればという感じですね。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.6 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-54-generic", arch: "amd64", family: "unix"

WildFlyは35.0.1.Finalを使います。

準備

Maven依存関係など。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>jaxrs-sse-mediatype-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.wildfly.bom</groupId>
                <artifactId>wildfly-ee-with-tools</artifactId>
                <version>35.0.1.Final</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise</groupId>
            <artifactId>jakarta.enterprise.cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.inject</groupId>
            <artifactId>jakarta.inject-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.enterprise.concurrent</groupId>
            <artifactId>jakarta.enterprise.concurrent-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>ROOT</finalName>
        <plugins>
            <plugin>
                <groupId>org.wildfly.plugins</groupId>
                <artifactId>wildfly-maven-plugin</artifactId>
                <version>5.1.2.Final</version>
                <executions>
                    <execution>
                        <id>package</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <overwrite-provisioned-server>true</overwrite-provisioned-server>
                    <discover-provisioning-info>
                        <version>35.0.1.Final</version>
                    </discover-provisioning-info>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Server-Sent Eventsを使ったJAX-RSソースコードを作成する

それでは、Server-Sent Eventsを使ったJAX-RSソースコードを作成していきます。

JAX-RSの有効化。

src/main/java/org/littlewings/jaxrs/sse/RestApplication.java

package org.littlewings.jaxrs.sse;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/")
public class RestApplication extends Application {
}

メディアタイプごとに指定するクラス。JSON用とXML用の2種類を作りましょう。

JSON向けはRecordにします。

src/main/java/org/littlewings/jaxrs/sse/Book.java

package org.littlewings.jaxrs.sse;

public record Book(String isbn, String title, Integer price) {
}

XML用。JAXBを使います。

src/main/java/org/littlewings/jaxrs/sse/XmlBook.java

package org.littlewings.jaxrs.sse;

import jakarta.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class XmlBook {
    private String isbn;
    private String title;
    private Integer price;

    public XmlBook() {
    }

    public static XmlBook create(String isbn, String title, Integer price) {
        XmlBook book = new XmlBook();
        book.setIsbn(isbn);
        book.setTitle(title);
        book.setPrice(price);

        return book;
    }

    // getter/setterは省略
}

Server-Sent Eventsを扱うリソースクラス。

src/main/java/org/littlewings/jaxrs/sse/SseResource.java

package org.littlewings.jaxrs.sse;

import jakarta.annotation.Resource;
import jakarta.enterprise.concurrent.ManagedExecutorService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.sse.OutboundSseEvent;
import jakarta.ws.rs.sse.Sse;
import jakarta.ws.rs.sse.SseEventSink;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

@Path("/sse")
@ApplicationScoped
public class SseResource {
    @Inject
    private Sse sse;

    @Resource
    private ManagedExecutorService executorService;

    @GET
    @Path("/text")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public void text(@Context SseEventSink sseEventSink) {
        executorService.submit(() -> {
            try (SseEventSink sink = sseEventSink) {
                String id = "last-id";
                IntStream.rangeClosed(1, 10).forEach(i -> {
                    OutboundSseEvent event =
                            sse.newEventBuilder()
                                    .id(id)
                                    .name("name-" + i)
                                    .data("data-" + i)
                                    .build();

                    sink.send(event);

                    sleep();
                });
            }
        });
    }

    @GET
    @Path("/entity")
    @Produces(MediaType.APPLICATION_JSON)
    public Book entity() {
        return new Book("978-4621303252", "Effective Java 第3版", 4400);
    }

    @GET
    @Path("/json")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public void json(@Context SseEventSink sseEventSink) {
        List<Book> books = List.of(
                new Book("978-4621303252", "Effective Java 第3版", 4400),
                new Book("978-4798180946", "独習Java 第6版", 3278),
                new Book("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520),
                new Book("978-4297146801", "改訂3版 パーフェクトJava (Perfect series 02)", 3740),
                new Book("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278)
        );

        executorService.submit(() -> {
            try (SseEventSink sink = sseEventSink) {
                String id = "last-id";
                books.forEach(book -> {
                    OutboundSseEvent event =
                            sse.newEventBuilder()
                                    .id(id)
                                    .name("name-" + books.indexOf(book))
                                    .mediaType(MediaType.APPLICATION_JSON_TYPE)
                                    .data(Book.class, book)
                                    .build();

                    sink.send(event);

                    sleep();
                });
            }
        });
    }

    @GET
    @Path("/entity-xml")
    @Produces(MediaType.APPLICATION_XML)
    public XmlBook entityXml() {
        return XmlBook.create("978-4621303252", "Effective Java 第3版", 4400);
    }


    @GET
    @Path("/xml")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public void xml(@Context SseEventSink sseEventSink) {
        List<XmlBook> books = List.of(
                XmlBook.create("978-4621303252", "Effective Java 第3版", 4400),
                XmlBook.create("978-4798180946", "独習Java 第6版", 3278),
                XmlBook.create("978-4297144357", "Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~", 3520),
                XmlBook.create("978-4297146801", "改訂3版 パーフェクトJava (Perfect series 02)", 3740),
                XmlBook.create("978-4774189093", "Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで", 3278)
        );

        executorService.submit(() -> {
            try (SseEventSink sink = sseEventSink) {
                String id = "last-id";
                books.forEach(book -> {
                    OutboundSseEvent event =
                            sse.newEventBuilder()
                                    .id(id)
                                    .name("name-" + books.indexOf(book))
                                    .mediaType(MediaType.APPLICATION_XML_TYPE)
                                    .data(XmlBook.class, book)
                                    .build();

                    sink.send(event);

                    sleep();
                });
            }
        });
    }

    private void sleep() {
        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            // ignore
        }
    }
}

比較のために5つのリソースメソッドを用意しました。

  • Server-Sent Eventsでデフォルトのメディアタイプでイベントを送信するもの
  • JSONを単一のレスポンスで返すもの
  • Server-Sent EventsでメディアタイプにJSONを指定してイベントを送信するもの
  • XMLを単一のレスポンスで返すもの
  • Server-Sent EventsでメディアタイプにXMLを指定してイベントを送信するもの

単一のレスポンスを返すものがあるのは、フィルターやインターセプターのかかり方の違いを見るためです。

また結果がわかりやすくなるように、スリープを入れています。

というわけで、フィルターを用意。

src/main/java/org/littlewings/jaxrs/sse/LoggingFilter.java

package org.littlewings.jaxrs.sse;

import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import org.jboss.logging.Logger;

@Provider
public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
    private Logger logger = Logger.getLogger(LoggingFilter.class);

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        logger.info("request logging");
    }

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        logger.info("response logging");
    }
}

インターセプター。

src/main/java/org/littlewings/jaxrs/sse/LoggingInterceptor.java

package org.littlewings.jaxrs.sse;

import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.ext.ReaderInterceptor;
import jakarta.ws.rs.ext.ReaderInterceptorContext;
import jakarta.ws.rs.ext.WriterInterceptor;
import jakarta.ws.rs.ext.WriterInterceptorContext;
import java.io.IOException;
import org.jboss.logging.Logger;

@Provider
public class LoggingInterceptor implements ReaderInterceptor, WriterInterceptor {
    private Logger logger = Logger.getLogger(LoggingInterceptor.class);

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
        logger.info("read intercept logging");
        return context.proceed();
    }

    @Override
    public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
        logger.info("write intercept logging");
        context.proceed();
    }
}

これらを使って動作確認していきます。

動作確認してみる

それでは、動作確認していきます。

Server-Sent Eventsでデフォルトのメディアタイプでイベントを送信する

まずは、デフォルトのメディアタイプでイベントを送信してみます。

$ curl -i localhost:8080/sse/text
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: text/event-stream
Date: Fri, 07 Mar 2025 14:50:50 GMT



event: name-1
id: last-id
data: data-1

event: name-2
id: last-id
data: data-2

event: name-3
id: last-id
data: data-3

event: name-4
id: last-id
data: data-4

event: name-5
id: last-id
data: data-5

event: name-6
id: last-id
data: data-6

event: name-7
id: last-id
data: data-7

event: name-8
id: last-id
data: data-8

event: name-9
id: last-id
data: data-9

event: name-10
id: last-id
data: data-10

この時のログ。フィルターのログが表示されます。

23:50:50,552 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) request logging
23:50:50,578 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) response logging

ログが表示されるのは、最初のレスポンスを返す時だけですね。

JSONを単一のレスポンスで返す

次はJSONを単一のレスポンスで返してみます。

$ curl -i localhost:8080/sse/entity
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json
Content-Length: 71
Date: Fri, 07 Mar 2025 14:52:03 GMT

{"isbn":"978-4621303252","price":4400,"title":"Effective Java 第3版"}

この時のログは、フィルターに加えてインターセプターのものも表示されます。

23:52:03,422 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) request logging
23:52:03,425 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) response logging
23:52:03,429 INFO  [org.littlewings.jaxrs.sse.LoggingInterceptor] (default task-1) write intercept logging
Server-Sent EventsでメディアタイプにJSONを指定してイベントを送信する

イベント送信時にメディアタイプにJSONを指定してみます。

$ curl -i localhost:8080/sse/json
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: text/event-stream
Date: Fri, 07 Mar 2025 14:53:19 GMT



event: name-0
id: last-id
data: {"isbn":"978-4621303252","price":4400,"title":"Effective Java 第3版"}

event: name-1
id: last-id
data: {"isbn":"978-4798180946","price":3278,"title":"独習Java 第6版"}

event: name-2
id: last-id
data: {"isbn":"978-4297144357","price":3520,"title":"Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~"}

event: name-3
id: last-id
data: {"isbn":"978-4297146801","price":3740,"title":"改訂3版 パーフェクトJava (Perfect series 02)"}

event: name-4
id: last-id
data: {"isbn":"978-4774189093","price":3278,"title":"Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで"}

データ自体はJSONで返ってきますが、指定したメディアタイプがレスポンスに現れることはありません。

この時のログ。

23:53:19,235 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) request logging
23:53:19,238 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) response logging

最初のレスポンスを返した時のフィルターのログのみで、ここはメディアタイプを指定しない時と同じですね。

またインターセプターのログもありません。

XMLを単一のレスポンスで返す

XMLを単一のレスポンスで返してみます。

$ curl -i localhost:8080/sse/entity-xml
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/xml;charset=UTF-8
Content-Length: 157
Date: Fri, 07 Mar 2025 14:55:54 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xmlBook><isbn>978-4621303252</isbn><price>4400</price><title>Effective Java 第3版</title></xmlBook>

ログ。

23:55:54,118 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) request logging
23:55:54,121 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) response logging
23:55:54,127 INFO  [org.littlewings.jaxrs.sse.LoggingInterceptor] (default task-1) write intercept logging

JSONの時と同じですね。

Server-Sent EventsでメディアタイプにXMLを指定してイベントを送信する

最後は、イベントのメディアタイプにXMLを指定します。

$ curl -i localhost:8080/sse/xml
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: text/event-stream
Date: Fri, 07 Mar 2025 14:56:52 GMT



event: name-0
id: last-id
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><xmlBook><isbn>978-4621303252</isbn><price>4400</price><title>Effective Java 第3版</title></xmlBook>

event: name-1
id: last-id
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><xmlBook><isbn>978-4798180946</isbn><price>3278</price><title>独習Java 第6版</title></xmlBook>

event: name-2
id: last-id
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><xmlBook><isbn>978-4297144357</isbn><price>3520</price><title>Javaエンジニアのための ソフトウェアテスト実践入門 ~自動化と生成AIによるモダンなテスト技法~</title></xmlBook>

event: name-3
id: last-id
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><xmlBook><isbn>978-4297146801</isbn><price>3740</price><title>改訂3版 パーフェクトJava (Perfect series 02)</title></xmlBook>

event: name-4
id: last-id
data: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><xmlBook><isbn>978-4774189093</isbn><price>3278</price><title>Java本格入門 ~モダンスタイルによる基礎からオブジ ェクト指向・実用ライブラリまで</title></xmlBook>

ログ。こちらもJSONと同じですね。

23:56:52,800 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) request logging
23:56:52,802 INFO  [org.littlewings.jaxrs.sse.LoggingFilter] (default task-1) response logging

というわけで、Server-Sent Eventsで送信するイベントにメディアタイプを指定した場合は、MessageBodyWriterだけが
動いていそうなことがわかりました。

実装を見てみる

最後に、RESTEasyでの実装を見てみましょう。

SseEventSink#sendの実装箇所はこちらです。

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventOutputImpl.java#L196-L231

この処理はCompletableFutureを使うので非同期処理なのですが、その時の状態に応じてそのまま書き込みを行うか
1度キューに入れるかに分岐します。

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventOutputImpl.java#L207-L220

書き込みが行われるのはここですね。

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventOutputImpl.java#L240-L307

ここでのwriterは、MessageBodyWriterのことです。

                    writer.writeTo(event, event.getClass(), null, new Annotation[] {}, mediaType, null, bout);

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventOutputImpl.java#L275

ただ、このMessageBodyWriterはServer-Sent Eventsの最初のレスポンスのメディアタイプを扱うようになっていて、

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventProvider.java#L37

実際にはこのメソッドの中でイベントのメディアタイプに対応するMessageBodyWriterを取り出して書き込みを行うように
なっています。

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/SseEventProvider.java#L108-L156

ちなみに、メディアタイプが未指定の場合はtext/plainとして扱われます。

https://github.com/resteasy/resteasy/blob/6.2.11.Final/resteasy-core/src/main/java/org/jboss/resteasy/plugins/providers/sse/OutboundSseEventImpl.java#L49

というわけで、確かにJAX-RSのパイプラインにあるフィルターやインターセプターは登場せず、ペイロード
扱う時にMessageBodyWriterが使われる、という感じみたいですね。

ちなみに今回使ったMessageBodyWriterは、JsonBindingProviderJAXBXmlRootElementProviderの2つです。

https://github.com/resteasy/resteasy/blob/6.2.11.Final/providers/json-binding/src/main/java/org/jboss/resteasy/plugins/providers/jsonb/JsonBindingProvider.java

https://github.com/resteasy/resteasy/blob/6.2.11.Final/providers/jaxb/src/main/java/org/jboss/resteasy/plugins/providers/jaxb/JAXBXmlRootElementProvider.java

おわりに

WildFly 35.0.0.1.Finalに含まれるRESTEasyを使って、Server-Sent Eventsのイベント送信時にメディアタイプを指定すると
どうなるかということを確認してみました。

それから、フィルターやインターセプターはServer-Sent Eventsを使う時にどのような扱いになるかも確認できたと
思います。

あとは実際に使うとなると、Jakarta Concurrentyまわりも押さえておいた方がよさそうですね。