From 7fc6e0908faf69b449411513d242367c5a1ac163 Mon Sep 17 00:00:00 2001 From: Santiago Pericas-Geertsen Date: Fri, 30 Aug 2024 10:10:14 -0400 Subject: [PATCH] Adds a Main class to manually test interop with other SSE clients. Signed-off-by: Santiago Pericas-Geertsen --- .../sse/SseSourceHandlerProvider.java | 1 + .../io/helidon/webserver/sse/SseSink.java | 21 +++---- webserver/sse/src/main/java/module-info.java | 2 +- webserver/tests/sse/pom.xml | 56 ++++++++++++++++++- .../io/helidon/webserver/tests/sse/Main.java | 47 ++++++++++++++++ 5 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 webserver/tests/sse/src/main/java/io/helidon/webserver/tests/sse/Main.java diff --git a/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java b/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java index bf8e240ad31..502bb1f8989 100644 --- a/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java +++ b/webclient/sse/src/main/java/io/helidon/webclient/sse/SseSourceHandlerProvider.java @@ -92,6 +92,7 @@ public > void handle(X source, HttpClientResponse res emit = false; } } + source.onClose(); } catch (IOException e) { source.onError(e); diff --git a/webserver/sse/src/main/java/io/helidon/webserver/sse/SseSink.java b/webserver/sse/src/main/java/io/helidon/webserver/sse/SseSink.java index 74815bd88fe..9fe86fd3544 100644 --- a/webserver/sse/src/main/java/io/helidon/webserver/sse/SseSink.java +++ b/webserver/sse/src/main/java/io/helidon/webserver/sse/SseSink.java @@ -62,7 +62,6 @@ public class SseSink implements Sink { 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; @@ -75,7 +74,7 @@ public class SseSink implements Sink { this.ctx = context.connectionContext(); this.mediaContext = ctx.listenerContext().mediaContext(); this.closeRunnable = context.closeRunnable(); - initResponse(); + writeStatusAndHeaders(); } @Override @@ -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); } diff --git a/webserver/sse/src/main/java/module-info.java b/webserver/sse/src/main/java/module-info.java index d3c9fcd3ef1..3fcba72944e 100644 --- a/webserver/sse/src/main/java/module-info.java +++ b/webserver/sse/src/main/java/module-info.java @@ -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; diff --git a/webserver/tests/sse/pom.xml b/webserver/tests/sse/pom.xml index 118c80fb763..379c4c1b4a9 100644 --- a/webserver/tests/sse/pom.xml +++ b/webserver/tests/sse/pom.xml @@ -28,11 +28,18 @@ Helidon WebServer Tests SSE WebServer SSE tests + + io.helidon.webserver.tests.sse.Main + + + + io.helidon.webserver + helidon-webserver + io.helidon.webserver helidon-webserver-sse - test io.helidon.webclient @@ -70,4 +77,51 @@ test + + + + + org.apache.maven.plugins + maven-resources-plugin + ${version.plugin.resources} + + + org.apache.maven.plugins + maven-dependency-plugin + ${version.plugin.dependency} + + + copy-libs + prepare-package + + copy-dependencies + + + ${project.build.directory}/libs + false + false + true + true + runtime + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${version.plugin.jar} + + + + true + libs + ${mainClass} + false + + + + + + diff --git a/webserver/tests/sse/src/main/java/io/helidon/webserver/tests/sse/Main.java b/webserver/tests/sse/src/main/java/io/helidon/webserver/tests/sse/Main.java new file mode 100644 index 00000000000..65de0bfac5d --- /dev/null +++ b/webserver/tests/sse/src/main/java/io/helidon/webserver/tests/sse/Main.java @@ -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(); + } +}