在初始话化一个Django工程之后,往往会在根目录的命令行,敲入python manage.py runserver 8080
运行起来工程。并打印如下日志
(venv) C:\Users\Administrator\PycharmProjects\webdemo>python manage.py runserver 8080
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 14 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
April 10, 2020 - 17:39:48
Django version 3.0.5, using settings 'webdemo.settings'
Starting development server at http://127.0.0.1:8080/
Quit the server with CTRL-BREAK.
那么,在敲了命令行之后,程序做了什么呢?
目录:runserver 8080之后程序做了什么 - 上篇
那么接下来直接进入正题吧,使用
PyCharm
翻看源码
1. 先看manage.py,程序的入口
manage.py
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
# 设置一下环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webdemo.settings')
# 检查相应的Django包是否存在
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
# sys.argv 读取命令行输入的参数,实际如下
# sys.argv = ['manage.py', 'runserver', '8080']
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
很明显,manage.py
主要做了两件事情
- 检查Django是否已经安装
- 读取命令行参数,调用Django提供的方法进行处理
2. 查看方法execute_from_command_line(argv=None)
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
# 构造ManagementUtility类
utility = ManagementUtility(argv)
# 处理命令
utility.execute()
此处class ManagementUtility
的构造函数具体就不讲了,就是赋值一下。
3. 查看方法ManagementUtility.execute()
### ManagementUtility.execute()
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:
# 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, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
except CommandError:
pass # Ignore any option errors at this point.
try:
settings.INSTALLED_APPS
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:
try:
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(dict)
apps.app_configs = {}
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()
self.autocomplete()
# 实际的 subcommand 是 runserver
if subcommand == 'help':
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
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')
elif self.argv[1:] in (['--help'], ['-h']):
sys.stdout.write(self.main_help_text() + '\n')
else:
# 最主要的就是这一句
# fetch_comman() 找到具体的实现类
# run_from_argv() 执行具体的方法
# subcommand = server, self.argv = ['manage.py', 'runserver', '8080']
self.fetch_command(subcommand).run_from_argv(self.argv)
这一段代码里面仍然很多事对环境的一些检查和设置,以及查看命令是否是python manage.py help
或者python manage.py version
,然后最重要的就是最后一句。
4. self.fetch_command(subcommand) 查找到专用于处理命令runserver的代码
其中fetch_command(subcommand)
其实是根据subcommond=runserver
去查找专门为runserver
编写的类,类似有点根据名称去查找到具体的类的意思,在Python里面就是模块的意思了,简而言之,就是找一个叫做runserver.py
的文件
那么,具体可以看一下Django包里面的结构,我们看的fetch_command(subcommand)
代码,都是在文件site-packages\django\core\management\__init__.py
里面,截图如下:
具体的fetch_command(subcommand)
方法实现就不贴出代码具体分析了,简要的过程就是通过命令runserver
可以import进runserver.py
模块,然后再执行其中的方法。
那么,专门处理runserver
命令的类,就在management\commands
下面,可以查看一下。
另外,有意思的是,还可以发现其他各种处理不同命令的代码文件也都是在这里。
5. run_from_argv(self.argv)做了什么?
首先,明确一下self.argv = ['manage.py', 'runserver', '8080']
runserver.py
代码的开头截图如下,可以发现其中的class Command
继承了class BaseCommand
查看同文件夹内其他文件,都是继承了该class BaseCommand
如下,是通过PyCharm
分析得到的base.BaseCommand
和runserver.Command
类的UML图
之所以看这个的原因是,有些方法在runserver.Command
继承了base.BaseCommand
之后,进行了重写,而有些方法并没有被重写。
显然,run_from_argv(self.argv)
并没有被重写,让我们看看代码如何。
### BaseCommand.run_from_argv(self.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.
"""
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])
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', ())
handle_default_options(options)
try:
# 调用的runserver.py中的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
发现好像没有什么特别的,之后就是两个类的方法调来调去,具体的调用过程也不分析了,我写一下调用的链路
base.BaseCommand.run_from_argv()
→runserver.Command.execute()
→base.BaseCommand.execute()
→runserver.Command.handle()
→runserver.Command.run()
→runserver.Command.inner_run()
→django.core.servers.basehttp.run()
→......
注意最后两个调用的方法,是整个过程中比较关键的。其他的方法就暂时不分析了。
6. runserver.Command.inner_run()做了什么?
这个runserver.Command.inner_run()
就是主要的启动入口了,在启动httpServer服务之前,代码都进行了哪些对工程的检查和环境的设置。我们接下来贴代码进行分析
### runserver.Command.inner_run()
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.
# 检查DataBase表结构同步情况
self.check_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:
handler = self.get_handler(*args, **options)
# 这里的run()执行是django.core.servers.basehttp.run(),启动一个http服务
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
except OSError 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)
其中目前比较明显的就是
- 处理一下线程,
threading = options['use_threading']
这一句还在看,还不知道为什么。 - 检查DataBase的差异情况
- 打印启动日志
- 启动http服务
run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
目前呢,暂时分析到这里,接下来一篇就是看看django.core.servers.basehttp.run()
做了什么,如何就启动了一个http服务