From 1824a3aa0499cae46557fbc55ea9b8e163a5f252 Mon Sep 17 00:00:00 2001 From: Daniel Kec Date: Thu, 3 Sep 2020 17:54:03 +0200 Subject: [PATCH] Tyrus support for native-image (#2097) Signed-off-by: Daniel Kec --- .../io/helidon/common/FeatureCatalog.java | 3 +- dependencies/pom.xml | 2 +- docs/se/aot/01_introduction.adoc | 2 +- .../extension/HelidonReflectionFeature.java | 3 +- tests/integration/native-image/se-1/README.md | 17 ++++++ tests/integration/native-image/se-1/pom.xml | 5 ++ .../integration/nativeimage/se1/Se1Main.java | 10 ++- .../nativeimage/se1/WebSocketEndpoint.java | 61 +++++++++++++++++++ .../native-image/reflection-config.json | 19 ++++++ 9 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebSocketEndpoint.java create mode 100644 webserver/tyrus/src/main/resources/META-INF/helidon/native-image/reflection-config.json diff --git a/common/common/src/main/java/io/helidon/common/FeatureCatalog.java b/common/common/src/main/java/io/helidon/common/FeatureCatalog.java index 4fe82c495d4..151212b903d 100644 --- a/common/common/src/main/java/io/helidon/common/FeatureCatalog.java +++ b/common/common/src/main/java/io/helidon/common/FeatureCatalog.java @@ -150,7 +150,8 @@ final class FeatureCatalog { .name("Websocket") .description("Jakarta Websocket implementation") .path("WebServer", "Websocket") - .nativeSupported(false)); + .nativeSupported(true) + .nativeDescription("Server only")); /* * MP Modules diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 3bf24685a62..ec925a79591 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -51,7 +51,7 @@ 2.3.1 1.30.5 2.3.3 - 20.0.0 + 20.1.0 1.27.1 28.1-jre 1.4.199 diff --git a/docs/se/aot/01_introduction.adoc b/docs/se/aot/01_introduction.adoc index aeed3b5eeee..cb1570e6d3e 100644 --- a/docs/se/aot/01_introduction.adoc +++ b/docs/se/aot/01_introduction.adoc @@ -102,7 +102,7 @@ for native image. |✅ |{nbsp} |JSON-P |{nbsp} |✅ |{nbsp} |Multi-part |{nbsp} |❓ |{nbsp} |Prometheus |Not yet tested. -|❓ |{nbsp} |Websocket |Not yet tested. +|✅ |{nbsp} |Websocket |Server only. |❓ |gRPC Server |gRPC Server |Not yet tested. |✅ |{nbsp} |Metrics |{nbsp} |❓ |gRPC Client |gRPC Client |Not yet tested. diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index 76cf34842b2..4c5f8cbc3af 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -794,9 +794,8 @@ private void addConstructors() { } void addFields(boolean all) { - Field[] fields = clazz.getFields(); - try { + Field[] fields = clazz.getFields(); // add all public fields for (Field field : fields) { add(field); diff --git a/tests/integration/native-image/se-1/README.md b/tests/integration/native-image/se-1/README.md index c5bc5827993..b802da71ccd 100644 --- a/tests/integration/native-image/se-1/README.md +++ b/tests/integration/native-image/se-1/README.md @@ -40,4 +40,21 @@ curl -i -H "Accept: application/json" http://localhost:7076/metrics # Should return ALL TESTS PASSED! after passing all webclient tests curl -i http://localhost:7076/wc/test + +# Should return: Upgrade: websocket +curl \ + --include \ + --no-buffer \ + --header "Connection: Upgrade" \ + --header "Upgrade: websocket" \ + --header "Host: localhost:7076" \ + --header "Origin: http://localhost:7076" \ + --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \ + --header "Sec-WebSocket-Version: 13" \ + http://localhost:7076/ws/messages + +# Bi-directional test is possible with websocat tool +# should return 'part1 part2' +for msg in "part1" "part2" "SEND"; do echo $msg; done \ +| websocat ws://127.0.0.1:7076/ws/messages ``` diff --git a/tests/integration/native-image/se-1/pom.xml b/tests/integration/native-image/se-1/pom.xml index 10f9549814a..89d31e11fe3 100644 --- a/tests/integration/native-image/se-1/pom.xml +++ b/tests/integration/native-image/se-1/pom.xml @@ -35,6 +35,7 @@ JSON-P Classpath static content File static content + Tyrus(web sockets) Config (with change support) File watch Classpath @@ -64,6 +65,10 @@ io.helidon.webserver helidon-webserver + + io.helidon.webserver + helidon-webserver-tyrus + io.helidon.media helidon-media-jsonp diff --git a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java index b0d76703733..033b7643320 100644 --- a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java +++ b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/Se1Main.java @@ -32,9 +32,12 @@ import io.helidon.webserver.Routing; import io.helidon.webserver.StaticContentSupport; import io.helidon.webserver.WebServer; +import io.helidon.webserver.tyrus.TyrusSupport; import org.eclipse.microprofile.health.HealthCheckResponse; +import javax.websocket.server.ServerEndpointConfig; + import static io.helidon.config.ConfigSources.classpath; import static io.helidon.config.ConfigSources.file; @@ -42,7 +45,6 @@ * Main class of this integration test. */ public final class Se1Main { - /** * Cannot be instantiated. */ @@ -143,6 +145,12 @@ private static Routing createRouting(Config config) { .register("/greet", greetService) .register("/wc", webClientService) .register("/zipkin", zipkinService) + .register("/ws", + TyrusSupport.builder().register( + ServerEndpointConfig.Builder.create( + WebSocketEndpoint.class, "/messages") + .build()) + .build()) .build(); } diff --git a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebSocketEndpoint.java b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebSocketEndpoint.java new file mode 100644 index 00000000000..92fe3459805 --- /dev/null +++ b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebSocketEndpoint.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 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.tests.integration.nativeimage.se1; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + + +public class WebSocketEndpoint extends Endpoint { + + private static final Logger LOGGER = Logger.getLogger(WebSocketEndpoint.class.getName()); + + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + + StringBuilder sb = new StringBuilder(); + + LOGGER.log(Level.INFO, "Session " + session.getId()); + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + LOGGER.log(Level.INFO, "WS Receiving " + message); + if (message.contains("SEND")) { + sendTextMessage(session, sb.toString()); + sb.setLength(0); + } else { + sb.append(message); + } + } + }); + } + + private void sendTextMessage(Session session, String msg) { + try { + session.getBasicRemote().sendText(msg); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Message sending failed", e); + } + } +} diff --git a/webserver/tyrus/src/main/resources/META-INF/helidon/native-image/reflection-config.json b/webserver/tyrus/src/main/resources/META-INF/helidon/native-image/reflection-config.json new file mode 100644 index 00000000000..28ed29528e8 --- /dev/null +++ b/webserver/tyrus/src/main/resources/META-INF/helidon/native-image/reflection-config.json @@ -0,0 +1,19 @@ +{ + "annotated": [], + "class-hierarchy": [ + "javax.websocket.Endpoint", + "javax.websocket.Encoder", + "javax.websocket.Decoder", + "org.glassfish.tyrus.spi.ClientContainer", + "io.netty.channel.ChannelInboundHandlerAdapter", + "io.netty.channel.ChannelHandlerAdapter", + "io.netty.channel.ChannelInboundHandler", + "io.netty.channel.ChannelOutboundHandler" + ], + "classes": [ + "javax.websocket.CloseReason", + "javax.naming.InitialContext" + ], + "exclude": [ + ] +} \ No newline at end of file