一、写在前面
一直没有搞懂openstack horizon wsgi加载机制,这次抽时间看了下django 源码,顺便在horizon组件上进行了调试,同时参考学习了网上一些博客的源码阅读说明用以帮助理解,当然还没有理解透彻,仅当个学习笔记。
邮箱地址:jpzhang.ht@gmail.com
个人博客:https://jianpengzhang.github.io/
CSDN博客:http://blog.csdn.net/u011521019
# 二、入口文件
horizon 开发阶段都是通过:
python manage.py runserver 0.0.0.0:8001
开始的,该命令启用Django提供的轻量级的开发用的Web服务器。默认情况下,服务器运行在IP地址127.0.0.1的8000端口上。如果要自定义服务器端口和地址,可以显式地传递一个IP地址和端口号给它。例如和上面命令一样。Horizon的manage.py 文件里只有简单的几行代码,基本上和其他django项目一样,这里主要通过horizon项目来学习Django 的启动入口。
|
|
from django.core.management import execute_from_command_line当这行代码开始执行时,首先会去运行django.core.management.init.py这一整个文件,接着找到execute_from_command_line函数并将其导入到当前程序的命名空间中。
由于整个django.core.management.init.py文件都是class类对象和function函数对象,很多时候很自然的就认为这个文件并没有执行任何命令,只是加载了这些个对象,然后在这些个对象中寻找是否包含有execute_from_command_line。最终忽视了其他很重要的代码块from和import。
import django 这行代码运行了django.init.py文件。
from django.apps import apps这行代码运行了django.apps.init.py文件,然而整个django的开端就是从这里开始的,它落实了非常多的事情(例如:初始化日志模块、加载INSTALL_APP、检查各APP是否正常、检查缓存模块是否正常等),当一切无误时才会往下走,否则将会报错退出程序。
manage.py:
这句话绑定了DJANGO_SETTINGS_MODULE的键值,然后将其加载。
设置完DJANGO_SETTINGS_MODULE环境变量之后,命令行参数的列表传到了 django/core/management/init.py 中的execute_from_command_line函数。
execute_from_command_line这个方法是一个工厂函数,它负责指挥ManagementUtility类利用execute方法来解析参数和启动wsgi服务。
django/core/management/init.py:execute_from_command_line方法:
django/core/management/init.py:ManagementUtility:命令管理工具
|
|
在这里prog_name 就是 manage.py。
实例化ManagementUtility后调用了 execute() 方法,这个函数主要处理子命令的执行,这里的子命令是相对于django-admin.py和manage.py的,举个例子:python manage.py runserver这里的runserver就是子命令。
在这个方法中,会对命令参数进行处理。当解析的的命令是 runserver 时,会有两条路,第一个是执行自动重装的路线(监听文件的修改变化,并实现自动重载),通过 autoreload.check_errors(django.setup)() 完成。另一个路线是参数中有 –noreload 时,就用 django.setup() 来启动服务。
如果不是 runserver 而是其他命令,那么会对命令参数 self.argv[1] 进行判断,包括错误处理,是否是 help ,是否是 version ,根据不同的情况显示不同的信息。
execute(self)函数中最重要的是最后一句,即前面的情况都不是,就进入 self.fetch_command(subcommand).run_from_argv(self.argv) ,这边分两步,一步是获取执行命令所需要的类,其次是将命令参数作为参数传递给执行函数执行(代码分析如下):
django/core/management/init.py:execute(self):
前面几行不难看懂是进行参数获取的,后面到了解析这块又用到CommandParser这个类。
|
|
通过进入该类查看实现过程,该类的路径为django/core/management/base.py中,代码如下:
可以看出这个类是对ArgumentParser的继承封装,而ArgumentParser是python基础类包中的一个类,这个函数的目的是将ArgumentParser封装成符合django内部调用的接口形式。
argparse模块可以轻松编写用户友好的命令行界面。 程序定义了它需要的参数,argparse将弄清楚如何解析sys.argv中的参数。 当用户给程序提供无效参数时,argparse模块还会自动生成帮助和使用消息并发出错误。
ArgumentParser通过parse_args()方法解析参数。 这将检查命令行,将每个参数转换为适当的类型,然后调用相应的操作。 在大多数情况下,这意味着将从命令行解析的属性构建一个简单的Namespace对象。
不懂的自行参看其手册。
ArgumentParser官方手册。
回过头来继续看execute函数。接下来的一段代码围绕着CommandParser进行。完成命令行的常规设置。运行到options, args = parser.parse_known_args(self.argv[2:])才开始进入解析的关键之处。
下面给出了python的官方文档中该函数的说明:
也就是说,这个函数将当前脚本需要命令参数和其他脚本所需的命令行进行分离,它的返回结果是一个tuple,包含一个填充好的命名空间和剩余的参数字符串列表。此处的options和args的结果形如:
代码接着运行,将前面函数填充好的命名空间(此处为options参数)传入handle_default_options这个方法去执行。handle_default_options同样在django/core/management/base.py文件中,代码如下:
|
|
它只是完成了两个目标,(1)options中包含setting则配置环境变量;(2)options中包含pythonpath则设置python模块的搜索路径。
继续回到:execute函数。
settings.INSTALLED_APPS(django 的配置采用了懒加载机制,后续通过另外的博文进行说明),这里即执行了django/conf/init__.py:class LazySettings(LazyObject)的getattr()函数,从全局settings更新此索引,使用 importlib.import_module 来加载’openstack_dashboard.settings’配置模块,完成全局Settings实例的初始化。实例化后,配置懒加载也就完成了,程序就回到 execute 函数,
- INSTALLED_APPS,它表示项目中哪些 app 处于激活状态。元组中的字符串,除了django默认自带的命令之外,就是我们自己定义的app,也就是用python manage.py所启动的app了。
回到 execute 函数,接下去的处理分为两条路,会对命令参数进行处理。当解析的subcommand是 runserver 时,会有两条路,第一个是会自动重装(后续的博文进行说明)的路线,通过 autoreload.check_errors(django.setup)() 代理完成。另一个路线是参数中有 –noreload 时,就用 django.setup() 来启动服务。
这里执行:autoreload.check_errors(django.setup)()
其实也是调用django.setup方法;check_errors(),一个装饰器函数,用来检查捕捉一些错误,而django.setup方法:
路径:django/init.py:setup()
这里主要是这个,apps.populate(settings.INSTALLED_APPS),加载应用程序配置和模型。
做了几件事情:
- app_config = AppConfig.create(entry)生成了一个AppConfig实例self.app_configs[app_config.label] = app_config将所有的app实例放到一个order_dict中维护。
- app_config.import_models(all_models) 导入models.py 这里不展开,具体可以看django/apps/registry.py
- 这些准备工作完成后,这里调用了类中自有的一个方法autocomlete,这个函数主要的功能是通过BASH去输出执行建议。
- 如果不是 runserver 而是其他命令,那么会对命令参数 self.argv[1] 进行判断,包括错误处理,是否是 help ,是否是 version ,根据不同的情况展示不同的信息。
|
|
最重要的是最后一句,即前面的情况都不是,就进入 self.fetch_command(subcommand).run_from_argv(self.argv) ,这边分两步,一步是会根据subcommand(这是我们执行python manage.py rumserver时传入的第二个参数:runserver),去django.core.management.commands中查找对应的command类,其次是将命令参数作为参数传递给执行函数执行(run_from_argv(self.argv))。
self.fetch_command:
是利用django内置的命令管理工具去匹配到具体的模块,例如self.fetch_command(subcommand)其实就相当于是self.fetch_command(‘runserver’),它最终找到了django.contrib.staticfiles.management.commands.runserver.Command这个命令工具。django中的命令工具代码组织采用的是策略模式+接口模式,也就是说django.core.management.commands这个目录下面存在各种命令工具,每个工具下面都有一个Command接口,当匹配到’runserver’时调用’runserver’命令工具的Command接口,当匹配到’migrate’时调用’migrate’命令工具的Command接口。
run_from_argv(self.argv):
run_from_argv的作用是初始化中间件、启动服务,也就是拉起wgsi(但实际上并不是由它来直接完成,而是由后续很多其他代码来完成)。
django/core/management/init.py:fetch_command(self, subcommand):
get_commands() 是返回是一个命令与模块映射作用的字典,字典的key是命令名称,value是这个命令实现所在的文件路径(get_commands通过pkgutil
第三方类库来做的):
接着根据“runserver”,返回命令路径:django.contrib.staticfiles,然后通过isinstance() 函数来判断django.contrib.staticfiles是否是BaseCommand类型,因为这里是app_name命令的路径,并不是一个对象,如果是对象就是属于BaseCommand。本质上这里主要用来判断django.contrib.staticfiles是否已经加载,如果加载直接返回该模块,如果不是就通过load_command_class函数动态加载模块。
load_command_class的目录在django/core/management/init.py中,代码如下:
|
|
这个方法调用python中importlib库中的import_module方法将模块动态载入,然后返回载入模块的Command()。参看management/commands下的每个文件,发现都拥有一个Command类对应相应的命令。
综上所诉,之前这个fetch_command返回了一个命令对象。
接着研究run_from_argv函数,这个函数同样位于django/core/management/base.py中,它是之前返回的BaseCommand对象中的一个方法,子类django.contrib.staticfiles.management.commands.runserver没有实现该函数,则调用父类BaseCommand中的。
runserver继承对象分布 依次按顺序如下:
• django.contrib.staticfiles.management.commands.runserver.Command
• django.core.management.commands.runserver.Command
• django.core.management.base.BaseCommand
• object
source/django/core/management/base.py:run_from_argv如下:
这个函数的作用就是设置好环境变量,然后取运行指令。这个文件的结构有点类似于前面的execute,当前类对象的run_from_argv方法中调用了self.execute(*args, **cmd_options)方法,由于请求的入口是django.contrib.staticfiles.management.commands.runserver.Command对象,因此python并不会去执行BaseCommand.execute而是执行django.core.management.commands.runserver.Command.execute,最后通过super(Command, self).execute(*args, **options)来执行BaseComand.execute。
django.core.management.commands.runserver.Command.execute函数在子类重新定义,这部分的代码如下:
没有做太多的事情,返回调用父类的execute执行方法,代码如下:
execute 中会做一些设置参数的错误检查,然后设置句柄,关键的核心在output = self.handle(*args, **options)这一行,这里又调用了自己的一个自有方法。
基本流转过程:
- BaseComand.execute方法中调用了self.handle(即:django.core.management.commands.runserver.Command.handle)
- Command.handle方法中调用了self.run(即:django.core.management.commands.runserver.Command.run)。
- Command.run方法调用了self.inner_run(即:django.core.management.commands.runserver.Command.inner_run)。
- Command.inner_run方法调用了self.get_handler(即:django.contrib.staticfiles.management.commands.runserver.Command.get_handler)
- Command.inner_run方法调用了run(即:django.core.servers.basehttp.run)。
代码:django/core/management/commands/runserver.py:handle(self, *args, **options)
前面的一大段就是运行runserver时候执行的一些参数准备,关键部分是最后一行的self.run(**options),run 方法主要时调用了 inner_run(*args, **options) 这个方法:
django/core/management/commands/runserver.py:run(self, **options)
在这里自动加载为True,即options[‘use_reloader’]:True。会调用django.utils.autoreload中的python_reloader新开一个线程:
|
|
这里的main_func是commands/runserver.py中的inner_run方法:
django/core/management/commands/runserver.py:inner_run(self, *args, **options)
这部分除了有熟悉的信息输出外,重要的是这个句柄:
gethandler 函数最终会返回一个 WSGIHandler 的实例。WSGIHandler 类只实现了 def \_call__(self, environ, start_response) , 使它本身能够成为 WSGI 中的应用程序, 并且实现 call 能让类的行为跟函数一样。
这里要特别强调一下self.get_handler,它非常重要,三个重点:
1. 因为它负责获取WSGIHandler。
2. 由于请求入口是django.contrib.staticfiles.management.commands.runserver.Command,正好它本来就有get_handler这个方法,因此并没有采用django.core.management.commands.runserver.Command.get_handler。
3. self.get_handler并不会返回一个常规的WSGIHandler而是返回一个StaticFilesHandler。
4. StaticFilesHandler类对象继承WSGIHandler,它的目的是为了判断每个请求,如果是常规的url请求则直接分配到某个view中去执行,如果是静态文件规则那么将不会找view而是响应这个文件。
django/contrib/staticfiles/management/commands/runserver.py:get_handler(self, *args, **options)
|
|
这里handler = super().get_handler(*args, **options)调用父类django.core.management.commands.runserver.Command.get_handler函数:
django.core.servers.basehttp.get_internal__wsgi_application():
没有设置settings.WSGI_APPLICATION(是None
),则返回django.core.wsgi.get_wsgi_application
返回的内容。在这里horizon的settings中没有设置WSGI_APPLICATION。
django.core.wsgi.py: get_wsgi_application():
source/django/init.py:setup(set_prefix=True)
完成django.setup(set_prefix=False)
执行后,紧接着返回return WSGIHandler()
|
|
通过__init__()初始化一个WSGIHandler对象,同时通过self.load_middleware()
加载中间件。其中self.load_middleware在当前调用的是父类的实现。
完成上述调用后返回到:
这里跟进去看下StaticFilesHandler(handler)
source/django/contrib/staticfiles/handlers.py:class StaticFilesHandler(WSGIHandler)
到这里完成handler = self.get_handler(*args, **options),返回一个StaticFilesHandler,StaticFilesHandler类对象继承WSGIHandler,它的目的是为了判断每个请求,如果是常规的url请求则直接分配到某个view中去执行,如果是静态文件规则那么将不会找view而是响应这个文件。
接着回到django/core/management/commands/runserver.py(149)inner_run(),执行:run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer)
django/core/servers/basehttp.py(158)run():
django.core.servers.basehttp.run工厂函数负责只会各个对象负责启动wsgi服务。
wsgi_handler参数,这里传递的是StaticFilesHandler。
httpd_cls = type(‘WSGIServer’, (socketserver.ThreadingMixIn, server_cls), {}) 是一种很特殊的写法,通过代码块中WSGIServer类对象可以看出它只继承了wsgiref.simple_server.WSGIServer、object这两个类对象,但是通过type这种写法相当于是强行赋予它一个socketserver.ThreadingMixIn继承对象,它的用意是每次调用这个对象的时候都会单独启用一个线程来处理。另外虽然 WSGIServer 只继承了 wsgiref.simple_server.WSGIServer、object两个对象,但是wsgiref.simple_server.WSGIServer却<递归式>的继承了一堆对象,下面完整的列出WSGIServer继承家族。
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)这行代码非常重要,因为它是WSGI服务器与django之间相互通信的唯一枢纽通道,也就是说,当WSGI服务对象收到socket请求后,会将这个请求传递给django的WSGIRequestHandler。
httpd.set_app(wsgi_handler)是将django.contrib.staticfiles.handlers.StaticFilesHandler 传递给WSGIServer当作一个application,当WSGIServer收到网络请求后,可以将数据分发给django.core.servers.basehttp.WSGIRequestHandler,最终由django.core.servers.basehttp.WSGIRequestHandler将数据传递给application(即:django.contrib.staticfiles.handlers.StaticFilesHandler)。
httpd.serve.forever()启动非堵塞网络监听服务。
小结
上面所有的过程都是django内部代码的为了启动服务而做的准备,简单的把流程给列出来。
1. 解析运行 python manage.py 所提供的参数,例如: runserver.
2. 根据参数 找到相对应的 命令管理工具。
3. 加载所有的app。
4. 检查端口、ipv4检测、ipv6检测、端口是否占用、线程检查、orm对象检查(表是否创建)。
5. 实例化WSGIRequestHandler,并且将它注册到python Lib库中的WSGIServer中。
6. 最后启动python Lib库中的WSGIServer。
二、处理请求
接下来的部分是python Lib库中的WSGIServer运作过程中,如何将接收到的请求分发会django的WSGIRequestHandler。
/usr/lib/python2.7/SocketServer.py
上面服务启动的最后一个动作是httpd.serve_forever,调用的是socketserver.BaseServer.serve_forever方法。该方法采用了selector网络模型进行等待数据,每0.5秒遍历一次文件描述符,当有数据进来时,ready变量会是一个socket请求对象,这时会将后续工作转交给self._handler_request_noblock方法(即:socketserver.BaseServer._handler_request_noblock)去处理。
socketserver.BaseServer._handler_request_noblock方法基本没做什么事情(self.verify_request压根就没有检查任何东西),直接就把后续工作转交给 socketserver.BaseServer.process_request 方法。
socketserver.BaseServer.process_request也没做什么事情,直接就将后续工作转交给socketserver.BaseServer.finish_request方法,只不过在最后加了一条关闭请求的命令。
socketserver.BaseServer.finish_request也没做什么事情,直接就将后续工作转交给socketserver.BaseServer.RequestHandlerClass。
socketserver.BaseServer.RequestHandlerClass是由上面httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
传递过来的参数django.core.servers.basehttp.WSGIRequestHandler。 也就是说当执行self.RequestHandler(request, client_address, self)时等同于执行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)。
小结
serve_forever开启了一个while来无限监听网络层的scoket请求,当一条请求过来时,就层层转交到django.core.servers.basehttp.WSGIRequestHandler手中。
django.core.servers.basehttp.py 单独列出WSGIRequestHandler代码:
继续分析,socketserver.BaseServer.RequestHandler(request, client_address, self)等同于django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)。
首先django.core.servers.basehttp.WSGIRequestHandler的继承分布:
- django.core.servers.basehttp.WSGIRequestHandler(django/core/servers/basehttp.py)
- wsgiref.simple_server.WSGIRequestHandler(/usr/lib/python2.7/wsgiref)
- http.server.BaseHTTPRequestHandler(/usr/local/lib/python2.7/dist-packages/http)
- socketserver.StreamRequestHandler
- socketserver.BaseRequestHandler
- object
从代码上看django.core.servers.basehttp.WSGIRequestHandler并没有init或者call方法,因此需要遍历所有父类对象。
最终在socketserver.BaseRequestHandler中看到了init实例初始化方法,它调用了self.handle方法(即回调了:django.core.servers.basehttp.WSGIRequestHandler.handle)。
|
|
handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())实例化了ServerHandler对象。
handler.run(self.server.get_app()),意思是将django.contrib.staticfiles.handlers.StaticFilesHandler转交给ServerHandler去运行。
ServerHandler对象并没有run方法,它的继承分布:
- django.core.servers.basehttp.ServerHandler(django/core/servers/basehttp.py)
- wsgiref.simple_server.ServerHandler(/usr/lib/python2.7/wsgiref)
- wsgiref.handlers.SimpleHandler(/usr/lib/python2.7/wsgiref)
- wsgiref.handlers.BaseHandler
- object
最终在 wsgiref.handlers.BaseHandler 中找到了run方法。
wsgiref.handlers.py:
|
|
application(self.environ, self.start_response)也就相当于是django.contrib.staticfiles.handlers.StaticFilesHandler.call(self.environ, lf.start_response)。
django.contrib.staticfiles.handlers.py:
通过层层流转,最终进入django的静态文件处理的Handler。
总结
environ这个变量在django的WSGIServer和WSGIRequestHandler中扮演这非常重要的角色,因为所有的客户端ip、请求的URL、cookie、session、header等等信息都保存在其中。
WSGIServer: 用于处理socket请求和对接WSGIRequestHandler。
WSGIRequestHandler:针对environ进行预处理和对接WSGIServerHandler。
ServerHandler: 用于执行应用程序(application)和返回响应给WSGIServer。
参考博文:
1、https://www.jianshu.com/p/17d78b52c732
2、http://www.hongweipeng.com/index.php/archives/1369/