Django深度剖析
启动过程
通过命令行执行
python manage.py runserver 127.0.0.1:8000
启动Django服务
manage.py模块获取到命令行参数manage.py runserver 127.0.0.1:8000
,然后将该参数交由execute_from_command_line
方法处理。
# manage.py
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
该模块调用django.core.management.__init__
包下的ManagementUtility
类方法,并把参数传递给该类初始化,
然后调用ManagementUtility
的execute
开始一连串动作
- 首先获取命令行第一个参数,也就是
runserver
,第0个参数是manage.py
程序本身,同时把剩下的参数存入到options
变量,
即options=['127.0.0.1:8000']
.
- 调用
ManagementUtility
的autocomplete
检查系统环境变量是否设置了DJANGO_AUTO_COMPLETE
。接着再调用ManagementUtility
的
fetch_command
方法获取命令模块,该fetch_command
通过调用get_commands
方法将django.core.management.commands
目录下所有
默认的命令模块获取,同时将settings里已注册的app目录INSTALLED_APPS
下顺序反转,依次将app目录下对应的management/commands
模块获取到。
这些模块都可以通过执行python manage.py 模块名
调用。由于django.contrib.staticfiles
app下已经重写了runserver
这个模块,所以默认
的django.core.management.commands
目录下的runserver
模块会被后面查找到的模块覆盖掉.然后fetch_command
再调用load_command_class
方法加载django.contrib.staticfiles.runserver
模块,并将该模块的Command
类实例化返回。
- 通过得到返回的
runserver
模块下的Command
对象,继续调用对象的run_from_argv
方法,将整个命令行参数传入该方法,run_from_argv
方法是基类django.core.management.base.BaseCommand
的方法,如果要自定义manage.py
的命令行参数模块,都必须实现该方法,或者可以直接
继承该基类,该方法默认会依次调用对象本身的create_parser
,创建命令行参数解析器,然后继续调用execute
执行一些系统检查,包括数据库连接同步,
可以通过改变对象的requires_system_checks
属性为False
则不进行该检查操作,requires_migrations_checks
控制是否检查数据迁移计划行为.
- 做完应用检查后,再调用对象的
handle
方法,该方法由于对象本身没有重写,所有是继承父类django.core.management.commands.runserver.Command
的handle
方法,在该父类模块runserver中,为了保持程序向后兼容,所以将BaseRunserverCommand = Command
,handle
方法首先检查配置文件
是否为DEBUG模式,如果是,则检查是否配置了ALLOWED_HOSTS
属性,然后检查命令行参数是否启用了ipv6,如果启用了,还得检查socket是否支持ipv6,
然后检查监听端口,默认端口8000,handle
方法会继续调用对象的run
方法,该方法会检查命令行参数是否包含--noreload
选项,如果包含则
不启用自动加载,由于命令行没有传入该选项,则使用默认行为,即:启用自动加载功能。
默认的命令行选项参数值
--ipv6 # 默认为False,不使用IPV6
--nothreading # 默认值True,默认值为使用多线程,使用该选项后,则不使用线程,
--noreload # 默认值True 默认自动加载,使用该选项后,则不自动加载代码,用于代码文件有改动时会重新加载代码
--nostatic # 默认值True 默认处理指定STATIC_URL配置的静态文件服务,使用该选项后,将不处理页面加载后的静态文件的URL请求
--insecure # 默认值False 不允许在非DEBUG模式提供处理静态文件的服务,使用该选项后,即使DEBUG模式,也处理静态文件的请求
- 所以
run
方法会获取命令行的选项--nothreading
值,默认没有使用该选项,所以值为True,即使用多线程。然后调用django.utils.autoreload.main
方法,并把对象本身的inner_run
方法及命令行参数传入main方法,通过平台选择后,由于我的环境非java平台,所以继续调用autoreload
模块下的
python_reloader
方法,该方法首先判断系统环境中是否包含RUN_MAIN
环境,且值为true
,如果不包含,则使用通过该模块的restart_with_reloader
方法,先设置环境变量RUN_MAIN='true''
然后执行subprocess
模块的call方法,重新运行命令行参数
通过这样一执行,等于上面所执行的步骤都将重来一遍,唯一不同的是再次调用python_reloader方法时,将是新开启一个线程执行之前传入对象的inner_run方法及命令行参数传入
这也就是为什么有人说django每次启动都执行了2次的原因,
- 以上步骤走完后,终于要到我们的
inner_run
方法了,该方法才是启动web server的关键,调用该方法,会打印一些我们每次启动Django服务的看到的基本信息,
Performing system checks...
System check identified no issues (0 silenced).
May 10, 2017 - 00:15:45
Django version 1.11, using settings 'cloud.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
inner_run
内部再次调用该对象的check_migrations
方法同步表结构迁移检查,检查完毕后则调用对象的get_handler
方法,
对象本身重写了get_handler
方法,同时又调用了父类的get_handler
的方法,通过调用父类的get_handler
获取一个WSGIHandler对象实例。
首先调用django.core.servers.basehttp.get_internal_wsgi_application
,该方法通过获取settings配置的WSGI_APPLICATION
属性,默认
的属性都是以项目名+'.wsgi.application'拼接而成的字符串值,默认的django项目下都有一个wsgi模块,即项目名.wsgi
这样的模块,
get_internal_wsgi_application
检测到该settings,再调用django.utils.module_loading.import_string
方法,该方法首先以.
分割字符串,
即WSGI_APPLICATION
的值会分隔为项目名.wsgi
和application
两部分,前半部分为模块路径,后半部分为模块内的属性,先导入该模块,再获取到
该模块的属性,然后返回,默认得到的是一个WSGIHandler实例化对象,django.core.handlers.wsgi.WSGIHandler
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super(WSGIHandler, self).__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
response._handler_class = self.__class__
status = '%d %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
start_response(force_str(status), response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response
实例化时,同时会调用WSGIHandler
父类django.core.handlers.base.BaseHandler
的load_middleware
方法。
该方法首先判断Django使用的哪种版本,1.10(不包括1.10)以前的版本对应settings配置的中间件都是写到MIDDLEWARE_CLASSES
列表里,
1.10开始改为MIDDLEWARE,
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
try:
mw_instance = middleware(handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
if mw_instance is None:
raise ImproperlyConfigured(
'Middleware factory %s returned None.' % middleware_path
)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.append(mw_instance.process_exception)
handler = convert_exception_to_response(mw_instance)
self._middleware_chain = handler
def convert_exception_to_response(get_response):
"""
包裹传入到response对象,所有的已知异常都会被转换成对应的4XX异常,如(Http404,
PermissionDenied, MultiPartParserError, SuspiciousOperation),其它异常都会转换为500错误,
这个装饰器自动应用到所有到中间件,确保中间件内部异常不会泄露而影响到后面到代码运行,
然后下一个中间件可以继续处理上一个中间件返回的response,而不是抛出的异常
"""
@wraps(get_response, assigned=available_attrs(get_response))
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc) # 对于中间件抛出的所有异常进行拦截,然后判断该响应是什么错误,
# 已知的则返回对应的错误码,未知的,都是500错误
return response
return inner
首先该django.core.handlers.base.BaseHandler._get_response
被convert_exception_to_response
包裹起来,然后传入到中间件列表的
最后中间件得到一个中间件实例对象,然后
判断该实例对象是否包含了process_view
方法,如果包含,则将该对象的process_view
插入到一个_view_middleware
的列表的第一个位置,
判断该实例对象是否包含了process_template_response
方法,如果包含,则将该对象的process_template_response
方法追加到
_template_response_middleware
列表末尾,
判读该实例对象是否包含了process_exception
方法,如果包含,则将该对象的process_exception
方法追加到_exception_middleware
列表末尾,
最后将该中间件实例通过convert_exception_to_response
方法包裹,进行下一次迭代时传给下一个中间件作为实例化时的参数对象,直到所有的中间件
都被实例化,然后将最后一个中间件的实例保存到
django.core.handlers.base.BaseHandler
对象的_middleware_chain
属性里。
def get_handler(self, *args, **options):
"""
Returns the static files serving handler wrapping the default handler,
if static files should be served. Otherwise just returns the default
handler.
"""
handler = super(Command, self).get_handler(*args, **options)
use_static_handler = options['use_static_handler']
insecure_serving = options['insecure_serving']
if use_static_handler and (settings.DEBUG or insecure_serving):
return StaticFilesHandler(handler)
return handler
整个WSGIHandler
对象实例化完毕,同时返回给django.core.management.commands.runserver.get_handler
,再由该方法返回给它的子类
django.contrib.staticfiles.management.commands.runserver.get_handler
方法。
get_handler
处理静态文件服务时,检查当前的程序模式是否为DEBUG或命令行是否包含--insecure
选项,如果是则将WSGIHandler
实例化对象
传入给StaticFilesHandler
类进行实例化,并返回,该类也继承了WSGIHandler
.如果非DEBUG模式,且没有包含--insecure
选项,则直接将之前的
实例化对象返回给django.core.management.commands.runserver.Command.inner_run
方法,
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
server_address = (addr, port)
if threading:
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if threading:
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
httpd.serve_forever()
# 将命令行的参数解析后的值传入run方法,并将handler对象传入
run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
看到这个run方法是不是有种很熟悉的感觉,没错,它就是像我们平时使用socketserver方法一样的,被传入的handler会在有连接请求的时候,
自动调用该handler类的handle方法处理请求,通过serve_forever
就进入无限循环状态,直到强制终止,socket服务端才停止服务。
到此,整个Django程序才算完成启动,剩下的都是socket server的操作方式了
Django深度剖析的更多相关文章
- Django深度剖析-二
WEBserver处理过程 先写个大家熟悉的socketserver例子 #! /usr/bin/env python # encoding: utf-8 """ @Au ...
- 《AngularJS深度剖析与最佳实践》简介
由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...
- ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程
从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...
- Objective-C类成员变量深度剖析
目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objec ...
- 大众点评开源分布式监控平台 CAT 深度剖析
一.CAT介绍 CAT系统原型和理念来源于eBay的CAL的系统,CAT系统第一代设计者吴其敏在eBay工作长达十几年,对CAL系统有深刻的理解.CAT不仅增强了CAL系统核心模型,还添加了更丰富的报 ...
- 深度剖析WordPress主题结构(转)
利用强大的技术,可以把基于wordpress的网站做成各种各样的形式,这除了要求wordpress主题开发人员精通html,PHP,JS,CSS等技术,还需要开发者掌握WordPress主题的框架. ...
- LCD深度剖析
LCD 深度剖析 来源:http://blog.csdn.net/hardy_2009/article/details/6922900 http://blog.csdn.net/jaylondon/a ...
- WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)
原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...
- 大兴雷克萨斯深度剖析2013款LS460L_深圳大兴雷克萨斯_太平洋汽车网
大兴雷克萨斯深度剖析2013款LS460L_深圳大兴雷克萨斯_太平洋汽车网 大兴雷克萨斯深度剖析2013款LS460L
随机推荐
- 使用 curses 函数库管理基于文本的屏幕
curses 函数库提供了终端无关的方式来编写全屏幕的基于字符的程序.curses 还可以管理键盘,提供了一种简单易用的非阻塞字符输入模式. curses 函数库能够优化光标的移动并最小化需要对屏幕进 ...
- html知识杂记
1.HTML中不支持 空格.回车.制表符,它们都会被解析成一个空白字符.2.HTML 是用来描述网页的一种语言.3.元素的内容是开始标签与结束标签之间的内容.4.即使 <br> 在所有浏览 ...
- codeforces 1037
题解: E-trips 哎哎哎好傻逼啊 没有想到算不能的一直在想怎么算能的 太傻逼了 其实很简单 我们只需要对好友<=k的首先dfs一下给他连接着的朋友-1 然后如果小于了就递归下去 这个正确性 ...
- [转]搭建Hadoop伪分布式环境
https://my.oschina.net/MyHeaven1987/blog/1821509 http://hadoop.apache.org/docs/current/hadoop-projec ...
- Linux 命令行敲命令 光标移动快捷键
在单词之间跳转,使用Ctrl+左右键. Ctrl+a跳到本行的行首, Ctrl+e则跳到页尾. Ctrl+u删除当前光标前面的文字 ctrl+k-删除当前光标后面的文字 Ctrl+w和Alt+d-对于 ...
- mysql-5.7.10-winx64 绿色版安装办法
mysql-5.7.10-winx64 绿色版安装办法 为了防止安装程序造成电脑系统冗余,经过测试,终于将绿色版的mysql for windows安装成功.当然很多是从事百度搜索到的,但作为一种积累 ...
- Python_ collections_defaultdict默认字典
defaultdict(): 默认类型为字典,继承了字典的方法 import collections dic = collections.defaultdict() dic['k1'] = 'hell ...
- Python_tuple部分功能介绍
x.count():元素在元组内的个数 x.index():元素在元组内的位置
- day 55 jQuery-part2
这里有一个DOM对象转换成jQuery对象的方法,在jQuery对象后面加上索引值0即可得到效果如图所示: $("#btn")[0] 这里我们这里的索引值为0 只是一种写法而已,只 ...
- 函数模拟sort快排
设计一个对一维数组进行排序的sort函数,并调用它实现数组排序 思路:函数调用不止调用一个,最主要对函数不熟悉: #include<stdio.h> #define N 10 int ma ...