转:http://www.ibm.com/developerworks/cn/web/wa-wsgi/index.html?S_TACT=105AGX52&S_CMP=09-w-cto
学习有关构建具有最大灵活性的 Web 应用程序的 Python 标准
2006 年 8 月 22 日
学习使用 Python 在 Web 服务器中创建并重用组件。Python 社区创建了 Web 服务器网关接口(Web Server Gateway Interface,WSGI),这是创建跨服务器和框架工作的 Python Web 组件的标准。它提供了一种利用许多不同的 Web 工具开发 Web 应用程序的方法。本文介绍了 WSGI 并展示如何开发出能够集成到设计出色的 Web 应用程序中的组件。
Web 成功的主要原因是它的灵活性。您会发现在设计、开发和部署 Web 站点和应用程序方面,每个开发人员几乎都有自己的特色。尽管有大量的选择,但是一个 Web 开发人员经常选择惟一的 Web 设计工具、页面风格、内容语言、Web 服务器、中间件和 DBMS 技术的组合,使用不同的实现语言和辅助工具包。为了给所有这些元素一起工作提供最大的灵活性,应该尽可能地通过组件提供 Web 的功能。这些组件应该执行有限数量的任务,并相互很好地配合工作。这么说很容易,但是在实践中完成这一要求是非常困难的,因为有许多实现 Web 技术的不同方法。
因此开发人员期望着 Web 组件协同工作标准的成熟。这些重要标准的一部分已经开发完成,而且最成功的 Web 开发平台已经成为它们的支柱。突出的例子包括 Java servlet API 和 Ruby on Rails 框架。一些长期流行的 Web 编程语言直到最近才提供了相同的组件化水平,而且借鉴了先前 Web 框架组件标准的经验。一个例子是 Zend Framework for PHP(参见 参考资料)。另一个例子是 Python 的 Web 服务器网关接口(WSGI)。
许多人抱怨流行的 Python 编程语言有太多的 Web 框架,从众所周知的 entrant(比如 Zope)到 under-the-radar 框架(比如 SkunkWeb)。有些人认为只要有一些基础标准化了,这种差异可能是件好事。Python 和 Web 专家 Phillip J. Eby 从事这样的标准化工作。他撰写的 Python Enhancement Proposal(PEP)333 定义了 WSGI。
WSGI 的目标是支持 Python 框架之间更大的协同工作能力。WSGI 的成功会产生一个插件组件生态系统,在这个系统中可以用您喜欢的框架获得最大的灵活性。在本文中,我将介绍 WSGI,主要关注它作为可重用 Web 组件架构的使用方法。在所有讨论和示例代码中,假设您正在使用 Python 2.4 或者更新的版本。
WSGI 的基本架构
WSGI 是在相当严格的约束条件下开发的,但是最重要的是需要对先于它的 Web 框架的向下兼容性。很遗憾,这个约束意味着 WSGI 不能像 Python 开发人员习惯的那么简洁和透明。通常,必须直接处理 WSGI 的开发人员只有那些需要构建框架和可重用组件的开发人员。大多数普通的 Web 开发人员会挑选容易使用的框架,而不接触 WSGI 的细节。
如果想开发可重用 Web 组件,就必须理解 WSGI。需要理解的第一件事是以 WSGI 的角度来看 Web 应用程序是如何构造的。图 1 说明了这个结构。
Web 服务器(也称为网关)是与请求客户机(通常是用户的浏览器)进行基本通信的非常低级的代码。应用层处理较高级别的细节,解释来自用户的请求并准备响应的内容。WSGI 本身的应用程序接口通常只是应用程序框架的更高层的最基本层,为通用的 Web 模式(比如 Ajax 技术或者内容模板系统)提供友好的设施。在服务器或者网关层之上是 WSGI 中间件。这个重要的层包含在服务器和应用程序实现中可以共享的组件。通用 Web 特性(比如用户会话、错误处理和身份验证)可以作为 WSGI 中间件来实现。
中间的代码
WSGI 中间件对于可重用组件是最自然的层。WSGI 中间件看上去对于应用程序是较低层,而对于服务器是较高层。为了添加一些特殊的特性,它监视请求、响应和 WSGI 环境的状态。遗憾的是,WSGI 规范提供了非常差的中间件例子,而且可以找到的大多数其他例子也很简单,只能提供如何快速编写中间件的感性认识。我将使用以下概述给出对 WSGI 中间件承担的过程的感性认识。这里忽略了大多数 WSGI 中间件作者都不必担心的问题。在 Python 中,我使用单词函数,意思是任何可调用的对象。
server.start_response
参数调用中间件函数。 middleware.start_response
。 middleware.start_response
。 server.start_response
。
处理 XHTML 的大胆步骤
许多组件技术相当复杂,所以作为指导的例子最好是简单的东西。这不是使用 WSGI 的案例,事实上,我将要介绍一个非常实际的例子。许多开发者更喜欢使用 XHTML Web 页面,因为 XML 技术比 “标记汤” 的 HTML 更容易管理,而且 Web 站点倾向于更容易由机器读取。问题是并非所有的 Web 浏览器都能正确地支持 XHTML。清单 1(safexhtml.py)是一个 WSGI 中间件模块,它检查到来的请求,看浏览器是否支持 XHTML;如果不支持,就将任何 XHTML 响应翻译成普通的 HTML。可以使用这样的模块让所有主要应用程序代码产生 XHTML,而中间件负责任何需要翻译成 HTML 的地方。仔细查看 清单 1 并试着把它与前面描述的 WSGI 中间件过程相结合。我提供了足够的注释,您可以识别出代码的不同阶段。
清单 1(safexhtml.py). WSGI 中间件为不能处理 XHTML 的浏览器把 XHTML 翻译成 HTML
import cStringIO from xml import sax from Ft.Xml import CreateInputSource from Ft.Xml.Sax import SaxPrinter from Ft.Xml.Lib.HtmlPrinter import HtmlPrinter XHTML_IMT = "application/xhtml+xml" HTML_CONTENT_TYPE = 'text/html; charset=UTF-8' class safexhtml(object): """ Middleware that checks for XHTML capability in the client and translates XHTML to HTML if the client can't handle it """ def __init__(self, app): #Set-up phase self.wrapped_app = app return def __call__(self, environ, start_response): #Handling a client request phase. #Called for each client request routed through this middleware #Does the client specifically say it supports XHTML? #Note saying it accepts */* or application/* will not be enough xhtml_ok = XHTML_IMT in environ.get('HTTP_ACCEPT', '') #Specialized start_response function for this middleware def start_response_wrapper(status, response_headers, exc_info=None): #Assume response is not XHTML; do not activate transformation environ['safexhtml.active'] = False #Check for response content type to see whether it is XHTML #That needs to be transformed for name, value in response_headers: #content-type value is a media type, defined as #media-type = type "/" subtype *( ";" parameter ) if ( name.lower() == 'content-type' and value.split(';')[0] == XHTML_IMT ): #Strip content-length if present (needs to be #recalculated by server) #Also strip content-type, which will be replaced below response_headers = [ (name, value) for name, value in response_headers if ( name.lower() not in ['content-length', 'content-type']) ] #Put in the updated content type response_headers.append(('content-type', HTML_CONTENT_TYPE)) #Response is XHTML, so activate transformation environ['safexhtml.active'] = True break #We ignore the return value from start_response start_response(status, response_headers, exc_info) #Replace any write() callable with a dummy that gives an error #The idea is to refuse support for apps that use write() def dummy_write(data): raise RuntimeError('safexhtml does not support the deprecated write() callable in WSGI clients') return dummy_write if xhtml_ok: #The client can handle XHTML, so nothing for this middleware to do #Notice that the original start_response function is passed #On, not this middleware's start_response_wrapper for data in self.wrapped_app(environ, start_response): yield data else: response_blocks = [] #Gather output strings for concatenation for data in self.wrapped_app(environ, start_response_wrapper): if environ['safexhtml.active']: response_blocks.append(data) yield '' #Obey buffering rules for WSGI else: yield data if environ['safexhtml.active']: #Need to convert response from XHTML to HTML xhtmlstr = ''.join(response_blocks) #First concatenate response #Now use 4Suite to transform XHTML to HTML htmlstr = cStringIO.StringIO() #Will hold the HTML result parser = sax.make_parser(['Ft.Xml.Sax']) handler = SaxPrinter(HtmlPrinter(htmlstr, 'UTF-8')) parser.setContentHandler(handler) #Don't load the XHTML DTDs from the Internet parser.setFeature(sax.handler.feature_external_pes, False) parser.parse(CreateInputSource(xhtmlstr)) yield htmlstr.getvalue() return |
safexhtml
类是完整的中间件实现。每个实例都是一个可调用对象,因为这个类定义了专门的 __call__
方法。向服务器传递类的实例,传递包装成初始化器 __init__
的应用程序。如果把 safexhtml
链到另一个中间件,包装的应用程序可能也是另一个中间件实例。当中间件作为向服务器请求的结果被调用时,这个类首先检查客户机发送的 Accept 报头,看它是否包括正式的 XHTML 媒体类型。如果是这样的话(xhtml_ok
标志),那么发送 XHTML 就是安全的,中间件就不在请求中做任何有意义的事情。
当客户机不能处理 XHTML 的时候,这个类定义专门的嵌套函数 start_response_wrapper
,这个函数的作用是检查来自应用程序的响应报头,看响应是否是 XHTML。如果是,这个响应需要翻译成普通的 HTML,在这个环境中以 safexhtml.active
标记这个事实。对于这个标志,使用这个环境的一个原因是因为它处理将标志返回到其余的中间件代码的范围问题。记住,在应用程序选择的时间异步调用 start_response_wrapper
,它可以技巧地管理必要的中间件状态。
使用这个环境的另一个原因是向下沿着 WSGI 堆栈通信时,内容已经修改。如果需要翻译响应体,不仅要运行 start_response_wrapper
设置 safexhtml.active
,而且它还把响应媒体类型改成 text/html
并删除任何 Content-Length
报头,因为翻译会改变响应体的长度,它必须向下游重新计算,这可能由服务器执行。
一旦应用程序开始发送响应体,如果需要翻译,它就把数据收集到 response_blocks
列表中。应用程序可能以块的形式发送响应,但是为了保持代码简单,它仅针对完整的 XHTML 输入运行翻译机制。可是,WSGI 规则规定应用程序每次产生块的时候,中间件必须向服务器传送一些东西。传递空字符串也是可以的,这就是它应该做的。一旦应用程序完成,它就固定响应体并通过翻译代码处理响应,然后用最后一个字符串产生完整的输出结果。
清单 2(wsgireftest.py)是测试中间件的服务器代码。它使用包含非常简单的 WSGI 服务器的 wsgiref。这个模块将包括在 Python 2.5 标准库中。
清单 2(wsgireftest.py). 测试清单 1 的服务器代码
import sys from wsgiref.simple_server import make_server from safexhtml import safexhtml XHTML = open('test.xhtml').read() XHTML_IMT = "application/xhtml+xml" HTML_IMT = "text/html" PORT = 8000 def app(environ, start_response): print "using IMT", app.current_imt start_response('200 OK', [('Content-Type', app.current_imt)]) #Swap the IMT used for response (alternate between XHTML_IMT and HTML_IMT) app.current_imt, app.other_imt = app.other_imt, app.current_imt return [XHTML] app.current_imt=XHTML_IMT app.other_imt=HTML_IMT httpd = make_server('', PORT, safexhtml(app)) print 'Starting up HTTP server on port %i...'%PORT # Respond to requests until process is killed httpd.serve_forever() |
清单 2 读取 清单 3(test.xhtml)给出的简单 XHTML 文件,并向它提供替代的媒体类型。对于第一次请求使用标准的 XHTML 媒体类型,对于第二次使用 HTML 媒体类型,第三次又使用 XHTML,如此循环。如果响应没有标成 XHTML,就保持响应不变,这个中间件功能留给读者作为练习。
清单 3(test.xhtml). 由清单 2 中的示例服务器使用的简单 XHTML 文件
|
如果运行 清单 2 并在理解 XHTML 的浏览器(比如 Firefox)以及不理解 XHTML 的浏览器(比如 Microsoft Internet Explorer)中观看,您应该能够看到这个中间件的效果。为了在每个浏览器中看到中间件操作对响应媒体类型的影响,要在一行中请求两次。使用 View Source 查看产生的响应体,使用 Page Info 特性查看报告的响应媒体类型。也可以使用命令行 HTTP 工具 cURL:用 curl -H 'Accept: application/xhtml+xml,text/html' http://localhost:8000/
模拟理解 XHTML 的浏览器,用 curl -H 'Accept: text/html' http://localhost:8000/
模拟不理解 XHTML 的浏览器,从而测试这个例子。如果想要看响应报头,在每一次调用 cURL 之后使用 -D
并检查给定的文件名。
![]() ![]() |
![]()
|
结束语
现在您学习了 Python 的 WSGI 以及如何使用它实现可以插入任何 WSGI 服务器和应用程序链的中间件服务。很容易将本文的中间件例子链到进行缓存或者调试的中间件。所有这些都成为组件,因此可以快速地向项目中添加经过反复测试的特性,而不必考虑选择的 WSGI 实现是什么。
WSGI 是一个相当新的规范,但是兼容的服务器、中间件和工具很快出现,使得 Python Web 框架的前景非常光明。下一次您在 Python 中开发重大的 Web 项目时,一定要通过使用现有的 WSGI 组件来采用 WSGI,还可以考虑为自己或者为您的 Web 开发伙伴创建 WSGI 组件。
参考资料
学习
获得产品和技术
easy_install 4Suite
。easy_install wsgiref
。从 Python 2.5 documentation sandbox 获得模块文档。
讨论
关于作者
![]() |
||
![]() |
Uche Ogbuji 是 Fourthought Inc. 的顾问和合作创始人。Fourthought 公司是一家软件供应商和咨询公司,致力于企业知识管理的 XML 解决方案。Fourthought 公司开发了 4Suite,这是一个用于 XML、RDF 和知识管理应用程序的开放源码平台。Ogbuji 先生还是 Versa RDF 查询语言的首席开发人员。Ogbuji 先生是一名计算机工程师和作家。他出生在尼日利亚,现在美国科罗拉多州的波德市工作和生活。可以通过他的博客 Copia 找到有关他的更多信息,还可以通过 [email protected] 与他联系。 |