Search in sources :

Example 1 with Event

use of io.micronaut.http.sse.Event in project micronaut-core by micronaut-projects.

the class DefaultHttpClient method eventStreamOrError.

private <I> Publisher<Event<ByteBuffer<?>>> eventStreamOrError(@NonNull io.micronaut.http.HttpRequest<I> request, @NonNull Argument<?> errorType) {
    if (request instanceof MutableHttpRequest) {
        ((MutableHttpRequest) request).accept(MediaType.TEXT_EVENT_STREAM_TYPE);
    }
    return Flux.create(emitter -> dataStream(request, errorType).subscribe(new Subscriber<ByteBuffer<?>>() {

        private Subscription dataSubscription;

        private CurrentEvent currentEvent;

        @Override
        public void onSubscribe(Subscription s) {
            this.dataSubscription = s;
            Disposable cancellable = () -> dataSubscription.cancel();
            emitter.onCancel(cancellable);
            if (!emitter.isCancelled() && emitter.requestedFromDownstream() > 0) {
                // request the first chunk
                dataSubscription.request(1);
            }
        }

        @Override
        public void onNext(ByteBuffer<?> buffer) {
            try {
                int len = buffer.readableBytes();
                // emit the current event
                if (len == 0) {
                    try {
                        Event event = Event.of(byteBufferFactory.wrap(currentEvent.data)).name(currentEvent.name).retry(currentEvent.retry).id(currentEvent.id);
                        emitter.next(event);
                    } finally {
                        currentEvent = null;
                    }
                } else {
                    if (currentEvent == null) {
                        currentEvent = new CurrentEvent();
                    }
                    int colonIndex = buffer.indexOf((byte) ':');
                    // SSE comments start with colon, so skip
                    if (colonIndex > 0) {
                        // obtain the type
                        String type = buffer.slice(0, colonIndex).toString(StandardCharsets.UTF_8).trim();
                        int fromIndex = colonIndex + 1;
                        // skip the white space before the actual data
                        if (buffer.getByte(fromIndex) == ((byte) ' ')) {
                            fromIndex++;
                        }
                        if (fromIndex < len) {
                            int toIndex = len - fromIndex;
                            switch(type) {
                                case "data":
                                    ByteBuffer content = buffer.slice(fromIndex, toIndex);
                                    byte[] d = currentEvent.data;
                                    if (d == null) {
                                        currentEvent.data = content.toByteArray();
                                    } else {
                                        currentEvent.data = ArrayUtils.concat(d, content.toByteArray());
                                    }
                                    break;
                                case "id":
                                    ByteBuffer id = buffer.slice(fromIndex, toIndex);
                                    currentEvent.id = id.toString(StandardCharsets.UTF_8).trim();
                                    break;
                                case "event":
                                    ByteBuffer event = buffer.slice(fromIndex, toIndex);
                                    currentEvent.name = event.toString(StandardCharsets.UTF_8).trim();
                                    break;
                                case "retry":
                                    ByteBuffer retry = buffer.slice(fromIndex, toIndex);
                                    String text = retry.toString(StandardCharsets.UTF_8);
                                    if (!StringUtils.isEmpty(text)) {
                                        Long millis = Long.valueOf(text);
                                        currentEvent.retry = Duration.ofMillis(millis);
                                    }
                                    break;
                                default:
                                    // ignore message
                                    break;
                            }
                        }
                    }
                }
                if (emitter.requestedFromDownstream() > 0 && !emitter.isCancelled()) {
                    dataSubscription.request(1);
                }
            } catch (Throwable e) {
                onError(e);
            } finally {
                if (buffer instanceof ReferenceCounted) {
                    ((ReferenceCounted) buffer).release();
                }
            }
        }

        @Override
        public void onError(Throwable t) {
            dataSubscription.cancel();
            if (t instanceof HttpClientException) {
                emitter.error(t);
            } else {
                emitter.error(new HttpClientException("Error consuming Server Sent Events: " + t.getMessage(), t));
            }
        }

        @Override
        public void onComplete() {
            emitter.complete();
        }
    }), FluxSink.OverflowStrategy.BUFFER);
}
Also used : Disposable(reactor.core.Disposable) MutableHttpRequest(io.micronaut.http.MutableHttpRequest) ByteBuffer(io.micronaut.core.io.buffer.ByteBuffer) HttpClientException(io.micronaut.http.client.exceptions.HttpClientException) Subscriber(org.reactivestreams.Subscriber) JsonSubscriber(io.micronaut.http.netty.stream.JsonSubscriber) IdleStateEvent(io.netty.handler.timeout.IdleStateEvent) Event(io.micronaut.http.sse.Event) Subscription(org.reactivestreams.Subscription) ReferenceCounted(io.micronaut.core.io.buffer.ReferenceCounted)

Example 2 with Event

use of io.micronaut.http.sse.Event in project micronaut-core by micronaut-projects.

the class HeadlineController method streamHeadlines.

// tag::streaming[]
// <1>
@Get(value = "/headlines", produces = MediaType.TEXT_EVENT_STREAM)
Flux<Event<Headline>> streamHeadlines() {
    return Flux.<Event<Headline>>create((emitter) -> {
        // <2>
        Headline headline = new Headline();
        headline.setText("Latest Headline at " + ZonedDateTime.now());
        emitter.next(Event.of(headline));
        emitter.complete();
    }, FluxSink.OverflowStrategy.BUFFER).repeat(// <3>
    100).delayElements(// <4>
    Duration.of(1, ChronoUnit.SECONDS));
}
Also used : Headline(io.micronaut.http.client.docs.streaming.Headline) Event(io.micronaut.http.sse.Event) Get(io.micronaut.http.annotation.Get)

Example 3 with Event

use of io.micronaut.http.sse.Event in project micronaut-core by micronaut-projects.

the class HeadlineController method streamHeadlines.

// tag::streaming[]
// <1>
@Get(value = "/headlines", processes = MediaType.TEXT_EVENT_STREAM)
Publisher<Event<Headline>> streamHeadlines() {
    return Flux.<Event<Headline>>create((emitter) -> {
        // <2>
        Headline headline = new Headline();
        headline.setText("Latest Headline at " + ZonedDateTime.now());
        emitter.next(Event.of(headline));
        emitter.complete();
    }, FluxSink.OverflowStrategy.BUFFER).repeat(// <3>
    100).delayElements(// <4>
    Duration.of(1, ChronoUnit.SECONDS));
}
Also used : Headline(io.micronaut.docs.streaming.Headline) Event(io.micronaut.http.sse.Event) Get(io.micronaut.http.annotation.Get)

Example 4 with Event

use of io.micronaut.http.sse.Event in project micronaut-core by micronaut-projects.

the class TextStreamCodec method encode.

@SuppressWarnings("MagicNumber")
@Override
public <T, B> ByteBuffer<B> encode(T object, ByteBufferFactory<?, B> allocator) {
    Event<Object> event;
    if (object instanceof Event) {
        event = (Event<Object>) object;
    } else {
        event = Event.of(object);
    }
    Object data = event.getData();
    ByteBuffer body;
    if (data instanceof CharSequence) {
        body = allocator.copiedBuffer(data.toString().getBytes(defaultCharset));
    } else {
        MediaTypeCodec jsonCodec = resolveMediaTypeCodecRegistry().findCodec(MediaType.APPLICATION_JSON_TYPE).orElseThrow(() -> new CodecException("No possible JSON encoders found!"));
        body = jsonCodec.encode(data, allocator);
    }
    ByteBuffer eventData = allocator.buffer(body.readableBytes() + 10);
    writeAttribute(eventData, COMMENT_PREFIX, event.getComment());
    writeAttribute(eventData, ID_PREFIX, event.getId());
    writeAttribute(eventData, EVENT_PREFIX, event.getName());
    Duration retry = event.getRetry();
    if (retry != null) {
        writeAttribute(eventData, RETRY_PREFIX, String.valueOf(retry.toMillis()));
    }
    // Write the data
    int idx = body.indexOf((byte) '\n');
    while (idx > -1) {
        int length = idx + 1;
        byte[] line = new byte[length];
        body.read(line, 0, length);
        eventData.write(DATA_PREFIX).write(line);
        idx = body.indexOf((byte) '\n');
    }
    if (body.readableBytes() > 0) {
        int length = body.readableBytes();
        byte[] line = new byte[length];
        body.read(line, 0, length);
        eventData.write(DATA_PREFIX).write(line);
    }
    // Write new lines for event separation
    eventData.write(NEWLINE).write(NEWLINE);
    return eventData;
}
Also used : Event(io.micronaut.http.sse.Event) CodecException(io.micronaut.http.codec.CodecException) Duration(java.time.Duration) ByteBuffer(io.micronaut.core.io.buffer.ByteBuffer) MediaTypeCodec(io.micronaut.http.codec.MediaTypeCodec)

Example 5 with Event

use of io.micronaut.http.sse.Event in project micronaut-core by micronaut-projects.

the class HeadlineControllerSpec method testConsumeEventStreamObject.

@Test
public void testConsumeEventStreamObject() {
    SseClient client = embeddedServer.getApplicationContext().createBean(SseClient.class, embeddedServer.getURL());
    List<Event<Headline>> events = new ArrayList<>();
    Flux.from(client.eventStream(HttpRequest.GET("/headlines"), Headline.class)).subscribe(events::add);
    await().until(() -> events.size() == 2);
    assertEquals("Micronaut 1.0 Released", events.get(0).getData().getTitle());
    assertEquals("Come and get it", events.get(0).getData().getDescription());
}
Also used : SseClient(io.micronaut.http.client.sse.SseClient) ArrayList(java.util.ArrayList) Event(io.micronaut.http.sse.Event) Test(org.junit.Test)

Aggregations

Event (io.micronaut.http.sse.Event)6 ByteBuffer (io.micronaut.core.io.buffer.ByteBuffer)2 Get (io.micronaut.http.annotation.Get)2 JsonProperty (com.fasterxml.jackson.annotation.JsonProperty)1 Splitter (com.google.common.base.Splitter)1 ImmutableMap (com.google.common.collect.ImmutableMap)1 SchemaRegistryClient (io.confluent.kafka.schemaregistry.client.SchemaRegistryClient)1 Value (io.micronaut.context.annotation.Value)1 Environment (io.micronaut.context.env.Environment)1 ReferenceCounted (io.micronaut.core.io.buffer.ReferenceCounted)1 StringUtils (io.micronaut.core.util.StringUtils)1 Headline (io.micronaut.docs.streaming.Headline)1 MutableHttpRequest (io.micronaut.http.MutableHttpRequest)1 Headline (io.micronaut.http.client.docs.streaming.Headline)1 HttpClientException (io.micronaut.http.client.exceptions.HttpClientException)1 SseClient (io.micronaut.http.client.sse.SseClient)1 CodecException (io.micronaut.http.codec.CodecException)1 MediaTypeCodec (io.micronaut.http.codec.MediaTypeCodec)1 JsonSubscriber (io.micronaut.http.netty.stream.JsonSubscriber)1 IdleStateEvent (io.netty.handler.timeout.IdleStateEvent)1