posts - 8,  comments - 5,  trackbacks - 0

学习有关构建具有最大灵活性的 Web 应用程序的 Python 标准

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项


拓展 Tomcat 应用

下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1


级别: 初级

Uche Ogbuji (uche@ogbuji.net), 首席顾问, Fourthougth, Inc.

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 说明了这个结构。


图 1. HTTP 请求 — 响应如何通过 WSGI 堆栈
WSGI 堆栈

Web 服务器(也称为网关)是与请求客户机(通常是用户的浏览器)进行基本通信的非常低级的代码。应用层处理较高级别的细节,解释来自用户的请求并准备响应的内容。WSGI 本身的应用程序接口通常只是应用程序框架的更高层的最基本层,为通用的 Web 模式(比如 Ajax 技术或者内容模板系统)提供友好的设施。在服务器或者网关层之上是 WSGI 中间件。这个重要的层包含在服务器和应用程序实现中可以共享的组件。通用 Web 特性(比如用户会话、错误处理和身份验证)可以作为 WSGI 中间件来实现。

中间的代码

WSGI 中间件对于可重用组件是最自然的层。WSGI 中间件看上去对于应用程序是较低层,而对于服务器是较高层。为了添加一些特殊的特性,它监视请求、响应和 WSGI 环境的状态。遗憾的是,WSGI 规范提供了非常差的中间件例子,而且可以找到的大多数其他例子也很简单,只能提供如何快速编写中间件的感性认识。我将使用以下概述给出对 WSGI 中间件承担的过程的感性认识。这里忽略了大多数 WSGI 中间件作者都不必担心的问题。在 Python 中,我使用单词函数,意思是任何可调用的对象。

  • 设置阶段。每当 Web 服务器启动时,设置阶段就发生了。它接受一个中间件实例,其中包括应用程序函数。
  • 处理客户机请求。每当 Web 服务器收到请求时,就会处理客户机请求。
    1. 服务器用这个环境和 server.start_response 参数调用中间件函数。
    2. 中间件处理这个环境并调用可调用的应用程序,传递这个环境和包装的函数 middleware.start_response
    3. 应用程序执行;首先它准备响应报头,然后调用 middleware.start_response
    4. 中间件处理响应报头并调用 server.start_response
    5. 服务器将控制传递回中间件,然后回到开始产生响应体块(比如字符串)的应用程序。
    6. 对于每一个响应,体块中间件做出任何修改并向服务器传递一些相应的字符串。
    7. 一旦处理完应用程序的所有块,中间件就向服务器返回控制,完成当前的请求。




回页首


处理 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 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
  <head>
    <title>Virtual Library</title>
  </head>
  <body>
    <p>Moved to <a href="http://vlib.org/">vlib.org</a>.</p>
  </body>
</html>



如果运行 清单 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 <filename> 并检查给定的文件名。





回页首


结束语

现在您学习了 Python 的 WSGI 以及如何使用它实现可以插入任何 WSGI 服务器和应用程序链的中间件服务。很容易将本文的中间件例子链到进行缓存或者调试的中间件。所有这些都成为组件,因此可以快速地向项目中添加经过反复测试的特性,而不必考虑选择的 WSGI 实现是什么。

WSGI 是一个相当新的规范,但是兼容的服务器、中间件和工具很快出现,使得 Python Web 框架的前景非常光明。下一次您在 Python 中开发重大的 Web 项目时,一定要通过使用现有的 WSGI 组件来采用 WSGI,还可以考虑为自己或者为您的 Web 开发伙伴创建 WSGI 组件。

posted on 2007-03-07 09:39 Steven.L 阅读(373) 评论(0)  编辑 收藏 引用 所属分类: python
只有注册用户登录后才能发表评论。
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(1)

随笔分类(12)

随笔档案(8)

文章档案(1)

最新随笔

搜索

  •  

最新评论

阅读排行榜

评论排行榜