Created
April 19, 2012 08:37
-
-
Save keesun/2419701 to your computer and use it in GitHub Desktop.
Spring 3.2's async demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function ChatViewModel() { | |
var that = this; | |
that.userName = ko.observable(''); | |
that.chatContent = ko.observable(''); | |
that.message = ko.observable(''); | |
that.activePollingXhr = ko.observable(null); | |
var keepPolling = false; | |
that.joinChat = function() { | |
if (that.userName().trim() != '') { | |
that.chatContent(''); | |
that.message(''); | |
keepPolling = true; | |
pollForMessages(); | |
} | |
} | |
function pollForMessages() { | |
if (!keepPolling) { | |
return; | |
} | |
var form = $("#joinChatForm"); | |
that.activePollingXhr($.ajax({url : getChatUrl(form), type : "GET", data : form.serialize(), | |
success : function(messages) { | |
for ( var i = 0; i < messages.length; i++) { | |
that.chatContent(that.chatContent() + "[" + messages[i].user + "] " + messages[i].text + "\n"); | |
} | |
}, | |
error : function(xhr) { | |
keepPolling = false; | |
if (xhr.statusText != "abort") { | |
console.error("Error getting chat messages: status=" + xhr.status + ", statusText=" + xhr.statusText); | |
} | |
}, | |
complete : pollForMessages | |
})); | |
} | |
function getChatUrl(form) { | |
return form.attr("action") + "/" + that.userName(); | |
} | |
that.postMessage = function() { | |
if (that.message().trim() != '') { | |
var form = $("#postMessageForm"); | |
$.ajax({url : getChatUrl(form), type : "POST", data : form.serialize(), | |
error : function(xhr) { | |
console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText); | |
} | |
}); | |
that.message(''); | |
} | |
} | |
that.leaveChat = function() { | |
keepPolling = false; | |
that.activePollingXhr().abort(); | |
that.activePollingXhr(null); | |
var form = $("#leaveChatForm"); | |
$.ajax({url : getChatUrl(form), type : 'DELETE', | |
error : function(xhr) { | |
console.error("Error while leaving chat: status=" + xhr.status + ", statusText=" + xhr.statusText); | |
} | |
}); | |
this.userName(''); | |
} | |
} | |
//Activate knockout.js | |
ko.applyBindings(new ChatViewModel()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.springframework.samples.async.chat; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.ConcurrentHashMap; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RequestMethod; | |
import org.springframework.web.bind.annotation.ResponseBody; | |
import org.springframework.web.context.request.async.DeferredResult; | |
@Controller | |
@RequestMapping("/mvc/chat/{topic}/{user}") | |
public class ChatController { | |
private final Map<String, ChatParticipant> participants = new ConcurrentHashMap<String, ChatParticipant>(); | |
/** | |
* Get chat messages immediately if any are cached or later when they arrive. | |
*/ | |
@RequestMapping(method=RequestMethod.GET, produces="application/json") | |
@ResponseBody | |
public Object getMessages(@PathVariable String topic, @PathVariable String user) { | |
ChatParticipant participant = getChatParticipant(user, topic); | |
DeferredResult deferredResult = new DeferredResult(); | |
List<ChatMessage> messages = participant.getMessagesWhenAvailable(deferredResult); | |
return (messages != null) ? messages : deferredResult; | |
} | |
/** | |
* Post a message to chat participants. | |
*/ | |
@RequestMapping(method=RequestMethod.POST) | |
@ResponseBody | |
public void postMessage(ChatMessage message) { | |
for (ChatParticipant participant : this.participants.values()) { | |
if (message.getTopic().equals(participant.getTopic())) { | |
participant.processMessage(message); | |
} | |
} | |
} | |
/** | |
* Remove a chat participant. | |
*/ | |
@RequestMapping(method=RequestMethod.DELETE) | |
@ResponseBody | |
public void removeParticipant(@PathVariable String topic, @PathVariable String user) { | |
removeChatParticipant(user, topic); | |
} | |
private ChatParticipant getChatParticipant(String user, String topic) { | |
String key = getKey(user, topic); | |
if (this.participants.containsKey(key)) { | |
return this.participants.get(key); | |
} | |
else { | |
ChatParticipant participant = new ChatParticipant(user, topic); | |
this.participants.put(key, participant); | |
return participant; | |
} | |
} | |
private void removeChatParticipant(String user, String topic) { | |
this.participants.remove(getKey(user, topic)); | |
} | |
private String getKey(String user, String topic) { | |
return user + ":" + topic; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2002-2012 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 org.springframework.samples.async.chat; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import org.springframework.web.context.request.async.DeferredResult; | |
import org.springframework.web.context.request.async.StaleAsyncWebRequestException; | |
/** | |
* A ChatParticipant represents a user participating in a chat on a specific topic. | |
* | |
* <p>A participant in a chat makes polling requests continuously and waits until | |
* a message arrives or the request times out. At the start of each polling | |
* request a DeferredResult is created which can be used to process a chat message | |
* as soon as it arrives (see {@link #processMessage(ChatMessage)}. In addition, | |
* an internal cache is used to save messages that arrive after one polling | |
* request ends and before the next one arrives. | |
*/ | |
public class ChatParticipant { | |
private final String user; | |
private final String topic; | |
private final List<ChatMessage> messageCache = new ArrayList<ChatMessage>(); | |
private DeferredResult deferredResult; | |
private final Object lock = new Object(); | |
/** | |
* Create a ChatParticipant instance. | |
*/ | |
public ChatParticipant(String user, String topic) { | |
this.user = user; | |
this.topic = topic; | |
} | |
public String getUser() { | |
return user; | |
} | |
public String getTopic() { | |
return topic; | |
} | |
/** | |
* Process the message by setting the DeferredResult or if a DeferredResult | |
* is not available (between polling requests?), save the message. | |
* | |
* <p>Note: this method is thread-safe since some threads may poll for new | |
* messages while other threads post messages. | |
*/ | |
public void processMessage(ChatMessage message) { | |
synchronized (lock) { | |
if (this.deferredResult != null) { | |
try { | |
this.deferredResult.set(Arrays.asList(message)); | |
return; | |
} | |
catch (StaleAsyncWebRequestException e) { | |
// fall through and save message | |
System.out.println("times out"); | |
} | |
finally { | |
this.deferredResult = null; | |
} | |
} | |
this.messageCache.add(message); | |
} | |
} | |
/** | |
* Return saved messages or if none are available return {@code null} and save | |
* the DeferredResult to process the messages when they become available. | |
* | |
* <p>Note: this method is thread-safe since some threads may poll for new | |
* messages while other threads post messages. | |
*/ | |
public List<ChatMessage> getMessagesWhenAvailable(DeferredResult deferredResult) { | |
synchronized(lock) { | |
if (this.messageCache.isEmpty()) { | |
this.deferredResult = deferredResult; | |
return null; | |
} | |
else { | |
ArrayList<ChatMessage> result = new ArrayList<ChatMessage>(this.messageCache); | |
this.messageCache.clear(); | |
return result; | |
} | |
} | |
} | |
@Override | |
public String toString() { | |
return "ChatParticipant [user=" + user + ", topic=" + topic + "]"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment