diff --git a/ci/smoke-tests.yml b/ci/smoke-tests.yml index 9d5c36f4..17e5f981 100644 --- a/ci/smoke-tests.yml +++ b/ci/smoke-tests.yml @@ -82,6 +82,7 @@ groups: - websocket - websocket-jetty - websocket-stomp + - websocket-undertow - name: integration smoke_tests: - integration diff --git a/settings.gradle b/settings.gradle index 4d5f0ba2..140820ec 100644 --- a/settings.gradle +++ b/settings.gradle @@ -115,4 +115,5 @@ include "webmvc-tomcat-tls" include "websocket" include "websocket-jetty" include "websocket-stomp" +include "websocket-undertow" diff --git a/websocket-undertow/README.adoc b/websocket-undertow/README.adoc new file mode 100644 index 00000000..218ba890 --- /dev/null +++ b/websocket-undertow/README.adoc @@ -0,0 +1 @@ +Tests WebSocket support with Undertow. diff --git a/websocket-undertow/build.gradle b/websocket-undertow/build.gradle new file mode 100644 index 00000000..6e9b07bb --- /dev/null +++ b/websocket-undertow/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'org.springframework.aot.smoke-test' + id 'org.graalvm.buildtools.native' +} + +dependencies { + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation(project(":aot-smoke-test-third-party-hints")) + implementation("org.springframework.boot:spring-boot-starter-websocket") + implementation("org.springframework.boot:spring-boot-starter-undertow") + modules { + module("org.springframework.boot:spring-boot-starter-tomcat") { + replacedBy("org.springframework.boot:spring-boot-starter-undertow", "Use Undertow instead of Tomcat") + } + } + testImplementation("org.springframework.boot:spring-boot-starter-test") + + aotSmokeTestImplementation(project(":aot-smoke-test-support")) + aotSmokeTestImplementation("org.awaitility:awaitility:4.2.0") +} + +aotSmokeTest { + webApplication = true +} diff --git a/websocket-undertow/src/aotSmokeTest/java/com/example/websocket/jetty/WebSocketJettyApplicationAotTests.java b/websocket-undertow/src/aotSmokeTest/java/com/example/websocket/jetty/WebSocketJettyApplicationAotTests.java new file mode 100644 index 00000000..20b1a37c --- /dev/null +++ b/websocket-undertow/src/aotSmokeTest/java/com/example/websocket/jetty/WebSocketJettyApplicationAotTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022 the original author or authors. + * + * 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 + * + * https://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 com.example.websocket.jetty; + +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.smoketest.support.assertj.AssertableOutput; +import org.springframework.aot.smoketest.support.junit.AotSmokeTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@AotSmokeTest +class WebSocketJettyApplicationAotTests { + + @Test + void clientShouldSendMessage(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)) + .untilAsserted(() -> assertThat(output).hasSingleLineContaining("Client: Sent 'Hello Websocket'")); + } + + @Test + void serverShouldReceiveMessage(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)) + .untilAsserted(() -> assertThat(output).hasSingleLineContaining("Server: Received 'Hello Websocket'")); + } + + @Test + void serverShouldReply(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)) + .untilAsserted(() -> assertThat(output).hasSingleLineContaining("Server: Sent 'Hello Websocket'")); + } + + @Test + void clientShouldReceiveReply(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)) + .untilAsserted(() -> assertThat(output).hasSingleLineContaining("Client: Received 'Hello Websocket'")); + } + +} diff --git a/websocket-undertow/src/main/java/com/example/websocket/undertow/CLR.java b/websocket-undertow/src/main/java/com/example/websocket/undertow/CLR.java new file mode 100644 index 00000000..a8ebedf1 --- /dev/null +++ b/websocket-undertow/src/main/java/com/example/websocket/undertow/CLR.java @@ -0,0 +1,49 @@ +package com.example.websocket.undertow; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +@Component +class CLR implements CommandLineRunner { + + private final WebSocketClient webSocketClient; + + private final ServletWebServerApplicationContext servletWebServerApplicationContext; + + CLR(WebSocketClient webSocketClient, ServletWebServerApplicationContext servletWebServerApplicationContext) { + this.webSocketClient = webSocketClient; + this.servletWebServerApplicationContext = servletWebServerApplicationContext; + } + + @Override + public void run(String... args) throws Exception { + URI uri = URI.create("ws://localhost:%d/echo".formatted(getServerPort())); + WebSocketSession session = this.webSocketClient.execute(new MyWebsocketHandler(), null, uri).get(5, + TimeUnit.SECONDS); + TextMessage message = new TextMessage("Hello Websocket"); + session.sendMessage(message); + System.out.printf("Client: Sent '%s'%n", message.getPayload()); + } + + private int getServerPort() { + return this.servletWebServerApplicationContext.getWebServer().getPort(); + } + + private static class MyWebsocketHandler extends TextWebSocketHandler { + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + System.out.printf("Client: Received '%s'%n", message.getPayload()); + } + + } + +} diff --git a/websocket-undertow/src/main/java/com/example/websocket/undertow/EchoWebSocketHandler.java b/websocket-undertow/src/main/java/com/example/websocket/undertow/EchoWebSocketHandler.java new file mode 100644 index 00000000..998834f1 --- /dev/null +++ b/websocket-undertow/src/main/java/com/example/websocket/undertow/EchoWebSocketHandler.java @@ -0,0 +1,33 @@ +package com.example.websocket.undertow; + +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +@Component +public class EchoWebSocketHandler extends TextWebSocketHandler { + + public EchoWebSocketHandler() { + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + System.out.printf("Opened new session in instance %s%n", this); + } + + @Override + public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + System.out.printf("Server: Received '%s' in %s%n", message.getPayload(), this); + session.sendMessage(message); + System.out.printf("Server: Sent '%s' in %s%n", message.getPayload(), this); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + System.out.printf("Closing connection in %s%n", this, exception); + session.close(CloseStatus.SERVER_ERROR); + } + +} diff --git a/websocket-undertow/src/main/java/com/example/websocket/undertow/WebSocketConfig.java b/websocket-undertow/src/main/java/com/example/websocket/undertow/WebSocketConfig.java new file mode 100644 index 00000000..4c0c5561 --- /dev/null +++ b/websocket-undertow/src/main/java/com/example/websocket/undertow/WebSocketConfig.java @@ -0,0 +1,31 @@ +package com.example.websocket.undertow; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.client.WebSocketClient; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + private final EchoWebSocketHandler echoWebSocketHandler; + + public WebSocketConfig(EchoWebSocketHandler echoWebSocketHandler) { + this.echoWebSocketHandler = echoWebSocketHandler; + } + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(this.echoWebSocketHandler, "/echo"); + } + + @Bean + public WebSocketClient webSocketClient() { + return new StandardWebSocketClient(); + } + +} diff --git a/websocket-undertow/src/main/java/com/example/websocket/undertow/WebSocketUndertowApplication.java b/websocket-undertow/src/main/java/com/example/websocket/undertow/WebSocketUndertowApplication.java new file mode 100644 index 00000000..7dea83b7 --- /dev/null +++ b/websocket-undertow/src/main/java/com/example/websocket/undertow/WebSocketUndertowApplication.java @@ -0,0 +1,16 @@ +package com.example.websocket.undertow; + +import org.springframework.aot.smoketest.thirdpartyhints.UndertowRuntimeHints; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ImportRuntimeHints; + +@SpringBootApplication +@ImportRuntimeHints(UndertowRuntimeHints.class) +public class WebSocketUndertowApplication { + + public static void main(String[] args) { + SpringApplication.run(WebSocketUndertowApplication.class, args); + } + +} diff --git a/websocket-undertow/src/test/java/com/example/websocket/undertow/WebSocketUndertowApplicationTests.java b/websocket-undertow/src/test/java/com/example/websocket/undertow/WebSocketUndertowApplicationTests.java new file mode 100644 index 00000000..f586ef73 --- /dev/null +++ b/websocket-undertow/src/test/java/com/example/websocket/undertow/WebSocketUndertowApplicationTests.java @@ -0,0 +1,14 @@ +package com.example.websocket.undertow; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class WebSocketUndertowApplicationTests { + + @Test + void contextLoads() { + } + +}