Merge branch '1.1.x'

Conflicts:
	spring-boot-dependencies/pom.xml
This commit is contained in:
Andy Wilkinson 2014-11-10 11:29:21 +00:00
commit 150b85e10d
8 changed files with 292 additions and 27 deletions

View File

@ -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

View File

@ -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<String> messagePayload;
@Autowired
public SimpleClientWebSocketHandler(GreetingService greetingService,
CountDownLatch latch) {
CountDownLatch latch, AtomicReference<String> 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();
}

View File

@ -26,12 +26,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;
@SpringBootApplication
@ -74,4 +76,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();
}
}

View File

@ -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());
}
}

View File

@ -25,6 +25,7 @@
<p>Please select the sample you would like to try.</p>
<ul>
<li><a href="./echo.html">Echo</a></li>
<li><a href="./reverse.html">Reverse</a></li>
<li><a href="./snake.html">Snake</a></li>
</ul>
</body>

View File

@ -0,0 +1,140 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.
-->
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Examples: Reverse</title>
<style type="text/css">
#connect-container {
float: left;
width: 400px
}
#connect-container div {
padding: 5px;
}
#console-container {
float: left;
margin-left: 15px;
width: 400px;
}
#console {
border: 1px solid #CCCCCC;
border-right-color: #999999;
border-bottom-color: #999999;
height: 170px;
overflow-y: scroll;
padding: 5px;
width: 100%;
}
#console p {
padding: 0;
margin: 0;
}
</style>
<script type="text/javascript">
var ws = null;
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('reverse').disabled = !connected;
}
function connect() {
var target = document.getElementById('target').value;
ws = new WebSocket(target);
ws.onopen = function () {
setConnected(true);
log('Info: WebSocket connection opened.');
};
ws.onmessage = function (event) {
log('Received: ' + event.data);
};
ws.onclose = function () {
setConnected(false);
log('Info: WebSocket connection closed.');
};
}
function updateTarget() {
if (window.location.protocol == 'http:') {
document.getElementById('target').value = 'ws://' + window.location.host + document.getElementById('target').value;
} else {
document.getElementById('target').value = 'wss://' + window.location.host + document.getElementById('target').value;
}
}
function disconnect() {
if (ws != null) {
ws.close();
ws = null;
}
setConnected(false);
}
function reverse() {
if (ws != null) {
var message = document.getElementById('message').value;
log('Sent: ' + message);
ws.send(message);
} else {
alert('WebSocket connection not established, please connect.');
}
}
function log(message) {
var console = document.getElementById('console');
var p = document.createElement('p');
p.style.wordWrap = 'break-word';
p.appendChild(document.createTextNode(message));
console.appendChild(p);
while (console.childNodes.length > 25) {
console.removeChild(console.firstChild);
}
console.scrollTop = console.scrollHeight;
}
</script>
</head>
<body onload="updateTarget()">
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
Javascript and reload this page!</h2></noscript>
<div>
<div id="connect-container">
<div>
<input id="target" type="text" size="40" style="width: 350px" value="/reverse"/>
</div>
<div>
<button id="connect" onclick="connect();">Connect</button>
<button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
</div>
<div>
<textarea id="message" style="width: 350px">Here is a message!</textarea>
</div>
<div>
<button id="reverse" onclick="reverse();" disabled="disabled">Reverse message</button>
</div>
</div>
<div id="console-container">
<div id="console"></div>
</div>
</div>
</body>
</html>

View File

@ -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<String> 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<String> 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<String> messagePayload = new AtomicReference<String>();
@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

View File

@ -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<String> 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<String> 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<String> messagePayload = new AtomicReference<String>();
@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