Django 初步运行过程分析笔记
阅读原文时间:2023年07月09日阅读:2

2. django运行过程分析
第一个过程分析:django启动过程
python mangage.py runserver 0.0.0.0:8000
这个命令先被python的sys.argv接收起来,保存成[ mangage.py, runserver, 0.0.0.0:8000]
然后execute_from_command_line(sys.argv)
它会调用ManagementUtility(argv).execute()
而execute()中,会先调用settings.INSTALLED_APPS,再调用django.setup()方法,然后调用self.fetch_command(subcommand).run_from_argv(self.argv)方法
*******先看settings.INSTALLED_APPS理论上就是访问settings.py中的INSTALLED配置
为什么?因为在运行manage.py时,第一个代码:os.environ.setdefault已经把配置文件设置到了DJANGO_SETTINGS_MODULE中,而这个DJANGO_SETTINGS_MODULE是保存在os.environ中的;然后就到这里的代码settings.INSTALLED_APPS,python导入settings后,才能使用settings.INSTALLED_APPS,导入settings时,会执行LazySettings(),而LazySettings中,重写了setattr方法,这意味着一旦我们访问LazySettings中的实例属性,那么就会自动执行setattr方法,又由于settings就是LazySettings(),也就是LazySettings的实例对象,所以settings.INSTALLED_APPS时,就会执行LazySettings()._setup()方法,这个方法中加载之前保存的DJANGO_SETTINGS_MODULE到LazySettings()中,也就是settings中,所以现在settings中就有了INSTALL_APPS,就能访问到了settings配置中的App字典)
然后再看apps.populate(settings.INSTALLED_APPS)中的apps.populate(…)
*******然后看django.setup()
django.setup()中,先配置日志模块configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
然后set_script_prefix设置前缀为:"/"
最后看apps.populate(settings.INSTALLED_APPS)
先看apps是什么,追踪源码可以很容易知道apps就是Apps的实例化对象
它主要的作用是把settings.INSTALLED_APPS中保存的字符串,用反射机制的加载成对应字符串匹配的类,也就是加载成app
然后把app保存到Apps类中的app_configs中,key是app的标签,value是app
然后把app保存到Apps类中的apps中
这样,django中的apps,就保存了当前django项目中的app信息后面就可以使用了,所以django.setup()主要做了三件事儿
1. 初始化日志配置
2. 设置settings.FORCE_SCRIPT_NAME的值为:"/"
3. 初始化app应用
*******然后分析:self.fetch_command(subcommand).run_from_argv(self.argv)
这里也要分成两个部分,self.fetch_command(subcommand)就是self.fetch_command('runserver')
它最终会返回一个runserver.Command()类
所以self.fetch_command(subcommand).run_from_argv(self.argv)就是调用runserver.Command().run_from_argv(self.argv)
那么runserver.Command().run_from_argv(self.argv)做了什么,核心看self.execute(*args, **cmd_options)
*******self.execute(*args, **cmd_options)
核心是:self.handle(*args, **options)
*******self.handle(*args, **options)
这里的self.handle要讨论下父子继承关系,直接用ctrl看self.handle的源码,发现没有什么可以看的,所以这里肯定用的是子类的handle方法,那么子类是谁,就是runserver.Command()类,所以我们直接看runserver.Command()类中的handler,
[图]
可以发现,这里又ipv6,port等等这些东西,这说明我们找对地方了,因为启动django服务器的本质就是启动web服务器,而web服务器的启动必须要有ip和端口的,因为之前我们讲过现在网络编程就是基于socket编程,所以底层一定又socket
再这里,self.handle(*args, **options)中,核心代码就是self.run(**options)
*******self.run(**options)
里面核心代码两条:
autoreload.run_with_reloader(self.inner_run, **options)
self.inner_run(None, **options)
其中autoreload.run_with_reloader主要是检查django的文件有没有变动,如果有变动则停止服务器,然后重启,重启时运行的代码还是self.inner_run,重启过程不是我们需要分析的重点,这里我们直接分析self.inner_run即可

*******self.inner_run(None, **options)
核心代码:
handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)

*******handler = self.get_handler(*args, **options)
由于目前的self还是Command,所以会调用Command().get_handler
Command().get_handler中,先调用了父类的get_handler,这个会根据settings配置来加载,最终得到WSGIHandler()
注意这里,它会导入wsgi.py文件,并执行里面的代码,会导致WSGIHandler()得到实例化,

然后有个if条件,满足if条件返回StaticFilesHandler(WSGIHandler())
不满足返回WSGIHandlers,这里我们讨论核心WSGIHandlers

********************************现在看下核心代码:WSGIHandler()

而WSGIHandler()实例化后,会调用WSGIHandler.load_middleware()方法,这个方法非常重要
它的源码如下:
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []

get_response = self._get_response_async if is_async else self._get_response
handler = convert_exception_to_response(get_response)
handler_is_async = is_async
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
middleware_can_sync = getattr(middleware, 'sync_capable', True)
middleware_can_async = getattr(middleware, 'async_capable', False)
if not middleware_can_sync and not middleware_can_async:
raise RuntimeError(
'Middleware %s must have at least one of '
'sync_capable/async_capable set to True.' % middleware_path
)
elif not handler_is_async and middleware_can_sync:
middleware_is_async = False
else:
middleware_is_async = middleware_can_async
try:
# Adapt handler, if needed.
adapted_handler = self.adapt_method_mode(
middleware_is_async, handler, handler_is_async,
debug=settings.DEBUG, name='middleware %s' % middleware_path,
)
mw_instance = middleware(adapted_handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
else:
handler = adapted_handler

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,
self.adapt_method_mode(is_async, mw_instance.process_view),
)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.append(
self.adapt_method_mode(is_async, mw_instance.process_template_response),
)
if hasattr(mw_instance, 'process_exception'):
# The exception-handling stack is still always synchronous for
# now, so adapt that way.
self._exception_middleware.append(
self.adapt_method_mode(False, mw_instance.process_exception),
)

handler = convert_exception_to_response(mw_instance)
handler_is_async = middleware_is_async

# Adapt the top of the stack, if needed.
handler = self.adapt_method_mode(is_async, handler, handler_is_async)
# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler
先看第一条:get_response = self._get_response_async if is_async else self._get_response
这句代码中,先不看异步处理部分,那么get_response = self._get_response
而self._get_response中,有一句代码:
callback, callback_args, callback_kwargs = self.resolve_request(request)
里面的self.resolve_request(request),就是用来解析请求的,这个代码是Django进行路由控制的第一行代码
这里可以思考一下,这个代码现在就会开始解析URL吗?显然不会,因为get_response = self._get_response只是赋值,没有调用
第二条:handler = convert_exception_to_response(get_response)
这个代码里面就像一个装饰器,里面封装了异步执行get_response的代码;这里没有执行get_response
第三条:handler_is_async = is_async 设置异步执行状态,默认为False
for middleware_path in reversed(settings.MIDDLEWARE): 反向遍历中间件配置
middleware = import_string(middleware_path) 导入中间件
middleware_can_sync = getattr(middleware, 'sync_capable', True) 获取中间件是否可以同步执行的重要标志,如果没有设置,则True
middleware_can_async = getattr(middleware, 'async_capable', False) 同上,设置异步状态默认为False
然后一对条件,判断是异步异常还是同步执行,这里我们只看同步
接着创建adapted_handler,在同步模式下,这个代码会直接返回handler,也就是get_response,也就是self._get_response
adapted_handler = self.adapt_method_mode(
middleware_is_async, handler, handler_is_async,
debug=settings.DEBUG, name='middleware %s' % middleware_path,
) # 这里也没有执行get_response
mw_instance = middleware(adapted_handler) 在同步状态下,adapted_handler就是get_response,所以这里就是把adapted_handler传入middleware中,交给middleware去执行;每个中间件执行时,都会传入当前的get_response方法
在中间件中会根据get_response进行初始化,但是并不会立刻执行

接着就是判断中间件有没有属性'process_view'、'process_template_response'、'process_exception',如果有就将其添加到中间件的列表中,再执行 handler = convert_exception_to_response(mw_instance) 把中间件实例化也做成一个convert_exception_to_response的函数,这个handler会保存在for循环中,下一次循环的handler就会变成handler = convert_exception_to_response(mw_instance)
所以这会形成一个中间件的调用链,调用链的顶部恰好就是中间件配置文件的第一个
最后把这个调用链保存在self._middleware_chain中

******run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
这个代码是启动Django服务器的代码,相当于:
run(self.addr, int(self.port),WSGIHandlers, ipv6=self.use_ipv6, threading=threading, server_cls=WSGIServer)
其中的核心代码是:
httpd_cls = server_cls
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
httpd.set_app(wsgi_handler)
httpd.serve_forever()
run的核心代码第二行的意思httpd = WSGIServer(server_address, WSGIRequestHandler, ipv6=ipv6)
然后调用WSGIServer(…).set_app(WSGIHandler)
最后调用httpd.serve_forever()
我们来逐个解析,先解析httpd = WSGIServer(server_address, WSGIRequestHandler, ipv6=ipv6)
这句话是做初始化,初始化WSGI服务器,里面封装了HTTPServer、TCPServe、BaseServer、sokect
初始化后,地址的绑定就不说了;重点是WSGIRequestHandler最终会赋值给BaseServer.RequestHandlerClass;
WSGIRequestHandler是什么东西等下再说
经过httpd = WSGIServer(server_address, WSGIRequestHandler, ipv6=ipv6)的执行,httpd就成了一个实例化的WSGIServer
里面保存了WSGIRequestHandler这个非常重要的控制器

第二个是在WSGIServer中的setup_environ方法,这个方法虽然在初始化时不会调用,可是后面serve_forever后会被调用,主要的作用就是设置WSGIServer的环境变量env,并且这个参数会保存在self.base_environ中

然后再看WSGIServer(…).set_app(WSGIHandler)
它主要是把WSGIHandler保存在WSGIServer.application中

******httpd.serve_forever()
相当于WSGIServer.serve_forever(),它会调用BaseServer中的serve_foreve()
然后执行核心代码:self._handle_request_noblock()
******self._handle_request_noblock()
核心代码第一个:request, client_address = self.get_request()
这句话会让服务器阻塞在这里,并接收客户端传递过来的数据
所以到这里,django的启动就完成了

---------------------------------------------------接下来看django框架如何处理请求和响应数据---------------------------
假设我们使用浏览器客户端发送了HTTP请求过来了,那么就会被request接收,此时的请求还是一个socket对象
接着就会执行self.process_request(request, client_address),它的代码不多,直接贴出来:
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)
这里会执行self.RequestHandlerClass(request, client_address, self)
之前讲过self.RequestHandlerClass就是WSGIRequestHandler,这是初始化时决定的
所以相当于执行WSGIRequestHandler(request, client_address),也就相当于初始化WSGIRequestHandler
初始化时,会通过父子继承关系,执行BaseRequestHandler.__init__的代码
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
主要把HTTP请求保存到了WSGIRequestHandler.request
把地址保存在WSGIRequestHandler.client_address
把WSGIServer保存在WSGIRequestHandler.server
然后调用WSGIRequestHandle.setup()方法
接着调用WSGIRequestHandle.handle()方法
最后调用WSGIRequestHandle.finish()方法
*******WSGIRequestHandle.setup()
根据继承关系,会调用到StreamRequestHandler.setup方法
def setup(self):
self.connection = self.request
if self.timeout is not None:
self.connection.settimeout(self.timeout)
if self.disable_nagle_algorithm:
self.connection.setsockopt(socket.IPPROTO_TCP,
socket.TCP_NODELAY, True)
self.rfile = self.connection.makefile('rb', self.rbufsize)
if self.wbufsize == 0:
self.wfile = _SocketWriter(self.connection)
else:
self.wfile = self.connection.makefile('wb', self.wbufsize)
这个里面会把socket对象保存在WSGIRequestHandle.connection中
然后把WSGIRequestHandle.connection对象做成一个可读文件WSGIRequestHandle.rfiles,然后我们就可以像操作文件一样操作self.rfile了
然后再得到一个:self.wfil对象,它也是一个文件对象,可以实现写操作

********WSGIRequestHandle.handle()
def handle(self):
self.close_connection = True
self.handle_one_request()
while not self.close_connection:
self.handle_one_request()
try:
self.connection.shutdown(socket.SHUT_WR)
except (AttributeError, OSError):
pass
----这里会执行self.handle_one_request()
然后看核心self.handle_one_request()的核心代码:
self.raw_requestline = self.rfile.readline(65537) # 读取一行数据,一行数据最大65537
if not self.parse_request(): # 调用self.parse_request()方法,如果执行成功没事,执行失败则停止执行,停止后最终会进入下一个循环开始
接收下一个http请求
而self.parse_request()中,
* 先将self.raw_requestline的数据去掉换行符,然后保存再self.requestline中
接着切割requestline,判断单词的数量,如果有达到了3个,那么获取版本号并保存在self.request_version中
* 切割后把请求方法和路径都接收起来: command, path = words[:2]
并保存在self.command, self.path中
* 接着执行这行代码:self.headers = http.client.parse_headers(self.rfile,_class=self.MessageClass)
它的作用是读取请求头并把请求 头保存在self.headers中
也就是说,执行self.parse_request()后,self.handle_one_request()读取了请求行、请求头HTTP结构中的两大数据了,并把相关信息保存在了WSGIRequestHandle中
然后看self.handle_one_request()核心代码 handler = ServerHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ())
它生成了一个ServerHandler实例对象,这个对象中,保存了stdin,stdout,stderror,evn信息
然后把WSGIRequestHandler保存在ServerHandler.request_handler中(对应代码:handler.request_handler = self)
最后调用 handler.run(self.server.get_app()) 由于self.server是之前初始化时,保存的WSGIServer
相当于运行 ServerHandler.run(self.WSGIServer.get_app()) 由于WSGIServer.get_app()会返回self.application,
相当于运行 ServerHandler.run(WSGIServer.application) 而WSGIServer.application的值是之前设置的wsgi_handler,也就是WSIGHandler
相当于运行 ServerHandler.run(WSGIHandler())
所以handler.run(self.server.get_app()),相当于ServerHandler.run(WSGIHandler())
* handler.run(self.server.get_app())
里面的核心代码3行:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
先看第一行self.setup_environ(),相当于ServerHandler.setup_environ(),这个里面会把之前os.environ中的环境都copy一份过来
再看第二行self.result = application(self.environ, self.start_response),这个application是传入的WSGIHandler()
所以相当于运行WSGIHandler()(self.environ,self.start_response)
其中self.environ是上一行执行后保存的,它是从os.environ中copy的一份环境
然后self.start_response是一个方法名,这个方法中编写了处理响应数据的方法
而WSGIHandler()(self.environ,self.start_response),会执行WSGIHandler.__call__方法
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 = [
*response.items(),
*(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
]
start_response(status, response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
# If `wsgi.file_wrapper` is used the WSGI server does not call
# .close on the response, but on the file wrapper. Patch it to use
# response.close instead which takes care of closing all files.
response.file_to_stream.close = response.close
response = environ['wsgi.file_wrapper'](response.file_to_stream, response.block_size)
return response

在这个WSGIHandler的__call__方法中,
set_script_prefix(get_script_name(environ)) 是设置/作为路由的前缀
signals.request_started.send(sender=self.__class__, environ=environ) 这个不用管他,解决弱引用问题的
request = self.request_class(environ) # 相当于WSGIRequest(environ)
response = self.get_response(request) # 执行get_response方法,里面会调用self._middleware_chain(request),执行中间件
调用self._middleware_chain(request)后,就会解析URL了