Django 源码WSGI剖析过程详解
作者:捣乱小子 发布时间:2023-09-14 22:19:58
前言
python 作为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也越来越多, Bottle, Django, Flask 等等.
在一个 HTTP 请求到达服务器时, 服务器接收并调用 web 应用程序解析请求, 产生响应数据并返回给服务器. 这里涉及了两个方面的东西: 服务器(server)和应用程序(application). 势必要有一个合约要求服务器和应用程序都去遵守, 如此按照此合约开发的无论是服务器还是应用程序都会具有较大的普遍性. 而这就好像在计算机通信的早期, 各大公司都有属于自己的通信协议, 如此只会让市场杂乱无章, 宁愿只要一种通信协议.
而针对 python 的合约是 WSGI(Python Web Server Gateway Interface). 具体的规定见 PEP 333.
实习的时候一直使用 Django, 下面是结合 Django 学习 WSGI 的笔记.
application/应用程序
在应用程序一方面, 必须提供下面的方法:
def simple_app(environ, start_response):
"""可能是最简单的处理了"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n'] # 返回结果必须可迭代
除了方法以外, 还可以用实现了 __call__ 的类实现.
它会被服务器调用, 在这里 environ 是一个字典, 包含了环境变量, REQUEST_METHOD,SCRIPT_NAME,QUERY_STRING 等; start_response 是一个回调函数, 会在 simple_app 中被调用, 主要用来开始响应 HTTP. start_response 原型大概是这样:
def start_response(status, response_headers, exc_info=None):
...
return write # 返回这 write 函数 只是为了兼容之前的 web 框架, 新的框架根本用不到.
参数有 status 即状态码; response_headers HTTP 头, 可以修改; exc_info 是与错误相关的信息, 在产生相应数据过程中可能发生错误, 这时需要更新 HTTP 头部, 通过再次调用 start_response 可以实现. 因此更为详尽的实现写法可能是这种:
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
# do stuff w/exc_info here
finally:
exc_info = None # Avoid circular ref.
return write
Server/服务器
在服务器方面, 可以想象最简单的工作就是调用 simple_app(), 然后向客户端发送数据:
result = simple_app(environ, start_response) #名字不一定为 simple_app
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()
注意 WSGI 并没有事无巨细规定 web 应用程序和服务器内部的工作方式, 只是是规定了它们之间连接的标准.
python wsgiref 模块
下面看看 Django 是如何实现 WSGI 的. Django 其内部已经自带了一个方便本地测试的小服务器, 所以在刚开始学习 Django 的时候并不需搭建 apache 或者 nginx 服务器. Django 自带的服务器基于 python wsgiref 模块实现, 它自带的测试代码:
# demo_app() 是 application
def demo_app(environ,start_response):
from StringIO import StringIO
stdout = StringIO()
print >>stdout, "Hello world!"
print >>stdout
h = environ.items(); h.sort()
for k,v in h:
print >>stdout, k,'=', repr(v)
start_response("200 OK", [('Content-Type','text/plain')])
return [stdout.getvalue()]
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
if __name__ == '__main__':
httpd = make_server('', 8000, demo_app)
sa = httpd.socket.getsockname()
print "Serving HTTP on", sa[0], "port", sa[1], "..."
import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc')
httpd.handle_request() # serve one request, then exit
python 的库有好多的工具, 这时可能因为需要的原因, 会生出好多的父类, 为了讲明, 根据 wsgiref 模块和它自带的测试用例得出下面的 UML 图(注意, 这只是 wsgiref, 没有涉及 Django):
我读完这些的时候已经晕了, 确实是里边的继承关系有些复杂. 因此, 简要的概括了测试代码的执行关系:
make_server() 中 WSGIServer 类已经作为服务器类, 负责接收请求, 调用 application 的处理, 返回相应;
WSGIRequestHandler 作为请求处理类, 并已经配置在 WSGIServer 中;
接着还设置了 WSGIServer.application 属性(set_app(app));
返回 server 实例.
接着打开浏览器, 即发起请求. 服务器实例 WSGIServer httpd 调用自身 handle_request() 函数处理请求. handle_request() 的工作流程如下:请求-->WSGIServer 收到-->调用 WSGIServer.handle_request()-->调用 _handle_request_noblock()-->调用 process_request()-->调用 finish_request()-->finish_request() 中实例化 WSGIRequestHandler-->实例化过程中会调用 handle()-->handle() 中实例化 ServerHandler-->调用 ServerHandler.run()-->run() 调用 application() 这才是真正的逻辑.-->run() 中在调用 ServerHandler.finish_response() 返回数据-->回到 process_request() 中调用 WSGIServer.shutdown_request() 关闭请求(其实什么也没做)
ps: 明明 application 是 WSGIServer 的属性, 为什么会在 ServerHandler 中调用? 因为在实例化 WSGIRequestHandler 的时候 WSGIServer 把自己搭进去了, 所以在 WSGIRequestHandler 中实例化 ServerHandler 时候可以通过 WSGIRequestHandler.server.get_app() 得到真正的 application.
总结
从上面可以得到, 启动服务器的时候, 无论以什么方式都要给它传递一个 application(), 是一个函数也好, 一个实现了 __call__ 的类也好; 当请求到达服务器的时候, 服务器自会调用 application(), 从而得到相应数据. 至于, 对请求的数据如何相应, application() 中可以细化.
确实, 其中的调用链太过长, 这期间还没有加入 HTTP 头的分析(提取 Cookie等). 如果只为响应一个 "helloworld", 在 WSGIServer.finish_request() 中直接相应数据就好了, WSGIRequestHandler 和 ServerHandler 类可以直接省去, 而只需要你提供一个 application()! 但事实上, 并不只是相应 "helloworld" 那样简单...
关于 Django 中的 WSGI 如何, 下一节再说. Django 源码剖析从这里开始! 我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧. 本文结合 python wsgiref, BaseHTTPServer.py, SocketServer.py 模块源码看更好.
来源:https://www.cnblogs.com/daoluanxiaozi/p/3301738.html
猜你喜欢
- 本文向大家分享23种JavaScript提高执行效率的小技巧、最佳实践等非常实用的内容。当然JavaScript的实用技巧不止这些,还有很多
- 前言终于下定决心学习Python了。既然从头开始,就需要认认真真。首先需要说的是,我是初学Python,这篇文章只是用于展示global和n
- 题目描述原题链接 :35. 搜索插入位置 - 力扣(LeetCode) (leetcode-cn.com)给定一个排序数组和一个目标值,在数
- ASP.net处理文件上传就简单的多了,我呢也是在学习中,顺便写写学习笔记。 先在表单中添加enctype="multipart/
- 为庆祝jQuery的四周年生日,jQuery官方团队正式发布了jQuery 1.4版本。在这个版本中,jQuery官方团队做了大量的编码、测
- 简介如果你经常网上冲浪,这样参差不齐的多栏布局,是不是很眼熟啊?类似的布局,似乎一夜之间出现在国内外大大小小的网站上,比如 Pinteres
- 开始制作符合标准的站点,第一件事情就是声明符合自己需要的DOCTYPE。查看本站首页原代码,可以看到第一行就是:<!DOCTYPE h
- 前言本文根据安前松的视频分享整理而来,视频回放地址如下:www.bilibili.com/video/BV1Hr…一、
- 代码如下:--函数 CREATE function fn_GetPy(@str nvarchar(4000)) returns nvarch
- 第一步肯定是打上SQL SERVER最新的安全补丁.如果这一步都没有做好,那我们也没有继续下去的必要了。 第二步是修改默认的1433端口,并
- 最近经常使用字符串查找功能。 包括 1、全匹配查找字符串 2、模糊查找字符串 CHARINDEX 和 PATINDEX 函数都返回指定模式的
- 本文实例讲述了PHP实现将浏览历史页面网址保存到cookie的方法。分享给大家供大家参考。具体如下:将浏览历史页面网址保存到cookie,大
- 这篇文章主要介绍了Python搭建HTTP服务过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 当很多人发现在DW4中定义CSS很方便的时候,开始报怨FP2000不能定义CSS,甚至就此抨击FP2000如何的不好。事实上,在FP2000
- 用header 发送cookie header("Set-Cookie: testcookie=中文
- 主键表的主键(primary key,主关键字)是表中的一个或多个字段,它的值用于惟一地标识表中的某一条记录。一个表不能有多个主关键字,并且
- 先给大家介绍下Python读取文件夹按数字排序的代码,内容如下所示:python中 os.listdir()方法用于返回指定的文件夹包含的文
- 本文详细列出了HTML中使用到的各种鼠标事件,如onclick,onmouseover等;页面相关事件如:onerror,onload等;h
- 工作时常遇到需要在其它地方拷贝样式,比如Firebug之类的,但是复制出来的样式是带有换行和空格的,对于我这种有点洁癖的人来说,经常会一个个
- 输入框Input 应当符合逻辑地划分为小组,这样大脑就可以很好的处理大堆区域间的关系。 ——《HTML权威指南》Web 应用程序总是利用表单