在上一篇文章 WSGI 的例子中我们多次利用了 wsgiref 来提供一个简单的 HTTP 服务器,但对其如何实现对 WSGI 应用的调用却没有深入了解。本文正是基于此点出发,以加深对 WSGI 的理解。
WSGIRef 本身很简单,在对 HTTPServer 的封装的基础上,实现了服务端的 WSGI 协议。其源代码量也是好少,很容易看懂。
本文打算用前文的例子来说清楚服务端是如何调用 WSGI 应用的:
1 | # -*- coding: utf-8 -*- |
服务器初始化
在这个例子中,我们用 make_server 这个函数就构建了一个简单的服务器,其定义如下:
1 | # wsgiref/simple_server.py |
下边我们来重点看下 WSGIServer 和 WSGIRequestHandler 这两个类。
WSGIServer
该类的继承关系为 WSGIServer -> BaseHTTPServer.HTTPServer -> SocketServer.TCPServer -> SocketServer.BaseServer
。这里我们只需重点关注 WSGIServer 和 SocketServer.BaseServer 这两个类就行了。我们先来看下前者。
WSGIServer
这个类比较简单。服务端大部分重要工作都由其最顶层父类 SocketServer.BaseServer 完成,它提供对服务端 WSGI 应用的注册
,看下代码就清楚了:
1 | # wsgiref/simple_server.py |
SocketServer.BaseServer
首先,比较重要的是,这个类提供了对请求处理类 RequestHandlerClass
的注册,方便以后处理请求时调用:
1 | # SocketServer.py |
另外,这个类提供了两种服务器工作方式,即 serve forever 和 server once。这两种方式只是工作方式不一样,底层的请求处理还是一样的。本文用到的是后者,具体负责处理请求的函数是 handle_request
:
1 | # SocketServer.py |
这里实际使用了 select
来对请求进行监听(轮询),最后调用函数 self._handle_request_noblock 对请求进行处理。这个函数最终会调用到 RequestHandlerClass
:self._handle_request_noblock -> self.process_request -> self.finish_request -> self.RequestHandlerClass(request, client_address, self)
而我们的 RequestHandlerClass
会最终继承自 SocketServer.BaseRequestHandler
,当我们对它进行初始化
的时候(处理一次请求初始化一次),它就会调用 handle
函数对请求进行处理:
1 | # SocketServer.py |
接下来请看我们实际注册到服务端的 WSGIRequestHandler。
WSGIRequestHandler
该类的继承关系为 WSGIRequestHandler -> BaseHTTPServer.BaseHTTPRequestHandler -> SocketServer.StreamRequestHandler -> SocketServer.BaseRequestHandler
。这里我们要重点关注的是 WSGIRequestHandler 这个类。不过记住,处理请求时每次都会对其初始化一次,然后调用 handle 函数
,见上一小节。
环境变量处理
直接上源代码吧:
1 | # wsgiref/simple_server.py |
请求处理
这里就比较重要了,是对请求的实际处理:
1 | # wsgiref/simple_server.py |
其中,ServerHandler 定义为:
1 | # wsgiref/simple_server.py |
执行 handler.run
最终会调用 WSGI 应用,请看下文。
调用 WSGI 应用
前边我们说到,请求处理最终是由类 BaseHandler
进行处理的。服务端的 WSGI 标准就是由它封装的,并最终调用应用和处理响应。我们看下它提供的 run 函数就很清楚了:
1 | # wsgiref/handlers.py |
到这里,顿感焕然大悟!原来服务端就是这么准备环境变量,然后按 WSGI 标准调用应用的。
WSGI 环境变量构造
接下来,我们可以看下, WSGI 环境变量
是如何构造的:
1 | # wsgiref/handlers.py |
WSGI start_response 函数
还有,WSGI start_response
是这样子构造的:
1 | # wsgiref/handlers.py |
处理应用返回数据
1 | # wsgiref/handlers.py |
总结
用博文 wsgiref 源码解析来总结我们前边提到的工作流程挺好的:
另外,该博文还详述了一条 HTTP 请求的旅程:
服务器端启动服务,等到客户端输入 curl -i http://localhost:8000/ 命令,摁下回车键,看到终端上的输出,整个过程中,wsgi 的服务器端发生了什么呢?
- 服务器程序创建 socket,并监听在特定的端口,等待客户端的连接(注:本文例子是在调用 httpd.
handle_request
() 后进入等待状态)- 客户端发送 http 请求
- socket server 读取请求的数据,交给 http server
- http server 根据 http 的规范解析请求,然后把请求交给 WSGIServer
- WSGIServer 把客户端的信息存放在 environ 变量里,然后交给绑定的 handler 处理请求
- HTTPHandler 解析请求,把 method、path 等放在 environ,然后 WSGIRequestHandler 把服务器端的信息也放到 environ 里
- WSGIRequestHandler 调用绑定的 wsgi ServerHandler,把上面包含了服务器信息,客户端信息,本次请求信息得 environ 传递过去
- wsgi ServerHandler 调用注册的 wsgi app,把 environ 和 start_response 传递过去
- wsgi app 将reponse header、status、body 回传给 wsgi handler
- 然后 handler 逐层传递,最后把这些信息通过 socket 发送到客户端
- 客户端的程序接到应答,解析应答,并把结果打印出来。