簡介
開發人員可透過 WebSockets 和 EventSource,建立能夠與伺服器即時通訊的網頁應用程式。Stream Congress (可在 Chrome 線上應用程式商店中取得) 會即時更新美國國會的運作情形。它能串流播放 House 和參議院的樓層更新、相關新聞快報、美國國會成員的推文,以及其他社群媒體動態。這個應用程式會記錄國會的業務,因此應全天開啟。
開始使用 WebSocket
WebSockets 規格因其可在瀏覽器和伺服器之間建立穩定的雙向 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 是目前最成熟且穩定的伺服器端解決方案之一。WebSocket 最適合事件驅動伺服器,例如 Node.js。如需替代實作項目,Python 開發人員可使用 Twisted 和 Tornado,而 Ruby 開發人員可以使用 EventMachine。
Cramp 簡介
Cramp 是 EventMachine 上執行的非同步 Ruby 網路架構。這篇文章是由 Ruby on Rails 核心團隊成員 Pratik Naik 撰寫。Cramp 為即時網頁應用程式提供特定領域語言 (DSL),是 Ruby 網頁開發人員的理想選擇。如果您熟悉在 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。
- 必須使用事件導向網路伺服器,內建支援 Thin 和 Rainbows。
- Cramp 應用程式必須與負責 Stream Congress 的主要 Rails 應用程式分開執行,並且可獨立重新啟動及監控。
目前限制
2010 年 12 月 8 日,WebSockets 因安全漏洞遭到公開而受到挫折。Firefox 和 Opera 都已移除瀏覽器對 WebSocket 的支援。雖然沒有純 JavaScript Polyfill,但有個 Flash 備用方案已被廣泛採用。然而,對 Flash 的看法卻不盡理想。雖然 Chrome 和 Safari 仍會繼續支援 WebSocket,但為了不依賴 Flash 就能支援所有現代瀏覽器,我們必須更換 WebSocket。
復原至 AJAX 輪詢
結果是決定捨棄 WebSockets 並改回「舊型」的 Vertex 輪詢。雖然從磁碟和網路 I/O 的角度來看,AJAX 輪詢的效率不高,但它簡化了 Stream Congress 的技術實作。最重要的是,我們捨棄了對單獨的 Cramp 應用程式的必要性。而是由 Rails 應用程式提供 AJAX 端點。用戶端程式碼已修改為支援 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 網域提供服務。這可以透過在網路伺服器上設定 Proxy 來達成。假設 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 端點。
穩定版聚酯纖維
EventSource 相較於 WebSocket 的一大優勢,就是備用方案完全以 JavaScript 為基礎,不需仰賴 Flash。Remy Sharp 的polyfill 在原生不支援 EventSource 的瀏覽器中實作長輪詢,以達成這項目標。因此,EventSource 目前可在所有已啟用 JavaScript 的新版瀏覽器上運作。
結論
HTML5 為網頁開發人員開啟了許多嶄新且令人期待的可能性。有了 WebSockets 和 EventSource,網路開發人員現在就能使用簡潔且明確定義的標準,實現即時網路應用程式。但並非所有使用者都會使用新式瀏覽器。選擇導入這些技術時,請務必考量降級功能。而伺服器端的 WebSockets 和 EventSource 工具仍處於初期階段。開發即時 HTML5 應用程式時,請務必考量這些因素。