Django框架,runserver 8080之后程序做了什么 - 上篇

在初始话化一个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.

那么,在敲了命令行之后,程序做了什么呢?


那么接下来直接进入正题吧,使用 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主要做了两件事情

  1. 检查Django是否已经安装
  2. 读取命令行参数,调用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.BaseCommandrunserver.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)

其中目前比较明显的就是

  1. 处理一下线程, threading = options['use_threading']这一句还在看,还不知道为什么。
  2. 检查DataBase的差异情况
  3. 打印启动日志
  4. 启动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服务

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值