化整為零的次世代網頁開發標準: WSGI

化整為零的次世代網頁開發標準: WSGI

from:http://blog.ez2learn.com/2010/01/27/introduction-to-wsgi/

今天,我要介紹Python網頁開發的標準: WSGI,我個人在看見這類英文縮寫時,都一定會試著去記住它的全寫,因為縮寫本身一點意義都沒有,難以記憶,WSGI的全寫是”Web Server Gateway Interface“,它的發音有點像是whiskey,光知道這個名字還是很難理解這到底是用來做什麼用的,簡單的來說,它是Python定義網頁程式和伺服器溝通的介面,如果你有寫過CGI (Common Gateway Interface),它的作用基本上就是和CGI類似的功用,定義一個標準的溝通方式,讓你寫的程式可以和伺服器溝通,但是WSGI不是設計用來給任何語言使用的,它是設計給Python用的,而它其實是基於CGI的延伸,在Python的部份進一步做更多的定義,而因為他是基於CGI,所以它也可以和CGI的介面相容,只要透過一個轉接器,就能把WSGI的程式接到CGI,說了這麼多,相信大部份人對於WSGI是什麼還是一頭霧水,會有一堆疑問,為什麼有了CGI還要有WSGI? Middleware又是什麼? 這很正常,我一開始也對WSGI一點概念都沒有,接下來我們就來介紹WSGI的特色。

 

一些基本的定義

WSGI是由Python的官方在PEP333所定義出來的,細節的定義請自行閱讀該規格,這篇文章希望能從較高的層面來著眼,所以在此只簡單介紹基本的概念,首先,如果你有寫過CGI的話,就知道CGI透過環境變數來取得外部資訊,基本上WSGI也是一樣透過環境變數來取得資訊,例如REQUEST_METHOD、SERVER_NAME、HTTP_xxx,如你所見它繼承了自CGI的環境變數,除此之外它還定義了一些額外的變數,例如wsgi.version是包含著wsgi介面版本的變數,我們取部份它定義的環境變數

REQUEST_METHOD
The HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.
SCRIPT_NAME
The initial portion of the request URL’s “path” that corresponds to the application object, so that the application knows its virtual “location”. This may be an empty string, if the application corresponds to the “root” of the server.
PATH_INFO
The remainder of the request URL’s “path”, designating the virtual “location” of the request’s target within the application. This may be an empty string, if the request URL targets the application root and does not have a trailing slash.
QUERY_STRING
The portion of the request URL that follows the “?”, if any. May be empty or absent.
(以下略…)

 

wsgi.version The tuple (1,0), representing WSGI version 1.0.
wsgi.url_scheme A string representing the “scheme” portion of the URL at which the application is being invoked. Normally, this will have the value "http"or"https", as appropriate.
wsgi.input An input stream (file-like object) from which the HTTP request body can be read. (The server or gateway may perform reads on-demand as requested by the application, or it may pre- read the client’s request body and buffer it in-memory or on disk, or use any other technique for providing such an input stream, according to its preference.)

(以下略…)

到這裡其實都還看不見任何特別的地方,接下來才是重點,它定義了WSGI的應用程式是用一個固定形式的函數來和伺服器進行溝通,我們取自PEP333的範例:

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

我知道單單這樣看來好像很平常,不過就是定義一個標準的函數介面,環境變數一律從environ丟進去,要開始進行response時就呼叫第二個參數start_response,第一個放狀態,第二個放headers,然後函數的回傳值是一個包含了網頁內容的list,可以是generator,也就是內容不一定要回傳時就產生,而是回傳一個generator再被呼叫從裡面取得要回傳給browser的網頁內容,好了,寫到這裡,一切似乎簡單到似乎沒有什麼用處,是的,跟據Python的哲學,簡單比複雜來得好,雖然它的定義簡單,但是背後卻隱涵著更重大的意義,你可能一開始和我一樣覺得二丈金剛摸不著頭腦,搞不清楚這到底有什麼好處,但很快你就會知道這簡單定義所帶來的強大威力,我們繼續看下去。

Middleware (中介層)

在PEP333裡最令人困惑的名字,我想莫過於Middleware,對於這類名詞我傾向於寫原文,但是如果硬要給它個中文翻譯,我暫時把它譯做”中介層”,不是什麼響亮的名詞,但是卻有極大的彈性,別被這個名字給嚇著了,我們先來看看一個例子,我們這樣說好了,假如你是注音文的推廣者,實在很討厭看見網頁裡都沒有注音文,那麼你要怎樣改寫你的程式呢? 最簡單的做法就是改寫原本的程式對吧? 但是在WSGI的架構下,我們有更好的做法,那就是寫一個Middleware來幫我們取代掉一般中文裡的某些字,我們先看看原本的程式:

from wsgiref.simple_server import make_server

def my_app(environ, start_response):
    """a simple wsgi application"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [u"你好! 歡迎來到Victor的第一個WSGI程式".encode('utf8')]

httpd = make_server('', 8000, my_app)
print "Serving on port 8000..."
httpd.serve_forever()

我們用Python內建的模組wsgiref裡的簡單伺服器來執行這個wsgi程式,我們來看看執行的結果

 

簡單WSGI程式的示範

一點都不吸引人對吧? 我們需要的是注音文! 滿滿的注音文才能讓人滿足,於是利用WSGI你可以寫一個Middleware不需改寫原本程式的任何一個部份,就能把你的網站變成一個充滿注音文的網站! 我們來看一下新版的程式:

from wsgiref.simple_server import make_server

def my_app(environ, start_response):
    """a simple wsgi application"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [u"你好! 歡迎來到Victor的第一個WSGI程式".encode('utf8')]

class MarsLanguage(object):

    def __init__(self, app, encoding='utf8'):
        self.app = app
        self.encoding = encoding
        self.dictionary = {
            u'好': u'ㄏ',
            u'到': u'ㄉ',
            u'個': u'ㄍ',
            u'的': u'ㄉ'
        }

    def __call__(self, environ, start_response):
        content = self.app(environ, start_response)
        for piece in content:
            piece = piece.decode(self.encoding)
            for key, value in self.dictionary.iteritems():
                piece = piece.replace(key, value)
            yield piece.encode(self.encoding)

# We warp the original WSGI application with
# the MarsLanguage middleware!!!
app = MarsLanguage(my_app)

httpd = make_server('', 8000, app)
print "Serving on port 8000..."
httpd.serve_forever()

看到了嗎? 我們原本的wsgi一點更動都沒有,我們只是在原本的app外面再包一層MarsLanguage這個Middleware,沒錯,MarsLanguage就是一個最簡單Middleware,它讓我們再不用改原本的程式下就能改變網頁的行為或是結果,我們不用改任何原本的程式,就可以把網頁產生的結果全部變成注音文,很棒不是嗎?  我們來看看結果

 

WSGI 火星文middleware demo

看到注音文讓人開心不是嗎? 當然,我知道,光是這火星文Middleware沒辦法滿足各位,火星文Middleware只存在於本篇文章,如果你有興趣可以把它應用在你的WSGI網站上,當然你可能得做好受到使用者投訴的心理準備。

Middleware的概念

其實Middleware並不是什麼新的概念,如果你有研究過Apache的Module怎麼寫,你會發現Apache有個機制叫做Filter,有類似的概念,如這份文件裡這張圖所示

 

Apache filter機制

Apache的模組可以在輸入和對資料進行修改,MarsLanguage的Middleware其實在Apache模組中也可以寫一個火星文模組用來達到同樣的效果,只是WSGI的Middleware不只有針對資料,還有其它像可以對環境變數進行控制的功能,它有錯誤的控制,所以當內層拋出例外時,它可以進行處理,有著極大的彈性。

再仔細想一想,如果你知道Design Pattern,你就會發現它其實是Decorator (裝飾者) 的pattern,它的威力和彈性就來自於一致的介面,使得可以做出Middleware來改變WSGI application的行為,上下游都不需要知道彼此的存在,他們只要在每一層做好他們該做的事就可以了。

實際的應用

當使用者送出一個Request來到你伺服器,接著你的網頁程式產生內容,然後送出Response回去給Browser,最簡單的網頁程式做的事情大概就像是這樣,但是真實世界的網頁程式要做的事情很多,他們得記錄Session資訊、取得Cookie、認證、錯誤處理等等雜事,而傳統的架構你可以想像一下這些東西都沒有標準可言,都只限於單一的網頁程式,他們可能都跟特定模組糾結在一起,而WSGI的Middleware將這些該做的事都限定於Middleware中,它們跟上下游溝通的方式都是按照標準來實作的,這表示這些Middleware都能重覆被利用,這就是WSGI帶來的好處之一。

我們借用來自Pylons文件的一張圖

 

 

從這張你可以看到,使用者的Request進到我們的網頁伺服器裡來,首先經過Registry Manager是用來管理request-local的相關物件,接著Status Code Redirect在Request上不做任何事,往下層跑ErrorHandler也是不對Request做任何事,接著是Cache Middle,視情況記錄Request相關資料,然後是Session Middleware取出cookie還有在環境中準備session的資料,接著Routes依網址決定要給哪個Controller來處理,最後Request經過層層關卡來到了真正的Pylons App,產生網頁結果後,Response會一層一層被回傳回去,到Cache層如果有需要可以把結果暫存起來,如果這之中有例外被丟出,Error Handler會處理,debug模式下會顯示友善的除錯介面,非debug模式下可以把錯誤報告寄到信箱去,然後回到Status Code Redirect如果有需要可以重導網頁到特定的錯誤頁面去,再來是Registry Manager,整個Request進去和產生Response的過程在Pylons裡大概就像這樣子,講到這裡,我相信大家已經對於WSGI越來越有感覺。

老闆,請給我一份雞排去骨去皮加顆蛋撒點蔥

相信大家對於框架之類的感覺可能往往是覺得一體成形的東西總是缺乏彈性,既有的系統通常都什麼都有,但是都難以重覆利用,當需求有一點點改變都要花很大力氣把東西從那糾結的程式中清乾靜之後拔出來,相較於基於WSGI的網頁程式卻是極有彈性的,它的特性使得你可以自由排列組合各種不同的Middleware來拼湊出你想要的網頁框架,

你不喜歡按照URL來Route Request? 那考慮靠使用者的作業系統和環境來決定如何? 又或著靠日期來決定要繞向哪個頁面,沒問題,你只要抽掉Route Middleware,換上你喜歡的,其它的部份都還是可以留著。

你不喜歡那Cache機制,你自己可以做一個,或是利用先進的共用快取機制,一樣在一般情況下,你不必改變其它的部份。

你覺得它的Error Handler實在遜斃了,只能顯示互動式的除錯器、或著是把出現的錯誤寄到你的信箱去,你想要更過份一點的錯誤處理機制? 發生錯誤自動打電話把工程師吵醒? 沒問題! 換掉Error Handler即可。

還有很多很多的需求….,這些都只是冰山一角,你甚至不必照它安排的順序或個數,你可以在其中加入你喜歡的Middleware,像是TurboGears2就有加入一個資料庫交易的Middleware,當發生錯誤時它會自動Rollback,你覺得不喜歡都可以拿掉、加入或是換上你自己的版本,這使得做一個符合你需求的變得非常容易,你只要挑你喜歡的積木把他們組合起來就可以了,就像堆樂高積木似的。

現有的資源

就如同我上面所提到的,自從Python提出了這樣的標準,它可重覆利用和有彈性的特性,使得原本很多各自為政的Python網頁技術都紛紛向WSGI靠隴,到了今天Python已經有一大堆的網頁技術都是架構在WSGI上,因為它的彈性使得現在還有更多新的技術如語後春筍般一直發芽,如今用Python寫網頁已經是一件很幸福的事,因為有這麼多現成可通用的資源,使得網頁開發可以更專注於更重要的地方上,例如使用者經驗、安全性等等,其實不只是Middleware,也有很多WSGI application,以下介紹我所知道的一些WSGI可用的資源

Paste

Paste提供的是一些常用的WSGI基礎建設,WSGI網頁的部屬、設定、WSGI的伺服器、測試用的框架、CGI的橋接器、樣版專案產生器等等。

Pylons

Pylons是輕量且有彈性的框架,他也提供了一些更進階基礎上所需要的東西,例如樣版、i18n、URL routing、設定檔等等

Repoze

Repoze是將原本知名的重量級CMS Zope和架構在上面的Plone的相關技術移植到WSGI上,所以這些技術都可以直接的被使用,例如repoze.bfg是一套基於這些技術的網頁框架,repoze.bitblt可以用來自動縮放圖片repoze.errorlog用來記錄錯誤訊息,repoze.profile用來測量記錄程式的效能,repoze.who則是用於身份認證,而repoze.what是用來做控制存取的,還有很多很棒的技術都是WSGI的形式隨時可以被整合和使用

TurboGears2

TurboGears是一套集大成的框架,TG1原本不是完全架構在WSGI上的,原本它以cherrypy為伺服器做為架構,後來TG2改架構在Pylons上,它的哲學是”The best of breeds”,意思就是它透過挑選並評估最好的函式庫和組件,把他們兜在一起形成一個框架,這樣你就不用花時間自己一個一個去評估,我個人網頁開發是都使用這套,當初Django和TurboGears在選擇時,我因為比較喜歡他的藕合比較鬆散這點,可以換掉自己不喜歡的組件,所以選擇TurboGears

Django

知名的Python網頁框架,相較於TurboGears是集大成,Django是原本由商業公司開發一應俱全的網頁框架,後來才開放原始碼,就藕合度來說,比TurboGears來得高,但是因為我沒有實際使用過,所以實際情況是如何我也不清楚

結論

上面列舉到的WSGI相關資源只是我所知道的一小部份,還有更多Python相關網頁資源都是基於WSGI,現代網頁開發者實在是太幸福了,但即使在先進的2010年,台灣一樣有一堆人還在用10年前的方式在寫網頁,WSGI也只是現代網頁開發技術的一個分支而已,身為一個資訊人,學習新的技術是職責,當一個資訊人不再學新技術時,資訊人就已經死了,在資訊領域沒有所謂學一次用一輩子的技術,我深深的覺得當這些次世代的技術越來越普及時,那些還在用10年前方式寫網頁的人可能很快就被淘汰了,當別人做一個網站是用兜出來的,時間超短,成品又好又安全,而你還在用土法鍊鋼,自然成本就遠比別人高,被淘汰是理所當然的事。

對於很多人來說,可能英文是學習這些新技術的障礙,並不是每個人都能快速地讀大量的技術文件,很多人可能都喜歡逐字閱讀都不懂得一些技巧,讀得很辛苦自然很排斥,所以到最後只能依賴別人翻譯,但是當有人幫你翻譯好成中文,往往可能已經過時了,再加上翻譯的品質很多都爛到暴炸,令人懷疑是不是用Dr. Eye直接翻譯的,過時加上破爛的翻譯,往往使得國內離所謂的先進…,不,是光離現代的技術就更遠了,所以給想要往這領域發展的人一個建議,英文絕對是很重要的,至少要有閱讀能力,等我考試完有空有閒的話我蠻想發幾篇英文學習心得的相關的文章。

每次看見國外在用先進的技術,別人的網站真的都是兜出來的,但回頭看國內很多人都以為寫網頁就只有PHP加上CGI其實真的蠻心酸的,比起吐嘈我還是比較喜歡介紹新的技術或寫寫心得之類的,其實我有在想在我研究所考試完後要來寫幾本書,目前我想到的有

  • 網頁開發即戰力: TurboGears2
  • 伺服器開發即戰力: Twisted
  • 視窗程式開發即戰力: wxPython

像這類的書,想歸想,要寫書不簡單,也要花不少時間,有人說寫書是做公益,其實我是蠻想知道國內市場的大小和接受度如何,不過這一切要等我研究所考試考完再說,再考試考完前我決定封筆,所以在這段期間都不會有新的文章。

以上,希望這些對國內的網頁開發的進步會有些幫助

发表评论

电子邮件地址不会被公开。 必填项已用*标注