Django源码分析之程序执行入口分析
一般我们开启一个django项目,最简单的方法是进入project 目录,这时目录结构是这样的 然后我们执行python manage.py runserver,程序就开始执行了。 那django是如何从一个命令就启动整个server,启动的流程是如何的实现的呢? 首先我们来打开目录下的manage.
打开目录下的manage.py,内容是这样的:
- #!/usr/bin/env python
- import os
- import sys
- if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_01.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.
- 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.
- try:
- settings.INSTALLED_APPS
- except ImproperlyConfigured as exc:
- self.settings_exception = exc
- if settings.configured:
- if subcommand == 'runserver' and '--noreload' not in self.argv:
- try:
- autoreload.check_errors(django.setup)()
- except Exception:
- apps.all_models = defaultdict(OrderedDict)
- apps.app_configs = OrderedDict()
- apps.apps_ready = apps.models_ready = apps.ready = True
- _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()
- 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])
- 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
- 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:
- if subcommand == 'runserver' and '--noreload' not in self.argv:
- try:
- autoreload.check_errors(django.setup)()
- except Exception:
- 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:
- httpd.daemon_threads = True
- httpd.set_app(wsgi_handler)
- httpd.serve_forever()
可以看出run函数其实就是启动一个WSGIServer实例(WSGIServer继承python内置类simple_server.WSGIServer),并把handler设置为前面get_handler的返回值
Django源码分析之程序执行入口分析的更多相关文章
- Django源码分析之启动wsgi发生的事
前言 好多人对技术的理解都停留在懂得使用即可,因而只会用而不会灵活用,俗话说好奇害死猫,不然我也不会在凌晨1.48的时候决定写这篇博客,好吧不啰嗦了 继续上一篇文章,后我有个问题(上文:&qu ...
- 2、Django源码分析之启动wsgi发生了哪些事
一 前言 Django是如何通过网络socket层接收数据并将请求转发给Django的urls层? 有的人张口就来:就是通过wsgi(Web Server Gateway Interface)啊! D ...
- Django 源码小剖: 应用程序入口 WSGIHandler
WSGI 有三个部分, 分别为服务器(server), 应用程序(application) 和中间件(middleware). 已经知道, 服务器方面会调用应用程序来处理请求, 在应用程序中有真正的处 ...
- django源码分析 python manage.py runserver
django是一个快速开发web应用的框架, 笔者也在django框架上开发不少web应用,闲来无事,就想探究一下django底层到底是如何实现的,本文记录了笔者对django源码的分析过程 I be ...
- spring源码系列(十): 读取xml入口类 ClassPathXmlApplicationContext 分析
环境准备: 使用spring5.1.6版本 1 xml配置文件 <?xml version="1.0" encoding="UTF-8"?> < ...
- Django 源码小剖: 初探 WSGI
Django 源码小剖: 初探 WSGI python 作为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也越来越多, Bottle, Djan ...
- Django 源码小剖: 响应数据 response 的返回
响应数据的返回 在 WSGIHandler.__call__(self, environ, start_response) 方法调用了 WSGIHandler.get_response() 方法, 由 ...
- Django 源码小剖: 初探中间件(middleware)
因为考虑到文章的长度, 所以 BaseHandler 的展开被推迟了. 在 BaseHandler 中隐藏着中间件的信息, 较常见的 SessionMiddleware 就已经默认安装. BaseH ...
- django源码阅读
最近再看django-bootstrap-toolkit,一直困惑于静态文件的路径问题.所以只能从源码入手了. 从manage.py开始.manage.py 比较简单就几句话. #!/usr/bi ...
随机推荐
- websocket简介
在我们做web项目的过程中,经常需要做的一个模块是消息模块.典型的场景:一个商城系统的后台管理,我想实现如果前台有客户下单,后台就会接到消息,以便尽快发货处理. 要实现上述的功能,我们有几种备选的方案 ...
- 【功能代码】---3 JS判断字符串是否包含某个字符串
JS判断字符串是否包含某个字符串 var str ="abc"; if(str.indexOf("bc")>-1){ alert('str中包含bc字符串 ...
- iOS手势冲突问题
今天在做一个效果的时候,由于子视图和父视图都有响应的事件,子视图的事件理所当然被父视图拦截掉了,接下来就做分析解决 1. tableviewcell可以触发点击,同时tableview的父视图有点击 ...
- 微信小程序教学第四章第一节(含视频):小程序中级实战教程:详情-页面制作
详情 - 页面制作 本文配套视频地址: https://v.qq.com/x/page/o0555o20xjd.html 开始前请把 ch4-1 分支中的 code/ 目录导入微信开发工具 这一章节中 ...
- 封装一个button上带图片的,图片在上,文字在下的按钮
#import "CJShoppingDetailButton.h" @implementation CJShoppingDetailButton - (void)layoutSu ...
- ArcGIS 网络分析[8.1] 资料1 使用AO打开或创建网络数据集之【打开】
为了创建或打开一个网络数据集,你必须使用NetworkDatasetFDExtension对象(文件地理数据库中的数据集)或NetworkDatasetWorkspaceExtension对象(对于S ...
- lesson - 5 Linux用户和组管理
1. /etc/passwd由 : 分隔成7个字段(1) 用户名 规则:大小写字母.数字.减号(不能出现在首位).点以及下划线,其他字符不合法 (2) x 放密码,安全起见放到 /etc/shadow ...
- Python的range函数详细用法
1. >>> range(1,5) #代表从1到5(不包含5) [1, 2, 3, 4]>>> 2. >>> range(1,5,2) #代表从 ...
- maven jar包冲三种解决方式
初次启动应用,一直包如下错误,起初怀疑引入pandora 版本冲突. Exception in thread "main" java.lang.NoSuchMethodError: ...
- shell脚本-批量执行机器命令
场景:通过跳板机,批量获取线上机器日志 使用方式:run2 host 'ls -al /home/admin/' #! /bin/sh USER_NAME=$USER if [ $# -ne 2 ]; ...