WSGI 是 python web开发中的网关协议,全称是:Web Server Gateway Interface,翻译过来就是 Web 服务器网关接口。需要说明的是到目前为止 WSGI 有两个版本,分别是 WSGI 1.0(PEP 3333) 和 WSGI 2.0,但事实上只有 WSGI 1.0 流行,WSGI 2.0 还处于讨论阶段,只是一个计划。

WSGI 的设计目标就是为了解耦,解什么耦?解谁的耦?解的就是 HTTP 服务器和 python 应用服务器之间的耦,你可能会奇怪了,HTTP服务器和python服务器不是同一个东西吗?显然不是的,工业上我们常见的HTTP服务器有 Apache 和 Nginx,这些不是 python 服务器吧,那什么是 python 服务器?比如你用 Django 编写了一个网站,或者用 Tonardo/Flask 编写了一个系统,那么这就是 python 应用服务器了。也许你还是有疑问,我平时用 Django 写服务器直接浏览器就可以访问啊,和 WSGI 有半毛钱关系吗?

肯定是有关系的啦,你要知道框架自带的 Web 服务器只能做开发调试使用,你要想多机部署或者负载均衡都是不容易的,假如你使用专业的 Web 服务器来转发,那么一切就很简单啦。而且,你要知道的是,即使是框架自带的,也是有Web服务器和你的业务逻辑的,这其实就隐含了Web服务器和应用服务器了,那么如何在Web服务器之间和应用服务器之间有个默契的协议呢,能够让我们可以随时替换任意一个,那么这就是 WSGI 的作用了。

好,进入正题,来介绍一下 WSGI 的具体内容。既然是协议,那么就分为两端,分别是服务器端和Web框架端,服务器端调用一个由应用程序端提供的可调用者(Callable),至于它是如何被调用的,这要取决于服务器/网关这一端。

那下面就先来说应用程序怎么写,这里举两个例子,就举 PEP 原文的例子吧,我也不想去创造些什么高难度的例子来为难读者了,第一个是函数类型的 WSGI 接口。

```
def simple_app(environ, start_response):
    """这可能是最简单的应用程序对象了。"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!']
```

这就是一个最简单的 WSGI 应用程序,你可以做的就是将它保存到一个叫做 simple_wsgi_server.py 文件中,然后找一个支持 WSGI 的服务器跑起来就可以了。

为了让读者有更直观的感觉,我使用 gunicorn 作为 HTTP 服务器,让她和这个简单的 WSGI 服务器进行交互运行,你可以跟着下面的脚步来:

    pip install gunicorn

然后运行命令:

    gunicorn -w 4 simple_wsgi_server:simple_app

你将会看到以下输出:

    (test)LukedeMacBook-Pro:wsgi luke$ gunicorn -w 4 simple_wsgi_server:simple_app
    [2016-01-12 23:56:56 +0800] [46359] [INFO] Starting gunicorn 19.4.5
    [2016-01-12 23:56:56 +0800] [46359] [INFO] Listening at: http://127.0.0.1:8000 (46359)
    [2016-01-12 23:56:56 +0800] [46359] [INFO] Using worker: sync
    [2016-01-12 23:56:56 +0800] [46362] [INFO] Booting worker with pid: 46362
    [2016-01-12 23:56:56 +0800] [46363] [INFO] Booting worker with pid: 46363
    [2016-01-12 23:56:56 +0800] [46364] [INFO] Booting worker with pid: 46364
    [2016-01-12 23:56:56 +0800] [46365] [INFO] Booting worker with pid: 46365

此时,你打开浏览器访问一下 http://127.0.0.1:8000, 告诉我你看到了什么。

是的,没错,经典亘古的 Hello World 出现在了你的眼前。这就是一个最简单的 WSGI Server 示例了,看看,你不需要做多少功夫,只需要按照 WSGI 的规范来,编写一个函数即可。

看了一个函数的例子,接下来我们看看另外一种方式,类的方式,直接上代码先:

    class AppClass:
        """生成相同的输出,但是使用的是一个类。
        (注意:这里'AppClass'就是一个"应用程序",故调用它会返回一个'AppClass'的实例,这个实例就是规范里面说的由一个"可调用的应用程序(application callable)"返回的可迭代者(iterable)。

        如果我们希望使用'AppClass'的实例,而不是应用程序对象,那么我们就必须实现这个'__call__'方法,这个方法将用来执行应用程序,然后我们需要创建一个实例来提供给服务器/网关使用。
        """

        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!"

还是那样,将这段代码保存到一个简单的文件 simple_wsgi_class.py,然后使用这条命令:

    gunicorn -w 4 simple_wsgi_class:AppClass

同样的,你将会看到以下的输出:

    [2016-01-13 00:01:12 +0800] [46406] [INFO] Starting gunicorn 19.4.5
    [2016-01-13 00:01:12 +0800] [46406] [INFO] Listening at: http://127.0.0.1:8000 (46406)
    [2016-01-13 00:01:12 +0800] [46406] [INFO] Using worker: sync
    [2016-01-13 00:01:12 +0800] [46409] [INFO] Booting worker with pid: 46409
    [2016-01-13 00:01:12 +0800] [46410] [INFO] Booting worker with pid: 46410
    [2016-01-13 00:01:12 +0800] [46411] [INFO] Booting worker with pid: 46411
    [2016-01-13 00:01:12 +0800] [46412] [INFO] Booting worker with pid: 46412

从浏览器中输入: http://127.0.0.1:8000 ,请再次告诉我你看到了什么!!

Ok, 这就是 WSGI 应用端的使用。

下面我就来详细解释一下 WSGI 应用端的编写规范。

首先,WSGI 规定应用程序对象必须接受两个位置参数,我们就以 simple_wsgi_server.py中的函数为例子进行讲解,我们发现函数的定义是这样的:

    def simple_app(environ, start_response):

simple_app 接受了两个参数,分别叫做 environ 和 start_response,但是名字不是必须的,但是为了方便阅读理解,我们一般都命名成这样,也就是说,其他 WSGI 只规定了这个应用函数必须有两个参数,但是没有规定他们的名字是什么。

首先说第一个参数 environ(刚才说了不一定要叫environ,但是习惯上这么写),这个参数要求是一个字典对象,对,而且还要是 python 原生的字典对象,别瞎传。应用程序必须允许以任何它需要的方式来修改这个字典, environ还必须包含一些特定的WSGI所需的变量(在后面章节里会提到),有时也可以包含一些服务器相关的扩展变量,通过下文提到的命名规范来命名。

然后是第二参数 start_response, 这是个 callable 对象,如果要说得通俗一点,一将他理解为函数即可,但是必须强调先,不一定是函数,但是可以这样理解。然后这个对象调用必须要接受两个位置参数以及一个可选参数,还是老惯例,我们没有强制要求名字,但是,习惯上我们将这三个参数写为:status, response_headers, exc_info。也就是说我们可以传入这样的一个函数:

    start_response(status,response_headers)

每个参数都是什么意思呢?这里也来拆解一下吧:

status:形式如 "200 OK" 这样的字符串 response_headers:形式如 (header_name,header_value) 参数列表的元组,用来描述HTTP的响应头 exc_info:只有在应用程序捕获到了错误并试图在浏览器中显示错误的时候才会被用到

start_response 的参数就是这样了,接下来就需要考虑返回值了,WSGI 规定 start_response 必须也返回一个 callable 对象,惯例讲这个对象称为 write,并且这个对象得接受一个参数:

    write(body_data)

通过调用这个对象得参数都将被当作 HTTP 内容返回到客户端!!!

到这里,simple_app 的两个参数 environ 和 start_reponse 也讲解得差不多了,那就接下来讲解返回值了。WSGI 规定 simple_app(应用服务器) 必须返回一个能够生成0个或多个字符串的 iterable。如果你对 iterable 这个概念不熟悉的话,我可以简约得告诉你你可以简单的理解为一定要返回一个 字符串列表 就好了。而这个字符串列表里面的内容也会被直接返回到客户端中。

好的,这就是一个简单的 WSGI 介绍了。如果你想知道更多关于 WSGI 详细的介绍,请查看我的《WSGI 深入一步》的文章。