我看到 Akka 模块的描述说Play有很好的 Comet 支持,但我以前从未使用过Comet,而且我在 Play 的文档中找不到任何提及它的内容。它在 Play 中是如何工作的?
我花了两天多的时间来解决这个问题,所以我想与其他 Play 初学者分享这些信息。
Play 包含一个示例聊天应用程序,它演示了如何使用 Comet。这个例子并没有解释发生了什么,所以这就是我想出的。
为了让其他人找到您发送的新更新,它们需要存储在某个地方。可以想象,这可能在缓存中,甚至在控制器本身中,但数据库将是最安全的选择,因此您需要一个模型。他们还需要一种方法来确定哪些更新对他们来说是新的,这意味着您可能需要一个日期字段(另请参阅:使用 JPA 的上次更新时间戳)。Chat 示例使用一个简单的模型:
@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();
}
}
控制器需要两种方法来方便 Comet。一个发布新数据的地方,它没有做任何特别的事情:
public static void postMessage(String message) {
new Message(session.get("nick"), message).save();
}
一个用于检索更新:
public static void newMessages() {
List<Message> messages = Message.find("date > ?", request.date).fetch();
if (messages.isEmpty()) {
suspend("1s");
}
renderJSON(messages);
}
这里的关键suspend("1s")
是保持 HTTP 请求打开,每秒检查一次新数据。
视图有三个职责——发送新数据,获取更新,然后渲染这些更新。
发送,就像相应的控制器动作一样,没有做任何特别的事情:
$('#send').click(function(e) {
var message = $('#message').val();
$('#message').val('');
$.post('@{postMessage()}', {message: message});
});
获取更新是神奇的一点:
// Retrieve new messages
var getMessages = function() {
$.ajax({
url: '@{newMessages()}',
success: function(messages) {
$(messages).each(function() {
display(this);
});
},
complete: function() {
getMessages();
},
dataType: 'json'
});
}
getMessages();
getMessages()
被调用一次以开始工作,然后在每次成功请求后递归调用自身。它获取newMessages()
查找新消息的操作,如果没有任何消息,它将保持请求打开,直到它有要报告的内容。找到新消息时,会将 JSON 数据传递给一个display
函数:
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主要通过suspend(time);
函数实现长轮询。这个函数做了两件非常重要的事情
它保持 HTTP 请求打开并在指定的时间内重试操作。这允许您保留 HTTP 请求并继续重试该操作,直到发生您想要通知浏览器的事情
并且非常重要的是,当请求被挂起时线程被释放。这意味着 http 请求不会为每个挂起的请求保留一个打开的线程。
当 play 处于 DEV 模式时,它仅在单个线程上运行,但您可以(并且我已经尝试过)在示例聊天应用程序上运行数十个用户,而不会导致服务器挂起或用户收到阻塞的请求。
然而,suspend 方法是 play 真正对 Comet 有帮助的唯一方法。如果您运行示例聊天应用程序并让客户端保持打开状态,您会注意到 120 秒后,请求将超时。示例应用程序不会重试该请求。您必须自己构建长轮询技术的客户端。
Guillaume 在论坛中提到,示例聊天应用程序只是一个演示可以使用多长时间的轮询。所以我不认为 Play 可以证明有很好的 Comet 支持,这只是朝着正确方向迈出的一步。
您可以在 Akka 中使用 Mist 进行服务器推送(彗星),支持 Servlet 3.00 或 Jetty7 Continuations:http ://doc.akkasource.org/http#Mist%20-%20Lightweight%20Asynchronous%20HTTP