简介
借助 WebSockets 和 EventSource,HTML5 让开发者能够构建可与服务器实时通信的 Web 应用。直播国会(可在 Chrome 应用商店中获取)可提供有关美国国会运作方式的实时动态。该频道会直播众议院和参议院的最新动态、相关新闻动态、国会议员的推文和其他社交媒体动态。该应用会全天开启,以便捕获国会的工作动态。
开始使用 WebSocket
WebSocket 规范因其实现的功能而备受关注:在浏览器和服务器之间建立稳定的双向 TCP 套接字。TCP 套接字没有强加的数据格式;开发者可以自由定义消息传递协议。在实践中,以字符串形式传递 JSON 对象是最方便的。用于监听实时更新的客户端 JavaScript 代码简洁明了:
var liveSocket = new WebSocket("ws://streamcongress.com:8080/live");
liveSocket.onmessage = function (payload) {
addToStream(JSON.parse(payload.data).reverse());
};
虽然浏览器对 WebSocket 的支持非常简单,但服务器端支持仍处于形成阶段。基于 Node.js 的 Socket.IO 提供了最成熟、最稳健的服务器端解决方案之一。Node.js 等事件驱动型服务器非常适合 WebSocket。对于其他实现,Python 开发者可以使用 Twisted 和 Tornado,而 Ruby 开发者可以使用 EventMachine。
隆重推出 Cramp
Cramp 是一个异步 Ruby Web 框架,可在 EventMachine 之上运行。本博文由 Ruby on Rails 核心团队成员 Pratik Naik 撰写。Cramp 为实时 Web 应用提供了特定领域的语言 (DSL),是 Ruby Web 开发者的理想之选。熟悉在 Ruby on Rails 中编写控制器的用户会认出 Cramp 的样式:
require "rubygems"
require "bundler"
Bundler.require
require 'cramp'
require 'http_router'
require 'active_support/json'
require 'thin'
Cramp::Websocket.backend = :thin
class LiveSocket < Cramp::Websocket
periodic_timer :check_activities, :every => 15
def check_activities
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end
routes = HttpRouter.new do
add('/live').to(LiveSocket)
end
run routes
由于 Cramp 位于非阻塞 EventMachine 之上,因此请注意以下几点:
- 必须使用非阻塞数据库驱动程序,例如 MySQLPlus 和 em-mongo。
- 必须使用事件驱动型 Web 服务器。内置了对细线和彩虹的支持。
- Cramp 应用必须与为 Stream Congress 提供支持的主 Rails 应用分开运行,并独立重启和监控。
当前限制
2010 年 12 月 8 日,WebSockets 遭遇了挫折,因为一个安全漏洞被公开。Firefox 和 Opera 都已移除对 WebSocket 的浏览器支持。虽然没有纯 JavaScript polyfill,但有一个被广泛采用的 Flash 回退。不过,依赖 Flash 远非理想之举。尽管 Chrome 和 Safari 继续支持 WebSocket,但很明显,为了在不依赖 Flash 的情况下支持所有新型浏览器,WebSocket 需要替换。
回滚到 AJAX 轮询
我们决定放弃 WebSocket,改回“老式”AJAX 轮询。虽然从磁盘和网络 I/O 的角度来看,AJAX 轮询的效率要低得多,但它简化了 Stream Congress 的技术实现。最重要的是,无需再使用单独的 Cramp 应用。相反,AJAX 端点由 Rails 应用提供。客户端代码已修改为支持 jQuery AJAX 轮询:
var fillStream = function(mostRecentActivity) {
$.getJSON(requestURL, function(data) {
addToStream(data.reverse());
setTimeout(function() {
fillStream(recentActivities.last());
}, 15000);
});
};
AJAX polling, though, is not without its downsides. Relying on the HTTP request/response cycle means that the server sees constant load even when there aren't any new updates. And of course, AJAX polling doesn't take advantage of what HTML5 has to offer.
## EventSource: The right tool for the job
Up to this point, a key factor was ignored about the nature of Stream Congress: the app only needs to stream updates one way, from server to client - downstream. It didn't need to be real-time, upstream client-to-server communication.
In this sense, WebSockets is overkill for Stream Congress. Server-to-client communication is so common that it's been given a general term: push. In fact, many existing solutions for WebSockets, from the hosted [PusherApp](http://pusherapp.com) to the Rails library [Socky](https://github.com/socky), optimize for push and don't support client-to-server communication at all.
Enter EventSource, also called Server-Sent Events. The specification compares favorably to WebSockets in the context to server to client push:
- A similar, simple JavaScript API on the browser side.
- The open connection is HTTP-based, not dropping to the low level of TCP.
- Automatic reconnection when the connection is closed.
### Going Back to Cramp
In recent months, Cramp has added support for EventSource. The code is very similar to the WebSockets implementation:
```ruby
class LiveEvents < Cramp::Action
self.transport = :sse
periodic_timer :latest, :every => 15
def latest
@latest_activity ||= nil
new_activities = find_activities_since(@latest_activity)
@latest_activity = new_activities.first unless new_activities.empty?
render new_activities.to_json
end
end
routes = HttpRouter.new do
add('/').to(LiveEvents)
end
run routes
使用 EventSource 时,需要注意的一个重要问题是,不允许跨网域连接。这意味着,Cramp 应用必须从与主要 Rails 应用相同的 streamcongress.com 网域提供。这可以通过在 Web 服务器上进行代理来实现。假设 Cramp 应用由 Thin 提供支持并在端口 8000 上运行,则 Apache 配置如下所示:
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_balancer_module /usr/lib/apache2/modules/mod_proxy_balancer.so
<VirtualHost *:80>
ServerName streamcongress.com
DocumentRoot /projects/streamcongress/www/current/public
RailsEnv production
RackEnv production
<Directory /projects/streamcongress/www/current/public>
Order allow,deny
Allow from all
Options -MultiViews
</Directory>
<Proxy balancer://thin>
BalancerMember http://localhost:8000
</Proxy>
ProxyPass /live balancer://thin/
ProxyPassReverse /live balancer://thin/
ProxyPreserveHost on
</VirtualHost>
此配置会在 streamcongress.com/live
中设置 EventSource 端点。
稳定的 Polyfill
EventSource 相较于 WebSocket 的最大优势之一是,回退完全基于 JavaScript,不依赖于 Flash。Remy Sharp 的 polyfill 通过在不原生支持 EventSource 的浏览器中实现长轮询来实现这一点。因此,EventSource 目前可在所有已启用 JavaScript 的新型浏览器中运行。
总结
HTML5 为许多令人兴奋的新 Web 开发可能性打开了大门。借助 WebSocket 和 EventSource,Web 开发者现在可以使用清晰、明确的标准来实现实时 Web 应用。但并非所有用户都使用的是现代浏览器。选择实现这些技术时,必须考虑顺畅降级。服务器端适用于 WebSocket 和 EventSource 的工具仍处于早期阶段。在开发实时 HTML5 应用时,请务必考虑这些因素。