Comet (Server Push) on Turbogears (2)

Sunday, November 5th, 2006 | 瀏覽:9,725人次

上一篇提到了Comet,以及Turbogears

Turbogears是用Python實做的一套Web development framework,有點像Ruby on Rails那樣,可以讓人用Python快速建構web application。這個framework包山包海,一般web app需要用的東西全都有,而且也很容易上手。我一連用它作了2個project,都很愉快的解決掉,直到最近要作即時性非常重要的web game,才發現我需要有server主動送出資料給client的能力。

我根據Server Push and Server SocketsXMLHttpRequest兩篇reference,在Turbogears上實做出一個簡單的Comet decorator
這個decorator可以非常方便的在Turbogears中(其實只要用CherryPy就可以了),加入支援Server Push的method。
大致上的作法是這樣子的。Turbogears底層所用的CherryPy可以藉由回傳一個generator來分段傳回結果,進而達到HTTP streaming的功能。但只有這樣還不夠,我們得改變CherryPy回傳的Content-type為”multipart/x-mixed-replace”,並且在每個片段前後都包上multipart delimiter。

Python 2.4後有了一個很棒的新功能: decorator,可以在任何一個function/method上包上另一個function,例如:

[code lang="python"]
def wrapper(f):
def _wrapper():
return "< < " + f() + " >>"
return _wrapper

@wrapper
def func():
return "Hello world"

print func() # The result is "< < Hello World >>"
[/code]

上面這個例子,利用decorator在func()的結果前後加上了新的文字,但卻不用更動func()以及主要的程式碼。所以,decorator可以很方便地在不更動其它code的前提下,在現有的function上包覆上新的功能。如果對decorator有興趣的,可以參考Python 2.4 Decorators這篇文章,裡面詳細介紹了decorator及其應用。

一開始提過,我正在作一個web game,第一個碰到的麻煩就是玩家們必須等其它玩家到齊,遊戲才能開始。在沒有server push的技術前,我是在前端用JavaScript的setTimeout()加上Turbogears(其實是Mochikit)的loadJSONDoc()不斷地去polling,問到server回應人已經到齊為止。

利用decorator的優點,我寫了一個comet decorator,可以讓Turbogears(CherryPy)的user們輕易地加上server push(Comet)的能力。 使用方法如下:

Server side(Turbogears):
[code lang="python"]
from Comet import comet

class Root(controllers.RootController):
@expose()
def comet_wait(self):

@comet(content_type='text/plain') # content_type can be omitted
def _waiting():
yield "waiting..."
time.sleep(5) # replace me with real code
yield "start"
return _waiting()
cherrypy.config.update({'/comet_wait':{'stream_response':True}})
[/code]

Client side(Javascript):
[code lang="javascript"]
function handler(evt){
logDebug("onLoad handler");
var t = evt.target.responseText;
$('msg').innerHTML = t;
}

var req = new XMLHttpRequest();
req.multipart = true;
req.onload = handler;
req.open("GET", "/comet_wait", true);
req.send(null);
[/code]

上面這個例子模擬了user等待server通知遊戲開始。我在Root下加了一個comet_wait method(相對的URL為http://SERVERNAME/comet_wait),並把stream_response設為true,讓這個method可以分段傳回資料。這裡的關鍵在generator method _waiting()前加上的@comet(content_type=’text/plain’),這個decorator會把HTTP response的content-type設為”multipart/x-mixed-replace”,並且讓_waiting()每次用yield傳給client的結果前後都包上multipart delimiter。

在client端,因為Mochikit不支援multipart的AJAX request,所以只好自己土法煉鋼一下。我們用XMLHTTPRequest打開一個不中斷的channel,等待server通知遊戲開始。為了要配合server的multipart data,我們必須把req.multipart設為true,並把req.onload設為每次有資料進來時的handler。

到此,就完成了一個簡單的Comet client和server。server一開始收到client的request後,會先發出”waiting…”的訊息,接著等待5秒後,再主動送出”start”的訊息。這中間的time.sleep,可以換成檢查玩家是否到齊的程式,而client只要在onload handler裡檢查遊戲是否開始就行了。

完整的Turbogears example和comet decorator的實做下載:

Comet (Server Push) on Turbogears

Saturday, November 4th, 2006 | 瀏覽:9,954人次

傳統Web的運作方式是建立在client對server的request上。也就是說client不送request,server就沒辦法主動送資料給client。這算是先天上的限制,沒辦法解決。而最近web application的花樣越來越多了,越來越多的網站打著web 2.0的口號,要把人們需要的所有軟體全都搬到web上面來。許多軟體都有即時性的資料更新問題,例如即時通訊、遊戲、股票行情等等,都會有許多資料主動從別的地方往我們送。但傳統的web不允許這種溝通方式,那究竟要怎麼實做這些功能?

最簡單的方法是polling,由client定時去問server有沒有需要更新的資料,理論上只要問得夠頻繁,user感覺起來就會像是即時的更新一樣。但polling會造成很多無謂的網路頻寬浪費,以及server的額外負擔,所以這麼作實在不是個好選擇。

另一個方法是最近開始有要被炒熱跡象的技術: Comet。 但說穿了,Comet其實就是許多年前蠻流行的CGI聊天室所用的Server Push技術。
這個方法一開始還是由client先對server建立連線,但是server在建立起連線後,送出的header中要把content-type設為”multipart/x-mixed-replace”,意思是server之後要分好幾次送出許多片段資料,請client保持連線不要中斷,並且把每次拿到的新片段取代之前的舊片段。接著,client就只要在這條保持不斷的HTTP連線上等著收server送過來的資料就好了。Comet利用這種特性,加上Ajax能在背景讀取server資料更新畫面的能力,變成一個開發rich client非常重要的技術。

但Comet不像Ajax那樣容易可以隨便套用到現成的http server上。它需要server上有適當的程式配合,而現成的open source工具最近才開始慢慢出現,似乎都還不太成氣候,更別提要整合進我慣用的Turbogears中了。

所以呢,我只好從頭開始作輪子了…(待續)