Django源码分析--服务启动

Django框架的整个数据流向、服务启动、端口监听等基础核心功能都是按照WSGI标准进行开发的。WSGI是Web Server Gateway Interface的缩写,它的主要作用是接收客户端(浏览器/软件/APP/调试程序)发送的请求和转发请求给上层服务(例如Django)。它采用了select网络模型进行数据的接收和分发,可以利用操作系统的非堵塞和线程池等特性,因此它是非常高效的(Django采用wsgi是纯python代码实现,开源项目有一个uWSGI是C语言代码实现,因此现在更多的django项目实际上是运行在uWSGI上的,uWSGI不再讨论范围之内)。

接下来是记录我利用pycharm提供的断点调试功能以及自己打日志整个过程的梳理和总结。

源码的分析过程取决与分析人员想要关注的原理点,不同的需求就会有不同的侧重点,我当前的需求点是,想要知道django是如何通过网络层接收数据(wsgi)并将请求转发给django的urls层,因此我会朝着这个主题方向列出每个流转环节中的核心代码并做相应的注解。以后如果有机会的话我将会从另外一个侧重点(程序设计)去分析源码。

每个过程我都会先将代码列出来然后在后面做注解,因此告诫自己以后重读自己的笔记时不需要努力的去看代码,而是直接看注解,然后找到对应的代码进行理解。

1、准备工作

  • Python 3.5.2
  • Django 2.1.2
  • PyCharm 2018.2.1 (Professional Edition)
  • 启动项目
1
[min:] ~/Desktop/python/Demo$ python manage.py runserver 0.0.0.0:8000

2、分析流程

manage.py
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TestDrivenDjango.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
...

execute_from_command_line(sys.argv)
  • from django.core.management import execute_from_command_line当这行代码开始执行时,首先会去运行django.core.management.__init__.py这一整个文件,接着找到execute_from_command_line函数并将其导入到当前程序的命名空间中;
  • 另外在导入之前,我们不可忽视的就是这个文件本身的import语句。
django/core/management/__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from __future__ import unicode_literals

import os
import pkgutil
import sys
from collections import OrderedDict, defaultdict
from importlib import import_module

import django # 这里
from django.apps import apps # 这里
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.base import (
BaseCommand, CommandError, CommandParser, handle_default_options,
)
from django.core.management.color import color_style
from django.utils import autoreload, lru_cache, six
from django.utils._os import npath, upath
from django.utils.encoding import force_text

@lru_cache.lru_cache(maxsize=None)
def get_commands():
commands = {name: 'django.core' for name in find_commands(upath(__path__[0]))}
if not settings.configured:
return commands
for app_config in reversed(list(apps.get_app_configs())):
path = os.path.join(app_config.path, 'management')
commands.update({name: app_config.name for name in find_commands(path)})
return commands

class ManagementUtility(object):

def __init__(self, argv=None):
...

def fetch_command(self, subcommand):
commands = get_commands()
try:
app_name = commands[subcommand]
except KeyError:
...
if isinstance(app_name, BaseCommand):
klass = app_name
else:
klass = load_command_class(app_name, subcommand)
return klass

def execute(self):
...
if subcommand == 'help':
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
elif len(options.args) < 1:
sys.stdout.write(self.main_help_text() + '\n')
else:
self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
elif subcommand == 'version' or self.argv[1:] == ['--version']:
sys.stdout.write(django.get_version() + '\n')
elif self.argv[1:] in (['--help'], ['-h']):
sys.stdout.write(self.main_help_text() + '\n')
else:
self.fetch_command(subcommand).run_from_argv(self.argv)

def execute_from_command_line(argv=None):
"""
A simple method that runs a ManagementUtility.
"""
utility = ManagementUtility(argv)
utility.execute()
  • import django 这行代码运行了django.__init__.py文件。from django.apps import apps这行代码运行了django.apps.__init__.py文件,然而整个django的开端就是从这里开始的,它落实了非常多的事情(例如:初始化日志模块、加载INSTALL_APP、检查各APP是否正常、检查缓存模块是否正常等),当一切无误时才会往下走,否则将会报错退出程序。

  • execute_from_command_line这个方法是一个工厂函数,它负责指挥ManagementUtility类利用execute方法来解析参数和启动wsgi服务。ManagementUtility.execute方法中的一大堆if条件就是判断参数是否合法,重点还是在self.fetch_command(subcommand).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(但实际上并不是由它来直接完成,而是由后续很多其他代码来完成),直观上看它应该是runserver.Command对象的一个方法,但实际上要稍微更复杂一些,因为没有列出关联代码,所以在下一个代码块中进行说明。

小结
这部分代码实际上分为两部分,第一部分负责执行一些Django的基础服务,第二部分是一个匹配命令行参数的一个过程,比如本例中通过提供的参数’runserver’到命令工具集中去找到runserver模块。

django/contrib/staticfiles/management/commands/runserver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from django.contrib.staticfiles.handlers import StaticFilesHandler
from django.core.management.commands.runserver import \
Command as RunserverCommand

class Command(RunserverCommand):

def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument(
'--nostatic', action="store_false", dest='use_static_handler', default=True,
help='Tells Django to NOT automatically serve static files at STATIC_URL.',
)
parser.add_argument(
'--insecure', action="store_true", dest='insecure_serving', default=False,
help='Allows serving static files even if DEBUG is False.',
)

def get_handler(self, *args, **options):
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
  • 当前类对象中不存在run_from_argv方法,因此我们要往下看它的继承对象django.core.management.commands.runserver.Command
django/core/management/commands/runserver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from django.core.servers.basehttp import get_internal_wsgi_application, run

class Command(BaseCommand):

def execute(self, *args, **options):
if options['no_color']:
os.environ[str("DJANGO_COLORS")] = str("nocolor")
super(Command, self).execute(*args, **options)

def get_handler(self, *args, **options):
return get_internal_wsgi_application()

def handle(self, *args, **options):
...
self.run(**options)

def run(self, **options):
use_reloader = options['use_reloader']

if use_reloader:
autoreload.main(self.inner_run, None, options)
else:
self.inner_run(None, **options)

def inner_run(self, *args, **options):
...
try:
handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading)
except socket.error as e:
...
  • 当前这个类对象中也没有run_from_argv这个方法,因此我们要往下看它的继承对象django.core.management.base.BaseCommand
django/core/management/base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class BaseCommand(object):

def run_from_argv(self, argv):
...
try:
self.execute(*args, **cmd_options)
except Exception as e:
...
finally:
connections.close_all()

def execute(self, *args, **options):
...
try:
...
output = self.handle(*args, **options)
...
finally:
if saved_locale is not None:
translation.activate(saved_locale)
return output
  • 由于接下来的代码重度使用了继承、多态、接口等设计模式的方式来运作,但是追溯起来并不复杂,所以就不列举出来了,这里重点强调一下inner_run中的self.get_handler(*args, **options)方法,它非常重要,三个重点:

    1. 因为它负责获取WSGIHandler;
    2. self.get_handler返回Django settings中WSGI_APPLICATION的值,一个WSGIHandler对象;
    3. 调用WSGIHandler的__init__方法,完成Django中重要的load_middleware操作。
  • Command.inner_run方法调用了run(即:django.core.servers.basehttp.run),由于没有列出代码块,因此在下一个环节中进行说明。

小结
这部分代码实际上就是一个初始化过程,全部都为runserver服务,虽然很多代码我没有列出来,但是它确实做了一些,例如参数解析、端口指定检测、ipv4检测、ipv6检测、端口是否占用、线程检查,中间件加载等工作。

django/core/servers/basehttp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from wsgiref import simple_server
from django.utils.six.moves import socketserver

class WSGIServer(simple_server.WSGIServer, object):

request_queue_size = 10

def __init__(self, *args, **kwargs):
if kwargs.pop('ipv6', False):
self.address_family = socket.AF_INET6
self.allow_reuse_address = kwargs.pop('allow_reuse_address', True)
super(WSGIServer, self).__init__(*args, **kwargs)

def server_bind(self):
super(WSGIServer, self).server_bind()
self.setup_environ()

def handle_error(self, request, client_address):
if is_broken_pipe_error():
logger.info("- Broken pipe from %s\n", client_address)
else:
super(WSGIServer, self).handle_error(request, client_address)

class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
def address_string(self):
return self.client_address[0]

def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
if threading:
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {}) # Work Here
else:
httpd_cls = WSGIServer
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if threading:
httpd.daemon_threads = True

httpd.set_app(wsgi_handler)
httpd.serve_forever()
  • run函数负责指挥各个对象负责启动wsgi服务。wsgi_handler参数,这里传递的是WSGIHandler
  • httpd_cls这个变量被定义完成之后,由于大量的继承关系,它其实已经不单纯的属于django,它其实更像是一个传统意义上的WSGI服务对象了。httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)这行代码非常重要,因为它是WSGI服务器与django之间相互通信的唯一枢纽通道,也就是说,当WSGI服务对象收到socket请求后,会将这个请求传递给django的WSGIRequestHandler(下节会列出WSGIRequestHandler是如何工作的)。

  • httpd.set_app(wsgi_handler)是将WSGIHandler 传递给WSGIServer当作一个application,当WSGIServer收到网络请求后,可以将数据分发给django.core.servers.basehttp.WSGIRequestHandler,最终由django.core.servers.basehttp.WSGIRequestHandler将数据传递给application(即:django.core.handlers.wsgi.WSGIHandler)。

  • httpd.serve.forever()启动非堵塞网络监听服务。

lib/python3.6/socketserver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class BaseServer:

def __init__(self, server_address, RequestHandlerClass):
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass
self.__is_shut_down = threading.Event()
self.__shutdown_request = False

def serve_forever(self, poll_interval=0.5):
self.__is_shut_down.clear()
try:

with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)

while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
self._handle_request_noblock() # 这里

self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()

def _handle_request_noblock(self):
try:
request, client_address = self.get_request()
except OSError:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address) # 这里
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
else:
self.shutdown_request(request)

def verify_request(self, request, client_address):
"""Verify the request. May be overridden.

Return True if we should proceed with this request.

"""
return True

def process_request(self, request, client_address):
self.finish_request(request, client_address) # 这里
self.shutdown_request(request)

def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self) # 这里
  • 上面我们看到最后一个动作是httpd.serve_forever,调用是socketserver.BaseServer.serve_forever方法。该方法采用了selector网络模型进行等待数据,每0.5秒遍历一次文件描述符,当有数据进来时,ready变量会是一个socket请求对象,这时会将后续工作转交给self._handler_request_noblock方法去处理,然后经过一系列的流转:

    1. _handler_request_noblock

    2. process_request

    3. finish_request

    4. socketserver.BaseServer.RequestHandlerClass

RequestHandlerClass是由httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)传递过来的参数django.core.servers.basehttp.WSGIRequestHandler。 也就是说具体执行的是WSGIRequestHandler(request, client_address, self)

小结
serve_forever开启了一个while来无限监听网络层的scoket请求,当一条请求过来时,就层层转交到django.core.servers.basehttp.WSGIRequestHandler手中。

django.core.servers.basehttp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
def address_string(self):
return self.client_address[0]

def get_environ(self):
for k, v in self.headers.items():
if '_' in k:
del self.headers[k]

env = super(WSGIRequestHandler, self).get_environ()
path = self.path
if '?' in path:
path = path.partition('?')[0]
path = uri_to_iri(path).encode(UTF_8)
env['PATH_INFO'] = path.decode(ISO_8859_1) if six.PY3 else path
return env

def handle(self):
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return

if not self.parse_request(): # An error code has been sent, just exit
return

handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
) # 实例化了ServerHandler对象

handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app()) # 将StaticFilesHandler转交给ServerHandler去运行。
  • 首先列举出来django.core.servers.basehttp.WSGIRequestHandler的继承分布,这个将对我们在分析源码的过程中起到一个非常重要的作用,方便我们理清楚各个类的作用:

    • django.core.servers.basehttp.WSGIRequestHandler
    • wsgiref.simple_server.WSGIRequestHandler
    • http.server.BaseHTTPRequestHandler
    • socketserver.StreamRequestHandler
    • socketserver.BaseRequestHandler
    • object

    因为WSGIRequestHandler并没有__init__或者__call__方法,因此需要遍历所有父类对象,最终在BaseRequestHandler中看到了__init__实例初始化方法,它调用了self.handle方法(即回调了:WSGIRequestHandler.handle),具体见代码注释。

  • ServerHandler对象并没有run方法,最终在 wsgiref.handlers.BaseHandler 中找到了run方法。

wsgiref.handlers.py
1
2
3
4
5
class BaseHandler:
def run(self, application):
...
self.result = application(self.environ, self.start_response) # 这里
self.finish_response()
  • application(self.environ, self.start_response)也就相当于是WSGIHandler.__call__(self.environ, self.start_response)
django.core.handlers.wsgi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest

def __init__(self, *args, **kwargs):
super().__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 = list(response.items())
for c in response.cookies.values():
response_headers.append(('Set-Cookie', c.output(header='')))
start_response(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,后续的动作我们会在接下来的章节继续介绍。
  • environ这个变量在django的WSGIServer和WSGIRequestHandler中扮演这非常重要的角色,因为所有的客户端ip请求的URLcookiesessionheader等等信息都保存在其中。

3、总结

上面所有的过程都是django内部代码的为了启动服务而做的准备,简单的把流程给列出来。

  1. 解析运行 python manage.py 所提供的参数,例如: runserver;
  2. 根据参数 找到相对应的 命令管理工具;
  3. 加载所有的app;
  4. 检查端口、ipv4检测、ipv6检测、端口是否占用、线程检查、orm对象检查(表是否创建);
  5. 实例化WSGIRequestHandler,并且将它注册到python Lib库中的WSGIServer中;
  6. 最后启动python Lib库中的WSGIServer;
  7. WSGIServer处理socket请求、对接WSGIRequestHandler;
  8. WSGIRequestHandler针对environ进行预处理、对接ServerHandler;
  9. ServerHandler最后调用了WSGIHandler;
  10. WSGIHandler用于执行应用程序(application)和返回响应给WSGIServer。

参考https://www.jianshu.com/p/17d78b52c732