Add Atmosphere sample application
Add Atmosphere example application based on http://github.com/Atmosphere/atmosphere-samples/tree/master/samples/chat Closes gh-2341
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
<module>spring-boot-sample-actuator-ui</module>
|
<module>spring-boot-sample-actuator-ui</module>
|
||||||
<module>spring-boot-sample-amqp</module>
|
<module>spring-boot-sample-amqp</module>
|
||||||
<module>spring-boot-sample-aop</module>
|
<module>spring-boot-sample-aop</module>
|
||||||
|
<module>spring-boot-sample-atmosphere</module>
|
||||||
<module>spring-boot-sample-batch</module>
|
<module>spring-boot-sample-batch</module>
|
||||||
<module>spring-boot-sample-data-elasticsearch</module>
|
<module>spring-boot-sample-data-elasticsearch</module>
|
||||||
<module>spring-boot-sample-data-gemfire</module>
|
<module>spring-boot-sample-data-gemfire</module>
|
||||||
|
|||||||
61
spring-boot-samples/spring-boot-sample-atmosphere/pom.xml
Normal file
61
spring-boot-samples/spring-boot-sample-atmosphere/pom.xml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<!-- Your own application should inherit from spring-boot-starter-parent -->
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-samples</artifactId>
|
||||||
|
<version>1.2.2.BUILD-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>spring-boot-sample-atmosphere</artifactId>
|
||||||
|
<name>Spring Boot Atmosphere Sample</name>
|
||||||
|
<description>Spring Boot Atmosphere Sample</description>
|
||||||
|
<url>http://projects.spring.io/spring-boot/</url>
|
||||||
|
<organization>
|
||||||
|
<name>Pivotal Software, Inc.</name>
|
||||||
|
<url>http://www.spring.io</url>
|
||||||
|
</organization>
|
||||||
|
<properties>
|
||||||
|
<main.basedir>${basedir}/../..</main.basedir>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.inject</groupId>
|
||||||
|
<artifactId>javax.inject</artifactId>
|
||||||
|
<version>1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.atmosphere</groupId>
|
||||||
|
<artifactId>atmosphere-runtime</artifactId>
|
||||||
|
<version>2.2.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.webjars</groupId>
|
||||||
|
<artifactId>atmosphere-javascript</artifactId>
|
||||||
|
<version>2.2.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-websocket</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2015 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 sample;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.atmosphere.config.managed.Decoder;
|
||||||
|
import org.atmosphere.config.managed.Encoder;
|
||||||
|
import org.atmosphere.config.service.Disconnect;
|
||||||
|
import org.atmosphere.config.service.ManagedService;
|
||||||
|
import org.atmosphere.config.service.Ready;
|
||||||
|
import org.atmosphere.cpr.AtmosphereResource;
|
||||||
|
import org.atmosphere.cpr.AtmosphereResourceEvent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
@ManagedService(path = "/chat")
|
||||||
|
public class ChatService {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(ChatService.class);
|
||||||
|
|
||||||
|
@Ready
|
||||||
|
public void onReady(final AtmosphereResource resource) {
|
||||||
|
this.logger.info("Connected", resource.uuid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disconnect
|
||||||
|
public void onDisconnect(AtmosphereResourceEvent event) {
|
||||||
|
this.logger.info("Client {} disconnected [{}]", event.getResource().uuid(),
|
||||||
|
(event.isCancelled() ? "cancelled" : "closed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@org.atmosphere.config.service.Message(encoders = JacksonEncoderDecoder.class, decoders = JacksonEncoderDecoder.class)
|
||||||
|
public Message onMessage(Message message) throws IOException {
|
||||||
|
this.logger.info("Author {} sent message {}", message.getAuthor(),
|
||||||
|
message.getMessage());
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JacksonEncoderDecoder implements Encoder<Message, String>,
|
||||||
|
Decoder<String, Message> {
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encode(Message m) {
|
||||||
|
try {
|
||||||
|
return this.mapper.writeValueAsString(m);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message decode(String s) {
|
||||||
|
try {
|
||||||
|
return this.mapper.readValue(s, Message.class);
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2015 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 sample;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class Message {
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
private String author;
|
||||||
|
|
||||||
|
private long time = new Date().getTime();
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return this.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return this.author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return this.time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(long time) {
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2015 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 sample;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
import org.atmosphere.cpr.AtmosphereInitializer;
|
||||||
|
import org.atmosphere.cpr.AtmosphereServlet;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.context.embedded.ServletContextInitializer;
|
||||||
|
import org.springframework.boot.context.embedded.ServletRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableAutoConfiguration
|
||||||
|
public class SampleAtmosphereApplication {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public EmbeddedAtmosphereInitializer atmosphereInitializer() {
|
||||||
|
return new EmbeddedAtmosphereInitializer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ServletRegistrationBean atmosphereServlet() {
|
||||||
|
// Dispatcher servlet is mapped to '/home' to allow the AtmosphereServlet
|
||||||
|
// to be mapped to '/chat'
|
||||||
|
ServletRegistrationBean registration = new ServletRegistrationBean(
|
||||||
|
new AtmosphereServlet(), "/chat/*");
|
||||||
|
registration.addInitParameter("org.atmosphere.cpr.packages", "sample");
|
||||||
|
registration.addInitParameter("org.atmosphere.interceptor.HeartbeatInterceptor"
|
||||||
|
+ ".clientHeartbeatFrequencyInSeconds", "10");
|
||||||
|
registration.setLoadOnStartup(0);
|
||||||
|
// Need to occur before the EmbeddedAtmosphereInitializer
|
||||||
|
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class MvcConfiguration extends WebMvcConfigurerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addViewControllers(ViewControllerRegistry registry) {
|
||||||
|
registry.addViewController("/").setViewName("forward:/home/home.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EmbeddedAtmosphereInitializer extends AtmosphereInitializer
|
||||||
|
implements ServletContextInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||||
|
onStartup(Collections.<Class<?>> emptySet(), servletContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
SpringApplication.run(SampleAtmosphereApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
server.servlet-path=/home/*
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Atmosphere Chat</title>
|
||||||
|
<!-- Atmosphere -->
|
||||||
|
<script type="text/javascript" src="webjars/atmosphere-javascript/2.2.3/atmosphere.js"></script>
|
||||||
|
<!-- Application -->
|
||||||
|
<script type="text/javascript" src="javascript/jquery-1.9.0.js"></script>
|
||||||
|
<script type="text/javascript" src="javascript/application.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: tahoma;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
width: 500px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
padding: 5px;
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
padding: 5px;
|
||||||
|
background: #f5deb3;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input {
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
width: 88px;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<h3>Atmosphere Chat. Default transport is WebSocket, fallback is
|
||||||
|
long-polling</h3>
|
||||||
|
</div>
|
||||||
|
<div id="content"></div>
|
||||||
|
<div>
|
||||||
|
<span id="status">Connecting...</span> <input type="text" id="input"
|
||||||
|
disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
$(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var header = $('#header');
|
||||||
|
var content = $('#content');
|
||||||
|
var input = $('#input');
|
||||||
|
var status = $('#status');
|
||||||
|
var myName = false;
|
||||||
|
var author = null;
|
||||||
|
var logged = false;
|
||||||
|
var socket = atmosphere;
|
||||||
|
var subSocket;
|
||||||
|
var transport = 'websocket';
|
||||||
|
|
||||||
|
// We are now ready to cut the request
|
||||||
|
var request = {
|
||||||
|
url : '/chat',
|
||||||
|
contentType : "application/json",
|
||||||
|
logLevel : 'debug',
|
||||||
|
transport : transport,
|
||||||
|
trackMessageLength : true,
|
||||||
|
reconnectInterval : 5000
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onOpen = function(response) {
|
||||||
|
content.html($('<p>', {
|
||||||
|
text : 'Atmosphere connected using ' + response.transport
|
||||||
|
}));
|
||||||
|
input.removeAttr('disabled').focus();
|
||||||
|
status.text('Choose name:');
|
||||||
|
transport = response.transport;
|
||||||
|
|
||||||
|
// Carry the UUID. This is required if you want to call
|
||||||
|
// subscribe(request) again.
|
||||||
|
request.uuid = response.request.uuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onClientTimeout = function(r) {
|
||||||
|
content
|
||||||
|
.html($(
|
||||||
|
'<p>',
|
||||||
|
{
|
||||||
|
text : 'Client closed the connection after a timeout. Reconnecting in '
|
||||||
|
+ request.reconnectInterval
|
||||||
|
}));
|
||||||
|
subSocket
|
||||||
|
.push(atmosphere.util
|
||||||
|
.stringifyJSON({
|
||||||
|
author : author,
|
||||||
|
message : 'is inactive and closed the connection. Will reconnect in '
|
||||||
|
+ request.reconnectInterval
|
||||||
|
}));
|
||||||
|
input.attr('disabled', 'disabled');
|
||||||
|
setTimeout(function() {
|
||||||
|
subSocket = socket.subscribe(request);
|
||||||
|
}, request.reconnectInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onReopen = function(response) {
|
||||||
|
input.removeAttr('disabled').focus();
|
||||||
|
content.html($('<p>', {
|
||||||
|
text : 'Atmosphere re-connected using ' + response.transport
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// For demonstration of how you can customize the fallbackTransport using
|
||||||
|
// the onTransportFailure function
|
||||||
|
request.onTransportFailure = function(errorMsg, request) {
|
||||||
|
atmosphere.util.info(errorMsg);
|
||||||
|
request.fallbackTransport = "long-polling";
|
||||||
|
header
|
||||||
|
.html($(
|
||||||
|
'<h3>',
|
||||||
|
{
|
||||||
|
text : 'Atmosphere Chat. Default transport is WebSocket, fallback is '
|
||||||
|
+ request.fallbackTransport
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onMessage = function(response) {
|
||||||
|
|
||||||
|
var message = response.responseBody;
|
||||||
|
try {
|
||||||
|
var json = atmosphere.util.parseJSON(message);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('This doesn\'t look like a valid JSON: ', message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.removeAttr('disabled').focus();
|
||||||
|
if (!logged && myName) {
|
||||||
|
logged = true;
|
||||||
|
status.text(myName + ': ').css('color', 'blue');
|
||||||
|
} else {
|
||||||
|
var me = json.author == author;
|
||||||
|
var date = typeof (json.time) == 'string' ? parseInt(json.time)
|
||||||
|
: json.time;
|
||||||
|
addMessage(json.author, json.message, me ? 'blue' : 'black',
|
||||||
|
new Date(date));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onClose = function(response) {
|
||||||
|
content.html($('<p>', {
|
||||||
|
text : 'Server closed the connection after a timeout'
|
||||||
|
}));
|
||||||
|
if (subSocket) {
|
||||||
|
subSocket.push(atmosphere.util.stringifyJSON({
|
||||||
|
author : author,
|
||||||
|
message : 'disconnecting'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
input.attr('disabled', 'disabled');
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onError = function(response) {
|
||||||
|
content.html($('<p>', {
|
||||||
|
text : 'Sorry, but there\'s some problem with your '
|
||||||
|
+ 'socket or the server is down'
|
||||||
|
}));
|
||||||
|
logged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
request.onReconnect = function(request, response) {
|
||||||
|
content.html($('<p>', {
|
||||||
|
text : 'Connection lost, trying to reconnect. Trying to reconnect '
|
||||||
|
+ request.reconnectInterval
|
||||||
|
}));
|
||||||
|
input.attr('disabled', 'disabled');
|
||||||
|
};
|
||||||
|
|
||||||
|
subSocket = socket.subscribe(request);
|
||||||
|
|
||||||
|
input.keydown(function(e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
var msg = $(this).val();
|
||||||
|
|
||||||
|
// First message is always the author's name
|
||||||
|
if (author == null) {
|
||||||
|
author = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
subSocket.push(atmosphere.util.stringifyJSON({
|
||||||
|
author : author,
|
||||||
|
message : msg
|
||||||
|
}));
|
||||||
|
$(this).val('');
|
||||||
|
|
||||||
|
input.attr('disabled', 'disabled');
|
||||||
|
if (myName === false) {
|
||||||
|
myName = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function addMessage(author, message, color, datetime) {
|
||||||
|
content.append('<p><span style="color:'
|
||||||
|
+ color
|
||||||
|
+ '">'
|
||||||
|
+ author
|
||||||
|
+ '</span> @ '
|
||||||
|
+ +(datetime.getHours() < 10 ? '0' + datetime.getHours()
|
||||||
|
: datetime.getHours())
|
||||||
|
+ ':'
|
||||||
|
+ (datetime.getMinutes() < 10 ? '0' + datetime.getMinutes()
|
||||||
|
: datetime.getMinutes()) + ': ' + message + '</p>');
|
||||||
|
}
|
||||||
|
});
|
||||||
9555
spring-boot-samples/spring-boot-sample-atmosphere/src/main/resources/static/javascript/jquery-1.9.0.js
vendored
Normal file
9555
spring-boot-samples/spring-boot-sample-atmosphere/src/main/resources/static/javascript/jquery-1.9.0.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2015 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 sample;
|
||||||
|
|
||||||
|
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.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||||
|
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||||
|
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||||
|
import org.springframework.boot.test.WebIntegrationTest;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
import org.springframework.web.socket.client.WebSocketConnectionManager;
|
||||||
|
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
|
||||||
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@SpringApplicationConfiguration(classes = SampleAtmosphereApplication.class)
|
||||||
|
@WebIntegrationTest(randomPort = true)
|
||||||
|
@DirtiesContext
|
||||||
|
public class SampleAtmosphereApplicationTests {
|
||||||
|
|
||||||
|
private static Log logger = LogFactory.getLog(SampleAtmosphereApplicationTests.class);
|
||||||
|
|
||||||
|
@Value("${local.server.port}")
|
||||||
|
private int port = 1234;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void chatEndpoint() throws Exception {
|
||||||
|
ConfigurableApplicationContext context = new SpringApplicationBuilder(
|
||||||
|
ClientConfiguration.class, PropertyPlaceholderAutoConfiguration.class)
|
||||||
|
.properties(
|
||||||
|
"websocket.uri:ws://localhost:" + this.port + "/chat/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();
|
||||||
|
assertThat(count, equalTo(0L));
|
||||||
|
assertThat(messagePayloadReference.get(),
|
||||||
|
containsString("{\"message\":\"test\",\"author\":\"test\",\"time\":"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
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(), this.webSocketUri);
|
||||||
|
manager.setAutoStartup(true);
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public StandardWebSocketClient client() {
|
||||||
|
return new StandardWebSocketClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TextWebSocketHandler handler() {
|
||||||
|
return new TextWebSocketHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterConnectionEstablished(WebSocketSession session)
|
||||||
|
throws Exception {
|
||||||
|
session.sendMessage(new TextMessage(
|
||||||
|
"{\"author\":\"test\",\"message\":\"test\"}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handleTextMessage(WebSocketSession session,
|
||||||
|
TextMessage message) throws Exception {
|
||||||
|
logger.info("Received: " + message + " ("
|
||||||
|
+ ClientConfiguration.this.latch.getCount() + ")");
|
||||||
|
session.close();
|
||||||
|
ClientConfiguration.this.messagePayload.set(message.getPayload());
|
||||||
|
ClientConfiguration.this.latch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user