一、写在前面
一直没有搞懂openstack horizon wsgi加载机制,这次抽时间看了下django 源码,顺便在horizon组件上进行了调试,同时参考学习了网上一些博客的源码阅读说明用以帮助理解,当然还没有理解透彻,仅当个学习笔记。
邮箱地址:
jpzhang.ht
@gmail.com
个人博客:
https://jianpengzhang.github.io/
CSDN博客:
http://blog.csdn.net/u011521019
二、入口文件
horizon 开发阶段都是通过:
python manage.py runserver 0.0.0.0:8001
开始的,该命令启用Django提供的轻量级的开发用的Web服务器。默认情况下,服务器运行在IP地址127.0.0.1的8000端口上。如果要自定义服务器端口和地址,可以显式地传递一个IP地址和端口号给它。例如和上面命令一样。
Horizon的manage.py
文件里只有简单的几行代码,基本上和其他django项目一样,这里主要通过horizon项目来学习Django 的启动入口。
import os
import sys
from django.core.management import execute_from_command_line
if __name__ == "__main__":
# 将settings模块设置到环境变量中,os.environ['DJANGO_SETTINGS_MODULE']:openstack_dashboard.settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"openstack_dashboard.settings")
# sys.argv:['manage.py', 'runserver', '0.0.0.0:8001']
# 运行ManagementUtility的简单方法。
# ManagementUtility:封装django-admin和manage.py程序的逻辑。
execute_from_command_line(sys.argv)
from django.core.management import execute_from_command_line当这行代码开始执行时,首先会去运行django.core.management.
init
.py这一整个文件,接着找到execute_from_command_line函数并将其导入到当前程序的命名空间中。
由于整个django.core.management.
init
.py文件都是class类对象和function函数对象,很多时候很自然的就认为这个文件并没有执行任何命令,只是加载了这些个对象,然后在这些个对象中寻找是否包含有execute_from_command_line。最终忽视了其他很重要的代码块from和import。
import functools
import os
import pkgutil
import sys
from collections import OrderedDict, defaultdict
from difflib import get_close_matches
from importlib import import_module
import django
from django.apps import apps
… …
import django 这行代码运行了django.
init
.py文件。
from django.apps import apps这行代码运行了django.apps.
init
.py文件,然而整个django的开端就是从这里开始的,它落实了非常多的事情(例如:初始化日志模块、加载INSTALL_APP、检查各APP是否正常、检查缓存模块是否正常等),当一切无误时才会往下走,否则将会报错退出程序。
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"openstack_dashboard.settings")
这句话绑定了DJANGO_SETTINGS_MODULE的键值,然后将其加载。
设置完DJANGO_SETTINGS_MODULE环境变量之后,命令行参数的列表传到了 django/core/management/
init
.py 中的execute_from_command_line函数。
execute_from_command_line这个方法是一个工厂函数,它负责指挥ManagementUtility类利用execute方法来解析参数和启动wsgi服务。
django/core/management/
init
.py:execute_from_command_line方法:
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
"""
实例化对象ManagementUtility对象后调用execute()
"""
utility = ManagementUtility(argv)
# execute方法来解析参数和启动wsgi服务
utility.execute()
django/core/management/
init
.py:ManagementUtility:命令管理工具
class ManagementUtility:
"""
Encapsulate the logic of the django-admin and manage.py utilities.
封装django-admin和manage.py程序的逻辑。
"""
def __init__(self, argv=None):
self.argv = argv or sys.argv[:] # 就是命令行参数
self.prog_name = os.path.basename(self.argv[0]) # 调用这个类的组件名,区分是django-admin还是manage.py;
if self.prog_name == '__main__.py':
self.prog_name = 'python -m django'
self.settings_exception = None # 记录异常
… …
在这里prog_name 就是
manage.py
。
实例化ManagementUtility后调用了 execute() 方法,这个函数主要处理子命令的执行,这里的子命令是相对于django-admin.py和manage.py的,举个例子:python
manage.py
runserver这里的runserver就是子命令。
在这个方法中,会对命令参数进行处理。当解析的的命令是 runserver 时,会有两条路,第一个是执行自动重装的路线(监听文件的修改变化,并实现自动重载),通过 autoreload.check_errors(django.setup)() 完成。另一个路线是参数中有 –noreload 时,就用 django.setup() 来启动服务。
如果不是 runserver 而是其他命令,那么会对命令参数 self.argv[1] 进行判断,包括错误处理,是否是 help ,是否是 version ,根据不同的情况显示不同的信息。
execute(self)函数中最重要的是最后一句,即前面的情况都不是,就进入 self.fetch_command(subcommand).run_from_argv(self.argv) ,这边分两步,一步是获取执行命令所需要的类,其次是将命令参数作为参数传递给执行函数执行(代码分析如下):
django/core/management/
init
.py:execute(self):
def execute(self):
"""
Given the command-line arguments, figure out which subcommand is being
run, create a parser appropriate to that command, and run it.
给定命令行参数,找出正在运行的子命令,创建适合该命令的解析器,然后运行它。
"""
try:
# 获取命令行输入第一个参数,如果没有则为help subcommand,这里表示 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.
parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False) # 添加命令说明
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*') # catch-all
try:
"""
options:Namespace(args=['0.0.0.0:9000'], pythonpath=None, settings=None)
args []
"""
options, args = parser.parse_known_args(self.argv[2:]) # 解析后面的参数,options:Namespace(args=[],pythonpath=None,settings=None)
handle_default_options(options) # 如果options中的pythonpath或者settings有,则使用传入的路径与文件
except CommandError:
pass # Ignore any option errors at this point.此时忽略任何选项错误。
try:
settings.INSTALLED_APPS # 当是django-admin命令输入时没有配置文件此时会报错,如果是已经生产的项目则可以导入配置文件中已经配置的应用
except ImproperlyConfigured as exc:
self.settings_exception = exc
except ImportError as exc:
self.settings_exception = exc
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:# 如果不是runserver并且没有关闭自动重载功能,则执行以下函数
try:
autoreload.check_errors(django.setup)() # 调用自动检测文件是否修改如果修改则自动重新启动Django服务
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
# Remove options not compatible with the built-in runserver
# (e.g. options for the contrib.staticfiles' runserver).
# Changes here require manually testing as described in
# #27522.
_parser = self.fetch_command('runserver').create_parser('django', 'runserver')
_options, _args = _parser.parse_known_args(self.argv[2:])
for _arg in _args:
self.argv.remove(_arg)
# In all other cases, django.setup() is required to succeed.
else:
django.setup() # 初始化django环境
self.autocomplete()# 检测是否是自动完成
if subcommand == 'help':# 如果解析命令为help
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')# 打印出help命令
elif not options.args:# 如果输入参数为空
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')# 则输出当前Django的版本
elif self.argv[1:] in (['--help'], ['-h']):# 如果输入参数中包括了--help -h 则打印帮助信息
sys.stdout.write(self.main_help_text() + '\n')
else:
self.fetch_command(subcommand).run_from_argv(self.argv)# 如果命令行输入单个命令,则寻找该命令,然后执行输入的参数
前面几行不难看懂是进行参数获取的,后面到了解析这块又用到CommandParser这个类。
# Preprocess options to extract --settings and --pythonpath.
# These options could affect the commands that are available, so they
# must be processed early.
parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False) # 添加命令说明
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*') # catch-all
try:
"""
options:Namespace(args=['0.0.0.0:9000'], pythonpath=None, settings=None)
args []
"""
options, args = parser.parse_known_args(self.argv[2:]) # 解析后面的参数,options:Namespace(args=[],pythonpath=None,settings=None)
handle_default_options(options) # 如果options中的pythonpath或者settings有,则使用传入的路径与文件
except CommandError:
pass # Ignore any option errors at this point.此时忽略任何选项错误。
通过进入该类查看实现过程,该类的路径为django/core/management/base.py中,代码如下:
class CommandParser(ArgumentParser):
"""
Customized ArgumentParser class to improve some error messages and prevent
SystemExit in several occasions, as SystemExit is unacceptable when a
command is called programmatically.
定制ArgumentParser类来改进一些错误消息,并在某些情况下防止SystemExit,因为在以编程方式调用命令时SystemExit是不可接受的。
"""
def __init__(self, *, missing_args_message=None, called_from_command_line=None, **kwargs):
self.missing_args_message = missing_args_message
self.called_from_command_line = called_from_command_line
super().__init__(**kwargs)
def parse_args(self, args=None, namespace=None):
# Catch missing argument for a better error message
# 捕获缺少的参数以获得更好的错误消息
if (self.missing_args_message and
not (args or any(not arg.startswith('-') for arg in args))):
self.error(self.missing_args_message)
return super().parse_args(args, namespace)
def error(self, message):
if self.called_from_command_line:
super().error(message)
else:
raise CommandError("Error: %s" % message)
可以看出这个类是对ArgumentParser的继承封装,而ArgumentParser是python基础类包中的一个类,这个函数的目的是将ArgumentParser封装成符合django内部调用的接口形式。
argparse模块可以轻松编写用户友好的命令行界面。 程序定义了它需要的参数,argparse将弄清楚如何解析sys.argv中的参数。 当用户给程序提供无效参数时,argparse模块还会自动生成帮助和使用消息并发出错误。
ArgumentParser通过parse_args()方法解析参数。 这将检查命令行,将每个参数转换为适当的类型,然后调用相应的操作。 在大多数情况下,这意味着将从命令行解析的属性构建一个简单的Namespace对象。
不懂的自行参看其手册。
ArgumentParser官方手册
。
回过头来继续看execute函数。接下来的一段代码围绕着CommandParser进行。完成命令行的常规设置。运行到options, args = parser.parse_known_args(self.argv[2:])才开始进入解析的关键之处。
下面给出了python的官方文档中该函数的说明:
Sometimes a script may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the parse_known_args() method can be useful. It works much like parse_args() except that it does not produce an error when extra arguments are present. Instead, it returns a two item tuple containing the populated namespace and the list of remaining argument strings.
也就是说,这个函数将当前脚本需要命令参数和其他脚本所需的命令行进行分离,它的返回结果是一个tuple,包含一个填充好的命名空间和剩余的参数字符串列表。此处的options和args的结果形如:
options:Namespace(args=['0.0.0.0:8001'], pythonpath=None, settings=None)
args:self = <django.core.management.ManagementUtility object at 0x7eff4f056ad0>
代码接着运行,将前面函数填充好的命名空间(此处为options参数)传入handle_default_options这个方法去执行。handle_default_options同样在django/core/management/base.py文件中,代码如下:
def handle_default_options(options):
"""
Include any default options that all commands should accept here
so that ManagementUtility can handle them before searching for
user commands.
包括所有命令在此处应接受的任何默认选项,以便ManagementUtility在搜索用户命令之前可以处理它们。
"""
# 上述例子运行解析后,options的传递过来的值:Namespace(args=['0.0.0.0:8001'], pythonpath=None, settings=None)
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
if options.pythonpath:
sys.path.insert(0, options.pythonpath)
它只是完成了两个目标,(1)options中包含setting则配置环境变量;(2)options中包含pythonpath则设置python模块的搜索路径。
继续回到:execute函数。
try:
settings.INSTALLED_APPS # 当是django-admin命令输入时没有配置文件此时会报错,如果是已经生产的项目则可以导入配置文件中已经配置的应用
except ImproperlyConfigured as exc:
self.settings_exception = exc
except ImportError as exc:
self.settings_exception = exc
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:# 如果是runserver并且没有关闭自动重载功能,则执行以下函数
try:
autoreload.check_errors(django.setup)() # 调用自动检测文件是否修改如果修改则自动重新启动Django服务
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
# Remove options not compatible with the built-in runserver
# (e.g. options for the contrib.staticfiles' runserver).
# Changes here require manually testing as described in
# #27522.
_parser = self.fetch_command('runserver').create_parser('django', 'runserver')
_options, _args = _parser.parse_known_args(self.argv[2:])
for _arg in _args:
self.argv.remove(_arg)
# In all other cases, django.setup() is required to succeed.
else:
django.setup() # 初始化django环境
settings.INSTALLED_APPS(django 的配置采用了懒加载机制,后续通过另外的博文进行说明),这里即执行了django/conf/
init
__.py:class LazySettings(LazyObject)的__getattr__()函数,从全局settings更新此索引,使用 importlib.import_module 来加载’openstack_dashboard.settings’配置模块,完成全局Settings实例的初始化。实例化后,配置懒加载也就完成了,程序就回到 execute 函数,
- INSTALLED_APPS,它表示项目中哪些 app 处于激活状态。元组中的字符串,除了django默认自带的命令之外,就是我们自己定义的app,也就是用python manage.py所启动的app了。
回到 execute 函数,接下去的处理分为两条路,会对命令参数进行处理。当解析的subcommand是 runserver 时,会有两条路,第一个是会自动重装(后续的博文进行说明)的路线,通过 autoreload.check_errors(django.setup)() 代理完成。另一个路线是参数中有 –noreload 时,就用 django.setup() 来启动服务。
这里执行:
autoreload.check_errors(django.setup)()
其实也是调用django.setup方法;check_errors(),一个装饰器函数,用来检查捕捉一些错误,而django.setup方法:
路径:django/
init
.py:setup()
def setup(set_prefix=True):
"""
Configure the settings (this happens as a side effect of accessing the
first setting), configure logging and populate the app registry.
Set the thread-local urlresolvers script prefix if `set_prefix` is True.
负责初始化日志模块以及所有应用
配置设置,配置日志记录并填充应用程序注册表。 如果`set_prefix`为True,则设置thread-local urlresolvers脚本前缀。
"""
from django.apps import apps
from django.conf import settings
from django.urls import set_script_prefix
from django.utils.log import configure_logging
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
if set_prefix:
set_script_prefix(
'/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME
)
apps.populate(settings.INSTALLED_APPS)
这里主要是这个,apps.populate(settings.INSTALLED_APPS),加载应用程序配置和模型。
做了几件事情:
app_config = AppConfig.create(entry) # 生成了一个AppConfig实例
self.app_configs[app_config.label] = app_config # 将所有的app实例放到一个order_dict中维护。
app_config.import_models(all_models) #
导入models.py
这里不展开,具体可以看django/apps/registry.py
这些准备工作完成后,这里调用了类中自有的一个方法autocomlete,这个函数主要的功能是通过BASH去输出执行建议。
如果不是 runserver 而是其他命令,那么会对命令参数 self.argv[1] 进行判断,包括错误处理,是否是 help ,是否是 version ,根据不同的情况展示不同的信息。
if subcommand == 'help':# 如果解析命令为help
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')# 打印出help命令
elif not options.args:# 如果输入参数为空
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')# 则输出当前Django的版本
elif self.argv[1:] in (['--help'], ['-h']):# 如果输入参数中包括了--help -h 则打印帮助信息
sys.stdout.write(self.main_help_text() + '\n')
else:
self.fetch_command(subcommand).run_from_argv(self.argv)# 如果命令行输入单个命令,则寻找该命令,然后执行输入的参数
最重要的是最后一句,即前面的情况都不是,就进入 self.fetch_command(subcommand).run_from_argv(self.argv) ,这边分两步,一步是会根据subcommand(这是我们执行python
manage.py
rumserver时传入的第二个参数:runserver),去django.core.management.commands中查找对应的command类,其次是将命令参数作为参数传递给执行函数执行(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(但实际上并不是由它来直接完成,而是由后续很多其他代码来完成)。
django/core/management/
init
.py:fetch_command(self, subcommand):
def fetch_command(self, subcommand):
"""
Try 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.
要获取给定的子命令,如果找不到,则使用从命令行调用的相应命令(通常为“django-admin”或“manage.py”)打印消息。
"""
# Get commands outside of try block to prevent swallowing exceptions
# get_commands() 返回是一个命令与模块映射作用的字典,字典的key是命令名称,value是这个命令实现所在的文件路径。
commands = get_commands()# 获取所有支持的命令
try:
# 这里runserver指令对应的返回的app_name应该为'django.contrib.staticfiles' -> 'runserver': 'django.contrib.staticfiles',
app_name = commands[subcommand]# 获取命令名称所在的路径或者实例
except KeyError:
if os.environ.get('DJANGO_SETTINGS_MODULE'):
# If `subcommand` is missing due to misconfigured settings, the
# following line will retrigger an ImproperlyConfigured exception
# (get_commands() swallows the original one) so the user is
# informed about it.
# 如果“subcommand”由于错误配置的设置而丢失,那么下面一行将重新触发一个错误配置的异常(get_commands()吞并原始异常),以便通知用户。
settings.INSTALLED_APPS
else:
sys.stderr.write("No Django settings specified.\n")
possible_matches = get_close_matches(subcommand, commands)
sys.stderr.write('Unknown command: %r' % subcommand)
if possible_matches:
sys.stderr.write('. Did you mean %s?' % possible_matches[0])
sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
sys.exit(1)
if isinstance(app_name, BaseCommand):# 判断app_name是否是基本命令的实例,还是命令的路径
# If the command is already loaded, use it directly.
# 如果已加载该命令,请直接使用它。
klass = app_name
else:
# 动态加载模块,模块是通过 load_command_class 来动态加载的
klass = load_command_class(app_name, subcommand)# 如果是路径则导入该命令
return klass # 将命令的实例化对象返回
get_commands() 是返回是一个命令与模块映射作用的字典,字典的key是命令名称,value是这个命令实现所在的文件路径(get_commands通过pkgutil
第三方类库来做的):
{'check': 'django.core',
'compilemessages': 'django.core',
'createcachetable': 'django.core',
'dbshell': 'django.core',
'diffsettings': 'django.core',
'dumpdata': 'django.core',
'flush': 'django.core',
'inspectdb': 'django.core',
'loaddata': 'django.core',
'makemessages': 'django.core',
'makemigrations': 'django.core',
'migrate': 'django.core',
'runserver': 'django.contrib.staticfiles',
'sendtestemail': 'django.core',
'shell': 'django.core',
'showmigrations': 'django.core',
'sqlflush': 'django.core',
'sqlmigrate': 'django.core',
'sqlsequencereset': 'django.core',
'squashmigrations': 'django.core',
'startapp': 'django.core',
'startproject': 'django.core',
'test': 'django.core',
'testserver': 'django.core',
'pull_catalog': 'horizon',
'startdash': 'horizon',
'startpanel': 'horizon',
'compress': 'compressor',
'mtime_cache': 'compressor',
'collectstatic': 'django.contrib.staticfiles',
'findstatic': 'django.contrib.staticfiles',
'clearsessions': 'django.contrib.sessions',
'changepassword': 'django.contrib.auth',
'createsuperuser': 'django.contrib.auth',
'remove_stale_contenttypes': 'django.contrib.contenttypes',
'extract_messages': 'openstack_dashboard',
'make_web_conf': 'openstack_dashboard',
'migrate_settings': 'openstack_dashboard',
'update_catalog': 'openstack_dashboard'}
接着根据“runserver”,返回命令路径:django.contrib.staticfiles,然后通过isinstance() 函数来判断django.contrib.staticfiles是否是BaseCommand类型,因为这里是app_name命令的路径,并不是一个对象,如果是对象就是属于BaseCommand。本质上这里主要用来判断django.contrib.staticfiles是否已经加载,如果加载直接返回该模块,如果不是就通过load_command_class函数动态加载模块。
load_command_class的目录在django/core/management/
init
.py中,代码如下:
def load_command_class(app_name, name):
"""
Given a command name and an application name, return the Command
class instance. Allow all errors raised by the import process
(ImportError, AttributeError) to propagate.
给定命令名称和应用程序名称,返回Command类实例。 允许导入过程引发的所有错误(ImportError,AttributeError)传播。
"""
# 如执行 runserver 命令的模块就是 django.contrib.staticfiles.management.commands.runserver
# 返回该模块中定义的 Command 类的实例。获得实例后调用了 run_from_argv(self.argv) :
module = import_module('%s.management.commands.%s' % (app_name, name)) # 导入命令所在的包
return module.Command()
这个方法调用python中importlib库中的import_module方法将模块动态载入,然后返回载入模块的Command()。参看management/commands下的每个文件,发现都拥有一个Command类对应相应的命令。
综上所诉,之前这个fetch_command返回了一个命令对象。
接着研究run_from_argv函数,这个函数同样位于django/core/management/base.py中,它是之前返回的BaseCommand对象中的一个方法,子类django.contrib.staticfiles.management.commands.runserver没有实现该函数,则调用父类BaseCommand中的。
runserver继承对象分布 依次按顺序如下:
• django.contrib.staticfiles.management.commands.runserver.Command
• django.core.management.commands.runserver.Command
• django.core.management.base.BaseCommand
• object
source/django/core/management/base.py:run_from_argv如下:
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.
设置所请求的任何环境更改(例如,Python路径和Django设置),然后运行此命令。 如果该命令引发一个``CommandError``,
则拦截它并将其合理地打印到stderr。 如果存在``--traceback``选项或者引发的``Exception``不是``CommandError``,则将其抬起。
"""
self._called_from_command_line = True
# create_parser接受两个参数,第一个是prog_name,即谁执行这个指令,此处是'manage.py'
# 第二个是subcommand,即运行的是什么指令,此处是'runserver'
parser = self.create_parser(argv[0], argv[1])
# options:Namespace(addrport='0.0.0.0:8001', insecure_serving=False, no_color=False, pythonpath=None, settings=None, traceback=False, use_ipv6=False, use_reloader=True, use_static_handler=True, use_threading=True, verbosity=1)
# 返回一个Namespace的实例
options = parser.parse_args(argv[2:])
# 对象转成字典,这里执行完cmd_options的值为:{'use_static_handler': True, 'settings': None, 'pythonpath': None, 'verbosity': 1, 'traceback': False, 'addrport': '0.0.0.0:8001', 'no_color': False, 'use_ipv6': False, 'use_threading': True, 'use_reloader': True, 'insecure_serving': False}
cmd_options = vars(options)
# Move positional args out of options to mimic legacy optparse
args = cmd_options.pop('args', ())
# 设置默认参数
handle_default_options(options)
try:
# 异常捕获包裹的execute
# 在 execute 中会做一些设置参数的错误检查,然后设置句柄:
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:
try:
connections.close_all()
except ImproperlyConfigured:
# Ignore if connections aren't setup at this point (e.g. no
# configured settings).
pass
这个函数的作用就是设置好环境变量,然后取运行指令。这个文件的结构有点类似于前面的execute,当前类对象的run_from_argv方法中调用了self.execute(*args, **cmd_options)方法,由于请求的入口是django.contrib.staticfiles.management.commands.runserver.Command对象,因此python并不会去执行BaseCommand.execute而是执行django.core.management.commands.runserver.Command.execute,最后通过super(Command, self).execute(*args, **options)来执行BaseComand.execute。
django.core.management.commands.runserver.Command.execute函数在子类重新定义,这部分的代码如下:
def execute(self, *args, **options):# 调用处理方法
if options['no_color']:
# We rely on the environment because it's currently the only
# way to reach WSGIRequestHandler. This seems an acceptable
# compromise considering `runserver` runs indefinitely.
# 我们依赖环境,因为这是目前到达WSGIRequestHandler的唯一途径。考虑到“runserver”无限期地运行,这似乎是一个可以接受的折衷方案。
os.environ["DJANGO_COLORS"] = "nocolor"
super().execute(*args, **options)# 调用父类的执行方法
没有做太多的事情,返回调用父类的execute执行方法,代码如下:
def execute(self, *args, **options):
"""
Try to execute this command, performing system checks if needed (as
controlled by the ``requires_system_checks`` attribute, except if
force-skipped).
尝试执行此命令,在需要时执行系统检查(由``requires_system_checks``属性控制,除非强制跳过)
"""
# 这里传递进来的options的值为:{'use_static_handler': True, 'settings': None, 'pythonpath': None, 'verbosity': 1, 'traceback': False, 'addrport': '0.0.0.0:8001', 'no_color': False, 'use_ipv6': False, 'use_threading': True, 'use_reloader': True, 'insecure_serving': False}
if options['force_color'] and options['no_color']:
# --no-color和--force-color选项不能一起使用
raise CommandError("The --no-color and --force-color options can't be used together.")
if options['force_color']:
self.style = color_style(force_color=True)
elif options['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['stderr'], self.stderr.style_func)
if self.requires_system_checks and not options.get('skip_checks'):
self.check()
if self.requires_migrations_checks:
self.check_migrations()
output = self.handle(*args, **options)
if output:
if self.output_transaction:
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
output = '%s\n%s\n%s' % (
self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),
output,
self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
)
self.stdout.write(output)
return output
execute 中会做一些设置参数的错误检查,然后设置句柄,关键的核心在output = self.handle(*args, **options)这一行,这里又调用了自己的一个自有方法。
基本流转过程:
- BaseComand.execute方法中调用了self.handle(即:django.core.management.commands.runserver.Command.handle)
- Command.handle方法中调用了self.run(即:django.core.management.commands.runserver.Command.run)。
- Command.run方法调用了self.inner_run(即:django.core.management.commands.runserver.Command.inner_run)。
- Command.inner_run方法调用了self.get_handler(即:django.contrib.staticfiles.management.commands.runserver.Command.get_handler)
- Command.inner_run方法调用了run(即:django.core.servers.basehttp.run)。
代码:django/core/management/commands/runserver.py:handle(self, *args, **options)
def handle(self, *args, **options):# 调用处理方法
if not settings.DEBUG and not settings.ALLOWED_HOSTS:# 检查是否是debug模式,如果不是则ALLOWED_HOSTS不能为空
raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')
self.use_ipv6 = options['use_ipv6']
if self.use_ipv6 and not socket.has_ipv6:# 检查输入参数中是否是ipv6格式,检查当前python是否支持ip
raise CommandError('Your Python does not support IPv6.')
self._raw_ipv6 = False
if not options['addrport']:# 如果输入参数中没有输入端口则使用默认的端口
self.addr = '' # 默认地址
self.port = self.default_port # 默认端口
else: # 如果设置了ip地址和端口号,用正则匹配出来
m = re.match(naiveip_re, options['addrport']) # 检查匹配的ip格式
if m is None:
raise CommandError('"%s" is not a valid port number '
'or address:port pair.' % options['addrport'])
# self.addr:'0.0.0.0'; self.port:'9000'
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:# 检查解析出的地址是否合法的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:# 如果没有输入ip地址则使用默认的地址
self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
self._raw_ipv6 = self.use_ipv6
self.run(**options) # 运行命令
前面的一大段就是运行runserver时候执行的一些参数准备,关键部分是最后一行的self.run(**options),run 方法主要时调用了 inner_run(*args, **options) 这个方法:
django/core/management/commands/runserver.py:run(self, **options)
def run(self, **options):
"""Run the server, using the autoreloader if needed.如果需要,使用自动重载程序运行服务器
run 方法主要时调用了 inner_run(*args, **options) 这个方法:
"""
use_reloader = options['use_reloader']# 根据配置是否自动加载,如果没有输入则default=True
if use_reloader:# 当开启了自动加载时,则调用自动启动运行
# 如果开启了自动重启功能则,调用django/utils/autoreload.py中的mian函数处理,
autoreload.main(self.inner_run, None, options)
else:
self.inner_run(None, **options)# 如果没有开启文件更新自动重启服务功能则直接运行
在这里自动加载为True,即options[‘use_reloader’]:True。会调用django.utils.autoreload中的python_reloader新开一个线程:
def python_reloader(main_func, args, kwargs):
if os.environ.get("RUN_MAIN") == "true":# 获取环境变量是RUN_MAIN是否为"true",第一次运行时,RUN_MAIN没有设置,此时会运行restart_with_reloader函数
_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 main(main_func, args=None, kwargs=None):
if args is None:
args = ()
if kwargs is None:
kwargs = {}
wrapped_main_func = check_errors(main_func)# 添加对man_func的出错处理方法
python_reloader(wrapped_main_func, args, kwargs)# 新开一个线程
这里的main_func是commands/runserver.py中的inner_run方法:
django/core/management/commands/runserver.py:inner_run(self, *args, **options)
def inner_run(self, *args, **options):
# 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['use_threading']# 是否开启多线程模式,当不传入时则默认为多线程模式运行
# 'shutdown_message' is a stealth option.
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.check(display_num_errors=True)# 检查
# Need to check migrations here, so can't use the
# requires_migrations_check attribute.
self.check_migrations()# 检查是否migrations是否与数据库一致
now = datetime.now().strftime('%B %d, %Y - %X')# 获取当前时间
# 打印时间等信息
self.stdout.write(now)
self.stdout.write((
"Django version %(version)s, using settings %(settings)r\n"
"Starting development server at %(protocol)s://%(addr)s:%(port)s/\n"
"Quit the server with %(quit_command)s.\n"
) % {
"version": self.get_version(),
"settings": settings.SETTINGS_MODULE,
"protocol": self.protocol,
"addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
"port": self.port,
"quit_command": quit_command,
})
try:
# 获取处理 http 的句柄,这部分除了有熟悉的信息输出外,重要的是这个句柄:
handler = self.get_handler(*args, **options)# 获取信息处理的handler,默认返回wsgi
run(self.addr, int(self.port), handler,# 调用运行函数
ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
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 = 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)
这部分除了有熟悉的信息输出外,重要的是这个句柄:
# 获取处理 http 的句柄,这部分除了有熟悉的信息输出外,重要的是这个句柄:
handler = self.get_handler(*args, **options)# 获取WSGIHandler
run(self.addr, int(self.port), handler,# 调用运行函数
ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
get_handler 函数最终会返回一个 WSGIHandler 的实例。WSGIHandler 类只实现了 def __call__(self, environ, start_response) , 使它本身能够成为 WSGI 中的应用程序, 并且实现
call
能让类的行为跟函数一样。
这里要特别强调一下self.get_handler,它非常重要,三个重点:
1. 因为它负责获取WSGIHandler。
2. 由于请求入口是django.contrib.staticfiles.management.commands.runserver.Command,正好它本来就有get_handler这个方法,因此并没有采用django.core.management.commands.runserver.Command.get_handler。
3. self.get_handler并不会返回一个常规的WSGIHandler而是返回一个StaticFilesHandler。
4. StaticFilesHandler类对象继承WSGIHandler,它的目的是为了判断每个请求,如果是常规的url请求则直接分配到某个view中去执行,如果是静态文件规则那么将不会找view而是响应这个文件。
django/contrib/staticfiles/management/commands/runserver.py:get_handler(self, *args, **options)
def get_handler(self, *args, **options):
"""
Return the static files serving handler wrapping the default handler,
if static files should be served. Otherwise return the default handler.
如果应该提供静态文件,则返回默认处理程序的静态文件服务处理程序。 否则返回默认处理程序。
"""
handler = super().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
这里handler = super().get_handler(*args, **options)调用父类django.core.management.commands.runserver.Command.get_handler函数:
def get_handler(self, *args, **options):
"""Return the default WSGI handler for the runner."""
# 返回运行器的默认WSGI处理程序。
# get_handler 函数最终会返回一个 WSGIHandler 的实例。WSGIHandler 类只实现了 def __call__(self, environ, start_response) ,
# 使它本身能够成为 WSGI 中的应用程序, 并且实现 __call__ 能让类的行为跟函数一样。
return get_internal_wsgi_application()
django.core.servers.basehttp.get_internal__wsgi_application():
def get_internal_wsgi_application():
"""
Load and return 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 server (runserver); external WSGI servers should just
be configured to point to the correct application object directly.
If settings.WSGI_APPLICATION is not set (is ``None``), return
whatever ``django.core.wsgi.get_wsgi_application`` returns.
按照用户在`settings.WSGI_APPLICATION``中的配置加载并返回WSGI应用程序。
使用默认的``startproject``布局,这将是``projectname / wsgi.py``中的``application``对象。
这个函数和``WSGI_APPLICATION``设置本身只对Django的内部服务器(runserver)有用。
外部WSGI服务器应该配置为直接指向正确的应用程序对象。
如果没有设置settings.WSGI_APPLICATION(是``None``),则返回``django.core.wsgi.get_wsgi_application``返回的内容。
"""
from django.conf import settings# 导入配置文件
app_path = getattr(settings, 'WSGI_APPLICATION')# 获取配置文件中的wsgi运行的路径
if app_path is None:
return get_wsgi_application()# 如果配置文件中没有则django/core/wsgi中的WSGIHandler
try:
return import_string(app_path) # 如果配置文件中配置,则使用配置文件中的包
except ImportError as err:
raise ImproperlyConfigured(
"WSGI application '%s' could not be loaded; "
"Error importing module." % app_path
) from err
没有设置settings.WSGI_APPLICATION(是
None
),则返回
django.core.wsgi.get_wsgi_application
返回的内容。在这里horizon的settings中没有设置WSGI_APPLICATION。
django.core.wsgi.py
: get_wsgi_application():
def get_wsgi_application():
"""
The public interface to Django's WSGI support. Return a WSGI callable.
Avoids making django.core.handlers.WSGIHandler a public API, in case the
internal WSGI implementation changes or moves in the future.
Django的WSGI支持的公共接口。返回一个可调用的WSGI。
"""
# setup是加载log和settings.INSTALLED_APPS
django.setup(set_prefix=False)# 初始化django环境
# 返回WSGIHhadler类的一个实例
return WSGIHandler()
source/django/
init
.py:setup(set_prefix=True)
def setup(set_prefix=True):
"""
Configure the settings (this happens as a side effect of accessing the
first setting), configure logging and populate the app registry.
Set the thread-local urlresolvers script prefix if `set_prefix` is True.
负责初始化日志模块以及所有应用
配置设置,配置日志记录并填充应用程序注册表。 如果`set_prefix`为True,则设置thread-local urlresolvers脚本前缀。
"""
from django.apps import apps
from django.conf import settings
from django.urls import set_script_prefix
from django.utils.log import configure_logging
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)# 配置日志记录
if set_prefix:
set_script_prefix(
# 设置前缀
'/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME
)
# 初始化所有应用,但调试的时候发现没有加载注册,标记下,回头继续看app加载。
apps.populate(settings.INSTALLED_APPS)
完成
django.setup(set_prefix=False)
执行后,紧接着返回
return WSGIHandler()
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)# 调用WSGIRequest实例化请求
response = self.get_response(request) # 调用处理方法处理request
response._handler_class = self.__class__# 设置_handler_class 为当前处理的类
status = '%d %s' % (response.status_code, response.reason_phrase)# 相应结果的状态码和对应描述
# 获取响应的响应头部信息,获取响应的cookie信息
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'):# 判断响应中是否有文件传输
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response# 返回处理结果
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)# 调用WSGIRequest实例化请求
response = self.get_response(request) # 调用处理方法处理request
response._handler_class = self.__class__# 设置_handler_class 为当前处理的类
status = '%d %s' % (response.status_code, response.reason_phrase)# 相应结果的状态码和对应描述
# 获取响应的响应头部信息,获取响应的cookie信息
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'):# 判断响应中是否有文件传输
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response# 返回处理结果
通过__init__()初始化一个WSGIHandler对象,同时通过
self.load_middleware()
加载中间件。其中self.load_middleware在当前调用的是父类的实现。
完成上述调用后返回到:
def get_handler(self, *args, **options):
"""
Return the static files serving handler wrapping the default handler,
if static files should be served. Otherwise return the default handler.
如果应该提供静态文件,则返回默认处理程序的静态文件服务处理程序。 否则返回默认处理程序。
"""
handler = super().get_handler(*args, **options)
use_static_handler = options['use_static_handler']# 使用静态处理程序,这里返回True
insecure_serving = options['insecure_serving']
# 判断当前环境是否是debug模式或者是不安全的模式
if use_static_handler and (settings.DEBUG or insecure_serving):
# use_static_handler:True,settings.DEBUG:True最终返回StaticFilesHandler对象
return StaticFilesHandler(handler)
return handler
这里跟进去看下StaticFilesHandler(handler)
source/django/contrib/staticfiles/handlers.py:class StaticFilesHandler(WSGIHandler)
class StaticFilesHandler(WSGIHandler):
"""
WSGI middleware that intercepts calls to the static files directory, as
defined by the STATIC_URL setting, and serves those files.
WSGI中间件拦截对STATIC_URL设置所定义的静态文件目录的调用,并为这些文件提供服务。
"""
# May be used to differentiate between handler types (e.g. in a
# request_finished signal)
handles_files = True
def __init__(self, application):
self.application = application
# 初始化静态目录
# ParseResult(scheme='', netloc='', path='/dashboard/static/', params='', query='', fragment='')
self.base_url = urlparse(self.get_base_url())
super().__init__()
到这里完成handler = self.get_handler(*args, **options),返回一个StaticFilesHandler,StaticFilesHandler类对象继承WSGIHandler,它的目的是为了判断每个请求,如果是常规的url请求则直接分配到某个view中去执行,如果是静态文件规则那么将不会找view而是响应这个文件。
接着回到django/core/management/commands/runserver.py(149)inner_run(),执行:
run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer)
django/core/servers/basehttp.py(158)run():
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
"""
这是一个标准的 wsgi 实现。httpd_cls 是 WSGIServer 类,最终的实例化方法在父类 SocketServer 中的 TCPServer 和 BaseServer 中。
包括初始化线程,初始化网络句柄,像下面的 __is_shut_down 和 __shutdown_request 都是在其中初始化的。
addr:'0.0.0.0'
port:8001
wsgi_handler:<django.contrib.staticfiles.handlers.StaticFilesHandler object at 0x7ff03a14c5d0>
ipv6:False
threading:True
server_cls:<class 'django.core.servers.basehttp.WSGIServer'>
"""
server_address = (addr, port)# 服务监听的地址和端口
if threading:# 如果是多线程运行
# 生成一个继承自socketserver.ThreadingMixIn, WSGIServer的类
httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
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.
# ThreadingMixIn.daemon_threads表示线程在突然关闭时的行为方式;
# 比如用户退出服务器或通过自动重新加载器重新启动。
# True表示服务器在退出之前不会等待线程终止。
# 这将使自动重新加载器更快,并且如果线程没有正确终止,将防止需要手动终止服务器。
httpd.daemon_threads = True# True表示服务器在退出之前不会等待线程终止。
httpd.set_app(wsgi_handler)# 设置服务类的处理handler
httpd.serve_forever()
django.core.servers.basehttp.run工厂函数负责只会各个对象负责启动wsgi服务。
wsgi_handler参数,这里传递的是StaticFilesHandler。
httpd_cls = type(‘WSGIServer’, (socketserver.ThreadingMixIn, server_cls), {}) 是一种很特殊的写法,通过代码块中WSGIServer类对象可以看出它只继承了wsgiref.simple_server.WSGIServer、object这两个类对象,但是通过type这种写法相当于是强行赋予它一个socketserver.ThreadingMixIn继承对象,它的用意是每次调用这个对象的时候都会单独启用一个线程来处理。另外虽然 WSGIServer 只继承了 wsgiref.simple_server.WSGIServer、object两个对象,但是wsgiref.simple_server.WSGIServer却<递归式>的继承了一堆对象,下面完整的列出WSGIServer继承家族。
1、django.core.servers.basehttp.WSGIServer;
2、wsgiref.simple_server.WSGIServer:实现Python WSGI协议的BaseHTTPServer;
3、socketserver.ThreadingMixIn:用于处理新线程中的每个请求的混合类;
4、http.server.HTTPServer
5、socketserver.TCPServer
6、socketserver.BaseServer
7、object
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)这行代码非常重要,因为它是WSGI服务器与django之间相互通信的唯一枢纽通道,也就是说,当WSGI服务对象收到socket请求后,会将这个请求传递给django的WSGIRequestHandler。
httpd.set_app(wsgi_handler)是将django.contrib.staticfiles.handlers.StaticFilesHandler 传递给WSGIServer当作一个application,当WSGIServer收到网络请求后,可以将数据分发给django.core.servers.basehttp.WSGIRequestHandler,最终由django.core.servers.basehttp.WSGIRequestHandler将数据传递给application(即:django.contrib.staticfiles.handlers.StaticFilesHandler)。
httpd.serve.forever()启动非堵塞网络监听服务。
小结
上面所有的过程都是django内部代码的为了启动服务而做的准备,简单的把流程给列出来。
1. 解析运行 python
manage.py
所提供的参数,例如: runserver.
2. 根据参数 找到相对应的 命令管理工具。
3. 加载所有的app。
4. 检查端口、ipv4检测、ipv6检测、端口是否占用、线程检查、orm对象检查(表是否创建)。
5. 实例化WSGIRequestHandler,并且将它注册到python Lib库中的WSGIServer中。
6. 最后启动python Lib库中的WSGIServer。
二、处理请求
接下来的部分是python Lib库中的WSGIServer运作过程中,如何将接收到的请求分发会django的WSGIRequestHandler。
/usr/lib/python2.7/SocketServer.py
class BaseServer:
timeout = None
def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override."""
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):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
一次处理一个请求直到关闭,每0.5秒遍历一次文件描述符。忽略
self.timeout。 如果您需要定期执行任务,请执行此操作另一个线程。
"""
# self.__is_shut_down = threading.Event()
# Python threading模块提供Event对象用于线程间通信,Python 通过threading.Event()产生一个event对象。event对象维护一个内部标志(标志初始值为False),通过set()将其置为True。wait(timeout)则用于堵塞线程直至Flag被set(或者超时,可选的),isSet()用于查询标志位是否为True,Clear()则用于清除标志位(使之为False)。
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
# 考虑使用另一个文件描述符或连接到套接字来唤醒它而不是轮询。 轮询降低了我们对关闭请求的响应速度,并在其他所有时间浪费cpu。
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
def _handle_request_noblock(self):
"""Handle one request, without blocking.
I assume that select.select has returned that the socket is
readable before this function was called, so there should be
no risk of blocking in get_request().
"""
try:
request, client_address = self.get_request()
except socket.error:
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):
"""Call finish_request.
Overridden by ForkingMixIn and ThreadingMixIn.
调用finish_request。 由ForkingMixIn和ThreadingMixIn重写。
"""
self.finish_request(request, client_address)
self.shutdown_request(request)#关闭请求
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass.
通过实例化RequestHandlerClass完成一个请求。
"""
self.RequestHandlerClass(request, client_address, self)
上面服务启动的最后一个动作是httpd.serve_forever,调用的是socketserver.BaseServer.serve_forever方法。该方法采用了selector网络模型进行等待数据,每0.5秒遍历一次文件描述符,当发现有请求后,就调用 _handle_request_noblock 进行处理self._handler_request_noblock方法(即:socketserver.BaseServer._handler_request_noblock)去处理。
socketserver.BaseServer._handler_request_noblock方法基本没做什么事情(self.verify_request压根就没有检查任何东西),直接就把后续工作转交给 socketserver.BaseServer.process_request 方法。
socketserver.BaseServer.process_request也没做什么事情,直接就将后续工作转交给socketserver.BaseServer.finish_request方法,只不过在最后加了一条关闭请求的命令。
socketserver.BaseServer.finish_request也没做什么事情,直接就将后续工作转交给socketserver.BaseServer.RequestHandlerClass。
socketserver.BaseServer.RequestHandlerClass是由上面
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
传递过来的参数django.core.servers.basehttp.WSGIRequestHandler。 也就是说当执行self.RequestHandler(request, client_address, self)时等同于执行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)。
小结
serve_forever开启了一个while来无限监听网络层的scoket请求,当一条请求过来时,就层层转交到django.core.servers.basehttp.WSGIRequestHandler手中。
django.core.servers.basehttp.py
单独列出WSGIRequestHandler代码:
class WSGIRequestHandler(simple_server.WSGIRequestHandler):
protocol_version = 'HTTP/1.1'
def address_string(self):
# Short-circuit parent method to not call socket.getfqdn
return self.client_address[0]
def log_message(self, format, *args):
extra = {
'request': self.request,
'server_time': self.log_date_time_string(),
}
if args[1][0] == '4':
# 0x16 = Handshake, 0x03 = SSL 3.0 or TLS 1.x
if args[0].startswith('\x16\x03'):
extra['status_code'] = 500
logger.error(
"You're accessing the development server over HTTPS, but "
"it only supports HTTP.\n", extra=extra,
)
return
if args[1].isdigit() and len(args[1]) == 3:
status_code = int(args[1])
extra['status_code'] = status_code
if status_code >= 500:
level = logger.error
elif status_code >= 400:
level = logger.warning
else:
level = logger.info
else:
level = logger.info
level(format, *args, extra=extra)
def get_environ(self):
# Strip all headers with underscores in the name before constructing
# the WSGI environ. This prevents header-spoofing based on ambiguity
# between underscores and dashes both normalized to underscores in WSGI
# env vars. Nginx and Apache 2.4+ both do this as well.
for k in self.headers:
if '_' in k:
del self.headers[k]
return super().get_environ()
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 (socket.error, AttributeError):
pass
def handle_one_request(self):
"""Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
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()
)
handler.request_handler = self # backpointer for logging & connection closing
handler.run(self.server.get_app())
继续分析,socketserver.BaseServer.RequestHandler(request, client_address, self)等同于django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)。
首先django.core.servers.basehttp.WSGIRequestHandler的继承分布:
- django.core.servers.basehttp.WSGIRequestHandler(django/core/servers/basehttp.py)
- wsgiref.simple_server.WSGIRequestHandler(/usr/lib/python2.7/wsgiref)
- http.server.BaseHTTPRequestHandler(/usr/local/lib/python2.7/dist-packages/http)
- socketserver.StreamRequestHandler
- socketserver.BaseRequestHandler
- object
从代码上看django.core.servers.basehttp.WSGIRequestHandler并没有init或者call方法,因此需要遍历所有父类对象。
最终在socketserver.BaseRequestHandler中看到了init实例初始化方法,它调用了self.handle方法(即回调了:django.core.servers.basehttp.WSGIRequestHandler.handle)。
class WSGIRequestHandler(simple_server.WSGIRequestHandler):
def get_environ(self):
# Strip all headers with underscores in the name before constructing
# the WSGI environ. This prevents header-spoofing based on ambiguity
# between underscores and dashes both normalized to underscores in WSGI
# env vars. Nginx and Apache 2.4+ both do this as well.
for k in self.headers:
if '_' in k:
del self.headers[k]
return super().get_environ()
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 (socket.error, AttributeError):
pass
def handle_one_request(self):
"""Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
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 & connection closing
handler.run(self.server.get_app())# 意思是将django.contrib.staticfiles.handlers.StaticFilesHandler转交给ServerHandler去运行。
handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())实例化了ServerHandler对象。
handler.run(self.server.get_app()),意思是将django.contrib.staticfiles.handlers.StaticFilesHandler转交给ServerHandler去运行。
ServerHandler对象并没有run方法,它的继承分布:
- django.core.servers.basehttp.ServerHandler(django/core/servers/basehttp.py)
- wsgiref.simple_server.ServerHandler(/usr/lib/python2.7/wsgiref)
- wsgiref.handlers.SimpleHandler(/usr/lib/python2.7/wsgiref)
- wsgiref.handlers.BaseHandler
- object
最终在 wsgiref.handlers.BaseHandler 中找到了run方法。
wsgiref.handlers.py
:
def run(self, application):
"""Invoke the application"""
# Note to self: don't move the close()! Asynchronous servers shouldn't
# call close() from finish_response(), so if you close() anywhere but
# the double-error branch here, you'll break asynchronous servers by
# prematurely closing. Async servers must return from 'run()' without
# closing if there might still be output to iterate over.
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
except:
try:
self.handle_error()
except:
# If we get an error handling an error, just give up already!
self.close()
raise # ...and let the actual server figure it out.
application(self.environ, self.start_response)也就相当于是django.contrib.staticfiles.handlers.StaticFilesHandler.
call
(self.environ, lf.start_response)。
django.contrib.staticfiles.handlers.py
:
def __call__(self, environ, start_response):
if not self._should_handle(get_path_info(environ)):
return self.application(environ, start_response)
return super().__call__(environ, start_response)
通过层层流转,最终进入django的静态文件处理的Handler。
总结
environ这个变量在django的WSGIServer和WSGIRequestHandler中扮演这非常重要的角色,因为所有的客户端ip、请求的URL、cookie、session、header等等信息都保存在其中。
WSGIServer: 用于处理socket请求和对接WSGIRequestHandler。
WSGIRequestHandler:针对environ进行预处理和对接WSGIServerHandler。
ServerHandler: 用于执行应用程序(application)和返回响应给WSGIServer。
参考博文:
1、
https://www.jianshu.com/p/17d78b52c732
2、
http://www.hongweipeng.com/index.php/archives/1369/