任何事情,你想做就有方法,不想做就有借口,你有选择的自由,也有承担后果的义务。不要明天再努力,只有努力过完今天的人才有明天。
简介
Django 是用 Python 开发的一个免费开源的 Web 框架,几乎囊括了 Web 应用的方方面面,可以用于快速搭建高性能、优雅的网站,Django 提供了许多网站后台开发经常用到的模块,使开发者能够专注于业务部分。
Django 提供了通用 Web 开发模式的高度抽象,为频繁进行的编程作业提供了快速解决方法,并为“如何解决问题”提供了清晰明了的约定。Django 通过 DRY(Don’t Repeat Yourself,不要重复自己)的理念来鼓励快速开发。
Django 框架支持主流的操作系统平台包括 Windows,Linux,MacOS 等。Python Django 框架是一款全能型框架,它内置了许多模块,极大的方便了 Web 开发者,也正是由于它的“全面性”,会在学习 Django 的时候感到不知该如何处下手。
Django 的基本使用就不说了,不是这篇文档的重点,这里附个中文文档,有需要的可以看下,下面主要简单分析下django的源码,基于1.8.14。
源码分析
命令行解析
我们都知道,启动一个django工程用的是python manage.py runserver命令,所以manage.py文件无疑就是启动django项目的入口文件,这里我将通过从入口文件出发,一步一步阅读跟django项目启动相关的源码,看看在这个过程中都做了些什么,同时给出我自己的解释。
入口文件django/conf/project_template/manage.py
:核心代码如下,前面根据配置设置一下环境,然后导包(判断是否安装django package),然后真正有用的就只有最后一句,也就是根据命令行的命令做相应的执行。
1 | #!/usr/bin/env python |
execute_from_command_line函数:核心代码如下,基本流程就是先实例化一个管理工具类对象,然后调用相应的执行函数。在执行函数中,首先接收来自命令行的命令,然后进行校验和判断,如果是runserver,则调用django.setup()函数(该函数是关键),其他的基本都是一些意外的处理。
1 | # django/core/management/__init__.py |
这一部分先看到这里,这里基本是启动项目前一半的过程,我们可以知道:django
项目的入口程序其实就是manage.py
通过对命令行参数进行解析,然后对不同的命令进行不同的处理。针对runserver
,django
会调用django.setup()
函数来进行项目的初始化配置,然后调用 self.fetch_command(subcommand).run_from_argv(self.argv)
来进行项目启动,包括启动WSGI等。
App 注册
django.setup()
函数
1 | # django/__init__.py |
apps.populate()
函数:在项目启动时,对开发者写好的项目中的app进行配置。因为是类中的函数,用到了一些属性,所以这里把init函数也一起放出来。
1 | # django/apps/registry.py |
上面代码中,有项目启动时候初始化django
的各个app的代码,而各个app代码中存在针对各个app的配置,这些是由AppConfig类来做处理的,所以可以看到上面的代码中一直有调用app_config.xxx())
函数,下面是三个比较主要的函数,为了更好地说明这个类,下面的代码中也把init函数放出来了:
AppConfig.create()
函数:所以,这个函数其实就是将我们自己生成的app
的配置类xxxConfig
进行实例化并返回,只是中间经过一系列路径解析和尝试import_module
所以代码才这么长。AppConfig.import_models()
函数:这里可以看到,import_models
其实是从总的apps
管理器对象那里去取自己对应的models
存起来。这是因为如Apps类中所注释的:Every time a model is imported, ModelBase.__new__ calls apps.register_model which creates an entry in all*models.
。总的apps
管理器会在import的时候注册所有的model
,所以在注册某一个app
配置实例(app_config*)
的时候,反而是app
配置实例去apps
管理器那里拿属于自己的models
进行属性赋值。AppConfig.ready()
函数:我们可以看到,ready
函数是空的,是为了给开发者在需要在启动项目时候做一些一次性的事情留了一个接口,只需要在apps.py
中重写ready
函数就可以了,而且确实会在启动过程中执行。
1 | # django/apps/config.py |
app
的相关配置有两个关键部分,一个是对单个app
的配置类AppConfig
,一个是对所有app
的管理类Apps
,这两个类紧密相关,是总分关系,同时相互索引。在初始化过程中,Apps
在控制流程,主要包括对所有app
进行路径解析、名字去重、对每个app
配置类进行初始化、赋予每个app
配置类它们的相关models
、还有就是执行每个app
开发者自己定义的ready
函数。
下面看下 self.fetch_command(subcommand).run_from_argv(self.argv)
启动
获取执行命令所需要的类
1 | # django/core/management/__init__.py |
1 | # django/core/management/base.py |
主要看下execute --> handle
方法
1 | # django/core/management/commands/runserver.py |
最后调用run
,user_reloader
为True
时,会通过restart_with_reloader
开启一个新的进程,这个进程再次重复上面的调用过程,当再次调用到python_reloader
时,开启一个新的线程:
1 | # django/utils/autoreload.py |
新开的线程运行inner_run
,输出我们常见到的信息:
1 | # django/core/management/commands/runserver.py |
这里有两个重要的方法需要看下:self.get_handler
和 run(...)
,这两个方法就是WSGI协议的具体实现过程,我们知道WSGI协议一般来说可以分为两部分,application
和server
:
WSGI Application
WSGI application 应该实现为一个可调用对象,例如函数、方法、类(包含call
方法)。需要接收两个参数:
- 一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文,一般叫做environment(编码中多简写为environ、env)
- 一个用于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数
通过回调函数将响应状态和响应头返回给server,同时返回响应正文(response body),响应正文是可迭代的、并包含了多个字符串。
下面是Django中application的具体实现部分:
1 | # django/core/management/commands/runserver.py |
可以看出application
的流程包括:
- 加载所有中间件,以及执行框架相关的操作,设置当前线程脚本前缀,发送请求开始信号;
- 处理请求,调用
get_response()
方法处理当前请求,该方法的的主要逻辑是通过urlconf
找到对应的view
和callback
,按顺序执行各种middleware
和callback
。 - 调用由
server
传入的start_response()
方法将响应header
与status
返回给server
。 - 返回响应正文
WSGI Server
WSGI server 负责获取http请求,将请求传递给WSGI application,由application处理请求后返回response。看下Django具体实现。
通过runserver运行django项目,在启动时都会调用下面的run方法,创建一个WSGIServer的实例,之后再调用其serve_forever()方法启动服务。
1 | # django/core/servers/basehttp.py |
下面表示WSGI server
服务器处理流程中关键的类和方法。
WSGIServer
run()方法会创建WSGIServer实例,主要作用是接收客户端请求,将请求传递给application,然后将application返回的response
返回给客户端。
- 创建实例时会指定
HTTP
请求的handler
:WSGIRequestHandler
类 - 通过
set_app
和get_app
方法设置和获取WSGIApplication
实例wsgi_handler
- 处理http请求时,调用
handler_request
方法,会创建WSGIRequestHandler
实例处理http请求。 WSGIServer
中get_request
方法通过socket
接受请求数据
- 创建实例时会指定
WSGIRequestHandler
- 由
WSGIServer
在调用handle_request
时创建实例,传入request
、cient_address
、WSGIServer
三个参数,__init__
方法在实例化同时还会调用自身的handle
方法 handle
方法会创建ServerHandler
实例,然后调用其run
方法处理请求
- 由
ServerHandler
WSGIRequestHandler
在其handle
方法中调用run
方法,传入self.server.get_app()
参数,获取WSGIApplication
,然后调用实例(__call__
),获取response
,其中会传入start_response
回调,用来处理返回的header
和status
。- 通过
application
获取response
以后,通过finish_response
返回response
WSGIHandler
WSGI
协议中的application
,接收两个参数,environ
字典包含了客户端请求的信息以及其他信息,可以认为是请求上下文,start_response
用于发送返回status
和header
的回调函数
虽然上面一个WSGI server
涉及到多个类实现以及相互引用,但其实原理还是调用WSGIHandler
,传入请求参数以及回调方法start_response()
,并将响应返回给客户端。
到这里,整个启动流程就结束了。这里我们使用的django自带的WSGI Server,一般用于调试,生产环境一般不用这个,需要注意。
比如我们使用 gunicorn
部署的时候可以执行
1 | exec gunicorn -c gunicorn_conf.py wsgi:application |
1 | from distutils.sysconfig import get_python_lib |
然后在你项目的dashboard下加上一个wsgi文件,类似:
1 | import logging |
django simple_server demo
django
的simple_server.py
模块实现了一个简单的HTTP
服务器,并给出了一个简单的demo
,可以直接运行,运行结果会将请求中涉及到的环境变量在浏览器中展示出来。
其中包括上述描述的整个http
请求的所有组件:ServerHandler
, WSGIServer
, WSGIRequestHandler
,以及demo_app
表示的简易版的WSGIApplication
。
可以看一下整个流程:
1 | if __name__ == '__main__': |
demo_app()
表示一个简单的WSGI application实现,通过make_server()
方法创建一个WSGIServer
实例,调用其handle_request()
方法,该方法会调用demo_app()
处理请求,并最终返回响应。
中间件分析
流程分析
我们在上面分析启动流程的时候看到了有个中间件加载的过程,这里单独分析下
中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
中间件中主要可以定义下面5个钩子函数来对请求的输入输出做处理:
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
它们的主要作用参见官方文档.
这5个钩子函数的触发时机可以见下面的图.
说明: 上面说的顺序都是中间件在settings文件中列表的注册顺序。
源码分析
我们上面说到,在启动WSGI application的时候会加载中间件,代码大概在这个位置:
1 | # django/core/handlers/wsgi.py |
进入到load_middleware
函数,可以看我们可以自定义的钩子函数都在这里了,放在5个列表里面。接下来判断settings里面的MIDDLEWARE
配置项是否为空,为空的话会去django.conf.global_settings.py
里面的默认的配置文件加载中间件,默认的中间件只有下面两个。
1 | # django.conf.global_settings.py |
从这里我们可以看到
1 | if hasattr(mw_instance, 'process_request'): |
process_request
和 process_view
是顺序执行,而 process_template_response
、process_response
、process_exception
几个都是逆序执行。那么在一个请求中,他们执行的顺序是什么?
在上面初始化WSGI的时候,调用了 self.get_response(request)
,看下这个方法:
1 |
|
从上段代码中可以看出来request执行的过程是,解析url,调用请求中间件过滤请求。如果响应处理,那么直接返回响应结果;如果不响应处理,解析url得到view。调用view中间件过滤;如果还没有得到响应;那么调用转化得到view类或者view响应方法,调用view的渲染方法得到response,最后调用返回response中间件过滤处理,返回response。
流程上解析完以后。具体查看中间件部分,文件夹结构:
1 | middleware |
参考: