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_METHODThe HTTP request method, such as “GET” or “POST”. This cannot ever be an empty string, and so is always required.SCRIPT_NAMEThe 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_INFOThe 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_STRINGThe portion of the request URL that follows the “?”, if any. May be empty or absent.(以下略…)
到这裡其实都还看不见任何特别的地方,接下来才是重点,它定义了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你可以写一个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,它让我们再不用改原本的程式下就能改变网页的行为或是结果,我们不用改任何原本的程式,就可以把网页产生的结果全部变成注音文,很棒不是吗? 我们来看看结果
看到注音文让人开心不是吗? 当然,我知道,光是这火星文Middleware没办法满足各位,火星文Middleware只存在于本篇文章,如果你有兴趣可以把它应用在你的WSGI网站上,当然你可能得做好受到使用者投诉的心理准备。
Middleware的概念
其实Middleware并不是什麽新的概念,如果你有研究过Apache的Module怎麽写,你会发现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
像这类的书,想归想,要写书不简单,也要花不少时间,有人说写书是做公益,其实我是蛮想知道国内市场的大小和接受度如何,不过这一切要等我研究所考试考完再说,再考试考完前我决定封笔,所以在这段期间都不会有新的文章。
以上,希望这些对国内的网页开发的进步会有些帮助