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