How does Play!'s Comet support work?
I saw the Akka module's description says that Play has great Comet support, but I've never used Comet before and I can't find any mention of it in 开发者_C百科Play's documentation. How does it work in Play?
I spent a few hours over two days figuring this out so I wanted to share this info for other Play beginners.
Play includes a sample Chat application which demonstrates how to use Comet. The example doesn't explain what's going on though, so here's what I've figured out.
Model
In order for others to find new updates you send, they'll need to be stored somewhere. Conceivably this could be in a cache or even in the controller itself, but the database is going to be the safest bet, so you'll want a model. They'll also need a way to determine which updates are new to them, which means you'll probably want a date field (see also: Last update timestamp with JPA). The Chat example uses a simple model:
@Entity
public class Message extends Model {
public String user;
public Date date;
public String text;
public Message(String user, String text) {
this.user = user;
this.text = text;
this.date = new Date();
}
}
Controller
The controller needs two methods to facilitate Comet. One where new data is posted, which doesn't do anything special:
public static void postMessage(String message) {
new Message(session.get("nick"), message).save();
}
and one for retrieving updates:
public static void newMessages() {
List<Message> messages = Message.find("date > ?", request.date).fetch();
if (messages.isEmpty()) {
suspend("1s");
}
renderJSON(messages);
}
The key bit here is suspend("1s")
which is what holds the HTTP request open, checking for new data once per second.
View
The view has three responsibilities -- sending new data, fetching updates and then rendering those updates.
Sending, like the corresponding controller action, doesn't do anything special:
$('#send').click(function(e) {
var message = $('#message').val();
$('#message').val('');
$.post('@{postMessage()}', {message: message});
});
Fetching updates is the magic bit:
// Retrieve new messages
var getMessages = function() {
$.ajax({
url: '@{newMessages()}',
success: function(messages) {
$(messages).each(function() {
display(this);
});
},
complete: function() {
getMessages();
},
dataType: 'json'
});
}
getMessages();
getMessages()
is called once to get things started, and afterwards it calls itself recursively after each successful request. It GETs the newMessages()
action which looks for new messages, and if there aren't any it holds the request open until it has something to report. When new messages are found, the JSON data is passed to a display
function:
var display = function(message) {
$('#thread').append(tmpl('message_tmpl', {message: message}));
}
The display
function applies a JavaScript Micro-Template to the JSON data to render new messages. Use of micro templates isn't necessary, but it does work pretty well. They're included right in the template of the page that's going to use them:
<script type="text/html" id="message_tmpl">
<div class="message <%= message.user == '${session.nick}' ? 'you' : '' %> <%= message.user == 'notice' ? 'notice' : '' %>">
<h2><%= message.user %></h2>
<p><%= message.text.replace('\n', '<br/>') %></p>
</div>
</script>
The type="text/html"
causes browsers, search engines and screen readers to ignore the whole script
block. The result is much easier to read and maintain than using jQuery to build nodes or concatenating strings. Overall it's pretty simple once you know which bits are relevant.
There are many ways to achieve server-push or Comet in web applications, and one of the most common is Long Polling due to it being well supported in most modern browsers.
Play achieves long polling mainly through the suspend(time);
function. This function does two very important things
it keeps the HTTP request open and retries the action in the time specified. This allows you to hold the HTTP request and keep retrying the action until something has happened that you want to inform the browser about
and very importantly, the thread is released when the request is suspended. This means that the http request does not hold an open thread for each suspended request.
When play is in DEV mode, it only runs on a single thread, yet you can (and I have tried) run dozens of users on the sample chat application without the server hanging, or users getting blocked requests.
The suspend method however is the only thing that play really does to help with Comet. If you run the sample chat application and leave a client open, you will notice that after 120 seconds, the request will timeout. The sample application does not re-try the request. The client side of the long-polling technology you have to build yourself.
Guillaume has mentioned in the forums that the sample chat application is just a demo of how long polling can be used. So I don't think Play can attest to having great Comet support, it is just a step in the right direction.
You can use Mist in Akka for server-push (Comet) backed with Servlet 3.00 or Jetty7 Continuations: http://doc.akkasource.org/http#Mist%20-%20Lightweight%20Asynchronous%20HTTP
精彩评论