PEP333翻译

转自:http://angeloce.iteye.com/blog/519286

英文原文:http://www.python.org/dev/peps/pep-0333/


摘要

       为促进python的web应用在各类web服务器上的移植, 本文档指定了web服务器和python的web框架间的标准接口.

原理和目标

       python目前拥有了大量的web框架,例如Zope, Quixote, Webware, SkunkWeb, PSO, Twisted Web.(想看更多点击http://wiki.python.org/moin/WebFrameworks .). 从如此多的框架中选择对刚刚进入python的新人来说是个问题,一般来说, 选择一个web框架限制了对web服务器的选择,反过来也一样.相比之下,尽管 Java也有众多的web框架, Java的 servlet  API使得程序员可以使用任何Java web框架编写在能运行在任何web服务器上的程序,只要他们都支持servlet API。

概述

        WSGI接口有两个方面, server 或 gateway 服务端,  以及 application 或 framework 应用端. 服务端会调用一个应用端的可调用对象, 如何传递可调用对象的细节由服务端决定.有可能一些服务器要求应用程序的开发者写一个脚本来创建服务器/网关的一个实例来获得应用程序;另一些服务器可能使用配置文件或者其他的机制来制定哪里有一个需要被import/ obtain 的应用程序.

              除了这些服务器,网管,应用程序和框架外,也可以创建了实现WSGI随意一侧接口的中间件组件.这些组件从服务器看就像应用,从应用程序看就像服务器,利用这样的组建可以提供更多的接口, 内容转换, 导航等等其他有用的功能。

             在整个概述中,我们使用术语"callable"来表示一个函数,方法, 类或者一个实现了__call__方法的实例.可调用对象的实现要根据服务端和应用端的需求来选择合适的方法.相反, 服务端和应用端调用这个可调用对象的时候并不用去关心到底是谁提供给它的, 仅仅是调用它们而已, 而不会对它们内省.

应用端

             应用程序对象是一个需要接受两个参数的可调用对象. 这个术语"object(对象)"不应该被误解为需要一个对象实例: 函数,方法, 类, 实现了__call__方法的实例都可以作为一个应用程序对象来使用.应用程序对象必须能够被多次调用, 几乎所有的服务器/网关(除了CGI)都会制造重复请求.(注意: 尽管我们一直说应用程序对象,  这不应该被理解为, 假设应用开发者使用已存在的高等web框架服务来开发他们的应用, 就必须使用WSGI作为web编程的API!WSGI对于框架和服务器开发者仅仅是一个工具,并没想要直接支持应用开发人员.)这有两个例子,一个是函数,另一个是类:

python代码:

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']
class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type','text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

服务端

              服务器和网关在得到每一个http客户端的请求后都会调用一次可调用的应用对象, 请求直接用于应用.为了说明,这有一个简单的CGI网关,实现了一个获得了应用对象的函数.注意这个简单的例子已经限制了错误处理, 因为默认情况下一个未被捕捉到的异常会被发送到sys.stderr并在web服务器上记录下来.

import os, sys

def run_with_cgi(application):

    environ = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1,0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']    = True

    if environ.get('HTTPS','off') in ('on','1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             sys.stdout.write('Status: %s\r\n' % status)
             for header in response_headers:
                 sys.stdout.write('%s: %s\r\n' % header)
             sys.stdout.write('\r\n')

        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status,response_headers,exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status,response_headers]
        return write

    result = application(environ, start_response)
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result,'close'):
            result.close()

中间件

              请注意一个单独的对象可能对于应用端扮演服务器的角色,对于服务端又扮演应用程序的角色. 这样的中间件可以实现这件功能:

              1. 根据目标地址将请求发送给不同的应用程序对象,并在之后重写环境变量.

              2. Allowing multiple applications or frameworks to run side-by-side in the same process 

                  Load balancing and remote processing, by forwarding requests and responses over a network

                  Perform content postprocessing, such as applying XSL stylesheets

from piglatin import piglatin

class LatinIter:

    """Transform iterated output to piglatin, if it's okay to do so

    Note that the "okayness" can change until the application yields
    its first non-empty string, so 'transform_ok' has to be a mutable
    truth value."""

    def __init__(self,result,transform_ok):
        if hasattr(result,'close'):
            self.close = result.close
        self._next = iter(result).next
        self.transform_ok = transform_ok

    def __iter__(self):
        return self

    def next(self):
        if self.transform_ok:
            return piglatin(self._next())
        else:
            return self._next()

class Latinator:

    # by default, don't transform output
    transform = False

    def __init__(self, application):
        self.application = application

    def __call__(self, environ, start_response):

        transform_ok = []

        def start_latin(status,response_headers,exc_info=None):

            # Reset ok flag, in case this is a repeat call
            transform_ok[:]=[]

            for name,value in response_headers:
                if name.lower()=='content-type' and value=='text/plain':
                    transform_ok.append(True)
                    # Strip content-length if present, else it'll be wrong
                    response_headers = [(name,value)
                        for name,value in response_headers
                            if name.lower()<>'content-length'
                    ]
                    break

            write = start_response(status,response_headers,exc_info)

            if transform_ok:
                def write_latin(data):
                    write(piglatin(data))
                return write_latin
            else:
                return write

        return LatinIter(self.application(environ,start_latin),transform_ok)


# Run foo_app under a Latinator's control, using the example CGI gateway
from foo_app import foo_app
run_with_cgi(Latinator(foo_app))

详细说明

              应用程序对象必须接受两个位置参数.为了更好的解释,我们叫他们一个为environ一个为start_response( = res),但他们并不要求一定是这个名字.服务端必须使用位置参数(而不是关键字参数)来调用应用程序对象.例如得调用 result = application(environ, start_response)

              environ是一个字典, 包括了CGI环境变量.environ必须是 python内建的字典对象dict(不是其子类, 用户字典或其他模拟的字典), 应用程序可以以任何方式改变environ. environ必须包含来一些WSGI必需的变量(在下一章节说明),和服务器专有的变量, 按照惯例命名方式见下文

            res参数是一个包含了两个位置参数,一个可选参数的可调用对象. 为了说明, 我们叫这三个参数分别为 status, response_headers 和 exc_info, 当然他们也不是必需使用这个名字.应用程序必须使用位置参数来调用他们.

                status参数是一个类似"999 Message here"格式的状态字符串, reponse_headers是一个包含描述HTTP响应头的(header_name, header_value)元祖的列表, 可选参数exc_info 仅仅被使用在应用程序捕获到错误并试图把错误信息显示到浏览器的时候.

                res对象必须返回一个接受一个位参(一个写成HTTP响应体的字符串)的可写可调用对象.(注意: write()现在仅提供给一些已存在的框架来支持其需要的输出接口.在编写新的应用或框架时如果可以避免就不要使用它.更多细节可参考Buffering and Streaming 部分)

               当服务端调用时, 应用程序对象必须返回一个可迭代的对象以生成零到多个字符串.这可以以多种方式实现,比如返回一个字符串列表,或者制作一个生成器函数来生成字符串(yield), 或者返回一个可迭代的实例.不论如何实现,应用程序对象必须返回一个可迭代输出零到多个字符串的迭代器.

               服务端必须把生成的字符串以无缓冲的方式传递给客户端, 在另一个请求到来前完成每个字符串的传输.参见下面的Buffering and Streaming章节了解关于应用程序如何处理输出.

               服务端应该将这些字符串视为二进制字节序列,尤其要确保行结束符没有改动.应用程序有责任确定字符串格式适用于客户端.(服务端可能会队HTTP传输编码,或者为了实现服务器特性执行其他转换例如字节范围内传输.更多细节参见下面Other HTTP Features章节.)

              如果对于返回的迭代器求长(len)成功了,服务端应该相信这个结果是精确的.就是说,如果从应用程序返回的迭代器提供了__len__()方法, 它肯定会返回一个精确地结果.(参看如何处理Content-Length Header章节会知道要使用这个结果.)

              如果迭代器还提供了close()方法,服务端必须在当前请求完成时调用该方法,无论该请求是正常的结束了还是由于错误提早终止了.(这是为了能释放应用程序资源. 该协议试图完成PEP325的生成器支持, 和其他平常的带有close()f方法的迭代器.)

              注意: 应用程序必须在该迭代器生成第一个字符串前调用start_response对象, 以使得服务端能在发送任何具体内容前发送头内容.然而, 这个调用可以再迭代器首次迭代前被执行, 所以服务端必须假定start_response()在迭代前被调用了.

              最后, 服务端不应该使用迭代器的任何其他属性, 除非它对服务端是一个专门制定的实例,例如由wsgi.file_wrapper返回的"file_wrapper"(请看可选的特定平台文件处理Platform-Specific File Handling). 一般情况下, 只是用这里指定的属性, 或者是PEP234规定的迭代器接口.


environ变量

              environ字典需要包含这些CGI环境变量, 作为对通用接口规范的定义.下面这些变量必须存在在字典中, 除非它们的值是空字符串, 它们被忽略掉.

              REQUEST_METHOD HTTP请求方法,诸如 GET 或 POST.不能写成空字符串,当然也是必需的.

              SCRIPT_NAME 相对于应用对象请求的URL地址的初始部分, 以使得应用程序知道请求的虚拟地址.这个可以是空字符串, 如果应用位置在服务端的根目录地址.

              PATH_INFO 请求URL地址的剩余部分, 指示请求应用目标的虚拟位置. 这个可以是空字符串, 如果请求目标位应用的根目录并且结尾没有反斜杠.

              QUERY_STRING 在"?"后面的请求URL部分.可以为空或者省略掉.

              CONTENT_TYPEHTTP请求中所有Content-Type字段的内容. 可以为空或省略.

              CONTENT_LENGTH HTTP请求中所有Content-Length 字段的内容. 可以为空或省略.

              SERVER_NAME, SERVER_PORT在连接SCRIPT_NAME 和PATH_INFO时,这两个变量可以用来完成整个URL. 请注意, 如果HTTP_HOST存在, 应该优先于使用它而不是SERVER_NAME来重建URL. 更多细节请参见下面的 URL Reconstruction 章节. 这两个变量都不能为空, 当然也是必需的.

             SERVER_PROTOCOL客户端发送请求使用的协议版本. 通常这是些类似 "HTTP/1.0" 或 "HTTP/1.1"的东西, 可能会使应用程序来决定如果处理每个HTTP请求头.(这个变量或许应该叫做REQUEST_PROTOCOL, 因为它指的是请求的协议, 不一定代表服务端响应的协议. 然而为了兼容CGI我们必须保留这个名字.)

             HTTP_ Variables与客户端支持的HTTP请求头一致的变量.(也就是以"HTTP_"开头命名的变量.)这些变量是否出现都要与HTTP请求头中的变量保持一致.

            

服务端应该尝试提供其他合适的CGI变量.如果使用了SSL, 也应该提供合适的Apache SSL环境变量, 例如 HTTPS=on 和 SSL_PROTOCOL. 然而请注意, 使用了除上述之外其他CGI两边的应用程序对于不支持相关扩展的web服务器来说是不可移植过来的.(例如, 一个不能发布文件的web服务器不可能提供有意义的DOCUMENT_ROOT 或 PATH_TRANSLATED 变量.)

 

一个遵守WSGI协议的服务端应该记录它会提供什么变量. 应用程序应该检查它需要的变量是否都存在, 如果缺少了某个变量应该有 后备的 方法.

 

注意: 缺失的变量(例如没有发生认证的REMOTE_USER变量)应该从字典中忽略掉. 而且要注意 CGI定义的变量如果存在就一定是个字符串. 它违反了 CGI变量可以是任何类型(不一定要是字符串)的规定.

          除了CGI定义的变量外, environ字典可以包括操作系统的环境变量, 但必须包括下列WSGI定义的变量:

          wsgi.version元祖 (1,0), 表示WSGI的版本是1.0

          wsgi.url_scheme表示哪个应用会被调用在URL的 "scheme"部分的字符串.通常,  应该是"http" 或"https".

          wsgi.input一个HTTP请求体能读出的输入流(类文件对象).(服务端可能等待应用程序请求再执行读出, 或者提前读出并缓存在内存或硬盘上, 或使用其它技术来提供类似的输入流.)

          wsgi.errors

一个输出流(类文件对象), 错误输出可以使用写人到 记录程序或者其它标准和可能的中央位置错误.(-_-#).这应该是个文本模式流; 例如, 应用程序应该使用"\n"作为行结尾, 并且可以假设它会被服务端转换为正确的行结尾.

 

对于大多数服务器, wsgi.errors是服务器主要的异常日志. 或者可以是sys.stderr或其他种类的log文件. 服务器文档应该包括如何配置这个或在哪里找到记录输出的说明.如果需要, 服务端应该提供不同的异常流到不同的应用中.

          wsgi.multithread如果应用程序可以同时被相同进程的不同现成调用, 这个值应该写为True, 否则写为False.

          wsgi.multiprocess如果同一个应用程序可以同时被不同进程调用, 写为True, 否则写为False.

          wsgi.run_once如果服务端期望(但不保证)应用程序在该进程期内仅被调用一次, 该值写为True.通常,对于建在CGI(或类似)的网关该值总为True.

          最后, environ字典可以包含某些服务器自定义的变量. 这些变量应该使用lower-case形式命名,包括字符,数字,小数点和下划线,并且应该使用能唯一定义服务端的名字作为前缀.例如,mod_python可以定义一个像 mod_python.some_variable名字的变量.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章