Django 框架使用及源码分析

任何事情,你想做就有方法,不想做就有借口,你有选择的自由,也有承担后果的义务。不要明天再努力,只有努力过完今天的人才有明天。

简介

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
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)

execute_from_command_line函数:核心代码如下,基本流程就是先实例化一个管理工具类对象,然后调用相应的执行函数。在执行函数中,首先接收来自命令行的命令,然后进行校验和判断,如果是runserver,则调用django.setup()函数(该函数是关键),其他的基本都是一些意外的处理。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# django/core/management/__init__.py
def execute_from_command_line(argv=None):
"""
A simple method that runs a ManagementUtility.
"""
utility = ManagementUtility(argv)
utility.execute()

class ManagementUtility(object):
"""
Encapsulates the logic of the django-admin and manage.py utilities.

A ManagementUtility has a number of commands, which can be manipulated
by editing the self.commands dictionary.
"""
# 初始化函数:在实例化时把命令行参数记下来,同时用项目文件夹名作为项目名记录下来
def __init__(self, argv=None):
self.argv = argv or sys.argv[:]
self.prog_name = os.path.basename(self.argv[0])
self.settings_exception = None

def main_help_text(self, commands_only=False):
...

def fetch_command(self, subcommand):
...

def autocomplete(self):
...

# 真正执行命令的函数,这里以runserver命名为例,看看项目启动都干了什么
def execute(self):
"""
Given the command-line arguments, this figures out which subcommand is
being run, creates a parser appropriate to that command, and runs it.
"""
try:
# 拿具体的命令行指令,比如'runserver'
subcommand = self.argv[1]
except IndexError:
subcommand = 'help' # Display help if no arguments were given.

# Preprocess options to extract --settings and --pythonpath.
# These options could affect the commands that are available, so they
# must be processed early.
# 下面部分是一些命令行参数的读取和校验,这一块只要settings配置和命令行参数没问题就可以过
parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*') # catch-all
try:
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
except CommandError:
pass # Ignore any option errors at this point.

no_settings_commands = [
'help', 'version', '--help', '--version', '-h',
'compilemessages', 'makemessages',
'startapp', 'startproject',
]

try:
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
# A handful of built-in management commands work without settings.
# Load the default settings -- where INSTALLED_APPS is empty.
if subcommand in no_settings_commands:
settings.configure()

# 如果settings已配置,则开始做命令的相应处理,这里是启动项目runserver部分
if settings.configured:
# Start the auto-reloading dev server even if the code is broken.
# The hardcoded condition is a code smell but we can't rely on a
# flag on the command class because we haven't located it yet.
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
# 这里的check_errors是个闭包实现,其实还是调用django.setup(),然后如果有exception就跑出来到下面进行处理。
autoreload.check_errors(django.setup)()
except Exception:
# The exception will be raised later in the child process
# started by the autoreloader. Pretend it didn't happen by
# loading an empty list of applications.
apps.all_models = defaultdict(OrderedDict)
apps.app_configs = OrderedDict()
apps.apps_ready = apps.models_ready = apps.ready = True

# In all other cases, django.setup() is required to succeed.
else:
django.setup()

# 下面是其他的一些可能的命令,如'version'或'help'
self.autocomplete()
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])
# Special-cases: We want 'django-admin --version' and
# 'django-admin --help' to work, for backwards compatibility.
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)

这一部分先看到这里,这里基本是启动项目前一半的过程,我们可以知道:django项目的入口程序其实就是manage.py通过对命令行参数进行解析,然后对不同的命令进行不同的处理。针对runserverdjango会调用django.setup()函数来进行项目的初始化配置,然后调用 self.fetch_command(subcommand).run_from_argv(self.argv) 来进行项目启动,包括启动WSGI等。

App 注册

django.setup() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# django/__init__.py
from django.utils.version import get_version

VERSION = (1, 8, 14, 'final', 0)

__version__ = get_version(VERSION)


def setup():
"""
Configure the settings (this happens as a side effect of accessing the
first setting), configure logging and populate the app registry.
"""
from django.apps import apps
from django.conf import settings
from django.utils.log import configure_logging

# 配置一下日志
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
# 配置app
apps.populate(settings.INSTALLED_APPS)

apps.populate() 函数:在项目启动时,对开发者写好的项目中的app进行配置。因为是类中的函数,用到了一些属性,所以这里把init函数也一起放出来。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# django/apps/registry.py
import os
import sys
import threading
import warnings
from collections import Counter, OrderedDict, defaultdict

from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
from django.utils import lru_cache
from django.utils._os import upath
from django.utils.deprecation import RemovedInDjango19Warning

from .config import AppConfig


class Apps(object):
"""
A registry that stores the configuration of installed applications.

It also keeps track of models eg. to provide reverse-relations.
"""
# 初始化函数放在这里主要是为了说明几个重要的实例变量:self.all_models, self.app_configs.
def __init__(self, installed_apps=()):
# installed_apps is set to None when creating the master registry
# because it cannot be populated at that point. Other registries must
# provide a list of installed apps and are populated immediately.
if installed_apps is None and hasattr(sys.modules[__name__], 'apps'):
raise RuntimeError("You must supply an installed_apps argument.")

# Mapping of app labels => model names => model classes. Every time a
# model is imported, ModelBase.__new__ calls apps.register_model which
# creates an entry in all_models. All imported models are registered,
# regardless of whether they're defined in an installed application
# and whether the registry has been populated. Since it isn't possible
# to reimport a module safely (it could reexecute initialization code)
# all_models is never overridden or reset.
# 存项目中所有的models,是在import的时候更新,不在setup的时候
self.all_models = defaultdict(OrderedDict)

# Mapping of labels to AppConfig instances for installed apps.
# 存各个app的配置实例
self.app_configs = OrderedDict()

# Stack of app_configs. Used to store the current state in
# set_available_apps and set_installed_apps.
self.stored_app_configs = []

# Whether the registry is populated.
self.apps_ready = self.models_ready = self.ready = False

# Lock for thread-safe population.
self._lock = threading.Lock()

# Pending lookups for lazy relations.
self._pending_lookups = {}

# Populate apps and models, unless it's the master registry.
if installed_apps is not None:
self.populate(installed_apps)

# 启动项目时最主要的执行函数
def populate(self, installed_apps=None):
"""
Loads application configurations and models.

This method imports each application module and then each model module.

It is thread safe and idempotent, but not reentrant.
"""
if self.ready:
return

# populate() might be called by two threads in parallel on servers
# that create threads before initializing the WSGI callable.
with self._lock:
if self.ready:
return

# app_config should be pristine, otherwise the code below won't
# guarantee that the order matches the order in INSTALLED_APPS.
# 这里因为self._lock用的是可重入锁,所以同一个线程是可以在配置过程中多次进入到这里的,所以通过加self.app_configs状态防止同一线程的多次setup
if self.app_configs:
raise RuntimeError("populate() isn't reentrant")

# Load app configs and app modules.
# 这里的installed_apps是settings.py传过来的我们自己写的那个列表
for entry in installed_apps:
if isinstance(entry, AppConfig):
# 这里是为了多一种配置方式吧,我们自己写的entry基本都是字符串,进入else分支
app_config = entry
else:
# 这里创建一个app配置类实例,代码在下面,也就现在在针对每一个app进行配置。这里最好先到后面看看每一个AppConfig对象里面属性的含义。
app_config = AppConfig.create(entry)
# 配置完之后检查是否有重复的app配置实例的label
if app_config.label in self.app_configs:
raise ImproperlyConfigured(
"Application labels aren't unique, "
"duplicates: %s" % app_config.label)
# 把每一个app的配置实例添加到self.app_configs中
self.app_configs[app_config.label] = app_config

# Check for duplicate app names.
# 这里确保配置类对象的名字不会重复
counts = Counter(
app_config.name for app_config in self.app_configs.values())
duplicates = [
name for name, count in counts.most_common() if count > 1]
if duplicates:
raise ImproperlyConfigured(
"Application names aren't unique, "
"duplicates: %s" % ", ".join(duplicates))

self.apps_ready = True

# Load models.
# 将每个模块所拥有的models分配到每个模块的app配置实例中,对应函数代码在下面
for app_config in self.app_configs.values():
all_models = self.all_models[app_config.label]
app_config.import_models(all_models)

self.clear_cache()

self.models_ready = True

# 跑每一个app_config的ready函数,对应函数代码和分析在下面(重点)
for app_config in self.get_app_configs():
app_config.ready()

self.ready = True

def check_apps_ready(self):

def check_models_ready(self):

def get_app_configs(self):

def get_app_config(self, app_label):

# This method is performance-critical at least for Django's test suite.
@lru_cache.lru_cache(maxsize=None)
def get_models(self, app_mod=None, include_auto_created=False,
include_deferred=False, include_swapped=False):

def get_model(self, app_label, model_name=None):

def register_model(self, app_label, model):

def is_installed(self, app_name):

def get_containing_app_config(self, object_name):

def get_registered_model(self, app_label, model_name):

def set_available_apps(self, available):

def unset_available_apps(self):

def set_installed_apps(self, installed):

def unset_installed_apps(self):

def clear_cache(self):

def load_app(self, app_name):

def app_cache_ready(self):

def get_app(self, app_label):

def get_apps(self):

def _get_app_package(self, app):

def get_app_package(self, app_label):

def _get_app_path(self, app):

def get_app_path(self, app_label):

def get_app_paths(self):

def register_models(self, app_label, *models):

# 先看看apps是什么,其实是Apps类对象是实例,实例化之后通过在该目录下__init__.py中__all__ = ['AppConfig', 'apps']暴露到全局
apps = Apps(installed_apps=None)

上面代码中,有项目启动时候初始化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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# django/apps/config.py
import os
from importlib import import_module

from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
from django.utils._os import upath
from django.utils.module_loading import module_has_submodule

MODELS_MODULE_NAME = 'models'


class AppConfig(object):
"""
Class representing a Django application and its configuration.
"""

# 各个属性的注释可以说很详细了,主要看例子!注意label和name的不同,同时两者都要求不能重复
def __init__(self, app_name, app_module):
# Full Python path to the application eg. 'django.contrib.admin'.
self.name = app_name

# Root module for the application eg. <module 'django.contrib.admin'
# from 'django/contrib/admin/__init__.pyc'>.
self.module = app_module

# The following attributes could be defined at the class level in a
# subclass, hence the test-and-set pattern.

# Last component of the Python path to the application eg. 'admin'.
# This value must be unique across a Django project.
if not hasattr(self, 'label'):
self.label = app_name.rpartition(".")[2]

# Human-readable name for the application eg. "Admin".
if not hasattr(self, 'verbose_name'):
self.verbose_name = self.label.title()

# Filesystem path to the application directory eg.
# u'/usr/lib/python2.7/dist-packages/django/contrib/admin'. Unicode on
# Python 2 and a str on Python 3.
if not hasattr(self, 'path'):
self.path = self._path_from_module(app_module)

# Module containing models eg. <module 'django.contrib.admin.models'
# from 'django/contrib/admin/models.pyc'>. Set by import_models().
# None if the application doesn't have a models module.
self.models_module = None

# Mapping of lower case model names to model classes. Initially set to
# None to prevent accidental access before import_models() runs.
self.models = None

def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.label)

def _path_from_module(self, module):
"""Attempt to determine app's filesystem path from its module."""
# See #21874 for extended discussion of the behavior of this method in
# various cases.
# Convert paths to list because Python 3.3 _NamespacePath does not
# support indexing.
paths = list(getattr(module, '__path__', []))
if len(paths) != 1:
filename = getattr(module, '__file__', None)
if filename is not None:
paths = [os.path.dirname(filename)]
if len(paths) > 1:
raise ImproperlyConfigured(
"The app module %r has multiple filesystem locations (%r); "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attribute." % (module, paths))
elif not paths:
raise ImproperlyConfigured(
"The app module %r has no filesystem location, "
"you must configure this app with an AppConfig subclass "
"with a 'path' class attribute." % (module,))
return upath(paths[0])

# 这里用类方法做实例化,其实这里才是真正的实例化逻辑,上面的init函数只是声明了一些属性
@classmethod
def create(cls, entry):
"""
Factory that creates an app config from an entry in INSTALLED_APPS.
"""
try:
# If import_module succeeds, entry is a path to an app module,
# which may specify an app config class with default_app_config.
# Otherwise, entry is a path to an app config class or an error.
# 这里导入对应的app module
module = import_module(entry)

except ImportError:
# Track that importing as an app module failed. If importing as an
# app config class fails too, we'll trigger the ImportError again.
module = None

mod_path, _, cls_name = entry.rpartition('.')

# Raise the original exception when entry cannot be a path to an
# app config class.
if not mod_path:
raise

else:
try:
# If this works, the app module specifies an app config class.
# 这里的entry是每个django app中对应的配置类,对应于apps.py
entry = module.default_app_config
except AttributeError:
# Otherwise, it simply uses the default app config class.
return cls(entry, module)
else:
mod_path, _, cls_name = entry.rpartition('.')

# If we're reaching this point, we must attempt to load the app config
# class located at <mod_path>.<cls_name>
mod = import_module(mod_path)
try:
# 这里拿到我们项目中某个app下apps.py中的那个xxxConfig类,继承自AppConfig
cls = getattr(mod, cls_name)
except AttributeError:
if module is None:
# If importing as an app module failed, that error probably
# contains the most informative traceback. Trigger it again.
import_module(entry)
else:
raise

# Check for obvious errors. (This check prevents duck typing, but
# it could be removed if it became a problem in practice.)
if not issubclass(cls, AppConfig):
raise ImproperlyConfigured(
"'%s' isn't a subclass of AppConfig." % entry)

# Obtain app name here rather than in AppClass.__init__ to keep
# all error checking for entries in INSTALLED_APPS in one place.
try:
# 这个是我们生成app的时候指定的那个名字,会自动填到xxxConfig的name属性中
app_name = cls.name
except AttributeError:
raise ImproperlyConfigured(
"'%s' must supply a name attribute." % entry)

# Ensure app_name points to a valid module.
app_module = import_module(app_name)

# Entry is a path to an app config class.
# 真正实例化这个类,这里的cls其实就是xxxConfig
return cls(app_name, app_module)

def check_models_ready(self):
"""
Raises an exception if models haven't been imported yet.
"""
if self.models is None:
raise AppRegistryNotReady(
"Models for app '%s' haven't been imported yet." % self.label)

def get_model(self, model_name):
"""
Returns the model with the given case-insensitive model_name.

Raises LookupError if no model exists with this name.
"""
self.check_models_ready()
try:
return self.models[model_name.lower()]
except KeyError:
raise LookupError(
"App '%s' doesn't have a '%s' model." % (self.label, model_name))

def get_models(self, include_auto_created=False,
include_deferred=False, include_swapped=False):
"""
Returns an iterable of models.

By default, the following models aren't included:

- auto-created models for many-to-many relations without
an explicit intermediate table,
- models created to satisfy deferred attribute queries,
- models that have been swapped out.

Set the corresponding keyword argument to True to include such models.
Keyword arguments aren't documented; they're a private API.
"""
self.check_models_ready()
for model in self.models.values():
if model._deferred and not include_deferred:
continue
if model._meta.auto_created and not include_auto_created:
continue
if model._meta.swapped and not include_swapped:
continue
yield model

# 这里可以看到,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配置实例去apps管理器那里拿属于自己的models进行属性赋值。
def import_models(self):
def import_models(self, all_models):
# Dictionary of models for this app, primarily maintained in the
# 'all_models' attribute of the Apps this AppConfig is attached to.
# Injected as a parameter because it gets populated when models are
# imported, which might happen before populate() imports models.
self.models = all_models

if module_has_submodule(self.module, MODELS_MODULE_NAME):
models_module_name = '%s.%s' % (self.name, MODELS_MODULE_NAME)
self.models_module = import_module(models_module_name)

# 注释很清晰了,用户可通过重写自定义项目启动时app的行为
def ready(self):
"""
Override this method in subclasses to run code when Django starts.
"""

app的相关配置有两个关键部分,一个是对单个app的配置类AppConfig,一个是对所有app的管理类Apps,这两个类紧密相关,是总分关系,同时相互索引。在初始化过程中,Apps在控制流程,主要包括对所有app进行路径解析、名字去重、对每个app配置类进行初始化、赋予每个app配置类它们的相关models、还有就是执行每个app开发者自己定义的ready函数。

下面看下 self.fetch_command(subcommand).run_from_argv(self.argv)

启动

获取执行命令所需要的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# django/core/management/__init__.py
class ManagementUtility(object):
def fetch_command(self, subcommand):
"""
Tries to fetch the given subcommand, printing a message with the
appropriate command called from the command line (usually
"django-admin" or "manage.py") if it can't be found.
"""
# Get commands outside of try block to prevent swallowing exceptions
commands = get_commands()
try:
app_name = commands[subcommand]
except KeyError:
# This might trigger ImproperlyConfigured (masked in get_commands)
settings.INSTALLED_APPS
sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" %
(subcommand, self.prog_name))
sys.exit(1)
if isinstance(app_name, BaseCommand):
# If the command is already loaded, use it directly.
klass = app_name
else:
klass = load_command_class(app_name, subcommand)
return klass
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# django/core/management/base.py
class BaseCommand(object):
def run_from_argv(self, argv):
"""
Set up any environment changes requested (e.g., Python path
and Django settings), then run this command. If the
command raises a ``CommandError``, intercept it and print it sensibly
to stderr. If the ``--traceback`` option is present or the raised
``Exception`` is not ``CommandError``, raise it.
"""
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])

if self.use_argparse:
options = parser.parse_args(argv[2:])
cmd_options = vars(options)
# Move positional args out of options to mimic legacy optparse
args = cmd_options.pop('args', ())
else:
options, args = parser.parse_args(argv[2:])
cmd_options = vars(options)
handle_default_options(options)
try:
self.execute(*args, **cmd_options)
except Exception as e:
if options.traceback or not isinstance(e, CommandError):
raise

# SystemCheckError takes care of its own formatting.
if isinstance(e, SystemCheckError):
self.stderr.write(str(e), lambda x: x)
else:
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(1)
finally:
connections.close_all()

def execute(self, *args, **options):
"""
Try to execute this command, performing system checks if needed (as
controlled by attributes ``self.requires_system_checks`` and
``self.requires_model_validation``, except if force-skipped).
"""
if options.get('no_color'):
self.style = no_style()
self.stderr.style_func = None
if options.get('stdout'):
self.stdout = OutputWrapper(options['stdout'])
if options.get('stderr'):
self.stderr = OutputWrapper(options.get('stderr'), self.stderr.style_func)

saved_locale = None
if not self.leave_locale_alone:
# Only mess with locales if we can assume we have a working
# settings file, because django.utils.translation requires settings
# (The final saying about whether the i18n machinery is active will be
# found in the value of the USE_I18N setting)
if not self.can_import_settings:
raise CommandError("Incompatible values of 'leave_locale_alone' "
"(%s) and 'can_import_settings' (%s) command "
"options." % (self.leave_locale_alone,
self.can_import_settings))
# Deactivate translations, because django-admin creates database
# content like permissions, and those shouldn't contain any
# translations.
from django.utils import translation
saved_locale = translation.get_language()
translation.deactivate_all()

try:
if (self.requires_system_checks and
not options.get('skip_validation') and # Remove at the end of deprecation for `skip_validation`.
not options.get('skip_checks')):
self.check()
output = self.handle(*args, **options)
if output:
if self.output_transaction:
# This needs to be imported here, because it relies on
# settings.
from django.db import connections, DEFAULT_DB_ALIAS
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
if connection.ops.start_transaction_sql():
self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
self.stdout.write(output)
if self.output_transaction:
self.stdout.write('\n' + self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()))
finally:
if saved_locale is not None:
translation.activate(saved_locale)

主要看下execute --> handle 方法

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
# django/core/management/commands/runserver.py
class Command(BaseCommand):
def handle(self, *args, **options):
from django.conf import settings

# 对DEBUG和ALLOWED_HOSTS进行检查
if not settings.DEBUG and not settings.ALLOWED_HOSTS:
raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')

self.use_ipv6 = options.get('use_ipv6')
if self.use_ipv6 and not socket.has_ipv6:
raise CommandError('Your Python does not support IPv6.')
self._raw_ipv6 = False
if not options.get('addrport'):
self.addr = ''
self.port = DEFAULT_PORT
else:
# 对端口 地址合法性进行检查
m = re.match(naiveip_re, options['addrport'])
if m is None:
raise CommandError('"%s" is not a valid port number '
'or address:port pair.' % options['addrport'])
self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
if not self.port.isdigit():
raise CommandError("%r is not a valid port number." % self.port)
if self.addr:
if _ipv6:
self.addr = self.addr[1:-1]
self.use_ipv6 = True
self._raw_ipv6 = True
elif self.use_ipv6 and not _fqdn:
raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
if not self.addr:
self.addr = '::1' if self.use_ipv6 else '127.0.0.1'
self._raw_ipv6 = bool(self.use_ipv6)
self.run(**options)

def run(self, **options):
"""
Runs the server, using the autoreloader if needed
"""
use_reloader = options.get('use_reloader')

if use_reloader:
# 如果use_reloader为True,则会在`autoreload.main`中开启一个新的线程
autoreload.main(self.inner_run, None, options)
else:
self.inner_run(None, **options)

最后调用runuser_reloaderTrue时,会通过restart_with_reloader开启一个新的进程,这个进程再次重复上面的调用过程,当再次调用到python_reloader时,开启一个新的线程:

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
# django/utils/autoreload.py
def main(main_func, args=None, kwargs=None):
if args is None:
args = ()
if kwargs is None:
kwargs = {}
if sys.platform.startswith('java'):
reloader = jython_reloader
else:
reloader = python_reloader

wrapped_main_func = check_errors(main_func)
reloader(wrapped_main_func, args, kwargs)

def python_reloader(main_func, args, kwargs):
if os.environ.get("RUN_MAIN") == "true":
thread.start_new_thread(main_func, args, kwargs)
try:
reloader_thread()
except KeyboardInterrupt:
pass
else:
try:
exit_code = restart_with_reloader()
if exit_code < 0:
os.kill(os.getpid(), -exit_code)
else:
sys.exit(exit_code)
except KeyboardInterrupt:
pass

def restart_with_reloader():
while True:
args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv
if sys.platform == "win32":
args = ['"%s"' % arg for arg in args]
new_environ = os.environ.copy()
new_environ["RUN_MAIN"] = 'true'
exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
if exit_code != 3:
return exit_code

新开的线程运行inner_run,输出我们常见到的信息:

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
# django/core/management/commands/runserver.py
class Command(BaseCommand):
def inner_run(self, *args, **options):
from django.conf import settings
from django.utils import translation

# If an exception was silenced in ManagementUtility.execute in order
# to be raised in the child process, raise it now.
autoreload.raise_last_exception()

threading = options.get('use_threading')
shutdown_message = options.get('shutdown_message', '')
quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'

self.stdout.write("Performing system checks...\n\n")
self.validate(display_num_errors=True)
try:
self.check_migrations()
except ImproperlyConfigured:
pass
now = datetime.now().strftime('%B %d, %Y - %X')
if six.PY2:
now = now.decode(get_system_encoding())
self.stdout.write((
"%(started_at)s\n"
"Django version %(version)s, using settings %(settings)r\n"
"Starting development server at http://%(addr)s:%(port)s/\n"
"Quit the server with %(quit_command)s.\n"
) % {
"started_at": now,
"version": self.get_version(),
"settings": settings.SETTINGS_MODULE,
"addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
"port": self.port,
"quit_command": quit_command,
})
# django.core.management.base forces the locale to en-us. We should
# set it up correctly for the first request (particularly important
# in the "--noreload" case).
translation.activate(settings.LANGUAGE_CODE)

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:
# Use helpful error messages instead of ugly tracebacks.
ERRORS = {
errno.EACCES: "You don't have permission to access that port.",
errno.EADDRINUSE: "That port is already in use.",
errno.EADDRNOTAVAIL: "That IP address can't be assigned-to.",
}
try:
error_text = ERRORS[e.errno]
except KeyError:
error_text = force_text(e)
self.stderr.write("Error: %s" % error_text)
# Need to use an OS exit because sys.exit doesn't work in a thread
os._exit(1)
except KeyboardInterrupt:
if shutdown_message:
self.stdout.write(shutdown_message)
sys.exit(0)

这里有两个重要的方法需要看下:self.get_handlerrun(...) ,这两个方法就是WSGI协议的具体实现过程,我们知道WSGI协议一般来说可以分为两部分,applicationserver

WSGI Application

WSGI application 应该实现为一个可调用对象,例如函数、方法、类(包含call方法)。需要接收两个参数:

  • 一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文,一般叫做environment(编码中多简写为environ、env)
  • 一个用于发送HTTP响应状态(HTTP status )、响应头(HTTP headers)的回调函数

通过回调函数将响应状态和响应头返回给server,同时返回响应正文(response body),响应正文是可迭代的、并包含了多个字符串。

下面是Django中application的具体实现部分:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# django/core/management/commands/runserver.py
class Command(BaseCommand):
def get_handler(self, *args, **options):
"""
Returns the default WSGI handler for the runner.
"""
return get_internal_wsgi_application()

# django/core/servers/basehttp.py
def get_internal_wsgi_application():
"""
Loads and returns the WSGI application as configured by the user in
``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
this will be the ``application`` object in ``projectname/wsgi.py``.

This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
for Django's internal servers (runserver, runfcgi); external WSGI servers
should just be configured to point to the correct application object
directly.

If settings.WSGI_APPLICATION is not set (is ``None``), we just return
whatever ``django.core.wsgi.get_wsgi_application`` returns.

"""
from django.conf import settings
app_path = getattr(settings, 'WSGI_APPLICATION')
if app_path is None:
return get_wsgi_application()

try:
return import_string(app_path)
except ImportError as e:
msg = (
"WSGI application '%(app_path)s' could not be loaded; "
"Error importing module: '%(exception)s'" % ({
'app_path': app_path,
'exception': e,
})
)
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
sys.exc_info()[2])

# django/core/wsgi.py
def get_wsgi_application():
"""
The public interface to Django's WSGI support. Should return a WSGI
callable.

Allows us to avoid making django.core.handlers.WSGIHandler public API, in
case the internal WSGI implementation changes or moves in the future.

"""
django.setup()
return WSGIHandler()

# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest

def __call__(self, environ, start_response):
# Set up middleware if needed. We couldn't do this earlier, because
# settings weren't available.
# 加载中间件 -- 这部分内容后面重点单独说下
if self._request_middleware is None:
with self.initLock:
try:
# Check that middleware is still uninitialized.
if self._request_middleware is None:
self.load_middleware()
except:
# Unload whatever middleware we got
self._request_middleware = None
raise

set_script_prefix(get_script_name(environ))
# 请求处理之前发送信号
signals.request_started.send(sender=self.__class__, environ=environ)
try:
request = self.request_class(environ)
except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError)',
exc_info=sys.exc_info(),
extra={
'status_code': 400,
}
)
response = http.HttpResponseBadRequest()
else:
response = self.get_response(request)

response._handler_class = self.__class__

status = '%s %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=''))))
# server提供的回调方法,将响应的header和status返回给server
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

可以看出application的流程包括:

  • 加载所有中间件,以及执行框架相关的操作,设置当前线程脚本前缀,发送请求开始信号;
  • 处理请求,调用get_response()方法处理当前请求,该方法的的主要逻辑是通过urlconf找到对应的viewcallback,按顺序执行各种middlewarecallback
  • 调用由server传入的start_response()方法将响应headerstatus返回给server
  • 返回响应正文

WSGI Server

WSGI server 负责获取http请求,将请求传递给WSGI application,由application处理请求后返回response。看下Django具体实现。

通过runserver运行django项目,在启动时都会调用下面的run方法,创建一个WSGIServer的实例,之后再调用其serve_forever()方法启动服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# django/core/servers/basehttp.py
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
if threading:
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
else:
httpd_cls = WSGIServer
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if threading:
# ThreadingMixIn.daemon_threads indicates how threads will behave on an
# abrupt shutdown; like quitting the server by the user or restarting
# by the auto-reloader. True means the server will not wait for thread
# termination before it quits. This will make auto-reloader faster
# and will prevent the need to kill the server manually if a thread
# isn't terminating correctly.
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
httpd.serve_forever()

下面表示WSGI server服务器处理流程中关键的类和方法。

  • WSGIServer

    run()方法会创建WSGIServer实例,主要作用是接收客户端请求,将请求传递给application,然后将application返回的response

    返回给客户端。

    • 创建实例时会指定HTTP请求的handlerWSGIRequestHandler
    • 通过set_appget_app方法设置和获取WSGIApplication实例wsgi_handler
    • 处理http请求时,调用handler_request方法,会创建WSGIRequestHandler实例处理http请求。
    • WSGIServerget_request方法通过socket接受请求数据
  • WSGIRequestHandler

    • WSGIServer在调用handle_request时创建实例,传入requestcient_addressWSGIServer三个参数,__init__方法在实例化同时还会调用自身的handle方法
    • handle方法会创建ServerHandler实例,然后调用其run方法处理请求
  • ServerHandler

    • WSGIRequestHandler在其handle方法中调用run方法,传入self.server.get_app()参数,获取WSGIApplication,然后调用实例(__call__),获取response,其中会传入start_response回调,用来处理返回的headerstatus
    • 通过application获取response以后,通过finish_response返回response
  • WSGIHandler

    • WSGI协议中的application,接收两个参数,environ字典包含了客户端请求的信息以及其他信息,可以认为是请求上下文,start_response用于发送返回statusheader的回调函数

虽然上面一个WSGI server涉及到多个类实现以及相互引用,但其实原理还是调用WSGIHandler,传入请求参数以及回调方法start_response(),并将响应返回给客户端。

到这里,整个启动流程就结束了。这里我们使用的django自带的WSGI Server,一般用于调试,生产环境一般不用这个,需要注意。

比如我们使用 gunicorn 部署的时候可以执行

1
exec gunicorn -c gunicorn_conf.py wsgi:application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from distutils.sysconfig import get_python_lib
import os

bind = "0.0.0.0:80"
workers = 8
chdir = os.path.join(get_python_lib(), "dashboard/wsgi")
pidfile = "/run/horizon.pid"
worker_class = "eventlet"
worker_tmp_dir = "/dev/shm"
timeout = 600
raw_env = ["SCRIPT_NAME=/ecs"]
accesslog = "-"
errorlog = "-"

# Time to wait after "keep-alive" request ends. Default is 2.
# Increasing for gunicorn is behind 2 load balancer (ingress and service).
keepalive = 5

然后在你项目的dashboard下加上一个wsgi文件,类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import logging
import os
import sys

from django.core.wsgi import get_wsgi_application
from django.conf import settings

# Add this file path to sys.path in order to import settings
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '../..')))
os.environ['DJANGO_SETTINGS_MODULE'] = 'dashboard.settings'
sys.stdout = sys.stderr

DEBUG = False

application = get_wsgi_application()

django simple_server demo

djangosimple_server.py模块实现了一个简单的HTTP服务器,并给出了一个简单的demo,可以直接运行,运行结果会将请求中涉及到的环境变量在浏览器中展示出来。
其中包括上述描述的整个http请求的所有组件:
ServerHandler, WSGIServer, WSGIRequestHandler,以及demo_app表示的简易版的WSGIApplication
可以看一下整个流程:

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
if __name__ == '__main__':
# 通过make_server方法创建WSGIServer实例
# 传入建议application,demo_app
httpd = make_server('', 8000, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc')
# 调用WSGIServer的handle_request方法处理http请求
httpd.handle_request() # serve one request, then exit
httpd.server_close()

def make_server(
host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
server.set_app(app)
return server

# demo_app可调用对象,接受请求输出结果
def demo_app(environ,start_response):
from io import StringIO
stdout = StringIO()
print("Hello world!", file=stdout)
print(file=stdout)
h = sorted(environ.items())
for k,v in h:
print(k,'=',repr(v), file=stdout)
start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
return [stdout.getvalue().encode("utf-8")]

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个钩子函数的触发时机可以见下面的图.

img

img

img

img

说明: 上面说的顺序都是中间件在settings文件中列表的注册顺序。

源码分析

我们上面说到,在启动WSGI application的时候会加载中间件,代码大概在这个位置:

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
70
71
72
73
74
75
76
77
78
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest

def __call__(self, environ, start_response):
# Set up middleware if needed. We couldn't do this earlier, because
# settings weren't available.
if self._request_middleware is None:
with self.initLock:
try:
# Check that middleware is still uninitialized.
if self._request_middleware is None:
# 初始化wsgi处理器,会调用这个来加载所有的中间件
self.load_middleware()
except:
# Unload whatever middleware we got
self._request_middleware = None
raise
......略

# django/core/handlers/base.py
class BaseHandler(object):
# Changes that are always applied to a response (in this order).
response_fixes = [
http.fix_location_header,
http.conditional_content_removal,
]

def __init__(self):
self._request_middleware = None
self._view_middleware = None
self._template_response_middleware = None
self._response_middleware = None
self._exception_middleware = None

def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE_CLASSES.

Must be called after the environment is fixed (see __call__ in subclasses).
"""
# 可以看我们可以自定义的钩子函数都在这里了,放在5个列表里面
self._view_middleware = []
self._template_response_middleware = []
self._response_middleware = []
self._exception_middleware = []

request_middleware = []
# 到setting.py文件中找需要加载的中间件
for middleware_path in settings.MIDDLEWARE_CLASSES:
# 这里是将我们配置的字符串形式的中间件类通过反射解析成类
mw_class = import_string(middleware_path)
try:
# 将中间件类实例化为一个对象
mw_instance = mw_class()
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 hasattr(mw_instance, 'process_request'):
request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.insert(0, mw_instance.process_template_response)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception)

# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._request_middleware = request_middleware

进入到load_middleware函数,可以看我们可以自定义的钩子函数都在这里了,放在5个列表里面。接下来判断settings里面的MIDDLEWARE配置项是否为空,为空的话会去django.conf.global_settings.py里面的默认的配置文件加载中间件,默认的中间件只有下面两个。

1
2
3
4
5
6
7
# django.conf.global_settings.py
MIDDLEWARE_CLASSES = [
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
]

MIDDLEWARE = None

从这里我们可以看到

1
2
3
4
5
6
7
8
9
10
if hasattr(mw_instance, 'process_request'):
request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
self._template_response_middleware.insert(0, mw_instance.process_template_response)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception)

process_requestprocess_view 是顺序执行,而 process_template_responseprocess_responseprocess_exception 几个都是逆序执行。那么在一个请求中,他们执行的顺序是什么?

在上面初始化WSGI的时候,调用了 self.get_response(request) ,看下这个方法:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

def get_response(self, request):
"Returns an HttpResponse object for the given HttpRequest"

# Setup default url resolver for this thread, this code is outside
# the try/except so we don't get a spurious "unbound local
# variable" exception in the event an exception is raised before
# resolver is set
urlconf = settings.ROOT_URLCONF
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try:
response = None
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
break

if response is None:
if hasattr(request, 'urlconf'):
# Reset url resolver with a custom urlconf.
urlconf = request.urlconf
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)

resolver_match = resolver.resolve(request.path_info)
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match

# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break

if response is None:
wrapped_callback = self.make_view_atomic(callback)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
# If the view raised an exception, run it through exception
# middleware, and if the exception middleware returns a
# response, use that. Otherwise, reraise the exception.
for middleware_method in self._exception_middleware:
response = middleware_method(request, e)
if response:
break
if response is None:
raise

# Complain if the view returned None (a common error).
if response is None:
if isinstance(callback, types.FunctionType): # FBV
view_name = callback.__name__
else: # CBV
view_name = callback.__class__.__name__ + '.__call__'
raise ValueError("The view %s.%s didn't return an HttpResponse object. It returned None instead."
% (callback.__module__, view_name))

# If the response supports deferred rendering, apply template
# response middleware and then render the response
if hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# Complain if the template response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_template_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__))
response = response.render()

except http.Http404 as e:
... ...

try:
# Apply response middleware, regardless of the response
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
# Complain if the response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__))
response = self.apply_response_fixes(request, response)
except: # Any exception should be gathered and handled
signals.got_request_exception.send(sender=self.__class__, request=request)
response = self.handle_uncaught_exception(request, resolver, sys.exc_info())

response._closable_objects.append(request)

return response

从上段代码中可以看出来request执行的过程是,解析url,调用请求中间件过滤请求。如果响应处理,那么直接返回响应结果;如果不响应处理,解析url得到view。调用view中间件过滤;如果还没有得到响应;那么调用转化得到view类或者view响应方法,调用view的渲染方法得到response,最后调用返回response中间件过滤处理,返回response。

img

流程上解析完以后。具体查看中间件部分,文件夹结构:

1
2
3
4
5
6
7
8
9
10
11
middleware
|----__init__.py
|----cache.py 缓存处理
|----clickjacking.py 框架跨站保护
|----common.py 公共部分
|----csrf.py csrf保护
|----doc.py 头信息中添加xView,具体作用未知
|----gzip.py gzip压缩
|----http.py 如果有Etag或者修改时间标记。做一些响应处理
|----locale.py 多语言处理
|----transaction.py 交易中间件,如果发生异常,数据库将回滚

参考:

django源码分析

django源码分析

django 部分中间件源码解析

django 各组件源码解析

-------------本文结束 感谢您的阅读-------------
作者Magiceses
有问题请 留言 或者私信我的 微博
满分是10分的话,这篇文章你给几分