All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ratpack.sse.ServerSentEvents Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc-1
Show newest version
/*
 * Copyright 2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ratpack.sse;

import io.netty.buffer.ByteBufAllocator;
import org.reactivestreams.Publisher;
import ratpack.func.Action;
import ratpack.handling.Context;
import ratpack.http.Response;
import ratpack.http.internal.HttpHeaderConstants;
import ratpack.render.Renderable;
import ratpack.sse.internal.DefaultEvent;
import ratpack.sse.internal.ServerSentEventEncoder;
import ratpack.stream.Streams;

/**
 * A {@link ratpack.handling.Context#render(Object) renderable} object for streaming server side events.
 * 

* A {@link ratpack.render.Renderer renderer} for this type is implicitly provided by Ratpack core. *

* Example usage: *

{@code
 * import org.reactivestreams.Publisher;
 * import ratpack.http.client.ReceivedResponse;
 * import ratpack.sse.ServerSentEvents;
 * import ratpack.test.embed.EmbeddedApp;
 *
 * import java.time.Duration;
 * import java.util.Arrays;
 * import java.util.Objects;
 *
 * import static ratpack.sse.ServerSentEvents.serverSentEvents;
 * import static ratpack.stream.Streams.periodically;
 *
 * import static java.util.stream.Collectors.joining;
 *
 * import static org.junit.Assert.assertEquals;
 *
 * public class Example {
 *   public static void main(String[] args) throws Exception {
 *     EmbeddedApp.fromHandler(context -> {
 *       Publisher stream = periodically(context, Duration.ofMillis(5), i ->
 *         i < 5 ? i.toString() : null
 *       );
 *
 *       ServerSentEvents events = serverSentEvents(stream, e ->
 *         e.id(Objects::toString).event("counter").data(i -> "event " + i)
 *       );
 *
 *       context.render(events);
 *     }).test(httpClient -> {
 *       ReceivedResponse response = httpClient.get();
 *       assertEquals("text/event-stream;charset=UTF-8", response.getHeaders().get("Content-Type"));
 *
 *       String expectedOutput = Arrays.asList(0, 1, 2, 3, 4)
 *         .stream()
 *         .map(i -> "event: counter\ndata: event " + i + "\nid: " + i + "\n")
 *         .collect(joining("\n"))
 *         + "\n";
 *
 *       assertEquals(expectedOutput, response.getBody().getText());
 *     });
 *   }
 * }
 * }
* * @see Wikipedia - Using server-sent events * @see MDN - Using server-sent events */ public class ServerSentEvents implements Renderable { private final Publisher> publisher; /** * Creates a new renderable object wrapping the event stream. *

* Takes a publisher of any type, and an action that mutates a created {@link Event} object for each stream item. * The action is executed for each item in the stream as it is emitted before being sent as a server sent event. * The state of the event object when the action completes will be used as the event. *

* The action MUST set one of the {@code id}, {@code event}, {@code data}. * * @param publisher the event stream * @param action the conversion of stream items to event objects * @param the type of object in the event stream * @return a {@link ratpack.handling.Context#render(Object) renderable} object */ public static ServerSentEvents serverSentEvents(Publisher publisher, Action> action) { return new ServerSentEvents(Streams.map(publisher, item -> { Event event = action.with(new DefaultEvent<>(item)); if (event.getId() == null && event.getEvent() == null && event.getData() == null) { throw new IllegalArgumentException("You must supply at least one of data, event, id"); } return event; })); } private ServerSentEvents(Publisher> publisher) { this.publisher = publisher; } /** * The stream of events. * * @return the stream of events */ public Publisher> getPublisher() { return publisher; } /** * {@inheritDoc} */ @Override public void render(Context context) throws Exception { ByteBufAllocator bufferAllocator = context.get(ByteBufAllocator.class); Response response = context.getResponse(); response.getHeaders().add(HttpHeaderConstants.CONTENT_TYPE, HttpHeaderConstants.TEXT_EVENT_STREAM_CHARSET_UTF_8); response.getHeaders().add(HttpHeaderConstants.CACHE_CONTROL, HttpHeaderConstants.NO_CACHE_FULL); response.getHeaders().add(HttpHeaderConstants.PRAGMA, HttpHeaderConstants.NO_CACHE); response.sendStream(Streams.map(publisher, i -> ServerSentEventEncoder.INSTANCE.encode(i, bufferAllocator))); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy