Comet sockets

使用分块响应来创建 Comet sockets

分块响应 的一个好处是用来创建 Comet sockets。一个 Comet socket 只是一块仅包含<script> 元素的 text/html 响应。在每个块中我们写入一个<script> 标签,它会被Web浏览器立即执行。用这种方式我们可以从服务器发送各种事件到浏览器: 每条信息被包装到到一个<script> 标签,它会调用一个 JavaScript 回调函数, 再将它写入响应块中。

让我们首先写个例子来验证这个概念: 枚举器生成<script> 标签,并且每个都调用浏览器console.log JavaScript 函数:

def comet = Action {
  val events = Enumerator(
    """<script>console.log('kiki')</script>""",
    """<script>console.log('foo')</script>""",
    """<script>console.log('bar')</script>"""
  )
  Ok.chunked(events).as(HTML)
}

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

我们可以用一种更好的方式来写前面的例子,通过使用play.api.libs.iteratee.Enumeratee,它只是一个能转换一个 Enumerator[A] 到另一个Enumerator[B] 的适配器。下面我们使用它将标准信息包装到<script> 标签中:

import play.twirl.api.Html

// Transform a String message into an Html script tag
val toCometMessage = Enumeratee.map[String] { data =>
  Html("""<script>console.log('""" + data + """')</script>""")
}

def comet = Action {
  val events = Enumerator("kiki", "foo", "bar")
  Ok.chunked(events &> toCometMessage)
}

提示: events &> toCometMessage 只是events.through(toCometMessage) 的另一种表达方式。

使用play.api.libs.Comet 助手方法

Play提供一个Comet助手方法来处理这些 Comet分块流,效果与上面所写的方法基本一样。

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

让我们用它来重写前面的例子:

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

永远的iframe技术

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

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

HTML 页面如下:

<script type="text/javascript">
  var cometMessage = function(event) {
    console.log('Received event: ' + event)
  }
</script>

<iframe src="/comet"></iframe>