WSGI协议

Posted 哦...

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WSGI协议相关的知识,希望对你有一定的参考价值。

原文地址是尝试理解Flask源码 之 搞懂WSGI协议

 在原文基础上有删节。

  1. 一个Web应用都是从接受请求开始, 通过分析请求, 匹配对应的路由规则, 再去调用视图函数, 返回响应结果的, 那么Flask中接受请求的入口在哪里?
  2. 由于Flask是遵守WSGI协议的, WSGI协议是Python中的一种Web规范, 一定有大家共同遵守的规则, 那么WGSI协议中, 大家遵守的规则是什么?
  3. 通过大家都遵守的规则, 也许就能找到Flask中处理请求的入口在哪里了. 同时, 任何遵循这个协议框架的请求入口都能找到.

那第一步就是研究一下究竟什么是WSGI.

WSGI

WSGI全称Web Server Gateway Interface, 不要被名字唬住, 先看这么一句话一起体会一下WSGI是什么:

This document specifies a proposed standard interface between web servers and Python web applications or frameworks, to promote web application portability across a variety of web servers.

这句话来自PEP3333的摘要部分, 简单翻译一下就是

这份文档(指的就是PEP3333)详细说明了Web服务器和Web应用之间的 标准接口, 旨在提升Web应用的 可移植性

这里面的关键字就是:

  • 可移植性: 这是WSGI存在的目的
  • 标准接口: 这是WSGI定义的规则

如果知道了WSGI的目的, 理解后续的内容就轻松不少, 那我们就先讲一下WSGI的目的, 理解一下可移植性的含义吧。

 WSGI协议的作用

当我们访问某个网站的时候:

  1. 浏览器作为用户代理为我们发送了HTTP请求
  2. 这条请求经过长途的跋涉终于找到了能够接受它的服务器
  3. 服务器上的HTTP服务器软件会将请求交给Web应用处理该请求,得到用户想要的数据
  4. Web应用再将数据交给HTTP服务器软件, 再由HTTP服务器软件返回响应结果
  5. 浏览器接受到响应, 显示响应内容

基本上就是这么一回事:

这里面一共出现了三个角色, 分别是:

  1. 服务器: 硬件层面, 也就是机房里面的计算机(或者集群), 运行着操作系统+一些软件.
  2. HTTP服务器软件: 运行在服务器上的软件, 用于监听客户端请求,将接受到的请求交给Web应用, 再将Web应用的响应结果返回给客户端.
  3. Web应用: 接受服务器软件传过来的请求, 处理请求, 并返回处理后的结果给服务器软件.(我们的Flask, Django都是这个层面的)

由于服务器硬件咱们也没什么好说的, 因此接下来提到的服务器(包括HTTP服务器)指的都是HTTP服务器软件.

对于初学者来说服务器好像就是硬件嘛, 和软件没有关系, 但实际上, 服务器既可以指硬件, 也可以指软件.  维基百科.

WSGI协议的作用在这里就是连接服务器软件Web应用桥梁, 这两方规定一系列协议, 要求两者之间传输的数据对方都能看得懂.

那这么做有什么好处呢?其实就是上面说的可移植性了, 如果对可移植性还是不太理解也没有关系, 下面让我们来看看这里面一个问题.

既然服务器软件和Web应用都可接受请求, 返回请求, 为什么搞这么复杂?非要搞个协议, 接受请求,处理请求和返回请求的都是一个人不可以么?我和我交流还需要协议么?

没错, 上一个人也是这么想的, 而且事实上, 我们确实可以这么做.接下来我们来看一下三种模型, 来更好的理解一下WSGI协议和可移植性.

第一种模型 - tornado

tornado是由Facebook收购并且开源出来的Web框架, 他的第一个特点就是将HTTP服务器Web应用整合到了一起.

所以可以用tornado搭建出来的服务器模型如下

这就是我们刚才说的接受请求, 处理请求, 返回请求都是一个人, 感觉也不错.

但是请注意了, 虽然表面上看起来这好像是一个人处理的, 但是我们在编写处理业务逻辑的代码时, 肯定不会去碰服务器相关的代码, 只需要写好视图和路由就可以了, 因此对于tornado来说, 他的服务器部分和逻辑处理部分还是分开的. 这就相当于虽然是一个人, 但是他的手和大脑是两个部分.

实际上长这样

这里有两个问题.

  1. 如果我不想用tornado这个框架写逻辑部分, 那么我也一定不能使用它的服务器部分.
  2. 如果我就想用它的Web框架, 想换一个性能更加强大的服务器(软件), 似乎也做不到.

谁叫他们是一个人呢!

这就是所谓的没有可移植性

请注意:
实际上, tornado的服务器和Web框架是兼容WSGI协议的, 有兴趣的话可以自己搜索一下相关内容.举这个例子是因为在Python Web框架中, tornado实现了高性能的HTTP服务器仅此而已.

如果百度过Tornado的也许知道他有一个很大的特点就是, 异步, 非阻塞, 高性能之类的. 其实, 这个特点说是他的HTTP服务器部分, tornado服务器擅长处理多个长连接, 可以用于在线聊天等业务场景.

第二种模型 - WSGI服务器+Web框架

终于轮到WSGI出场了.

对于一个遵守WSGI协议的服务器和Web应用来说, 它并不在意到底是谁传过来的数据, 只需要知道传过来的数据符合某种格式, 两边都能处理对方传入的数据.

打个比方, 你特别特别想吃水饺, 于是请了俩人, A专门擀面皮, B专门包饺子, 由于擀面皮的人动作比较快, A还要负责把包好的饺子拿过来下锅, 这样你才能吃到水饺.

A擀面皮的时候需要遵守 饺子协议, 一定要把面皮擀成圆的(没错, 馄饨皮就是方的!), 并且皮还不能太大, 太厚, 这样B才能保证包出饺子来.

同样的, B也要遵守 饺子协议, 再拿到饺子皮后使用灵巧的手法捏出造型各异的水饺, 但是他包的一定是饺子, 不能是一坨A看不懂的东西!

B把包好水饺拿给A去下饺子, 好在这二人都遵守了 饺子协议, A一看, 不错, 这家伙包出的的确是饺子, 那我也保证我能够最后煮熟的东西是水饺了.

最后, 你吃到了水饺, 但是A个B始终也不认识对方, 如果在这个过程中你吧B换了另一个遵循 饺子协议的C, 他与A还是能够紧密合作.

A只在乎自己擀出的是面皮, 拿到的是饺子

B只在乎拿到的面皮, 包出的是饺子

于是我们可以搭建像这样的模型:

目前被广泛应用的WSGI服务器(又称为WSGI容器), 主要有gunicornuWSGI.完整列表

而遵循WSGI协议的python web框架有DjangoFlaskPyramidweb2pyBottle 等等等.完整列表

最终, 我们有:

随便怎么组合都可以, 怎么样, 是不是可移植性更强了?

第三种模型 - 最终形态

然而在真正的生产环境中, 以我们上面的模型是完全扛不住很多人一起访问的, 于是就有了服务器集群的概念, 使用一个性能更好的服务器打头阵, 然后它所做的事情就是把接收到的请求再分发给其他计算机去处理.

这就好像后来你开了个饺子馆, 为了能够接待更多的人, 你必须再雇对几个包饺子的组合. 当然, 还必须有一个收银人员.

这里拿NGINX来举例

在这个模型中, 我们的WSGI服务器起到了承上启下的作用, 它只处理nginx丢给他的请求.

当然, 这也不意味着我们对WSGI服务器性能要求不高了, 因为真正去调用Web应用的还是WSGI服务器, 我们只不过使用NGINX去实现了负载均衡.

其实第三种形态远没有这么简单. 我了解的也不是很多, 但是到了这里, 我们应该已经完全理解了WSGI协议的目的了. 那接下来就来看一下WSGI协议到底指定了哪些规则吧!

了解过部署的朋友可能知道还有另一个高性能的服务器-Apache, 但是通过阿帕奇与WSGI应用的交互似乎是通过阿帕奇自带的模块去进行的.

How to use Django with Apache and mod_wsgi

WSGI协议简单分析

WSGI协议这么厉害!那一定很难实现吧!

其实WSGI协议内容没有这么神秘, 也特别容易实现, 它只是一系列简单的规则, 这个规则有多简单呢, 一会儿我们用3行代码就可以写完一个简单的WSGI应用, 而且不需要导入任何模块和包.

由于涉及服务器与Web应用交互, WSGI的规则分为两个部分, 分别对WSGI服务器端和WSGI应用端做了要求. 作为Web后端开发人员, 我们和框架(应用)打交道, 只需要了解WSGI协议对应用的要求.(我才不会说我也没有看服务器端的协议内容)

假设现在已经有了一个WSGI服务器, 现在需要编写一个WSGI应用, 那么我们的应用该如何和服务器进行交互呢?

换句话说, 我们的应用需要接受什么样的数据, 需要返回什么样的数据呢?

OK, 让我们把WSGI应用端规则罗列一下:

  1. Web应用必须是一个可调用对象, 它必须*接受*两个参数
  2. 其中第一个参数是environ(字典类型), 另一个参数是start_response(函数)
  3. 需要在返回响应之前调用start_response
  4. 最终返回的是一个可迭代对象
什么?你问我什么是可调用对象, 什么是可迭代对象?不如去问问 吧.

这两个参数的具体内容为:

  1. environ 本次请求的所有内容, 我挑了几个看着眼熟的.
  'REQUEST_METHOD': 'GET',    
      'RAW_URI': '/',
     'SERVER_PROTOCOL': 'HTTP/1.1',
     'HTTP_HOST': '127.0.0.1:5000',
     'HTTP_CONNECTION': 'keep-alive',
     'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
     'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9,en;q=0.8',
     'REMOTE_ADDR': '127.0.0.1',
     'REMOTE_PORT': '62445',
     'PATH_INFO': '/',
     ... 
  1. start_response函数, 返回数据之前需要调用, 用于设置状态码响应头
  • 注意, 调用start_response函数时, 第一个参数用于设置状态码, 是一个字符串
  • 第二个参数用于设置响应头, 要注意该参数的格式(见下方代码).

根据这些个规则, 我们很容易写出一个遵循WSGI协议的Web应用:

# 1. 必须是一个**可调用对象**, 它必须接受**两个参数**
def demo_app(environ,start_response):
    # 2. 其中第一个参数是`environ`(字典类型), 另一个参数是`start_response`(函数)

    # print(environ)

    # 3. 返回之前调用一次start_response  返回状态码和响应头, 注意参数格式, 不同的请求头信息用元组隔开
    start_response("200 OK", [('Content-Type','text/html')])  

    # 4. 返回一个可迭代对象, 这里就是最终的响应体
    return [b"<h1>Hello WSGI!</h1>"]

是不是很简单? 排除注释一共就只有3行代码.

至此, 我们就看完了WSGI协议应用端的部分. 那么现在, 来看一下Flask中的WSGI是怎么体现的吧

上面的函数是一个完全符合WSGI协议的函数, 因此 他就可以作为一个WSGI应用, 你甚至可以将它跑起来!

如果你已经安装了Gunicorn, 将上述代码保存, 并命名为 my_app.py, 你只需要将终端切换到该文件所在的路径下, 然后输入

gunicorn -b 127.0.0.1:5000 my_app:demo_app

就可以正常运行起来, 此时用浏览器访问127.0.0.1:5000, 就能看到返回的结果!
你也可以将print所在行注释掉, 看一下environ里面包含的完整信息

注意: Gunicorn只支持类Unix系统! 不支持Windows!

 

Flask中的WSGI

我们知道了, 遵循WSGI协议的应用一定是一个可调用对象, 所以它既可以是一个函数, 也可以是一个实现了__call__()方法的类.

话不多说, 上源码

class Flask(_PackageBoundObject):
    .....

    def __call__(self, environ, start_response):
         return self.wsgi_app(environ, start_response)
    ...

我们轻松找到了Flask中处理请求的入口, 但是, 它又调用了另一个方法, 趁热打铁, 让我们来看一下这个方法.

class Flask(_PackageBoundObject):
    ...

        def wsgi_app(self, environ, start_response):
            ctx = self.request_context(environ)
            error = None
            try:
                try:
                    ctx.push()
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.handle_exception(e)
                except:
                    error = sys.exc_info()[1]
                    raise
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)

    ...

这个就是Flask中真正请求的入口了, 在兜里一个圈子后, 最终将两个参数传给了wsgi_app()这个方法, 并交由它来处理.

所有的请求就将会在这几行代码中处理完成, 并且最终返回. 只要搞懂了这几步, 就能知道Flask是怎么处理请求的啦!

补充

  1. 我们在上面只讨论了服务器和应用这两个角色, 其实, 还有另外一个角色叫做中间件middleware, 顾名思义, 它存在于这两者之间. 对于服务器来说, 它是一个应用, 而对于应用来说, 它又是一个服务器, 中间件必须也满足两方的WSGI协议.
  2. Flask中自带的服务器也是一个WSGI服务器, 只不过它的性能不好, 只能用于测试, 但是原理都是和上面一样的.
  3. Python的WSGI协议是参考了Java的servlet协议.
  4. 在其他语言里面也有类似WSGI协议的规定, 比如Ruby中的Rack, Perl中的PSGI,他们的目的都是一样的.
  5. uWSGI这个服务器也有自己的协议, 就叫做uwsgi, 但是它也支持WSGI协议. 另外uWSGI是C写的, Gunicorn是Python写的. 这二位应该是在部署的时候的唯二之选.

以上是关于WSGI协议的主要内容,如果未能解决你的问题,请参考以下文章

WSGI协议

wsgi 协议

WSGI协议

马牧野面皮加盟低费用项目,轻松月入十几万!

浅析WSGI协议

Python Web开发 - WSGI & uWSGI协议