Django源码分析之执行入口
魔法门
一般我们启动django,最简单的方法是进入project 目录,这时目录结构是这样的

然后我们执行python manage.py runserver,程序就开始执行了。
那django是如何从一个命令就启动整个server,启动的流程是如何的?
踏门而入
打开目录下的manage.py,内容是这样的:
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_learning.settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)
看来manage.py只是把命令行参数传给django.core.management模块中的execute_from_command_line 函数。
查看execute_from_command_line函数,可以发现实际执行的是ManagementUtility类的excute方法:
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:
            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(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()
        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(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()
        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)
其中
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.
CommandParser其实类似于Argparse的一个解析命令行参数的类,从代码里可以看出我们可以直接在命令行指定settings文件和pythonpath。
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()
这块代码就可以解释我们执行python manage.py start project 时django在背后会调用settings.configure方法,这里的settings是指django.conf.LazySettings的一个实例,configure方法其实就是使用django.conf.global_settings.py中的默认设置创建一份新的配置文件,作为我们新创建的project的settings.py
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(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()
autoreload.check_errors(django.setup)()其实也是调用django.setup方法,而django.setup方法
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)
    apps.populate(settings.INSTALLED_APPS)
负责初始化日志模块以及所有应用.
抽丝剥茧
剩下的代码最重要的就是这一句:
self.fetch_command(subcommand).run_from_argv(self.argv)
fetch_command会根据subcommand(这是我们执行python manage.py rumserver时传入的第二个参数:runserver),去django.core.management.commands中查找对应的command类,然后把所有命令行参数传给run_from_argv方法并执行,在runserver这个示例中,最终会调用django.utils.autoreload中的python_reloader或者jython_reloader新开一个线程:
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
这里的main_func是commands/runserver.py中的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.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.check(display_num_errors=True)
        self.check_migrations()
        now = datetime.now().strftime('%B %d, %Y - %X')
        if six.PY2:
            now = now.decode(get_system_encoding())
        self.stdout.write(now)
        self.stdout.write((
            "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"
        ) % {
            "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,
        })
        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)
最关键的是这两条语句:
handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler,ipv6=self.use_ipv6, threading=threading)
get_handler会返回django.core.servers.basehttp中定义的一个application(其实就是我们project下的wigs.py中定义的application)
这是run函数的内容
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()
可以看出run函数其实就是启动一个WSGIServer实例(WSGIServer继承python内置类simple_server.WSGIServer),并把handler设置为前面get_handler的返回值
水落石出
这样,一条python manage.py runserver命令的执行生命周期就一览无余了。
接下来,server就开始接收请求了。
Django源码分析之执行入口的更多相关文章
- Django drf:序列化增删改查、局部与全局钩子源码流程、认证源码分析、执行流程
		一.序列化类的增.删.改.查 用drf的序列化组件 -定义一个类继承class BookSerializer(serializers.Serializer): -写字段,如果不指定source ... 
- django源码分析 python manage.py runserver
		django是一个快速开发web应用的框架, 笔者也在django框架上开发不少web应用,闲来无事,就想探究一下django底层到底是如何实现的,本文记录了笔者对django源码的分析过程 I be ... 
- Django源码分析之启动wsgi发生的事
		前言  好多人对技术的理解都停留在懂得使用即可,因而只会用而不会灵活用,俗话说好奇害死猫,不然我也不会在凌晨1.48的时候决定写这篇博客,好吧不啰嗦了  继续上一篇文章,后我有个问题(上文:&qu ... 
- 2、Django源码分析之启动wsgi发生了哪些事
		一 前言 Django是如何通过网络socket层接收数据并将请求转发给Django的urls层? 有的人张口就来:就是通过wsgi(Web Server Gateway Interface)啊! D ... 
- django源码分析——本地runserver分析
		本文环境python3.5.2,django1.10.x系列 1.根据上一篇文章分析了,django-admin startproject与startapp的分析流程后,根据django的官方实例此时 ... 
- 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler
		该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ... 
- 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载
		该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ... 
- 深入源码分析SpringMVC执行过程
		本文主要讲解 SpringMVC 执行过程,并针对相关源码进行解析. 首先,让我们从 Spring MVC 的四大组件:前端控制器(DispatcherServlet).处理器映射器(HandlerM ... 
- 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler
		该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ... 
随机推荐
- 【洛谷P3469】[POI2008]BLO-Blockade
			BLO-Blockade 题目链接 若一个点为割点:统计出每个子树的大小,两两相乘再相加, 再加上n-1,为这个点与其他点的拜访数, 因为拜访是互相的,最后再乘二即可 若一个点不是割点:只有(n-1) ... 
- 【洛谷P2168】[NOI2015]荷马史诗
			荷马史诗 建一个k叉哈夫曼树,用堆维护一下 // luogu-judger-enable-o2 #include<iostream> #include<cstdio> #inc ... 
- 在CentOs6.5下安装Python2.7.6和Scrapy
			总在网上看大家的安装教程,这回自己也贡献一份!!! 和民航大学合作项目,去给人家装环境,连简单的Scrapy都没装上,虽然有对方硬件设施坑爹的因素,但主要还是自己准备不充分. 一份好的安装文档应该是可 ... 
- 【题解】洛谷P2822 [NOIP2016TG ]组合数问题 (二维前缀和+组合数)
			洛谷P2822:https://www.luogu.org/problemnew/show/P2822 思路 由于n和m都多达2000 所以暴力肯定是会WA的 因为整个组合数是不会变的 所以我们想到存 ... 
- lucene&solr学习——solr学习(一)
			1.什么是solr solr是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文检索服务器.Solr提供了比lucene风味丰富的查询语言,同时实现了可配置,可扩展,并对索 ... 
- logistic regression svm hinge loss
			二类分类器svm 的loss function 是 hinge loss:L(y)=max(0,1-t*y),t=+1 or -1,是标签属性. 对线性svm,y=w*x+b,其中w为权重,b为偏置项 ... 
- Viewpager实现今日头条顶部导航的功能
			利用简单的Textview 和Viewpager实现滑动.点击换页的效果,效果图如下: 先上布局文件代码: <?xml version="1.0" encoding=&quo ... 
- 轻量ORM-SqlRepoEx (三)Select语句
			一.示例用数据库为Northwind数据库,可在百度网盘下载 https://pan.baidu.com/s/1er0Mm48kUfeAsYkSW6DfnA 密码:r7pm 二.如何初始化SqlRep ... 
- Showing All Messages : error: open /Users/apple/Library/Developer/Xcode/DerivedData/xxx-dkhmpttmnuppvbcxijlcxacfpzcl/Build/Products/Debug-iphoneos/xxx.app/EaseUIResource.bundle/arrow@2x.png: N
			2报错 Showing All Messages : error: open /Users/apple/Library/Developer/Xcode/DerivedData/xxx-dkhmpttm ... 
- jwPlayer为js预留的回调方法
			参考地址:http://www.cnblogs.com/lori/archive/2014/05/05/3709459.html 应用场合 播放时记录当前视频的时间,播放完成时写入完成的时间,像这些功 ... 
