器→工具, 编程语言

PEP 333:Python Web服务器Gateway接口 v1.0

钱魏Way · · 535 次浏览

对于Web系统来说,客户端一般就是浏览器,客户端与服务器之间使用HTTP协议通讯。 在Python的Web开发中,服务器与Python Web应用之间交互的协议就是WSGI。它由PEP333提出, 并在PEP3333中做了补充。如果你也想阅读 WSGI 相关的 PEP 规范,可以直接阅读 PEP 3333,因为 PEP 3333 对 PEP 333 是向下兼容的。这里先翻译了PEP 333,后面还会翻译PEP 3333。

前言 Preface

注意:有关支持Python 3.x的此规范的更新版本,包含社区勘误,补充和说明,请参阅PEP 3333 。

简介 Abstract

本文档描述一份在Web服务器与Web应用/Web框架之间的标准接口,此接口的目的是使得web应用在不同web服务器之间具有可移植性。

基本原理与目标 Rationale and Goals

Python目前拥有大量的Web框架,比如 Zope, Quixote, Webware, SkunkWeb, PSO, 和Twisted Web等。大量的选择使得新手无所适从,因为总得来说,框架的选择都会限制Web服务器的选择,反之亦然。

相比之下,虽然JAVA也拥有许多Web框架,但是JAVA的”servlet” API使得使用任何Java Web应用程序框架编写出来的应用程序可以在任何支持”servlet” API的Web服务器上运行。服务器中这种针对Python的API的使用和普及,无论这些服务器是用Python编写的(例如Medusa)、嵌入Python(例如mod_python)还是通过gateway协议(例如CGI,FastCGI等等)调用Python,将分离人们对Web框架和对Web服务器的选择,用户可以自由选择适合他们的组合,而Web服务器和Web框架的开发者也能够把精力集中到各自偏好的专业领域。

因此,这份PEP建议在Web服务器和Web应用/Web框架之间建立一种简单的通用的接口规范,Python Web Server Gateway Interface (WSGI)。但是,仅仅存在这么一份规范并不能改变Web服务器和Web应用/框架的现状。只有Web服务器和Web框架的作者们真正实现WSGI才能产生效果。然而,既然还没有任何框架或服务器实现了WSGI,对实现WSGI也没有什么直接的奖励,那么WSGI必须容易实现,这样才能降低作者的初始投资。因此,服务器和框架两边接口的实现的简单性,对于WSGI的作用来说,绝对是非常重要的。所以这一点是任何设计决策的首要依据。

对于框架作者来说,实现的简单和使用的方便是不一样的。WSGI为框架作者提供一套绝对没有”frills”的接口,因为像response对象和对cookie的处理这些问题和框架现有的对这些问题的处理是矛盾的。再次重申,WSGI的目的是使得Web框架和Web服务器之间轻松互连,而不是创建一套新的Web框架。

还要注意这个目标使得WSGI不能依赖任何在当前已部署版本的Python没有提供的任何功能,因此,也不会依赖于任何新的标准模块,并且WSGI也不要求Python版本大于2.2.2。(当然,将来的Python版本会在标准库提供的Web服务器中包含对此接口的支持也是个不错的主意。)

除了要让现有的或将要出现的框架和服务器容易实现,也应该容易创建请求预处理器、响应处理程序和其他基于WSGI的中间件组件,对于服务器来说他们是应用程序,而对于他们包含的应用程序来说他们是服务器。

如果中间件既简单又健壮,而且在服务器和框架中WSGI被广泛实现,那么就有可能出现全新的Python Web框架:整个框架都是由几个松散耦合的WSGI中间件组件组成。甚至现有框架的作者都会选择将已实现的服务以这种方式重构,变得更像一些和WSGI配合使用的库而不是一个独立的框架。这样,应用程序开发人员就可以选择具有特定功能的“最佳组件”,而不必承担单一框架的所有优点和缺点。

当然,在写这篇文章的时候,这一天无疑是相当遥远的。与此同时,WSGI使任何服务器都可以使用任何框架是一个足够的短期目标。

最后,需要指出的是当前版本的WSGI并没有规定一个应用具体以何种方式部署在Web服务器或gateway上。目前,这个需要由服务器或gateway的具体实现来定义。如果足够多实现了WSGI的服务器或gateway通过领域实践产生了这个需求,也许可以产生另一份PEP来描述WSGI服务器和应用框架的部署标准。

概述 Specification Overview

WSGI接口有两种形式:一个是针对服务器或gateway的,另一个针对应用程序或框架。服务器接口请求一个由应用接口提供的可调用的对象,至于该对象是如何被请求的取决与服务器或gateway。我们假定一些服务器或gateway会需要应用程序的部署人员编写一个简短的脚本来启动一个服务器或gateway的实例,并把应用程序对象提供的服务器,而其他的服务器或gateway需要配置文件或其他机制来指定从哪里导入或者或得应用程序对象。

除了纯粹的服务器/gateway和应用程序/框架,还可以创建实现了这份规格说明书的中间件组件,对于包含他们的服务器他们是应用程序,而对于他们包含的应用程序来说他们是服务器,他们可以用来提供可扩展的API,内容转换,导航和其他有用的功能。

在整个说明书中,我们将使用术语“可调用”来表示“函数,方法,类或带有__call__方法的实例”。 实现可调用的服务器,gateway或应用程序根据需要选择合适的实现技术。相反,调用可调用对象的服务器,gateway或应用程序必须不依赖于提供给它的可调用对象类型。

应用程序/框架端 The Application/Framework Side

一个应用程序对象是一个简单的接受两个参数的可调用对象,这里的对象并不是真的需要一个对象实例,一个函数、方法、类、或者带有 __call__ 方法的对象实例都可以用来做应用程序对象。应用程序对象必须可以多次被请求,实际上服务器/gateway(而非CGI)确实会产生这样的重复请求。

注意:虽然我们把他叫做”应用程序”对象,但并不是说程序员要把WSGI当作API来调用,我们假定应用程序开发者仍然使用更高层面上的框架服务来开发应用程序,WSGI是提供给框架和服务器开发者使用的工具,并不打算直接对应用程序开发者提供支持。

这里有两个应用程序对象的示例,一个是函数,另一个是类:

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"

服务器/gateway接口 The Server/Gateway Side

服务器或gateway调用可调用的应用程序,对于从HTTP客户端接收的每个请求,该应用程序都是针对应用程序的。为了说明,这里是一个简单的CGIgateway,作为一个应用程序对象的函数实现。请注意,这个简单的例子有限的错误处理,因为默认情况下,未捕获的异常将被转储到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()

中间件: 同时扮演两种角色的组件 Middleware: Components that Play Both Sides

注意到单个对象可以作为请求应用程序的服务器存在,也可以作为被服务器调用的应用程序存在。这样的中间件可以执行这样一些功能:

  • 重写前面提到的 environ 之后,可以根据目标URL将请求传递到不同的应用程序对象
  • 允许多个应用程序和框架在同一个进程中运行
  • 通过在网络传递请求和响应,实现负载均衡和远程处理
  • 对内容进行后加工,比如附加xsl样式表

一般来说,中间件的存在对于服务器接口和应用接口来说都应该是透明的,并且不需要特别的支持。希望在应用程序中加入中间件的用户只需简单得把中间件当作应用提供给服务器,并配置中间件足见以服务器的身份来请求应用程序。当然,中间件组件包裹的可能是包裹应用程序的另一个中间件组件,这样循环下去就构成了我们称为”中间件堆栈”的东西了。

中间件大部分要符合应用接口和服务器接口提出的一些限制和要求,有些时候这样的限制甚至比纯粹的服务器或应用程序还要严格,这些地方我们会特别指出。

这里有一个中间件组件的例子,他用Joe Strout的piglatin.py将text/plain的响应转换成pig latin(注意:真正的中间件应该使用更加健壮的方式——应该检查内容的类型和内容的编码,同样这个简单的例子还忽略了一个单词可能被分割到一个块边界的可能性)。

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
            del 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))

规格的详细说明 Specification Details

应用程序对象必须接受两个参数,为了方便说明我们不妨分别命名为 environ 和 start_response ,但并非必须取这个名字。服务器或gateway必须用这两个参数请求应用程序对象(比如像上面展示的,这样调用 result = application(environ,start_response))

参数 environ 是个字典对象,包含CGI风格的环境变量。这个对象必须是一个Python内建的字典对象(不能是子类、UserDict或其他对字典对象的模仿),应用程序可以以任何他愿意的方式修改这个字典,environ 还应该包含一些特定的WSGI需要的变量(在后面的节里会描述),还可以包含一些服务器特定的扩展变量,按照下面将要描述的约定进行命名。

start_response 参数是一个接受两个必须参数和一个可选参数的可调用。方便说明,我们分别把他们命名为 status, response_headers ,和 exc_info 。应用程序必须用这些参数来请求可调用 start_response (比如象这样 start_response(status,response_headers))

参数 status 是一个形式像”999 Message here”的状态字符串。而response_headers是描述HTTP响应头的(header_name,header_value)元组列表。可选的exc_info 参数会在下面的start_response() 可调用和 Error Handling 两节中描述,他只有在应用程序产生了错误并希望在浏览器上显示错误的时候才有用。

start_response 可调用必须返回一个 write(body_data) 可调用,它接受一个可选参数:一个将要被作为HTTP响应体的一部分输出的字符串(注意:提供可调用write()只是为了支持现有框架的必要的输出API,新的应用程序或框架尽量避免使用,详细情况请看Buffering and Streaming一节。)

当被服务器请求的时候,应用程序对象必须返回一个0或多个可迭代的字符串,这可以通过多种方法完成,比如返回一个字符串的列表,或者应用程序本身是一个生产字符串的函数,或者应用程序是一个类而他的实例是可迭代的,不管怎么完成,应用程序对象必须总是返回0或多个可迭代的字符串。

服务器必须将产生的字符串以一种无缓冲的方式传送到客户端,每次传完一个字符串再去获取下一个。(换句话说,应用程序应该实现自己的缓冲,更多关于应用程序输出必须如何处理的细节请阅读下面的Buffering and Streaming一节。)

服务器或gateway应该把产生的字符串当字节流对待:特别是应该确保行结束不被改变。应用程序负责确保字符串是以与客户端匹配的编码输出(服务器/gateway可能会附加HTTP传送编码,或者为了实现一些HTTP的特性而进行一些转换比如byte-range transmission,更多细节请看下面的Other HTTP Features)

如果调len(iterable)成功,服务器将认为结果是正确的。也就是说,应用程序返回的可迭代的字符串提供了一个有用的__len__()方法,那么肯定返回了正确的结果(关于这个方法正常情况下如何被使用的请阅读 Handling the Content-Length Header )

如果应用程序返回的可迭代者有close()方法,则不管该请求是正常结束还是由于错误而终止,服务器/gateway都必须在结束该请求之前调用这个方法,(这是用来支持应用程序对资源的释放,该协议旨在补充PEP 325的生成器支持,以及其他常用的close()方法。

(注意:应用程序必须在可迭代者产生第一个字符串之间请求start_response()可调用,这样服务器才能在发送任何主体内容之前发送响应头,然而这一步也可以在可迭代者第一次迭代的时候执行,所以服务器不能假定开始迭代之前 start_response() 已经被调用过了)

最后,服务器或gateway不能应用程序返回的可迭代者的任何其他属性,除非是针对服务器或gateway特定类型的实例,比如wsgi.file_wrapper返回的“file wrapper”(阅读 Optional Platform-Specific File Handling )。通常情况下,只有在这里指定的属性,或者通过PEP 234 迭代API访问的属性才是可以接受的。

environ变量 environ Variables

environ 字典被用来包含这些在Common Gateway Interface specification中定义了的CGI环境变量。下面这些变量必须存在, 除非其值是空字符串,这种情况下如果下面没有特别指出的话他们可能会被忽略。

  • REQUEST_METHOD:HTTP请求的方式, 比如 “GET” 或者 “POST”。这个不可能是空字符串并且也是必须给出的。
  • SCRIPT_NAME:请求URL中路径的开始部分,对应应用程序对象,这样应用程序就知道它的虚拟位置。如果该应用程序对应服务器的“根”的话, 它可能是为空字符串。
  • PATH_INFO:请求URL中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置。如果请求的目标是应用程序跟并且没有trailing slash的话,可能为空字符串 。
  • QUERY_STRING:请求URL中跟在”?”后面的那部分,可能为空或不存在。
  • CONTENT_TYPE:HTTP请求中任何 Content-Type 域的内容。
  • CONTENT_LENGTH:HTTP请求中任何 Content-Length 域的内容。可能为空或不存在。
  • SERVER_NAME, SERVER_PORT:这些变量可以和SCRIPT_NAME、PATH_INFO一起组成完整的URL。然而要注意的是,重建请求URL的时候应该优先使用HTTP_HOST 而非SERVER_NAME 。详细内容请阅读下面的 URL Reconstruction 。 SERVER_NAME 和 SERVER_PORT 永远是空字符串,也总是必须存在的。
  • SERVER_PROTOCOL:客户端发送请求所使用协议的版本。通常是类似 “HTTP/1.0” 或 “HTTP/1.1” 的东西可以被用来判断如何处理请求headers。(既然这个变量表示的是请求中使用的协议,而且和服务器响应时使用的协议无关,也许它应该被叫做 REQUEST_PROTOCOL 。然后,为了保持和CGI的兼容性,我们还是使用已有的名字。)
  • HTTP_Variables:对应客户端提供的HTTP请求headers (也就是说名字以 “HTTP_” 开头的变量)。这些变量的存在与否应该和请求中的合适的HTTP header一致。

服务器或gateway 应该尽可能提供其他可用的CGI变量。另外,如果用了SSL,服务器或gateway也应该尽可能提供可用的Apache SSL环境变量,比如 HTTPS=on和SSL_PROTOCOL。不过要注意,使用了任何上面没有列出的变量的应用程序对不支持相关扩展的服务器来说就有点necessarily non-portable。(比如,不发布文件的web服务器就不能提供一个有意义的DOCUMENT_ROOT 或 PATH_TRANSLATED 。)

一个符合WSGI的服务器或gateway 应该在描述它们自己的同时说明它们可以提供些什么变量应用程序应该对所有他们需要的变量的存在性进行检查,并且在某变量不存在的时候有备用的措施。

注意: 不需要的变量 (比如在不需要验证的情况下的 REMOTE_USER ) 应该被移出 environ字典。同样注意CGI定义的变量如果存在的话必须是字符串。任何 str 类型以外的CGI变量的存在都是对本规范的违反。

除了CGI定义的变量, environ 字典也可以包含任意操作系统的环境变量,并且必须包含下面这些WSGI定义的变量:

变量
wsgi.version 元组(1,0)表示WSGI版本1.0
wsgi.url_scheme 表示正在调用应用程序的URL的“方案”部分的字符串。 通常情况下,这将有适当的值“http”或“https” 。
wsgi.input 一个输入流(文件类对象),HTTP请求体可以从中读取。 (服务器或gateway可以根据应用程序的请求执行按需读取,或者可以预先读取客户端的请求主体并将其缓存在内存中或磁盘上,或者使用任何其他技术来提供这样的输入流根据自己的喜好)。
wsgi.errors 一个输出流(类文件对象),为了在一个标准化的,可能集中的位置记录程序或其他错误,可以写入错误输出。 这应该是一个“文本模式”流,即应用程序应该使用“ n”作为行尾,并假定它将被转换为由服务器/gateway结束的正确行。

对于许多服务器,wsgi.errors将是服务器的主要错误日志。或者,这可能是sys.stderr,或某种类型的日志文件。 服务器的文档应该包括如何配置这个或在哪里找到记录输出的解释。 如果需要,服务器或gateway可以向不同的应用程序提供不同的错误流。

wsgi.multithread 如果应用程序对象可能被同一进程中的另一个线程同时调用,则此值应为真,否则应为false。
wsgi.multiprocess 如果一个等价的应用程序对象可能被另一个进程同时调用,则此值应为真,否则应为false。
wsgi.run_once 如果服务器或gateway希望(但不能保证!)该应用程序在其包含的进程的生命周期中只被调用一次,则该值应该为真。 通常情况下,对于基于CGI(或类似的)的gateway来说,这只会是真的。

最后 environ 字典也可以包含服务器定义的变量。这些变量的名字必须是小写字母、数字、点和下划线,并且应该带一个能唯一代表服务器或gateway的前缀。例如,mod_python可能会定义名称为mod_python.some_variable的变量。

输入和错误流 input and Error Streams

服务器提供的输入输出流必须提供以下的方法:

Method Stream Notes
read(size) input 1
readline() input 1, 2
readlines(hint) input 1, 3
__iter__() input  
flush() errors 4
write(str) errors  
writelines(seq) errors  

以上方法语义同Python Library Reference相同,处理下面列表指出的注意点之外。

  • 服务器不一定需要读取客户端指定的Content-length, 它可以模拟一个流结束(end-of-file)的条件,如果应用程序试图去读取它的话。另外应用程序不应该去尝试读取比指定Content-length长度更多的数据。
  • 可选参数size是不支持用于readline()方法中的。因为它有可能是服务器程序作者复杂的实现,而且它不怎么用的上。
  • 注意这个用户readlines(hint)中的参数hint,对于调用方和实现方都是可选的。应用程序可以自由选择是否支持,服务器也可以自由选择是否忽略它。
  • 由于错误流可能不能回转(rewound),服务器和gateway可以立即自由的不需要缓存地向前写(forward write)。在这种情况下flush()方法可能是个空操作(no-op)。然后可移植的程序不能假定这个output是无缓冲的或者flush是空操作。在需要确保数据流被真正写入的情况下,他们大多都会调用flush()。(例如:从多个进程中写入同一个日志文件的时候,可以做到最小化的数据交织)

上表列出的方法,每个服务器都必须符合此规范。应用程序方也需符合此规范,不要用其他的其他不同方法或属性的input 和 errors对象。尤其,应用程序端不要试图去关闭这些流,即使他们有close()方法。

start_response()可调用 The start_response() Callable

传递给应用程序的第二个参数是一个可调用的形式,start_response(status, reponse_headers, exc_info=None)。(同所有的WSGI调用,参数位置必须是通过位置对应,而不是用key来对应)。

start_response调用被用来开始HTTP响应,并且必须返回一个 write(body_data) callable(参考下面的buffering and streaming节)

status参数是HTTP的’status’字符,如”200 OK”, “404 Not Found”。也就是说,它是一个由状态码和理由词组组成的字符串,由一个空格分开,没有周围的空格或其他字符。 (有关更多信息,请参见RFC 2616第6.1.1节。)字符串不能包含控制字符,也不能以回车,换行符等其他组合结束的符号。

response_headers参数是(header_name,header_value)元组的列表。 它必须是一个Python列表,即类型(response_headers)是ListType ,并且服务器可以以其期望的任何方式改变其内容。每个header_name必须是一个有效的HTTP标题字段名称(由RFC 2616 ,第4.2节定义),没有尾部冒号或其他标点符号。

所有的header_value不能包含任何控制字符,包括回车和换行。(这样的要求是为了方便那些必须检查相应头的服务器,gateway,中间件,使他们所必须的解析工作复杂度降到最低)

一般来说,服务器或gateway负责确保正确的头信息发送到客户端,如果应用程序(application)遗漏了必要的头信息(或其他相关的规范信息),服务器或gateway须补上。比如:HTTP date:和Server:头信息通常是由服务器或gateway提供。

(一个要提醒给服务器/gateway作者的事情: HTTP 头名称是区分大小写的,所以在检查application提供的头信息时一定要考虑大小写的问题)

应用程序和中间件禁止使用HTTP/1.1的’hop-by-hop’特性或头信息,任何在HTTP/1.0中等价或类似的特性或头信息,都会影响到客户端和服务器的持久的连接。这些功能是实际web服务器的独占区域,服务器或gateway应将应用程序尝试发送这些功能视为致命错误,并在将它们提供给start_response()时引发错误。(为了解更多’hop-by-hop’的细节和特性,请参阅下面的Other HTTP Features段)。

该start_response调用不能实际传输响应头。相反,它必须将它们存储在服务器或gateway,只有在产生一个非空字符串的应用程序返回值的第一个循环之后发送,或者在应用程序的第一个的调用write()方法调用的时候。换句话说,response头不能被发送在没有实际的body数据前,或者是应用程序返回的迭代器终止前。(唯一的例外就是response头信息里明确包含了Content-Length为0。)

响应头传输的延迟这是为了确保有缓存或异步的应用程序能用出错信息替换掉原来可能要发送的数据,直到最后一刻。例如,应用程序可能需要从“200 OK”响应状态改变为“500内部错误”,如果当正在应用缓冲器内产生的body发生错误。

exc_info参数是可选的,如果提供的话,必须是一个Python sys.exc_info()元组。此参数只有在start_response请求错误handler才要求被提供。start_response应与新提供的那些替换当前存储的HTTP响应头,从而使得当发生错误有关输出应用程序“改变主意”。

然而,如果exc_info被提供,并且HTTP头已经被发送,start_response必须抛出错误,并且抛出exc_info tuple,也就是下面的

raise exc_info[0], exc_info[1], exc_info[2]

抛出的异常会被应用程序重新捕获到,原则上应到终止应用程序。(应用程序会尝试发送error output到浏览器,一旦HTTP headers被发送,这样是不安全的)应用程序必须不捕获任何来自于start_response调用的异常,如果调用start_response用来exc_info参数,替代的做法应该允许这样的异常传回给服务器/gateway。更多信息见下面的Error Handing。

应用程序可能多次调用start_response当且仅当exc_info参数提供的时候。更确切的说,如果start_response已经被当且应用程序调用过后,再次调用没有exc_info参数的start_response是个很致命的错误。(参考上面CGI gateway示例,其中说明了正确的逻辑)

注意:服务器/gateway/中间件实现start_response应当确保exc_info没有持续指向任何引用,当start_response方法调用完成之后。为了避免通过traceback和frames involved创建了回环的引用,最简单的例子如下:

def start_response(status, response_headers, exc_info=None):
    if exc_info:
         try:
             # do stuff w/exc_info here
         finally:
             exc_info = None    # Avoid circular ref.

示例CGIgateway例子提供了这种技术的另一个说明。

处理 Content-Length头信息 Handling the Content-Length Header

如果应用程序没有提供Content-Length头,服务器/gateway可以选择几种方式之一来处理它,最简单的方式便是当response完成的时候关闭客户端连接。

然而在某些情况下,服务器/gateway之一可能会生成Content-Length头,或者至少避免了要关闭客户端连接,如果应用程序没有调用write(),并返回一个len()是1的iterable,那么服务器通过第一个iterable出来的字符串。便可以自动的确定Content-Length长度。

如果服务器和客户段都支持HTTP/1.1 “chunked encoding”,那么服务器可能在调用write()或迭代字符串的时候使用chunked encoding来发送块数据,因此会为每个chunk块数据生成Content-Length。这允许服务器保持客户端长连接如果需要的话,注意如果真要这么做的话,服务器必须完全符合RFC2616规范,否则便回到了另外的策略上来处理Content-Length的缺失。

(注意:应用程序必须不能提供适合任何类型的Transfer-Encoding输出,像chunking or gzipping, as “hop-by-hop”操作,这些编码都是实际的服务器/gateway的职权。详细信息参见下面的Other HTTP Features一节)

缓冲和流 Buffering and Streaming

一般而言,应用程序都会通过缓存output并且一次性发送来提高吞吐量。Zope框架就有按此流程进行处理的:它通过缓存output到StringIO或类似的对象里面,并且顺着response响应头一次性发送。

在WSGI中相应的处理方法是应用程序简单地返回一个单元素迭代(single-element iterable)如列表list,其中包含单一的response body字符串。这是对于绝大多数应用程序都推荐的方案,渲染HTML页面的文本很容易存放在内存中。

然后对于大文件或专门用途的HTTP流媒体(如“服务器推送”),应用程序或许需要提供小块状的输出(比如为了避免加载一个大的文件到内存中),有些时候部分的response可能需要花费更多的时间来生成,但是在它之前发送一部分的数据还是很有用的。

这种情况下,应用程序通常会返回一个可迭代的(通常是迭代生成器),使生成的输出是一块一块的。这些块大小可能会超过最大的边界( “服务器推”),或者在某个耗时的任务之前(比如在磁盘中读取其他块)。

WSGI服务器/gateway和中间件不能延迟传输任何块。他们要么完全将所有的块都传输给客户端,或者保证他们会继续传输即使应用程序正在生成下一个块。服务器/gateway或中间件可能会提供以下三种方案中的一种:

  • 发送整个块到操作系统(要求任何的O/S缓存被刷新)在返回控制之前给应用程序
  • 使用不同的线程确保块被继续传输,而应用程序生成下一个块。
  • (仅中间件)发送整个块到它的父服务器/gateway

通过提供这样的保证措施,WSGI就可以允许应用程序确保在他们输出数据的任意点上不会变得停滞。这对正常运行是至关重要的,例如多媒体服务器推送流,多个边界间的数据应该被完整的传输到客户端。

中间件处理块边界 Middleware Handling of Block Boundaries

为了更好地支持异步应用程序和服务器,中间件组件不能阻止迭代从一个应用程序可迭代等待多个值如果中间件需要在自己能生成输出流之前积攒从应用程序中的值,那么它必须yield一个空字符串。或者达到这种需求的其他方式,一个中间件必须每次在它底层应用程序生成一个值的时候至少生成一个值,如果中间件不能产生其他值那么它必须生成一个空字符串。

这样的要求确保了异步的服务器和应用程序能共同协作,在需要同时提供多个应用程序实例的时候减少线程的数量。

注意,这样的要求意味着中间件必须返回一个iterable一旦其底层应用程序返回一个iterable。它也不允许中间件调用write()方法来发送数据。中间件仅可能用它父服务器的write()方法来传输数据,它底层的应用程序利用中间件提供的write()发送数据。

The write() Callable

一些现有框架API与WSGI的一个不同处理方式是他们支持无缓存的输出,特别指出的是,他们提供一个write函数或方法来写一个无缓冲的块或数据,或者他们提供一个缓冲的write函数和一个flush机制来flush缓冲。

不幸的是,这样的API无法实现像WSGI这样应用程序可迭代返回值,除非使用多线程或其他的机制。

因此为了允许这些框架继续使用这些必要的API,WSGI包含一个特殊的write()调用,由start_response调用返回。

新的WSGI应用程序和框架如果可以避免不应该使用write()调用。这个write()调用是完全hack用来支持必要的流能力的API。一般来说,应用程序应该通过返回的iterable来生成输出流,因为这样可以让web服务器交错在同一个Python的其他线程上,总的来说可以为服务器提供更大的吞吐量。

这个write()调用是由start_response调用返回的,并且它接受一个单一的参数:一个将用来写入到HTTP 一部分response体中的字符串,它被看作是已经被迭代生成后的结果。换句话说,在writer()返回前,它必须保证传入的字符串要么是完全发送给客户机, 或者已经缓冲了应用程序要向前的传输。

一个应用程序必须返回一个可迭代的对象,即使它使用write()来生成全部或部分response body。返回的可迭代可以是空的(例如一个空字符串),但是如果它不生成空字符串,那么output必须被处理,通常是由服务器/gateway来做(例如它必须立即发送或加入队列中)。应用程序必须不在他们返回的iterable内调用write()。因此所有由iterable生成的字符串会在传递给write()之后发送到客户端。

Unicode问题 Unicode Issues

HTTP不直接支持Unicode,并且也没有这样的接口。所有的编码解码必须由应用程序来处理,所有传递给服务器的都必须是Python标准的字节字符串而不是Unicode对象。使用其中在需要字符串对象一个Unicode对象的结果,是不确定的。

注意 所有的作为response头的字符传递到start_response必须遵循RFC2616的编码,也就是它们必须是ISO-8859-1字符,或者使用RFC 2047 MIME编码。

Python平台的str或StringType实际上是基于Unicode(如Jython,ironPython, python 3000等),所有的字符串都只包含ISO-8859-1编码规范中可表示的代码点(从\u0000-\u00FF)。如果应用程序提供的字符串包含任何Unicode字符或编码点,这将是个致命的错误。同样地,服务器/gateway也必须不提供任何Unicode字符到应用程序。。

再次声明,本规定所有的字符串都必须是str或StringType,并且不能是unicode或UnicodeType。尽管一个开发的平台允许超过8位字节的字符对象,对于本规范所指定的字符串来说,也仅仅是低8位的字节被用到。

错误处理 Error Handling

一般来说,应用程序应到捕获自己的内部错误,并显示有用的信息到浏览器上。(由浏览器来决定哪些信息是有用的)

然后要显示这些信息,应用程序不能实际地发送任何数据到浏览器上,否则会破坏response。因此WSGI提供了一种机制,要么允许应用程序发送它自己的错误信息,要不自动终止:这个exc_info参数start_response。这里有个如何使用的例子。

try:
    # regular application code here
    status = "200 Froody"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers)
    return ["normal body goes here"]
except:
    # XXX should trap runtime issues like MemoryError, KeyboardInterrupt
    #     in a separate handler before this bare 'except:'...
    status = "500 Oops"
    response_headers = [("content-type", "text/plain")]
    start_response(status, response_headers, sys.exc_info())
    return ["error body goes here"]

如果当有异常发送的时候没有信息被写入,调用start_response将正常返回,并且应用程序返回的包含错误信息的body将被发送到浏览器。然而如果有任何的output被发送到浏览器,start_response会重新抛出提供的异常。此异常不应该被应用程序捕获,并且应用程序会终止。服务器/gateway会捕获异常这个致命的异常并终止响应。

服务器应该捕获任何异常并记录日志当终止应用程序或反复迭代他们的返回值。如果当应用程序出错的时候已经有一部分response被发送到浏览器了,服务器或gateway可以试图添加一个错误消息到output,如果已经发送了指明text/*的头信息,那么服务器就知道如何修改清理。

一些中间件或许希望提供额外的异常处理服务,或拦截并替换应用程序的错误信息。在此情况下,中间件可能选择不再重新抛出提供的exc_info到start_response,但换作抛出中间件特制的错误,或者在存贮了提供的参数后简单地返回且不包含错误。这将导致应用程序返回错误的body iterable(或调用write().。允许中间件来拦截并修改错误流,这些技术只会工作只要应用程序作者做到:

  • 总是提供exc_info当开始一个错误的响应的时候。
  • 当exc_info已经提供的情况下不要捕获来自start_response的异常。

HTTP1.1的长连接 HTTP 1.1 Expect/Continue

实现了HTTP1.1的服务器/gateway必须提供对HTTP1.1中”expect/continue”(长连接)的透明支持。可以用下面的几种方式完成。

  • 用一个立即执行的”100 Continue”response响应一个包含Expect: 100-continue的请求,并且正常处理
  • 正常处理请求,但提供给应用程序一个input流,当应用程序首先尝试从输入流中读取的时候发送一个”100 Continue”响应。这个读取必须保持阻塞至到客户端的响应。
  • 等待客户端判断服务器不支持expect/continue以及客户端自身发来的request body。(不好的方式,不推荐)

注意这些行为的限制不适用于HTTTP 1.O,或请求不是针对特定的应用程序对象。更多HTTP 1.1 Except/Continue的信息,请参阅RFC 2616的8.2.3段和10.1.1段。

其他的HTTP特性 Other HTTP Features

通常来说,应用程序对其的output有完全的控制权,服务器/gateway对此因不理不问,他们只做一些不影响应用程序响应语义的修改。往往很可能的是为应用程序开发者提供中间件来支持一些特性,所以服务器/gateway开发者在他们实现的时候应当偏保守。在某种意义上,一个服务器应当使自己更像是一个HTTPgateway服务器,配合一个应用程序来作为一个HTTP “origin server”(这些定义参加RFC 2616 1.3段)

然后由于WSGI服务器和应用程序不通过HTTP通信,RFC 2616提到的”hop-by-hop”在WSGI内部通信中不适用。WSGI应用程序必须不生成任何”hop-by-hop”头信息,试图使用HTTP的特性需要他们生成这样的头,或者依赖任何传入的”hop-by-hop”头内容在environ字典中。WSGI服务器必须自己处理掉任何被支持入站的”hop-by-hop”头信息,像传输编码,包括适用的分块编码。

将这些原则应用到各种各样的HTTP特性中去,应当清楚一个服务器可能需要处理缓存验证、If-None-Match和If-Modified-Sine的请求头,和一些Last-Modified和ETag响应头。然而它并不是必须要这样做,并且一个应用程序应到处理自己的缓存验证等,如果它自身相提供这样的特性,然后服务器/gateway就不需要做这样的验证。

同样地,服务器可能会重新编码或传输编码一个应用程序的响应,但是应用程序应当对自己发送的内容做适当的编码,并且不提供传输编码。如果客户端需要服务器可能以字节码的方式传输应用程序的响应,并且应用程序本身是不支持字节码方式。再次申明,如果需要,应用程序应当自己执行相应的方法。

注意,这些在应用程序上的限制不是要求应用程序为每个HTTP特性重新实现一次,许多HTTP特性可以完全或部分地由中间件来实现,这样便可以让服务器和应用程序作者在一遍又一遍的实现这些特性中解放出来。

线程支持 Thread Support

线程的支持或缺乏,也是服务器依赖。服务器可以同时运行多个请求,也应当提供让应用程序运行在单一线程上的选项,因此让一些不是线程安全的应用程序或框架仍旧可以在这些服务器上使用。

实现/应用事项 Implementation/Application Notes

服务扩展API Server Extension APIs

有些服务器作者可能希望提供更多的高级API,应用程序和框架作者便可以专门的使用。例如一个以mod_python为基础的gateway可能就希望暴露一些Apache的API作为WSGI的扩展。

在最简单的情况下,它只需要定义一个environ变量,类似mod_python.some_api。但是,更多情况下,可能存在的中间件会使此变得更困难。例如,一个API提供的在environ变量中访问的HTTP 头,可能会被中间件修改掉而返回不一样的值。

通常,任何扩展的API,复制、取代或者绕过部分WSGI的功能与中间件会有不兼容的风险。服务器/gateway开发者不应当假设没人会使用中间件,因为一些框架作者专门的打算重新改写或组织他们的框架代码,使之几乎就像是各种各样的中间件。

所以,为了提供最大的兼容性,服务器/gateway提供一些扩展的API,取代一些WSGI的功能,必须设计这些API以便他们使用部分替换过的API调用。例如:一个允许访问HTTP请求头的扩展API需要求应用程序通过当前的environ,所以服务器/gateway可能会验证那些可被访问的HTTP头,保证没有被中间件修改过。如果这扩展API不能保证,它总是evniron和HTTP头信息里面的内容,它必须拒绝向应用程序提供服务。例如,通过抛出一个错误,返回None代替头信息的收集等等是适合API的。

同样地,如果扩展API提供交替的方法来写response数据或是头信息,它应当要求start_response调用在应用程序能获得的扩展的服务之前已被传入。如果传入的对象和最开始服务器/gateway提供给应用程序的不一样,它不能保证正确的操作并且必须拒绝给应用程序提供扩展的服务。

这些指导方针同样适用于中间件,添加类似解析过的cookies信息,表单变量,sessions,或者像evniron。特别地,这样的中间件应当使提供这些的特性类似一个在environ里操作的函数,而不是简单的想evniron里面添加些东西。这些有助于保证来自evniron里面的信息计算是在任何中间件对URL重写过后或者是其他evniron变量的修改之后。

或许以后一些中间件作者为了保证应用程序在使用他们的中间件过程中不被跳过,他们不得不从environ中删除全部或部分的扩展API。为了避免这些,这些安全的规则对所有的服务器/gateway和中间件作者来说都是非常重要的。

应用程序结构配置 Application Configuration

这些规格说明没有定义服务器如何选择如何得到一个应用程序来调用。这些和其他一些配置选项都是高度地服务于服务器的。这些期望服务器/gateway作者能讲如何配置选项(如线程的选项),如何配置执行一个特定的应用程序对象等文档化。

另一方面,框架作者应当将如何创建一个被框架功能包装过的应用程序对象文档化。用户可以选择服务器和应用程序框架,但必须将两者连接在一起。然而,因为现在有了两者共同的接口,这应该只是一个机械式的问题,而不是为了将新的应用程序或服务器配对组合的重大工程了。

最后,一些应用程序,框架,中间件可能希望使用evniron字典来接受一些简单的字符串配置选项。服务器/gateway应当通过允许应用程序部署者向evniron里面指定特殊的名值对来支持这些。在最简单的情况下,这些支持可以仅仅由拷贝操作系统提供的环境变量os.environ到environ字典中,那么部署者原则上可以配置这些外部的信息到服务器上,或者在CGI的情况下他们可能是通过服务器的配置文件来设置。

应用程序应该保持这些变量的依赖到最小程度,并不是所有的服务器都支持简单地配置它们。当然,即使在最槽糕的情况下,部署一个应用程序可以创建一个脚本来提供一些必要的选项值:

from the_app import application

def new_app(environ, start_response):
    environ['the_app.configval1'] = 'something'
    return application(environ, start_response)

但是,大多存在的应用程序和框架可能只需要从environ里面一个单一的配置值,用来指示它们的应用程序或框架专门的配置文件位置。(当然,应用程序应当缓存这些配置,以避免每次调用都重复读取)

URL的构建 URL Reconstruction

如果应用程序希望改造请求的完整URL,可以使用如下的算法,由lan Bicking提供:

from urllib import quote
url = environ['wsgi.url_scheme']+'://'

if environ.get('HTTP_HOST'):
    url += environ['HTTP_HOST']
else:
    url += environ['SERVER_NAME']

    if environ['wsgi.url_scheme'] == 'https':
        if environ['SERVER_PORT'] != '443':
           url += ':' + environ['SERVER_PORT']
    else:
        if environ['SERVER_PORT'] != '80':
           url += ':' + environ['SERVER_PORT']

url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
    url += '?' + environ['QUERY_STRING']

另外,这样的重建URL可能不完全相同URI由客户端的请求。服务器重写规则,例如,可能已经修改了客户的最初请求的URL,以将其放置在一个规范的形式。

支撑的Python旧版(<2.2)版本 Supporting Older (<2.2) Versions of Python

有些服务器,gateway或应用程序可能希望支持Python中的较旧的(<2.2)版本。这一点尤其重要,如果Jython是一个目标平台,因为在写本书的Jython 2.2的生产就绪版本尚未公布。

对于服务器和gateway,这是相对简单的:面向Python 2.2之前版本的服务器和gateway必须限制自己只使用标准的“For”循环来迭代应用程序返回的任何iterable。这是确保与2.2版之前的迭代器协议(下面将进一步讨论)和“今天的”迭代器协议(参见PEP 234)的源代码级兼容性的唯一方法。

(请注意,此技术只适用于用Python编写的服务器、gateway或中间件。关于如何从其他语言正确使用迭代器协议的讨论不在本PEP的范围之内。)

对于应用程序,支持Python 2.2之前的版本稍微复杂一些:

  • 你可能不会返回一个文件对象,并期望它作为一个迭代的工作,因为Python 2.2之前,文件没有迭代。(一般来说,您不应该这样做,因为它在大多数情况下性能都很差!)使用file_包装或应用程序特定的文件包装类。(有关详细信息,请参阅可选的特定于平台的文件处理wsgi.file_包装,以及可用于将文件包装为iterable的示例类。)
  • 如果返回自定义iterable,则它必须实现2之前的迭代器协议。也就是说,提供一个接受整数键并在耗尽时引发IndexError的方法。(请注意,内置序列类型也是可接受的,因为它们也实现了此协议。)

最后,希望支持Python 2.2之前版本、迭代应用程序返回值或本身返回iterable(或两者都返回)的中间件必须遵循上述适当的建议。

(注意:为了支持Python 2.2之前的版本,任何服务器、gateway、应用程序或中间件也必须只使用目标版本中可用的语言功能,使用1和0而不是True和False等。)

可选的特别的平台文件处理 Optional Platform-Specific File Handling

一些操作环境提供高性能的文件传输设施,像Unix的sendfile()方法。服务器和gateway可能会通过environ中的wsgi.file_wrapper选项来暴露这功能。应用程序可以使用此“文件包装器”将文件或类似文件的对象转换为iterable,然后返回,例如:

if 'wsgi.file_wrapper' in environ:
    return environ['wsgi.file_wrapper'](filelike, block_size)
else:
    return iter(lambda: filelike.read(block_size), '')

如果服务器或gateway提供wsgi.file_wrapper,它必须是个可调用的,并且接受一个必要的位置参数,和一个可选的位置参数。第一个参数是将被发送的类文件对象,第二个参数是可选的,标示分快大小的建议(这个服务器/gateway不需要)。这个调用必须返回一个可迭代的对象,并且不能执行任何的数据传输直到服务器/gateway真正接受到了作为应用程序返回值的迭代对象(否则会阻止中间件解析或覆盖响应体)。

由应用程序提供的被认为是类文件的对象必须有可选大小参数的read()方法。它或许有close()方法,如果有,那么wsgi.file_wrapper必须有一个会调用类文件对象原始close()方法的close()方法。如果类文件对象有任何的方法或属性与Python内置的文件对象的属性或方法名相同(例如fileno()),那么wsgi.file_warpper可能会假定这些方法和属性与Python内置的语义是相同的。

实际在任何特定平台上实现的文件处理必须发生在应用程序返回之后,并且服务器/gateway检查是否是一个包装对象被返回。(再次声明,因为存在的中间件,错误处理等等类似的东西,它不保证任何生成的包装会被实际使用)

除了处理close(),从语义上讲,应用程序返回一个包装的文件应该和返回iter(filelike.read, ”)一样。换句话说,当传输开始的时候,应当从文件的当前位置开始传输,并且继续直到最后完成。

当然,特定平台的文件传输API通常不接受随意的类文件对象,所以,一个wsgi.file_wrapper为了判断类文件对象是否适应于他们特定的平台,不得不对提供的对象做一些像fileno()(Unix-like OSes)或者是java.nio.FileChannel(在Jython下)的自省检查。

注意:即使对象不适用与特定的平台API,wsgi.file_wrapper必须仍旧返回一个包装了read()和close()的迭代,因此应用程序使用这文件包装器便可以再不同平台间移植。这里有个简单的平台无关的文件包装类,适应于老(2.2之前)的和新的Python,如下:

class FileWrapper:

    def __init__(self, filelike, blksize=8192):
        self.filelike = filelike
        self.blksize = blksize
        if hasattr(filelike, 'close'):
            self.close = filelike.close

    def __getitem__(self, key):
        data = self.filelike.read(self.blksize)
        if data:
            return data
        raise IndexError

这里是一个用它来访问特定平台API的服务器/gateway代码片段。

environ['wsgi.file_wrapper'] = FileWrapper
result = application(environ, start_response)

try:
    if isinstance(result, FileWrapper):
        # check if result.filelike is usable w/platform-specific
        # API, and if so, use that API to transmit the result.
        # If not, fall through to normal iterable handling
        # loop below.

    for data in result:
        # etc.

finally:
    if hasattr(result, 'close'):
        result.close()

QA问答 Questions and Answers

为什么evniron必须是字典?用它的子类会有什么问题呢?

用字典的原理是为了最大化满足在服务器间的移植性。另一种选择就是定义一些字典的子集,用字典的方法作为标准的便捷的接口。然而事实上,大多服务器可能只需要找到一个字典就足够了,并且框架的作者会预计完整的字典特性可用,因为多半情况是这样的。但是如果一些服务器选择不使用字典,那么将可能会有互用性的问题出现尽管服务器给出了一致性的规范。因此使用强制的字典简化了规范和互相的验证确保。注意这些不妨碍服务器或框架开发者向evnrion字典里加入自定义的变量来提供特别的服务。这是提供任何value-added服务推荐的方式。

为什么你能执行write()并且yield 字符串/返回一个迭代器?我们不应该选择一种做法吗?

如果我们仅仅使用迭代的做法,那么现存的框架面临可用的”push”被恶化。如果我们只支持通过write()推送,那么服务器在传输大文件的时候性能将恶化(如果一个工作线程没有将所有的output都发送完成,那么将无法进行下一个新的request请求)。因此,这种平衡允许应用程序支持两个方法,视情况而定,比起需要push-only的方式来说,只给服务器实现者一点负担。

close()是什么?

当writes在应用程序执行期间完成,应用程序可以通过try/finally块来保证资源被释放。但是如果应用程序返回一个迭代,直到迭代器被垃圾收集器收集前任何资源都不会被释放。约定俗成的close()允许应用程序在结束一个request请求时释放临界资源,并且它向前兼容于在PEP 325中被提议的在迭代器中的try/finally.

为什么这个接口如此低级?我如果想加入一些特性呢(例如cookies, sessions, persistence,…)

这并不是另一个python的web框架,这仅仅是一个框架和web服务器之间通信的方法,反之亦然。如果你想使用这些特性,你需要选择一个提供这些你想要的特性的框架。并且如果这些框架让你创建一个WSGI应用程序,你应当能让它在大部份支持WSGI的服务器上运行。同样,一些WSGI服务器可能通过在他们提供的evniron字典里的对象来提供一些额外的服务,可以参阅这些服务器适当的文档了解详情。(当然,使用这样扩展的应用程序将无法移植到其他的WSGI-based服务器上)

为什么使用CGI的变量代替好的老HTTP头,并且为什么让他们和WSGI定义的变量混合在一起呢?

许多现存的框架很大程序上是建立在CGI规范的基础上的,并且现存的web服务器知道如何生成CGI变量。相比之下,一些可选的方式来呈现HTTP信息会是分散破碎的并且缺乏市场。因此使用”CGI标准”看起来是个好的办法来利用现有的实现。对于将他们同WSGI变量混合在一起,分离他们的话会导致需要两个字典参数需要传入,这样做没什么好处。

关于状态字符串,我们可不可以仅仅使用数字来代替,像用200代替”200 OK”?

这样做会使服务器或gateway被复杂化,因为他们需要一个字符串与数值的映射表。相比之下,应用程序或框架作者在他们输入一些额外的信息到他们处理细节的响应代码中要简单,并且现存的框架时常有一个表包含这些必要的信息。所以,总体来说这个让应用程序/框架来负责要比服务器或gateway来负责要好。

为什么wsgi.run_once不能保证app只运行一次?

因为它只不过是建议应用程序应当”装配稀少地运行”。这是用于应用程序框架用不同的模式来操作缓存,sessions等待。在”multiple run”模式下,这样的框架可能会预先装载缓存,并且在每个request请求之后不会写操作,如写入logs或session数据到硬盘上。在”single run”模式下,这样的框架避免预加载,避免每个request请求之后flush所有必要的写操作。然而,为了验证在后者的模式下应用程序或框架的正确动作,可能会必要地(或是权宜之计)调用它不止一次。因此,一个应用程序应当不能仅仅因为设置了wsgi.run_once为True就假定它肯定不会再运行,

Feature x(dictionaries, callables, etc.)对于应用程序代码来说是很丑陋的,我们可以使用对象来代替吗?

WSGI所有的这些选择实现都是为了从另一个特性中解耦合;重新组合这些特性到一个封装的对象中会使更难写服务器/gateway,并且会使书写中间件来代替或修改一部分整体的功能的难度上一个数量级。本质上,中间件希望有个”责任链”模式,凭这个对于一些功能来说它可以看作是一个”handler”,而让其他东西保持不变。这用普通的python对象是非常难的,如果这接口是保持可扩展性的。例如,一个必须使用__getattr__或者__getattribut__的覆盖,来确保这些扩展(比如一个WSGI未来的版本定义的一个变量)是被通过的。这种类型的代码是出了名的难以保证100%正确,有些人可能会自己重写。他们会因此复制其他人的实现,但是从其他已经正确的案例中复制过来的时候却未能成功更新。进一步讲,这样必要的样本将纯碎excise,一个开发者为了能让应用程序框架得到更漂亮的API而向中间件开发者支付税务。但是应用程序框架开发者会通常只升级一个框架用来支持WSGI,并且对于框架整体来说只是非常小的一部分。这可能会成为他们第一次(甚至是最后一次)的WSGI实现,并且因此他们可能实现这些规范起来伸手可及。因此,努力使使用对象属性创造出来的API漂亮这类的做法,可能会遗失掉这些支持者。我们鼓励那些希望有漂亮的(或者是改进的)WSGI接口的人,对直接开发web应用程序项目(与web框架开发)的用户开发为方便应用程序开发者而包装过WSGI的API或框架,通过这种方式,WSGI可以保持对服务器或中间件的便利性,而不是对应用程序作者”丑陋”。

建议/讨论 Proposed/Under Discussion

这些项目目前正在在Web-SIG讨论和其他地方,或者是PEP作者的“待办事项”列表:

  • 如果input是一个迭代器,而不是一个文件?这将有助于为异步应用和编码分块输入流。
  • 可选的扩展正在用于暂停的应用程序的输出的迭代直到输入可用,或者直到一个回调发生讨论。
  • 添加在这些领域进行同步和异步应用程序和服务器,相关的线程模型和问题/设计目标部分。

致谢 Acknowledgements

感谢Web SIG邮件列表上的许多人,他们周到的反馈使修订草案成为可能。尤其是:

  • Gregory “Grisha” Trubetskoy, mod_python的作者,他抨击初稿没有提供比“普通的CGI”任何优势,因此鼓励我寻找更好的方法。
  • IanBicking,他帮助我正确地指定多线程和多进程选项,并要求我提供一种机制,让服务器向应用程序提供自定义扩展数据。
  • Tony Lownds提出了一个start_response函数的概念,该函数接收状态和头,并返回一个write函数。他的输入还指导了异常处理设施的设计,特别是在允许覆盖应用程序错误消息的中间件方面。
  • alankennedy勇敢地尝试在Jython上实现WSGI(早在规范最终确定之前)帮助形成了“支持Python的旧版本”部分以及可选的file_包装设施。
  • marknottingham,他对规范进行了广泛的审查,发现了httprfc遵从性方面的问题,特别是关于HTTP/1.1特性的问题,直到他指出这些问题时,我才知道这些特性的存在。

参考 References

参考链接:

发表回复

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