Comet sockets

使用分块响应来创建 comet sockets

分块响应(chunked responses)的一个好处是可以用来创建 comet sockets。comet socket 是一块只包含 <script> 元素的 text/html 响应。在每一个块中,我们都写入一个 <script> 标签,这样一来,它会被浏览器立即执行。通过这种方式,我们可以实时地从服务器发送各种事件到浏览器:将每条信息包在一个 <script> 标签中(它会调用一个 JavaScript 回调函数),然后将它写到响应块里。

让我们通过以下例子来验证上面的概念:下面的枚举器(enumerator)产生了 3 个 <script> 标签,每个都会调用浏览器的 console.log JavaScript 函数:

  1. def comet = Action {
  2. val events = Enumerator(
  3. """<script>console.log('kiki')</script>""",
  4. """<script>console.log('foo')</script>""",
  5. """<script>console.log('bar')</script>"""
  6. )
  7. Ok.chunked(events).as(HTML)
  8. }

如果你在浏览器中运行这个 action,你会看到 3 个事件在浏览器控制台打印日志。

对于上述例子,我们可以用一种更好的方法来做,即使用 play.api.libs.iteratee.Enumeratee 这个适配器,它能将 Enumerator[A] 转成 Enumerator[B]。下面我们使用它将标准信息包到 <script> 标签里:

  1. import play.twirl.api.Html
  2. // Transform a String message into an Html script tag
  3. val toCometMessage = Enumeratee.map[String] { data =>
  4. Html("""<script>console.log('""" + data + """')</script>""")
  5. }
  6. def comet = Action {
  7. val events = Enumerator("kiki", "foo", "bar")
  8. Ok.chunked(events &> toCometMessage)
  9. }

注意:events &> toCometMessage 只是 events.through(toCometMessage) 的另一种表述方式。

使用 helper:play.api.libs.Comet

Play 提供了相应的 helper 来处理 comet 分块流,效果与上面所写的方法基本一样。

注意:事实上,它做得更多。比如为了浏览器的兼容性,它提供了一个初始的空的缓冲数据。此外,它支持字符串和 JSON 格式的信息。你还可以通过类型类来扩展它,使它支持更多信息类型。

前面的例子可以重写如下:

  1. def comet = Action {
  2. val events = Enumerator("kiki", "foo", "bar")
  3. Ok.chunked(events &> Comet(callback = "console.log"))
  4. }

iframe 流技术

写 comet socket 的标准方法是在 HTML iframe 中加载无限的分块 comet 响应,并指定一个回调函数来调用父级 frame:

  1. def comet = Action {
  2. val events = Enumerator("kiki", "foo", "bar")
  3. Ok.chunked(events &> Comet(callback = "parent.cometMessage"))
  4. }

HTML 页面如下:

  1. <script type="text/javascript">
  2. var cometMessage = function(event) {
  3. console.log('Received event: ' + event)
  4. }
  5. </script>
  6. <iframe src="/comet"></iframe>

注意:本章概念可参考:http://en.wikipedia.org/wiki/Comet_(programming)