Skip to content

Commit

Permalink
Adds a Main class to manually test interop with other SSE clients.
Browse files Browse the repository at this point in the history
Signed-off-by: Santiago Pericas-Geertsen <santiago.pericasgeertsen@oracle.com>
  • Loading branch information
spericas committed Aug 30, 2024
1 parent 197f68d commit 7fc6e09
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public <X extends Source<SseEvent>> void handle(X source, HttpClientResponse res
emit = false;
}
}

source.onClose();
} catch (IOException e) {
source.onError(e);
Expand Down
21 changes: 11 additions & 10 deletions webserver/sse/src/main/java/io/helidon/webserver/sse/SseSink.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public class SseSink implements Sink<SseEvent> {
private static final byte[] SSE_COMMENT = ":".getBytes(StandardCharsets.UTF_8);
private static final byte[] OK_200 = "HTTP/1.1 200 OK\r\n".getBytes(StandardCharsets.UTF_8);
private static final byte[] DATE = "Date: ".getBytes(StandardCharsets.UTF_8);

private static final WritableHeaders<?> EMPTY_HEADERS = WritableHeaders.create();

private final ServerResponse response;
Expand All @@ -75,7 +74,7 @@ public class SseSink implements Sink<SseEvent> {
this.ctx = context.connectionContext();
this.mediaContext = ctx.listenerContext().mediaContext();
this.closeRunnable = context.closeRunnable();
initResponse();
writeStatusAndHeaders();
}

@Override
Expand Down Expand Up @@ -120,30 +119,32 @@ public void close() {
ctx.serverSocket().close();
}

void initResponse() {
void writeStatusAndHeaders() {
ServerResponseHeaders headers = response.headers();

// verify response has no status or content type
HttpMediaType ct = headers.contentType().orElse(null);
if (response.status().code() != Status.OK_200.code()
|| ct != null && !CONTENT_TYPE_EVENT_STREAM.values().equals(ct.mediaType().text())) {
throw new IllegalStateException("ServerResponse instance cannot be used to create SseResponse");
throw new IllegalStateException("ServerResponse instance cannot be used to create SseSink");
}
if (ct == null) {
headers.add(CONTENT_TYPE_EVENT_STREAM);
}
headers.add(CACHE_NO_CACHE_ONLY);

// start writing heading to buffer
// start writing status line
BufferData buffer = BufferData.growing(256);
buffer.write(OK_200);

// serialize headers
// serialize a date header if not included
if (!headers.contains(HeaderNames.DATE)) {
buffer.write(DATE);
byte[] dateBytes = DateTime.http1Bytes();
buffer.write(dateBytes);
}

// set up and write headers
if (ct == null) {
headers.add(CONTENT_TYPE_EVENT_STREAM);
}
headers.add(CACHE_NO_CACHE_ONLY);
for (Header header : headers) {
header.writeHttp1Header(buffer);
}
Expand Down
2 changes: 1 addition & 1 deletion webserver/sse/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

requires static io.helidon.common.features.api;

requires io.helidon.common.socket;
requires transitive io.helidon.common.socket;
requires transitive io.helidon.common;
requires transitive io.helidon.http.sse;
requires transitive io.helidon.webserver;
Expand Down
56 changes: 55 additions & 1 deletion webserver/tests/sse/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,18 @@
<name>Helidon WebServer Tests SSE</name>
<description>WebServer SSE tests</description>

<properties>
<mainClass>io.helidon.webserver.tests.sse.Main</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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
Expand Down Expand Up @@ -70,4 +77,51 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${version.plugin.resources}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${version.plugin.dependency}</version>
<executions>
<execution>
<id>copy-libs</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<overWriteIfNewer>true</overWriteIfNewer>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${version.plugin.jar}</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs</classpathPrefix>
<mainClass>${mainClass}</mainClass>
<useUniqueVersions>false</useUniqueVersions>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.webserver.tests.sse;

import java.util.stream.IntStream;

import io.helidon.http.sse.SseEvent;
import io.helidon.webserver.WebServer;
import io.helidon.webserver.http.HttpRouting;
import io.helidon.webserver.http.ServerRequest;
import io.helidon.webserver.http.ServerResponse;
import io.helidon.webserver.sse.SseSink;

/**
* Simple SSE server that can be used to manually test interop with other
* clients such as Postman.
*/
public class Main {

static void sse(ServerRequest req, ServerResponse res) {
try (SseSink sseSink = res.sink(SseSink.TYPE)) {
IntStream.range(0, 1000).forEach(i -> sseSink.emit(SseEvent.create("hello world " + i)));
}
}

public static void main(String[] args) {
WebServer.builder()
.port(8080)
.routing(HttpRouting.builder().get("/sse", Main::sse))
.build()
.start();
}
}

0 comments on commit 7fc6e09

Please sign in to comment.