diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index fe7fdc75d93..a205985bb2f 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -98,7 +98,7 @@ 1.13 4.7.2 0.7-groovy-2.0 - 4.0.7.RELEASE + 4.0.8.BUILD-SNAPSHOT 1.3.6.RELEASE 3.0.2.RELEASE Dijkstra-SR4 diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index cec330ae72f..a967d4931fb 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -617,6 +617,26 @@ change the version properties, e.g. for a simple webapp or service: +[[howto-create-websocket-endpoints-using-serverendpoint]] +=== Create WebSocket endpoints using @ServerEndpoint +If you want to use `@ServerEndpoint` in a Spring Boot application that used an embedded +container, you must declare a single `ServerEndpointExporter` `@Bean`: + +[source,java,indent=0,subs="verbatim,quotes,attributes"] +---- + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } +---- + +This bean will register any `@ServerEndpoint` annotated beans with the underlying +WebSocket container. When deployed to a standalone servlet container this role is +performed by a servlet container initializer and the `ServerEndpointExporter` bean is +not required. + + + [[howto-spring-mvc]] == Spring MVC diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/client/SimpleClientWebSocketHandler.java b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/client/SimpleClientWebSocketHandler.java index 1b3dc4ef896..a3bbfc2f1cf 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/client/SimpleClientWebSocketHandler.java +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/client/SimpleClientWebSocketHandler.java @@ -17,6 +17,7 @@ package samples.websocket.client; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -33,11 +34,14 @@ public class SimpleClientWebSocketHandler extends TextWebSocketHandler { private final CountDownLatch latch; + private final AtomicReference messagePayload; + @Autowired public SimpleClientWebSocketHandler(GreetingService greetingService, - CountDownLatch latch) { + CountDownLatch latch, AtomicReference message) { this.greetingService = greetingService; this.latch = latch; + this.messagePayload = message; } @Override @@ -51,6 +55,7 @@ public class SimpleClientWebSocketHandler extends TextWebSocketHandler { throws Exception { this.logger.info("Received: " + message + " (" + this.latch.getCount() + ")"); session.close(); + this.messagePayload.set(message.getPayload()); this.latch.countDown(); } diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/config/SampleWebSocketsApplication.java b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/config/SampleWebSocketsApplication.java index 8d84d2fcd2d..7bc9fd37a89 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/config/SampleWebSocketsApplication.java +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/config/SampleWebSocketsApplication.java @@ -27,12 +27,14 @@ import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.handler.PerConnectionWebSocketHandler; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; import samples.websocket.client.GreetingService; import samples.websocket.client.SimpleGreetingService; import samples.websocket.echo.DefaultEchoService; import samples.websocket.echo.EchoService; import samples.websocket.echo.EchoWebSocketHandler; +import samples.websocket.reverse.ReverseWebSocketEndpoint; import samples.websocket.snake.SnakeWebSocketHandler; @Configuration @@ -76,4 +78,14 @@ public class SampleWebSocketsApplication extends SpringBootServletInitializer im return new PerConnectionWebSocketHandler(SnakeWebSocketHandler.class); } + @Bean + public ReverseWebSocketEndpoint reverseWebSocketEndpoint() { + return new ReverseWebSocketEndpoint(); + } + + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + } diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/reverse/ReverseWebSocketEndpoint.java b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/reverse/ReverseWebSocketEndpoint.java new file mode 100644 index 00000000000..a7802edcd22 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/samples/websocket/reverse/ReverseWebSocketEndpoint.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-2014 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 + * + * 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 samples.websocket.reverse; + +import java.io.IOException; + +import javax.websocket.OnMessage; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +@ServerEndpoint("/reverse") +public class ReverseWebSocketEndpoint { + + @OnMessage + public void handleMessage(Session session, String message) throws IOException { + session.getBasicRemote().sendText( + "Reversed: " + new StringBuilder(message).reverse()); + } +} diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/index.html b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/index.html index 39069b15d76..e2b76b6e445 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/index.html +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/index.html @@ -25,6 +25,7 @@

Please select the sample you would like to try.

diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/reverse.html b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/reverse.html new file mode 100644 index 00000000000..a87d2e82554 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/reverse.html @@ -0,0 +1,140 @@ + + + + + WebSocket Examples: Reverse + + + + + +
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/SampleWebSocketsApplicationTests.java b/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/SampleWebSocketsApplicationTests.java similarity index 63% rename from spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/SampleWebSocketsApplicationTests.java rename to spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/SampleWebSocketsApplicationTests.java index cd85062126d..cff0db54907 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/SampleWebSocketsApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/SampleWebSocketsApplicationTests.java @@ -14,19 +14,20 @@ * limitations under the License. */ -package samples.websocket.echo; +package samples.websocket; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.ConfigurableApplicationContext; @@ -54,42 +55,64 @@ public class SampleWebSocketsApplicationTests { private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class); - private static String WS_URI; - @Value("${local.server.port}") - private int port; + private int port = 1234; - @Before - public void init() { - WS_URI = "ws://localhost:" + this.port + "/echo/websocket"; + @Test + public void echoEndpoint() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class) + .properties( + "websocket.uri:ws://localhost:" + this.port + "/echo/websocket") + .run("--spring.main.web_environment=false"); + long count = context.getBean(ClientConfiguration.class).latch.getCount(); + AtomicReference messagePayloadReference = context + .getBean(ClientConfiguration.class).messagePayload; + context.close(); + assertEquals(0, count); + assertEquals("Did you say \"Hello world!\"?", messagePayloadReference.get()); } @Test - public void runAndWait() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run( - ClientConfiguration.class, "--spring.main.web_environment=false"); + public void reverseEndpoint() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class) + .properties("websocket.uri:ws://localhost:" + this.port + "/reverse") + .run("--spring.main.web_environment=false"); long count = context.getBean(ClientConfiguration.class).latch.getCount(); + AtomicReference messagePayloadReference = context + .getBean(ClientConfiguration.class).messagePayload; context.close(); assertEquals(0, count); + assertEquals("Reversed: !dlrow olleH", messagePayloadReference.get()); } @Configuration static class ClientConfiguration implements CommandLineRunner { + @Value("${websocket.uri}") + private String webSocketUri; + private final CountDownLatch latch = new CountDownLatch(1); + private final AtomicReference messagePayload = new AtomicReference(); + @Override public void run(String... args) throws Exception { logger.info("Waiting for response: latch=" + this.latch.getCount()); - this.latch.await(10, TimeUnit.SECONDS); - logger.info("Got response: latch=" + this.latch.getCount()); + if (this.latch.await(10, TimeUnit.SECONDS)) { + logger.info("Got response: " + this.messagePayload.get()); + } + else { + logger.info("Response not received: latch=" + this.latch.getCount()); + } } @Bean public WebSocketConnectionManager wsConnectionManager() { WebSocketConnectionManager manager = new WebSocketConnectionManager(client(), - handler(), WS_URI); + handler(), this.webSocketUri); manager.setAutoStartup(true); return manager; @@ -102,7 +125,8 @@ public class SampleWebSocketsApplicationTests { @Bean public SimpleClientWebSocketHandler handler() { - return new SimpleClientWebSocketHandler(greetingService(), this.latch); + return new SimpleClientWebSocketHandler(greetingService(), this.latch, + this.messagePayload); } @Bean diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/CustomContainerWebSocketsApplicationTests.java b/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/CustomContainerWebSocketsApplicationTests.java index 7860c560d8d..1df58228905 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/CustomContainerWebSocketsApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-websocket/src/test/java/samples/websocket/echo/CustomContainerWebSocketsApplicationTests.java @@ -18,13 +18,16 @@ package samples.websocket.echo; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.test.IntegrationTest; @@ -60,8 +63,6 @@ public class CustomContainerWebSocketsApplicationTests { private static int PORT = SocketUtils.findAvailableTcpPort(); - private static final String WS_URI = "ws://localhost:" + PORT + "/ws/echo/websocket"; - @Configuration protected static class CustomContainerConfiguration { @Bean @@ -71,31 +72,59 @@ public class CustomContainerWebSocketsApplicationTests { } @Test - public void runAndWait() throws Exception { - ConfigurableApplicationContext context = SpringApplication.run( - ClientConfiguration.class, "--spring.main.web_environment=false"); + public void echoEndpoint() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class) + .properties("websocket.uri:ws://localhost:" + PORT + "/ws/echo/websocket") + .run("--spring.main.web_environment=false"); long count = context.getBean(ClientConfiguration.class).latch.getCount(); + AtomicReference messagePayloadReference = context + .getBean(ClientConfiguration.class).messagePayload; context.close(); assertEquals(0, count); + assertEquals("Did you say \"Hello world!\"?", messagePayloadReference.get()); + } + + @Test + public void reverseEndpoint() throws Exception { + ConfigurableApplicationContext context = new SpringApplicationBuilder( + ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class) + .properties("websocket.uri:ws://localhost:" + PORT + "/ws/reverse").run( + "--spring.main.web_environment=false"); + long count = context.getBean(ClientConfiguration.class).latch.getCount(); + AtomicReference messagePayloadReference = context + .getBean(ClientConfiguration.class).messagePayload; + context.close(); + assertEquals(0, count); + assertEquals("Reversed: !dlrow olleH", messagePayloadReference.get()); } @Configuration static class ClientConfiguration implements CommandLineRunner { + @Value("${websocket.uri}") + private String webSocketUri; + private final CountDownLatch latch = new CountDownLatch(1); + private final AtomicReference messagePayload = new AtomicReference(); + @Override public void run(String... args) throws Exception { logger.info("Waiting for response: latch=" + this.latch.getCount()); - this.latch.await(10, TimeUnit.SECONDS); - logger.info("Got response: latch=" + this.latch.getCount()); + if (this.latch.await(10, TimeUnit.SECONDS)) { + logger.info("Got response: " + this.messagePayload.get()); + } + else { + logger.info("Response not received: latch=" + this.latch.getCount()); + } } @Bean public WebSocketConnectionManager wsConnectionManager() { WebSocketConnectionManager manager = new WebSocketConnectionManager(client(), - handler(), WS_URI); + handler(), this.webSocketUri); manager.setAutoStartup(true); return manager; @@ -108,7 +137,8 @@ public class CustomContainerWebSocketsApplicationTests { @Bean public SimpleClientWebSocketHandler handler() { - return new SimpleClientWebSocketHandler(greetingService(), this.latch); + return new SimpleClientWebSocketHandler(greetingService(), this.latch, + this.messagePayload); } @Bean