WSGI 协议运行原理

0. 前言

基本概念:
WSGI:全称是Web Server Gateway InterfaceWSGI是一种规范,用来描述web server如何与web application通信的规范。serverapplication的规范在PEP 3333中有具体描述。要实现WSGI协议,必须同时实现web serverweb application,当前运行在WSGI协议之上的web框架有 FlaskDjango等

WSGI协议主要包括serverapplication两部分:

  • WSGI server负责从客户端接收请求,将request转发给application,将application返回的response返回给客户端;
  • WSGI application接收由server转发的request,处理请求,并将处理结果返回给serverapplication中可以包括多个栈式的中间件(middlewares),这些中间件需要同时实现server与application,因此可以在WSGI服务器与WSGI应用之间起调节作用:对服务器来说,中间件扮演应用程序(执行程序),对应用程序来说,中间件扮演服务器(WSGI服务器)。

WSGI协议其实是定义了一种serverapplication解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的serverapplication组合实现自己的web应用。例如uWSGIGunicorn都是实现了WSGI server协议的服务器,DjangoFlask是实现了WSGI application协议的web框架,可以根据项目实际情况搭配使用。

注:

  • 贴图代码都只是截取了关键入口的那部分关键语句和函数方法, 其他细节可看详细代码,我们把主流程走一遍就行了 。
  • 我每个标记了粉红色加粗文字的都是那一步或者几小步的主要工作,前面标了序号,完了可以把他们串联起来,就是一个完整的流程了。

一. 程序入口:runserver命令

python manage.py runserver 0.0.0.0:8000

注:runserver文件里的Command类继承了Django的BaseCommand,从而可以通过python manage.py <文件名> 运行该文件,命令运行的函数为handle()

文件地址:

/<我自己的虚拟环境地址>/lib/python3.7/site-packages/django/core/management/commands/runserver.py

注:可以找到文件从这里开始查看源码

  • <1>执行runserver命令handle()时调用run()方法:

WSGI 协议运行原理_第1张图片

  •  执行run()时调用inner_run()方法:

WSGI 协议运行原理_第2张图片

  • 执行inner_run()时<2>调用WSGI的入口函数run()

WSGI 协议运行原理_第3张图片

 

 二.  WSGI 入口:basehttp模块

注:由于inner_run()时调用了basehttp模块的WSGI服务入口函数run(),执行的文件变成了basehttp.py(文件名即模块名)

文件地址:

/<我自己的虚拟环境地址>/lib/python3.7/site-packages/django/core/servers/basehttp.py

  • <3>实例化WSGIServer并执行对象的server_forever()方法(该方法继承自socketserver模块的BaseServer类),forever代表持续监听的意思,代码中有一个while语句(while not self.__shutdown_request:),意思是在请求处理完毕或者处理出现异常时,继续保持监听。代码如下:
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    server_address = (addr, port)
    if threading:
        httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
    else:
        httpd_cls = server_cls

    # 实例化WSGIServer,即WSGI协议的server部分
    # 传入了WSGIRequestHandler参数,会在WSGIServer内部调用来处理请求
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)

    if threading:
        httpd.daemon_threads = True

    # 设置WSGI协议的web application部分,即WSGIHandler
    httpd.set_app(wsgi_handler)

    # 启动监听,该方法继承自父类BaseServer
    httpd.serve_forever()

三. WSGI 协议server 部分:

server部分主要分别由以下三个类(皆继承自python自带类)依次处理,将http请求转化为app(如django)可以处理的符合WSGI协议的请求:

1. WSGIServer:

继承关系:

WSGIServer(

    simple_server.WSGIServer(

        HTTPServer(

            socketserver.TCPServer(

                  BaseServer    # serve_forever()方法继承自它

))))

  • 它的实例化对象的serve_forever()方法作为程序入口
  • 接收http请求
  • 实例化WSGIRequestHandler
  • 将接收的http请求传给WSGIRequestHandler对象;

2. WSGIRequestHandler:

继承关系:

WSGIRequestHandler(

    simple_server.WSGIRequestHandler(

        BaseHTTPRequestHandler(

            socketserver.StreamRequestHandler(

                BaseRequestHandler))))

  • 它主要起承上(对接WSGIServer)启下(对接ServerHandler)的连接作用;
  • 接收到WSGIServer传来的http请求并处理;
  • 它实例化时会自动调用handle()方法,该方法中会实例化ServerHandler;
  • 将自己处理好的请求传给ServerHandler对象;

3. ServerHandler:

继承关系:

ServerHandler(

    simple_server.ServerHandler(

        SimpleHandler(

            BaseHandle

)))

  • 它主要起WSGI server部分和WSGI application(简称app,如django)部分的对接作用;
  • 接收来自WSGIRequestHandler对象处理好的请求,并将其组装成环境变量
  • 实例化WSGI application(WSGIHandler);
  • 将配置好的环境变量和回调函数start_response()传递给app对象(无外乎对数据库进行增删改查并返回响应,也就是我们写的那些代码);
  • 通过回调函数start_response()设置好对象的部分属性将app的处理结果发送到客户端(状态码和响应头)的说法有待考证,因为回调函数只是返回了write函数对象,并没有去执行。

流程大概如下(盗个图,改了点):

WSGI 协议运行原理_第4张图片

 

接下来就是server部分的主要流程:

 

1. WSGIServer:

由于继承python自带的BaseServer类方法,所以文件地址:

/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/socketserver.py

  • 执行server_forever()时<4>调用_handle_request_noblock()方法处理请求:

WSGI 协议运行原理_第5张图片

  • <5>实例化WSGIRequestHandler
  • 注:WSGIServer实例化时传入了WSGIRequestHandler参数,如代码所示:httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)

WSGI 协议运行原理_第6张图片

2. WSGIRequestHandler:

文件地址:

/<我自己的虚拟环境地址>/lib/python3.7/site-packages/django/core/servers/basehttp.py

  • 在上面WSGIServer中WSGIRequestHandler已经实例化了,实例化时会自动<5>调用handle()方法
  • 实例化时传入了三个参数,如上图,最后一个是‘self’,也就是WSGIServer对象(为什么我创建其他对象的时候还要把自己传给它呢?),留着,后面会用(也就是下图的‘server’):

WSGI 协议运行原理_第7张图片

  • handle()方法被WSGIRequestHandler重写(其实代码一个字没变,就是复制了一份,因为在方法中<6>实例化ServerHandler的类从python自带的simple_server模块中的ServerHandler变成了basehttp模块中的ServerHandler),用来处理单个http请求;
  • ServerHandler实例化后,<6>调用run()方法,这个run()方法就是我们WSGI server部分的最后一个类ServerHandler的入口:

WSGI 协议运行原理_第8张图片

3. ServerHandler:

由于继承python自带的BaseHandler类方法,所以文件地址:

/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/wsgiref/handlers.py

番外篇:

  • 在上面实例化时,传入了一个参数,也就是‘self.server.get_app()’,你看,我就说那个‘server’留着有用吧,这个‘server’就是WSGIServer对象,它的get_app()方法可以获取到application(也就是WSGI application部分,后面简称app),而WSGI的入口,WSGIServer实例化后设置了application(httpd.set_app(wsgi_hxandler)),好了好了,怕你们忘了在哪儿了我再贴一次代码:

WSGI 协议运行原理_第9张图片

要是又有人问你怎么知道设置的这个 app是WSGIHandler?我...我......我.........就告诉你吧!!!

还记得我们开天辟地的启动命令吧!它给basehttp模块的run()函数传了一个handler,也就是上面截图中那个wsgi_handler:

这个handler参数又是通过命令中的get_handler()方法获取的: 

真的是老母猪的胸罩一套有一套...这个继续追溯下去,不想说话了,直接上图:

 这些找到这个WSGIHandler对象(注意是对象,不是类)了吧,就是我们的app。

言归正传:

  • 我们上一步实例化了ServerHandler并执行了它的run(app对象)方法,<7>调用app处理请求
  • 调用app对象时传入了两个参数, 一个是前面ServerHandler将http请求组装成的环境变量, 一个是作为app处理成功后的回调函数(题外话:这应该是阻塞的,只有app执行成功返回之后才会执行

WSGI 协议运行原理_第10张图片

四. WSGI 协议app 部分:

WSGI application应该实现为一个可调用对象,例如函数、方法、类(包含`call`方法)。需要接收两个参数:

  • 一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文,一般叫做environment(编码中多简写为environenv
  • 一个用于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数

通过回调函数将响应状态和响应头返回给server,同时返回响应正文(response body),响应正文是可迭代的、并包含了多个字符串。

我们知道了实例化ServerHandler对象的run()方法传入的是app对象(WSGIHandler对象),为何给对象传参数就会返回数据呢?

那就顺便巩固下基础(复制过来的):

__call__():Python中,只要在创建类型的时候定义了__call__()方法,这个类型就是可调用的。

Python中的所有东西都是对象,其中包括Int/str/func/class这四类,它们都是对象,都是从一个类创建而来的。元类就是创建这些对象的东西,type就是Python的内建元类。

其中,func是可调用的对象,说明在创建它的类型(父类或它本身)的时候,定义了__call__()方法。

原来是因为WSGIHandler类中定义了<__call__>呀!大彻大悟,看来基础薄弱简直是洪水猛兽啊!

那下面就来看看WSGIHandler中是如何定义<__call__>的:

  • <8>解析环境变量(ServerHandler组装好的
  • <8>获取程序响应(也就是我们平时写的逻辑代码,当然中间还有很多中间价处理包装了的)

WSGI 协议运行原理_第11张图片

到这里我们的一个http请求主要的处理流程差不多就已经完了,可以不用往下看了,需要继续了解的如何返回响应的,那就往下瞧:

  • <9>执行回调函数start_response(),看一下回调时执行了啥:上图可以看出,回调函数的返回值并没有被接收,只是像18岁少女一样单纯地执行了一下,我还没看代码,我是边看代码边写笔记的,我猜测是为了给响应铺平道路,把响应时需要的环境搭好,开始看代码:

WSGI 协议运行原理_第12张图片

看嘛!我就说嘛,红框中明显就是设置ServerHandler对象的属性,先把响应需要使用到的对象熟悉先配好,然后返回了self.write......这是个对象呀,也没有变量接收它呀,所以回调到这儿就没啥事儿了,shutdown。

另外通过注释可以看出,这是为了我们PEP 3333 -- Python Web Server Gateway Interface(顺便查了下)特殊服务的。

  • <10>返回响应:上面可知,ServerHandler对象的result属性接收了app的响应(一个HttpResponse object),然后执行finish_response()方法:

WSGI 协议运行原理_第13张图片

看看都做了哪些事:1. 把接收到的app响应 中的数据循环遍历发送到客户端(通过write()方法);2. finish_content()清理战场;3. 关闭对象,等待下一个请求到来,once again!

WSGI 协议运行原理_第14张图片

上面未实现的两个方法在它的子类SimpleHandler中被实现了。

基本一个http请求的流程就差不多结束了,game over!(不一定准确哈,只是自己看代理逻辑的理解,如有错误或者不恰当的欢迎指正!)

最后把上面左右的步骤串起来就是:

真相は一つしかない。(推一推眼镜)

<1>执行runserver命令

<2>调用WSGI的入口函数run()

<3>实例化WSGIServer并执行对象的server_forever()方法

<4>调用_handle_request_noblock()方法

<5>实例化WSGIRequestHandler,自动调用handle()方法;

<6>实例化ServerHandler并调用run()

<7>调用app处理请求

<8>解析环境变量并获取程序响应

<9>执行回调

<10>返回响应

终于ojbk了!

有不足或者错误请指正!どうぞ お願いしましょう。

你可能感兴趣的:(学习总结,Django,工作总结)