Отправленные сервером события (SSE) в JAX-RS
1. обзор
Отправленные сервером события (SSE) - это спецификация на основе HTTP, которая обеспечивает способ установить длительное и одноканальное соединение от сервера к клиенту.
Клиент инициирует соединение SSE, используя тип носителяtext/event-stream в заголовкеAccept.
Позже он обновляется автоматически без запроса сервера.
Мы можем проверить более подробную информацию о спецификации наthe official spec.
В этом руководстве мы познакомим вас с новой реализацией SSE в JAX-RS 2.1.
Следовательно, мы рассмотрим, как мы можем публиковать события с помощью API сервера JAX-RS. Кроме того, мы рассмотрим, как их можно использовать с помощью клиентского API JAX-RS или просто HTTP-клиента, такого как инструментcurl.
2. Понимание событий SSE
Событие SSE - это блок текста, состоящий из следующих полей:
-
Event: тип события. Сервер может отправлять множество сообщений разных типов, а клиент может прослушивать только определенный тип или может по-разному обрабатывать каждый тип события.
-
Data: сообщение, отправленное сервером. Мы можем иметь много строк данных для одного и того же события
-
Id: идентификатор события, используемый для отправки заголовкаLast-Event-ID после повторной попытки подключения. Это полезно, так как может предотвратить отправку сервером уже отправленных событий.
-
Retry: время в миллисекундах, в течение которого клиент устанавливает новое соединение при потере текущего. Последний полученный идентификатор будет автоматически отправлен через заголовокLast-Event-ID
-
«:»: это комментарий, который клиент игнорирует.
Кроме того, два последовательных события разделяются двойной новой строкой « ».
Кроме того, данные в одном и том же событии могут быть записаны в несколько строк, как показано в следующем примере:
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 _, 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. Настройка проекта
У нас уже естьa tutorial о настройке проекта Maven на основе JAX RS. Не стесняйтесь заглянуть туда, чтобы узнать, как установить зависимости и начать работу с JAX RS.
3.2. Метод ресурсов SSE
Метод ресурсов SSE - это метод 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 Runtime сделает доступным для внедрения.
Мы могли бы использовать его как фабрику для создания:
-
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 также имеет два ярлыка для создания события только с полем данных или полями типа и данных:
OutboundSseEvent sseEvent = sse.newEvent("cool Event");
OutboundSseEvent sseEvent = sse.newEvent("typed event", "data Event");
3.4. Отправка простого события
Теперь, когда мы знаем, как создавать события, и мы понимаем, как работает Ресурс SSE. Отправим простое событие.
ИнтерфейсSseEventSink абстрагирует одно HTTP-соединение. JAX-RS Runtime может сделать его доступным только путем внедрения в методе ресурсов 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(), либо, предпочтительно, используяtry-with-resource,, посколькуSseEventSink расширяет интерфейсAutoClosable:
try (SseEventSink sink = sseEventSink) {
OutboundSseEvent sseEvent = //..
sink.send(sseEvent);
}
В нашем примере приложения мы можем увидеть это, если мы посетим
http://localhost:9080/sse-jaxrs-server/sse.html
3.5. Трансляция событий
Вещание - это процесс, посредством которого события отправляются нескольким клиентам одновременно. Это достигается с помощью APISseBroadcaster, и это делается в три простых шага:
Сначала мы создаем объектSseBroadcaster из внедренного контекста Sse, как показано ранее:
SseBroadcaster sseBroadcaster = sse.newBroadcaster();
Затем клиенты должны подписаться, чтобы иметь возможность получать Sse Events. Обычно это делается в методе ресурсов SSE, куда вводится экземпляр контекстаSseEventSink:
@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-клиент, но в этом руководстве мы будем использовать клиентский API JAX RS.
4.1. Клиентский API JAX RS для SSE
Чтобы начать работу с клиентским API для SSE, нам нужно предоставить зависимости для реализации JAX RS Client.
Здесь мы будем использовать реализацию клиента Apache CXF:
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. Мы предоставили пример, который демонстрирует, как отправлять события одному клиенту, а также как транслировать события на несколько клиентов.
Наконец, мы использовали эти события, используя клиентский API JAX-RS.
Как обычно, код этого руководства находится вover on Github.