WSGI 协议

背景

Python Web 开发中,服务端程序能够分为两个部分,一是服务器程序,二是应用程序。前者负责把客户端请求接收,整理,后者负责具体的逻辑处理。为了方便应用程序的开 发,咱们把经常使用的功能封装起来,成为各类Web开发框架,例如 Django, Flask, Tornado。不一样的框架有不一样的开发方式,可是不管如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就须要为不一样 的框架提供不一样的支持。这样混乱的局面不管对于服务器仍是框架,都是很差的。对服务器来讲,须要支持各类不一样框架,对框架来讲,只有支持它的服务器才能被 开发出的应用使用。html

这时候,标准化就变得尤其重要。咱们能够设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就能够配合使用。一旦标准肯定,双方各自实现。这样,服务器能够支持更多支持标准的框架,框架也可使用更多支持标准的服务器。python

Python Web开发中,这个标准就是 The Web Server Gateway Interface, 即 WSGI. 这个标准在PEP 333中描述,后来,为了支持 Python 3.x, 而且修正一些问题,新的版本在PEP 3333中描述。git

WSGI 是什么

WSGI 是服务器程序与应用程序的一个约定,它规定了双方各自须要实现什么接口,提供什么功能,以便两者可以配合使用。github

WSGI 不能规定的太复杂,不然对已有的服务器来讲,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在 WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。web

另外一方面,WSGI须要使得middleware(是中间件么?)易于实现。middleware处于服务器程序与应用程序之间,对服务器程序来讲,它相 当于应用程序,对应用程序来讲,它至关于服务器程序。这样,对用户请求的处理,能够变成多个 middleware 叠加在一块儿,每一个middleware实现不一样的功能。请求从服务器来的时候,依次经过middleware,响应从应用程序返回的时候,反向经过层层 middleware。咱们能够方便地添加,替换middleware,以便对用户请求做出不一样的处理。ubuntu

WSGI 内容概要

WSGI主要是对应用程序与服务器端的一些规定,因此,它的主要内容就分为两个部分。浏览器

应用程序

WSGI规定:服务器

1. 应用程序须要是一个可调用的对象

在Python中:cookie

  • 能够是函数数据结构

  • 能够是一个实例,它的类实现了__call__方法

  • 能够是一个类,这时候,用这个类生成实例的过程就至关于调用这个类

同时,WSGI规定:

2. 可调用对象接收两个参数

这样,若是这个对象是函数的话,它看起来要是这个样子:

# callable function
def application(environ, start_response):
    pass

若是这个对象是一个类的话,它看起来是这个样子:

# callable class
class Application:
    def __init__(self, environ, start_response):
        pass

若是这个对象是一个类的实例,那么,这个类看起来是这个样子:

# callable object
class ApplicationObj:
    def __call__(self, environ, start_response):
        pass

最后,WSGI还规定:

3.可调用对象要返回一个值,这个值是可迭代的。

这样的话,前面的三个例子就变成:

HELLO_WORLD = b"Hello world!\n"


# callable function
def application(environ, start_response):
    return [HELLO_WORLD]


# callable class
class Application:
    def __init__(self, environ, start_response):
        pass

    def __iter__(self):
        yield HELLO_WORLD


# callable object
class ApplicationObj:
    def __call__(self, environ, start_response):
        return [HELLO_WORLD]

你可能会说,不是啊,咱们平时写的web程序不是这样啊。 好比若是使用web.py框架的话,一个典型的应用多是这样的:

class hello:
    def GET(self):
        return 'Hello, world!'

这是因为框架已经把WSGI中规定的一些东西封装起来了,咱们平时用框架时,看不到这些东西,只须要直接实现咱们的逻辑,再返回一个值就行了。其它的东西框架帮咱们作好了。这也是框架的价值所在,把经常使用的东西封装起来,让使用者只须要关注最重要的东西。

固然,WSGI关于应用程序的规定不仅这些,可是如今,咱们只须要知道这些就足够了。下面,再介绍服务器程序。

服务器程序

服务器程序会在每次客户端的请求传来时,调用咱们写好的应用程序,并将处理好的结果返回给客户端。

WSGI规定:

4.服务器程序须要调用应用程序

服务器程序看起来大概是这个样子的:

def run(application):
    environ = {}

    def start_response(status, response_headers, exc_info=None):
        pass

    result = application(environ, start_response)

    def write(data):
        pass

    for data in result:
        write(data)

这里能够看出服务器程序是如何与应用程序配合完成用户请求的。

WSGI规定了应用程序须要一个可调用对象,有两个参数,返回一个可迭代对象。在服务器 程序中,针对这几个规定,作了如下几件事:

  • 把应用程序须要的两个参数设置好

  • 调用应用程序

  • 迭代访问应用程序的返回结果,并将其传回客户端

你能够从中发现,应用程序须要的两个参数,一个是一个dict对象,一个是函数。它们到底有什么用呢?这都不是咱们如今应该关心的,如今只须要知道,服务器程序大概作了什么事情就行了,后面,咱们会深刻讨论这些细节。

middleware

另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不一样的URL须要交由不一样的函数处理,这个功能叫作 URL Routing,这个功能就能够放在两者中间实现,这个中间层就是 middleware。

middleware对服务器程序和应用是透明的,也就是说,服务器程序觉得它就是应用程序,而应用程序觉得它就是服务器。这就告诉我 们,middleware须要把本身假装成一个服务器,接受应用程序,调用它,同时middleware还须要把本身假装成一个应用程序,传给服务器程 序。

其实不管是服务器程序,middleware 仍是应用程序,都在服务端,为客户端提供服务,之因此把他们抽象成不一样层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。

下面,咱们看看middleware大概是什么样子的。

# URL Routing middleware
def urlrouting(url_app_mapping):
    def midware_app(environ, start_response):
        url = environ['PATH_INFO']
        app = url_app_mapping[url]

        result = app(environ, start_response)

        return result

    return midware_app

函数 midware_app就是一个简单的middleware:对服务器而言,它是一个应用程序,是一个可调用对象, 有两个参数,返回一个可调用对象。对应用程序而言,它是一个服务器,为应用程序提供了参数,而且调用了应用程序。

另外,这里的urlrouting函数,至关于一个函数生成器,你给它不一样的 url-app 映射关系,它会生成相应的具备 url routing功能的 middleware。

若是你仅仅想简单了解一下WSGI是什么,相信到这里,你差很少明白了,下面会介绍WSGI的细节,这些细节来自 PEP3333, 若是没有兴趣,到这里 能够中止了。

WSGI详解

注意:以 点 开始的解释是WSGI规定 必须知足 的。

应用程序

  • 应用程序是可调用对象

  • 可调用对象有两个位置参数
    所谓位置参数就是调用的时候,依靠位置来肯定参数的语义,而不是参数名,也就是说服务 器调用应用程序时,应该是这样:

application(env, start_response)

而不是这样:

application(start_response=start_response, environ=env)

因此,参数名实际上是能够随便起的,只不过为了表义清楚,咱们起了environ 和 start_response

  • 第一个参数environ是Python内置的dict对象,应用程序能够对这个参数任意修改。

  • environ参数必须包含 WSGI 须要的一些变量(详见后文)
    也能够包含一些扩展参数,命名规范见后文

  • start_response参数是一个可调用对象。接受两个位置参数,一个可选参数。 例如:start_response(status, response_headers, exc_info=None) status参数是状态码,例如 200 OK 。

response_headers参数是一个列表,列表项的形式为(header_name, header_value)。

exc_info参数在错误处理的时候使用。

status和response_headers的具体内容能够参考 HTTP 协议 Response部分

  • start_response必须返回一个可调用对象: write(body_data)

  • 应用程序必须返回一个可迭代对象。

  • 应用程序不该假设返回的可迭代对象被遍历至终止,由于遍历过程可能出现错误。

  • 应用程序必须在第一次返回可迭代数据以前调用 start_response 方法。
    这是由于可迭代数据是 返回数据的 body 部分,在它返回以前,须要使用 start_response 返回 response_headers 数据。

服务器程序

  • 服务器必须将可迭代对象的内容传递给客户端,可迭代对象会产生bytestrings,必须彻底完成每一个bytestring后才能请求下一个。

  • 假设result 为应用程序的返回的可迭代对象。若是len(result) 调用成功,那么result必须是可累积的。

  • 若是result有close方法,那么每次完成对请求的处理时,必须调用它,不管此次请求正常完成,仍是遇到了错误。

  • 服务器程序禁止使用可迭代对象的其它属性,除非这个可迭代对象是一个特殊类的实例,这个类会被 wsgi.file_wrapper定义。

根据上述内容,咱们的服务器程序看起来会是这个样子:

def run(application):
    environ = {}

    # set environ
    def write(data):
        pass

    def start_response(status, response_headers, exc_info=None):
        return write

    try:
        result = application(environ, start_response)
    finally:
        if hasattr(result, 'close'):
            result.close()

    if hasattr(result, '__len__'):
        # result must be accumulated
        pass


    for data in result:
        write(data)

应用程序看起来是这个样子:

HELLO_WORLD = b"Hello world!\n"


# callable function
def application(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)

    return [HELLO_WORLD]

下面咱们再详细介绍以前提到的一些数据结构

environ 变量

environ 变量须要包含 CGI 环境变量,它们在The Common Gateway Interface Specification 中定义,下面列出的变量必须包含在 enciron变量中:

  • REQUEST_METHOD
    HTTP 请求方法,例如 "GET", "POST"

  • SCRIPT_NAME
    URL 路径的起始部分对应的应用程序对象,若是应用程序对象对应服务器的根,那么这个值能够为空字符串

  • PATH_INFO
    URL 路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,若是请求的路径就是根路径,这个值为空字符串

  • QUERY_STRING
    URL路径中 ? 后面的部分

  • CONTENT_TYPE
    HTTP 请求中的 Content-Type 部分

  • CONTENT_LENGTH
    HTTP 请求中的Content-Lengh 部分

  • SERVER_NAME, SERVER_PORT
    与 SCRIPT_NAME,PATH_INFO 共同构成完整的 URL,它们永远不会为空。可是,若是 HTTP_HOST 存在的话,当构建 URL 时, HTTP_HOST优先于SERVER_NAME。

  • SERVER_PROTOCOL
    客户端使用的协议,例如 "HTTP/1.0", "HTTP/1.1", 它决定了如何处理 HTTP 请求的头部。这个名字其实应该叫REQUEST_PROTOCOL,由于它表示的是客户端请求的协议,而不是服务端响应的协议。可是为了和CGI兼容,咱们只好叫这个名字了。 *HTTP_ Variables
    这个是一个系列的变量名,都以HTTP开头,对应客户端支持的HTTP请求的头部信息。

WSGI 有一个参考实现,叫 wsgiref,里面有一个示例,咱们这里引用这个示例的结果,展示一下这些变量,以便有一个直观的体会,这个示例访问的 URL 为 http://localhost:8000/xyz?abc

上面提到的变量值为:

REQUEST_METHOD = 'GET'
SCRIPT_NAME = ''
PATH_INFO = '/xyz'
QUERY_STRING = 'abc'
CONTENT_TYPE = 'text/plain'
CONTENT_LENGTH = ''
SERVER_NAME = 'minix-ubuntu-desktop'
SERVER_PORT = '8000'
SERVER_PROTOCOL = 'HTTP/1.1'

HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_HOST = 'localhost:8000'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'

另外,服务器还应该(非必须)提供尽量多的CGI变量,若是支持SSL的话,还应该提供Apache SSL 环境变量

服务器程序应该在文档中对它提供的变量进行说明,应用程序应该检查它须要的变量是否存在。

除了 CGI 定义的变量外,服务器程序还能够包含和操做系统相关的环境变量,但这并不是必须。

可是,下面列出的这些 WSGI 相关的变量必需要包含:

  • wsgi.version
    值的形式为 (1, 0) 表示 WSGI 版本 1.0

  • wsgi.url_scheme
    表示 url 的模式,例如 "https" 仍是 "http"

  • wsgi.input
    输入流,HTTP请求的 body 部分能够从这里读取

  • wsgi.erros
    输出流,若是出现错误,能够写往这里

  • wsgi.multithread
    若是应用程序对象能够被同一进程中的另外一线程同时调用,这个值为True

  • wsgi.multiprocess
    若是应用程序对象能够同时被另外一个进程调用,这个值为True

  • wsgi.run_once
    若是服务器但愿应用程序对象在包含它的进程中只被调用一次,那么这个值为True

这些值在 wsgiref示例中的值为:

wsgi.errors = <open file '<stderr>', mode 'w' at 0xb735f0d0>
wsgi.file_wrapper = <class wsgiref.util.FileWrapper at 0xb70525fc>
wsgi.input = <socket._fileobject object at 0xb7050e6c>
wsgi.multiprocess = False
wsgi.multithread = True
wsgi.run_once = False
wsgi.url_scheme = 'http'
wsgi.version = (1, 0)

另外,environ中还能够包含服务器本身定义的一些变量,这些变量应该只包含

  • 小写字母

  • 数字

  • 下划线

  • 独立的前缀

例如,mod_python定义的变量名应该为mod_python.var_name的形式。

输入流及错误流(Input and Error Streams)

服务器程序提供的输入流及错误流必须包含如下方法:

  • read(size)

  • readline()

  • readlines(hint)

  • iter()

  • flush()

  • write()

  • writelines(seq)

应用程序使用输入流对象及错误流对象时,只能使用这些方法,禁止使用其它方法,特别是, 禁止应用程序关闭这些流。

start_response()

start_response是HTTP响应的开始,它的形式为:

start_response(status, response_headers, exc_info=None)

返回一个可调用对象,这个可调用对象形式为:

write(body_data)

status 表示 HTTP 状态码,例如 "200 OK", "404 Not Found",它们在 RFC 2616中定义,status禁止包含控制字符。

response_headers 是一个列表,列表项是一个二元组: (header_name, heaer_value) , 每一个 header_name 都必须是 RFC 2616 4.2 节中定义的HTTP 头部名。header_value 禁止包含控制字符。

另外,服务器程序必须保证正确的headers 被返回给客户端,若是应用程序没有返回headers,服务器必须添加它。

应用程序和middleware禁止使用 HTTP/1.1 中的 "hop-by-hop"特性,以及其它可能影响客户端与服务器永久链接的特性。

start_response 被调用时,服务器应该检查 headers 中的错误,另外,禁止 start_response直接将 response_headers传递给客户端,它必须把它们存储起来,一直到应用程序第一次迭代返回一个非空数据后,才能将 response_headers传递给客户端。这实际上是在说,HTTP响应body部分必须有数据,不能只返回一个header。

start_response的第三个参数是一个可选参数,exc_info,它必须和Python的 sys.exc_info()返回的数据有相同类型。当处理请求的过程遇到错误时,这个参数会被设置,同时调用 start_response。若是提供了exc_info,可是HTTP headers 尚未输出,那么 start_response须要将当前存储的 HTTP response headers替换成一个新值。可是,若是提供了exc_info,同时 HTTP headers已经输出了,那么 start_response 必须 raise 一个 error。禁止应用程序处理 start_response raise出的 exceptions,应该交给服务器程序处理。

当且仅当提供 exc_info参数时,start_response才能够被调用多于一次。换句话说,要是没提供这个参数,start_response在当前应用程序中调用后,禁止再调用。

为了不循环引用,start_response实现时须要保证 exc_info在函数调用后再也不包含引用。 也就是说start_response用完 exc_info后,须要保证执行一句

exc_info = None

这能够经过 try/finally实现。

处理 Content-Length Header

若是应用程序支持 Content-Length,那么服务器程序传递的数据大小不该该超过 Content-Length,当发送了足够的数据后,应该中止迭代,或者 raise 一个 error。固然,若是应用程序返回的数据大小没有它指定的Content-Length那么多,那么服务器程序应该关闭链接,使用Log记录,或者报告 错误。

若是应用程序不支持Content-Length,那么服务器程序应该选择一种方法处理这种状况。最简单的方法就是当响应完成后,关闭与客户端的链接。

缓冲与流(Buffering and Streaming)

通常状况下,应用程序会把须要返回的数据放在缓冲区里,而后一次性发送出去。以前说的应用程序会返回一个可迭代对象,多数状况下,这个可迭代对象,都只有 一个元素,这个元素包含了HTML内容。可是在有些状况下,数据太大了,没法一次性在内存中存储这些数据,因此就须要作成一个可迭代对象,每次迭代只发送 一块数据。

禁止服务器程序延迟任何一块数据的传送,要么把一块数据彻底传递给客户端,要么保证在产生下一块数据时,继续传递这一块数据。

middleware 处理数据

若是 middleware调用的应用程序产生了数据,那么middleware至少要产生一个数据,即便它想等数据积累到必定程度再返回,它也须要产生一个空 的bytestring。 注意,这也意味着只要middleware调用的应用程序产生了一个可迭代对象,middleware也必须返回一个可迭代对象。 同时,禁止middleware使用可调用对象write传递数据,write是middleware调用的应用程序使用的。

write 可调用对象

一些已经存在的应用程序框架使用了write函数或方法传递数据,而且没有使用缓冲区。不幸的是,根据WSGI中的要求,应用程序须要返回可迭代对象,这 样就没法实现这些API,为了容许这些API 继续使用,WSGI要求 start_response 返回一个 write 可调用对象,这样应用程序就能使用这个 write 了。

可是,若是能避免使用这个 write,最好避免使用,这是为兼容之前的应用程序而设计的。这个write的参数是HTTP response body的一部分,这意味着在write()返回前,必须保证传给它的数据已经彻底被传送了,或者已经放在缓冲区了。

应用程序必须返回一个可迭代对象,即便它使用write产生HTTP response body。

这里能够发现,有两中传递数据的方式,一种是直接使用write传递,一种是应用程序返回可迭代对象后,再将这个可迭代对象传递,若是同时使用这两种方式,前者的数据必须在后者以前传递。

Unicode

HTTP 不支持 Unicode, 全部编码/解码都必须由应用程序完成,全部传递给或者来自server的字符串都必须是 str 或者bytes类型,而不是unicode

注意传递给start_response的数据,其编码都必须遵循 RFC 2616, 即便用 ISO-8859-1 或者 RFC 2047 MIME 编码。

WSGI 中听说的 bytestrings , 在Python3中指 bytes,在之前的Python版本中,指 str

错误处理(Error Handling)

应用程序应该捕获它们本身的错误,internal erros, 而且将相关错误信息返回给浏览器。 WSGI 提供了一种错误处理的方式,这就是以前提到的 exc_info参数。下面是 PEP 3333中提供的一段示例:

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的exc_info参数被设置成 sys.exc_info(),这个函数会返回当前的异常。

HTTP 1.1 Expect/Continue

若是服务器程序要实现 HTTP 1.1,那么它必须提供对 HTTP 1.1 expect/continue机制的支持。

其它内容

在 PEP 3333 中,还包含了其它内容,例如:

  • HTTP 特性

  • 线程支持

  • 实现时须要注意的地方:包括,扩展API,应用程序配置,URL重建等

这里就不做过多介绍了。

扩展阅读

这篇文章主要是我阅读 PEP 3333 后的理解和记录,有些地方可能没有理解正确或者没有写全,下面提供一些资源供扩展阅读。

  • PEP 3333
    不解释

  • WSGI org
    看起来好像官方网站的样子,覆盖了关于WSGI的方方面面,包含学习资源,支持WSGI的框架列表,服务器列表,应用程序列表,middleware和库等等。

  • wsgiref
    WSGI的参考实现,阅读源代码后有利于对WSGI的理解。我在GitHub上有本身阅读后的注释版本,而且做了一些图,有须要能够看这里:wsgiref 源代码阅读

另外,还有一些文章介绍了一些基本概念和一些有用的实例,很是不错。

相关文章
相关标签/搜索