02-Python中wsgiref[WSGI参考]

WSGI协议的简单实用

      WSGI是所有使用Python的Web框架必须遵守的协议,Python中有一个简单的WSGI服务器的实现,名为wsgiref包,意为WSGI参考实现,通过这个包,我们来了解一下WSGI的具体实现

from wsgiref.simple_server import make_server, demo_app

server = make_server('127.0.0.1', 8000, demo_app)
server.serve_forever()

      是不是看起来简单的离谱。,接下来分别看一看make_serverdemo_app干了啥。

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server

      可以看到 make_server 只是生成了一个服务器对象,设置了ip和端口号,传递了 demo_app, 最后返回了这个服务器对象,在主题代码中调用了 server.server_forever 看起来是一个启动了服务器。

def demo_app(environ,start_response):
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k,v in h:
        print(k,'=',repr(v), file=stdout)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]

      demo_app通常是我们需要开发的部分,这个参数函数就是WSGI协议的关键,web 服务器接收请求,解析出环境变量写进 environ,并且传递一个可执行的函数,这个可执行函数的最重要的工作就是返回状态码,因为返回状态码是跟后端的工作情况有关的,必须由开发app的人来指定。demo_app的主要工作就是提取了环境变量中的值,写进一个内存缓冲区,然后将内存缓冲区中的值返回出去。我们在浏览器上看到的一对对键值对就是demo_app解析出来的环境变量,

WSGI协议的思考

      WSGI是一种协议,规定了python Web 服务器与应用之间的交互,服务器是服务器,应用是应用,他们之间的关系就好像手机上的软件跑在操作系统之上,手机上的软件是操作系统来调用的。同样的,web应用或者说是框架是由web服务器来调用的。web 服务器接收到请求,对请求进行封装,然后将一个封装后的环境变量和一个回调函数一起传给应用。

      现在我们回想一下,接收网络请求,解析并封装网络请求相关参数,以及返回网络请求,这些步骤其实是共通的,在一个web开发中,属于共同代码,可以抽取出来,就算我们开发出花儿来,他也是这么些步骤。顶多是做出性能以及稳定上面的优化,但是他绝对不可能针对千变万化的web请求做出反应,因此有必要将业务代码抽取出来,做出解耦。

      web服务器数量比较少,如果每一种 web 框架都要开发出一套自己的web服务器,那样也太繁琐了,而且为什么不直接使用最好的web服务器呢,这种情况下就需要约束 web 服务器与 web 应用之间的沟通,于是就有了WSGI协议。有了协议,以后就算出现了更好的web服务器,web应用也可以直接使用,对于web服务器来说同样如此,如果有更好的 web 应用,也可以直接用到 web 服务器上。

      web开发使用的是HTTP或者HTTPS协议,HTTP协议有一些请求头,里面包含一些和请求相关的东西,这些数据的解析乃是web开发所必需的,因此直接交给web服务器解析出来就好了,再就是web返回必须有状态,因为HTTP请求和状态息息相关,读者可以自己做个试验,返回值可以什么都没有,但是不能没有状态,因此还需要一个返回状态的设定。start_response 函数是web服务器传递给 app 应用的。不是我们自己写的。

      以上是我个人对WSGI协议的理解。那么接下来带着这些理解去翻看 wsgiref 的源码,看看他是不是按照上述的结构来生成的一个服务器。

带着思考去看 wsgiref 的源码

      注意:wsgiref的源码有点难懂,不能直接从 serve_forever 函数去看,可能看不明白,我们先从 make_server 函数开始看,进入 make_server 函数

make_server

      在我们没有指定 server_class 的时候他就是 WSGIServer 看这个名字就知道是遵守 WSGI 协议的服务器, handler_class 请求控制器类,这个我们后面再说。
      make_server 函数构造生成了一个 WSGIServer 的对象,同时设置web应用,这个应用就是我们传递进来的 demo_app。接下来进入 WSGIServer 类看看

WSGIServer

      可以看到 WSGIServer 继承自 HTTPServer, 也就是在 HTTPServer 的基础上增加了一点点内容,分别是设置环境变量,设置和获取web应用,就是 demo_app, 他没有重写 初始化函数,则初始化函数调用的是父类的。回想一下前面的对 WSGI 协议的猜想,app 是 WSGI协议中的东西,因此 app 理论上在父类中式看不到的,确实如此,在 make_server 中主动调用了 set_app 方法,因此 server 对象的 application 才会有值。依据WSGI 的内容,我们猜想 HTTPServer 以及更上层,应该是看不到跟 WSGI协议相关的东西的。接下来我们去 HTTPServer 中去一看究竟

HTTPServer & TCPServer

      HTTPServer 确实也没啥东西,就是一个将服务器名和端口号写进对象属性中,再看看 TCPServer

      TCPServer 内容也是乏善可陈。在我们自己写网络编程的小程序玩一玩的时候,就写过TCP协议通信,只不过python这里对它进行了封装。写的比我们更加完善

      在这部分的代码我们也就知道了,HTTP 协议确实是建立在 TCP 协议之上的,这个协议本身就是 TCP 协议传输的内容而已

      到了这里我们进行一个大单的猜想。按照分层开发和解耦的思想,HTTP 协议中涉及到的一些请求头应该是在 HTTPServer 中解析出来的。但是我们看 HTTPServer 代码并没有看到它的解析代码。这是为什么?我们放到后面再去解释。

      接下来再看看 TCPServer 的父类,因为我们从下到上看了这么多的 Server 类,还没看到一个最关键的东西,serve_forever 方法

BaseServer

       Server类的基类,看一下这个类的所有的方法,可以发现,都是跟服务器和请求相关的方法,以及我们启动的一个最简单的WSGI服务器的入口方法 serve_forever
也就是说 wsgiref 模块中,启动服务器的方法一定是这个服务器基类的 serve_forever 方法,因为从下向上找,我们没有找到其它的 serve_forever 方法。

serve_forever

      诶 serve_forever 方法的内容我们看不懂,我们自己写网络通信的代码的时候还有什么 socket,以及 accept,但是这里一个都看不到,怎么搞?但是我们可以大胆分析一下。

      服务器不可能只接受一个请求就完事了,他必须不断接受请求,然后调用具体的处理函数,极有可能是一个线程或者进程去解决这个请求,那么他肯定是在一个循环里,一个 While 永久循环中。假设ServerBase也遵守这个原则,那么我们尝试据此分析一下源码。正好 serve_forever 中就有一个 While 永久循环。

serve_forever 源码思考

      看代码的时候我们没必要直接搞懂每一段代码,先看核心处,如果事实跟我们的猜想不相符合再去思考其它的内容

  1. 只要请求没有关闭,就一直接收请求,这个比直接使用 While True 好,将关闭服务器交到我们手上,这样我们还可以在手动关闭服务器的时候做一些其它的收尾工作,比如关闭连接什么的,更加安全
  2. ready 可能是接收到什么东西,比如收到了一个请求。
  3. 在 ready 为真或者有值的情况下,就执行 _handle_request_noblock 方法,至于下面的 service_actions 则不用管,在分析每一层的 Server 类的时候,service_actions 方法只有 ServerBase 存在,ServerBase.service_actions 的内容为空,因此不予理会,我们需要把重点放在 _handle_request_noblock 中。

_handle_request_noblock

      我们看到一个方法调用的时候,在Pycharm中直接进入它的方法体中,看到的东西可能跟我们想象的不一样,这是因为,如果子类覆盖了父类的同名方法的时候,调用的实际上是子类的这个方法,我们读源码的时候要时刻警惕,这个方法实际执行的是当前类的还是子类的。

      回过头看前面的所有的 Server 类,我们可以发现 _handle_request_noblock 方法没有被重写过,因此这个方法执行的一定是 ServerBase 类的,下面来读一下 ServerBase._handle_request_noblock 的源码

      _handle_request_noblock 方法首先是获取请求,然后是校验请求,接着是新起一个进程处理这个请求,如果这中间任何一步出了问题都会关闭请求。

get_request

      注意 ServerBase 是没有 get_request 这个方法的,我们一路看过来,只有 TCPServer 才有这个方法,因此调用的是这个方法。

终于发现了 accept 方法了,也就是说 wsgiref 包,使用了网络编程,他也离不开 accept 这个方法。

verify_request

      verify_request 方法只有 BaseServer 才有,不过在 wsgiref 这个体系中,他啥也没干

process_request & finish_request

      到了这里基本上 serve_forever 方法都看完了,但是需要注意的是,到目前为止,上述出现的所有 request 是个什么东西?它是一个 socket,是服务端的 socket 监听到请求的时候返回的那个 socket,是与客户端的 socket 进行通信的东西,可不是我们在进行 web 开发的时候那个 request,我最开始就被这个同名给搞晕了,怎么着都没看懂,剩下的所有方法,一路看过去,不管在哪一层,代码都十分之简单,都是对 request 这个socket 进行操作的,依然找不到我们想要的东西。

      还剩下什么没看?RequestHandlerClass

      所有的秘密都在这里将揭晓。到了这里我们在不断的去看源码,靠脑子去推测,就太累了,我们已经知道了大体的流程,就该使用bug调试来看看,前面所有的推测是否符合预期。验证推测我们后续再做,接下来通过调试分析,这个 RequestHandlerClass 到底是个什么东西

请求控制类

      首先,我们在 finish_request 方法中,生成 RequestHandlerClass 对象这里打上断点,进行调试

      需要注意的是我打上的断点部分,只有在请求来了才会出发,因此点击断点调试还要在浏览器输入 http://127.0.0.1:8000/,请读者输入自己启动 WSGI 服务器时候写上的ip和端口号。然后就会发现 RequestHandlerClass 的本来面目竟然是我们在调用 make_server 的时候默认传递的 WSGIRequestHandler
make_server 函数在初始化服务器类的时候将 WSGIRequestHandler 传递给了最顶层的 ServerBase 的初始化函数

      在我们浏览所有的 Server 类的时候,只有顶层的 ServerBase 才有初始化函数,这里我们明白了 赋值操作让 RequestHandlerClass 指向了 WSGIRequestHandler
好了我们又要开始阅读源码了,看看 WSGIRequestHandler 到底干了什么。

      老规矩,我们先浏览请求控制类的整体,有父类就一直向上看去,浏览全局。

WSGIRequestHandler

      WSGIRequestHandler 在初始化的时候啥也没干

BaseHTTPRequestHandler

      HTTP请求控制类基类 BaseHTTPRequestHandler 内容太多,截图截不下来,但是我们可以发现,他同样在初始化的时候也没做啥。

StreamRequestHandler

      流式请求控制类同样没有初始化操作,但是我们可以跟 TCP 通信的时候联想到一起,TCP 通信获取一个socket 是不是需要设定 流式获取数据。

BaseRequestHandler

      还记得吗?process_request 方法里只是生成了一下对象就完事了,要知道最终给前端返回数据的可是 Server, 再看 __init__ 方法,它把直接把所有的活儿都干了,首先我们需要明确,request 对象是跟客户端沟通的 socket,client_address 是客户端地址,server是 finish_request 中传入的 WSGIServer 的对象本身。

      setup,handle,finish 都为空,因此必然调用的是子类的同名方法,我们需要从子类开始看起,知道,这三个方法分别做了什么。

setup

      setup方法 ,WSGIRequestHandler没有,所以是调用的父类的。一直找到了 StreamRequestHandler

      可以看到 setup 方法是设置属性,rfile像是生成了一个数据操作器,类似于文件IO操作

finish

       WSGIRequestHandler 没有,所以是调用的父类的。一直找到了 StreamRequestHandler

handle

      可以在调试器中看到,raw_requestline 的内容,同时我们也明确了,setup方法确实返回了一个类似于文件的对象,方便后端对它进行IO操作

      在handle 中的代码内,还看到了什么?生成了一个 ServerHandler 对象,传递了 socket 写对象和读对象,错误对象,以及抓取了环境变量。

      WSGIRequestHandler.get_environ 首先调用了 server,也就是 WSGIServerenviron 属性,是拷贝过来的,然后做了一些其它的操作,具体的并不重要,读者可以自己去看看内部内容,只需要知道这个函数确实生成了环境变量。

handler.run

      紧接着 run 方法被调用,并且传入了一个 application, 这个 application 就是这次我们阅读源码的 demo_app,也就是我们需要自己写的部分。

      到了这里终于有种拨云见雾的感觉了。除了 application 都不需要我们关心,因为那些步骤都是死的,跟业务无关,这就是WSGI协议,我们只是使用了 WSGI生成了一个 WSGI 服务器。

      WSGI协议只是规定了pythonweb服务器和 web 应用之间的接口。

      好了,其实到了这里我们已经没必要继续深挖源码了,毕竟我们不是写web服务器的,没必要搞懂它的每一个细节,只需要知道它的具体流程就好了。

总结

wsgiref 只是python对于 web 开发的一个简单实现,不能作为企业开发使用的web服务器,但是它描述了web服务器和web应用之间的交互。对于理解这些十分有意义。下面对 wsgiref 做一个总结

  1. WSGIServer 是一个服务器,负责接收请求
  2. WSGIRequestHandler 是一个请求处理类,在wsgiref中属于中间件,他负责提取跟WSGI相关的请求字段,它继承自 BaseHTTPRequestHandler,从 BaseHTTPRequestHandler 这里获取了 HTTP 请求的一些请求头,并负责与 web 应用交互,他收集请求头,和自己的 start_reponse 开始返回方法一起传给应用端,也就是 demo_app
  3. demo_app 是需要我们自定义,真正处理请求的部分。
  4. wsgiref 我们可以看到,WSGI是一种思想,只是由于它应用在了HTTP服务中,才会让人觉得它是HTTP的一部分。

wsgiref 我学到了分层解耦的思想,以及标准的好处。

此处我们做一个大胆的猜想,整个 Django 其实就是一个 demo_app。后续我将会开始真正的 Django 源码分析

posted @   yaowy  阅读(169)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示