Skip to content

Commit

Permalink
New simple SSE test that shows how to use the API in Helidon SE and a…
Browse files Browse the repository at this point in the history
…lso shows how to interact with an SSE endpoint using WebClient.
  • Loading branch information
spericas committed Oct 23, 2024
1 parent 0e76cfa commit 39db5d7
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/webserver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
<module>tracing</module>
<module>tutorial</module>
<module>websocket</module>
<module>sse</module>
</modules>
</project>
17 changes: 17 additions & 0 deletions examples/webserver/sse/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Helidon SE SSE Example

This is a very simple example showing how to use the Helidon SSE API to send events from the
server to a client. It defines a couple of SSE "hello world" endpoints that send either
text or JSON messages. It also uses the SSE extensions to the Helidon WebClient API to test
these endpoints.

## Build, run and test

Build and start the server:
```shell
mvn package
java -jar target/helidon-examples-webserver-sse.jar
```

See `SseServiceTest` for additional information on the tests that are executed during
the build process.
94 changes: 94 additions & 0 deletions examples/webserver/sse/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2024 Oracle and/or its affiliates.
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.
-->
<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>
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-se</artifactId>
<version>4.2.0-SNAPSHOT</version>
<relativePath/>
</parent>

<groupId>io.helidon.examples.webserver</groupId>
<artifactId>helidon-examples-webserver-sse</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>Helidon Examples WebServer SSE</name>

<properties>
<mainClass>io.helidon.examples.webserver.sse.SseMain</mainClass>
</properties>

<dependencies>
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-sse</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient-sse</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jsonp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jsonb</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.webserver.testing.junit5</groupId>
<artifactId>helidon-webserver-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-libs</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.examples.webserver.sse;

import io.helidon.webserver.WebServer;
import io.helidon.webserver.http.HttpRouting;

class SseMain {

private SseMain() {
}

public static void main(String[] args) {
WebServer.builder()
.port(8080)
.routing(HttpRouting.builder().register(new SseService()))
.build()
.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.examples.webserver.sse;

import io.helidon.http.sse.SseEvent;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.http.HttpService;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
import io.helidon.webserver.sse.SseSink;

import jakarta.json.Json;
import jakarta.json.JsonObject;

class SseService implements HttpService {

@Override
public void routing(HttpRules httpRules) {
httpRules.get("/sse_text", this::sseText)
.get("/sse_json", this::sseJson);
}

void sseText(ServerRequest req, ServerResponse res) {
try (SseSink sseSink = res.sink(SseSink.TYPE)) {
sseSink.emit(SseEvent.builder()
.comment("first line")
.name("first")
.data("hello")
.build())
.emit(SseEvent.builder()
.name("second")
.data("world")
.build());
}
}

void sseJson(ServerRequest req, ServerResponse res) {
JsonObject json = Json.createObjectBuilder()
.add("hello", "world")
.build();
try (SseSink sseSink = res.sink(SseSink.TYPE)) {
sseSink.emit(SseEvent.create(json));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.examples.webserver.sse;
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.examples.webserver.sse;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import io.helidon.http.sse.SseEvent;
import io.helidon.webclient.http1.Http1Client;
import io.helidon.webclient.http1.Http1ClientResponse;
import io.helidon.webclient.sse.SseSource;
import io.helidon.webserver.http.HttpRules;
import io.helidon.webserver.testing.junit5.ServerTest;
import io.helidon.webserver.testing.junit5.SetUpRoute;

import jakarta.json.JsonObject;
import org.junit.jupiter.api.Test;

import static io.helidon.http.HeaderValues.ACCEPT_EVENT_STREAM;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;

@ServerTest
class SseServiceTest {

private final Http1Client client;

SseServiceTest(Http1Client client) {
this.client = client;
}

@SetUpRoute
static void routing(HttpRules rules) {
rules.register(new SseService());
}

@Test
void testSseText() throws InterruptedException {
try (Http1ClientResponse r = client.get("/sse_text").header(ACCEPT_EVENT_STREAM).request()) {
CountDownLatch latch = new CountDownLatch(1);
r.source(SseSource.TYPE, new SseSource() {
private int state = 0;

@Override
public void onEvent(SseEvent event) {
switch (state) {
case 0 -> {
assertThat(event.comment().isPresent(), is(true));
assertThat(event.comment().get(), is("first line"));
assertThat(event.name().isPresent(), is(true));
assertThat(event.name().get(), is("first"));
assertThat(event.data(), is("hello"));
}
case 1 -> {
assertThat(event.name().isPresent(), is(true));
assertThat(event.name().get(), is("second"));
assertThat(event.data(), is("world"));
}
}
state++;
}

@Override
public void onClose() {
latch.countDown();
}
});
assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
}
}

@Test
void testSseJson() throws InterruptedException {
try (Http1ClientResponse r = client.get("/sse_json").header(ACCEPT_EVENT_STREAM).request()) {
CountDownLatch latch = new CountDownLatch(1);
r.source(SseSource.TYPE, event -> {
JsonObject json = event.data(JsonObject.class);
assertThat(json, is(notNullValue()));
assertThat(json.getString("hello"), is("world"));
latch.countDown();
});
assertThat(latch.await(5, TimeUnit.SECONDS), is(true));
}
}
}

0 comments on commit 39db5d7

Please sign in to comment.