django启动过程剖析

在manage.py文件中

 1 if __name__ == '__main__':
 2     os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'test11.settings')
 3     try:
 4         from django.core.management import execute_from_command_line
 5     except ImportError as exc:
 6         raise ImportError(
 7             "Couldn't import Django. Are you sure it's installed and "
 8             "available on your PYTHONPATH environment variable? Did you "
 9             "forget to activate a virtual environment?"
10         ) from exc
11     execute_from_command_line(sys.argv) #在这里获取命令行参数

在execute_from_command_line函数中实现一系列操作:

def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv) #实例化ManagementUtility对象
    utility.execute()

在ManagementUtility对象的execute方法中:

 1     def execute(self):
 2         """
 3         Given the command-line arguments, figure out which subcommand is being
 4         run, create a parser appropriate to that command, and run it.
 5         """
 6         try:
 7             subcommand = self.argv[1] #这里获取第一个参数(命令)如runserver
 8         except IndexError:
 9             subcommand = 'help'  # Display help if no arguments were given.
10 
11         # Preprocess options to extract --settings and --pythonpath.
12         # These options could affect the commands that are available, so they
13         # must be processed early.
14         parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
15         parser.add_argument('--settings')
16         parser.add_argument('--pythonpath')
17         parser.add_argument('args', nargs='*')  # catch-all
18         try:
19             options, args = parser.parse_known_args(self.argv[2:])  #这里把后面的参数装入options中如['127.0.0.1:8000']
20             handle_default_options(options)
21         except CommandError:
22             pass  # Ignore any option errors at this point.
23 
24         try:
25             settings.INSTALLED_APPS
26         except ImproperlyConfigured as exc:
27             self.settings_exception = exc
28         except ImportError as exc:
29             self.settings_exception = exc
30 
31         if settings.configured:
32             # Start the auto-reloading dev server even if the code is broken.
33             # The hardcoded condition is a code smell but we can't rely on a
34             # flag on the command class because we haven't located it yet.
35             if subcommand == 'runserver' and '--noreload' not in self.argv:
36                 try:
37                     autoreload.check_errors(django.setup)()
38                 except Exception:
39                     # The exception will be raised later in the child process
40                     # started by the autoreloader. Pretend it didn't happen by
41                     # loading an empty list of applications.
42                     apps.all_models = defaultdict(OrderedDict)
43                     apps.app_configs = OrderedDict()
44                     apps.apps_ready = apps.models_ready = apps.ready = True
45 
46                     # Remove options not compatible with the built-in runserver
47                     # (e.g. options for the contrib.staticfiles' runserver).
48                     # Changes here require manually testing as described in
49                     # #27522.
50                     _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
51                     _options, _args = _parser.parse_known_args(self.argv[2:])
52                     for _arg in _args:
53                         self.argv.remove(_arg)
54 
55             # In all other cases, django.setup() is required to succeed.
56             else:
57                 django.setup() #加载APP
58 
59         self.autocomplete()
60 
61         if subcommand == 'help':
62             if '--commands' in args:
63                 sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
64             elif not options.args:
65                 sys.stdout.write(self.main_help_text() + '\n')
66             else:
67                 self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
68         # Special-cases: We want 'django-admin --version' and
69         # 'django-admin --help' to work, for backwards compatibility.
70         elif subcommand == 'version' or self.argv[1:] == ['--version']:
71             sys.stdout.write(django.get_version() + '\n')
72         elif self.argv[1:] in (['--help'], ['-h']):
73             sys.stdout.write(self.main_help_text() + '\n')
74         else:
75             self.fetch_command(subcommand).run_from_argv(self.argv)

在django.setup中调用apps.populate方法:

 1     def populate(self, installed_apps=None):
 2         """
 3         Load application configurations and models.
 4 
 5         Import each application module and then each model module.
 6 
 7         It is thread-safe and idempotent, but not reentrant.
 8         """
 9         if self.ready:
10             return
11 
12         # populate() might be called by two threads in parallel on servers
13         # that create threads before initializing the WSGI callable.
14         with self._lock:
15             if self.ready:
16                 return
17 
18             # An RLock prevents other threads from entering this section. The
19             # compare and set operation below is atomic.
20             if self.loading:
21                 # Prevent reentrant calls to avoid running AppConfig.ready()
22                 # methods twice.
23                 raise RuntimeError("populate() isn't reentrant")
24             self.loading = True
25 
26             # Phase 1: initialize app configs and import app modules.
27             for entry in installed_apps:
28                 if isinstance(entry, AppConfig):
29                     app_config = entry
30                 else:
31                     app_config = AppConfig.create(entry) #创建AppConfig对象
32                 if app_config.label in self.app_configs:
33                     raise ImproperlyConfigured(
34                         "Application labels aren't unique, "
35                         "duplicates: %s" % app_config.label)
36 
37                 self.app_configs[app_config.label] = app_config
38                 app_config.apps = self
39 
40             # Check for duplicate app names.
41             counts = Counter(
42                 app_config.name for app_config in self.app_configs.values()) #判断是否有重复的AppConfig对象
43             duplicates = [
44                 name for name, count in counts.most_common() if count > 1]
45             if duplicates:
46                 raise ImproperlyConfigured(
47                     "Application names aren't unique, "
48                     "duplicates: %s" % ", ".join(duplicates))
49 
50             self.apps_ready = True
51 
52             # Phase 2: import models modules.
53             for app_config in self.app_configs.values(): #导入models
54                 app_config.import_models()
55 
56             self.clear_cache()
57 
58             self.models_ready = True
59 
60             # Phase 3: run ready() methods of app configs.
61             for app_config in self.get_app_configs(): #注册AppConfig对象
62                 app_config.ready()
63 
64             self.ready = True

 在autocomplete中:

 1     def autocomplete(self):
 2         """
 3         Output completion suggestions for BASH.
 4 
 5         The output of this function is passed to BASH's `COMREPLY` variable and
 6         treated as completion suggestions. `COMREPLY` expects a space
 7         separated string as the result.
 8 
 9         The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used
10         to get information about the cli input. Please refer to the BASH
11         man-page for more information about this variables.
12 
13         Subcommand options are saved as pairs. A pair consists of
14         the long option string (e.g. '--exclude') and a boolean
15         value indicating if the option requires arguments. When printing to
16         stdout, an equal sign is appended to options which require arguments.
17 
18         Note: If debugging this function, it is recommended to write the debug
19         output in a separate file. Otherwise the debug output will be treated
20         and formatted as potential completion suggestions.
21         """
22         # Don't complete if user hasn't sourced bash_completion file.
23         if 'DJANGO_AUTO_COMPLETE' not in os.environ:
24             return
25 
26         cwords = os.environ['COMP_WORDS'].split()[1:]
27         cword = int(os.environ['COMP_CWORD'])
28 
29         try:
30             curr = cwords[cword - 1]
31         except IndexError:
32             curr = ''
33 
34         subcommands = list(get_commands()) + ['help']
35         options = [('--help', False)]
36 
37         # subcommand
38         if cword == 1:
39             print(' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands))))
40         # subcommand options
41         # special case: the 'help' subcommand has no options
42         elif cwords[0] in subcommands and cwords[0] != 'help':
43             subcommand_cls = self.fetch_command(cwords[0]) #在这里获取命令模块
44             # special case: add the names of installed apps to options
45             if cwords[0] in ('dumpdata', 'sqlmigrate', 'sqlsequencereset', 'test'):
46                 try:
47                     app_configs = apps.get_app_configs()#获取所有AppConfig对象
48                     # Get the last part of the dotted path as the app name.
49                     options.extend((app_config.label, 0) for app_config in app_configs)#把所有AppConfig对象加载到options中
50                 except ImportError:
51                     # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
52                     # user will find out once they execute the command.
53                     pass
54             parser = subcommand_cls.create_parser('', cwords[0]) #创建命令行解析器
55             options.extend(
56                 (min(s_opt.option_strings), s_opt.nargs != 0)
57                 for s_opt in parser._actions if s_opt.option_strings
58             )
59             # filter out previously specified options from available options
60             prev_opts = {x.split('=')[0] for x in cwords[1:cword - 1]}
61             options = (opt for opt in options if opt[0] not in prev_opts)
62 
63             # filter options by current input
64             options = sorted((k, v) for k, v in options if k.startswith(curr))
65             for opt_label, require_arg in options:
66                 # append '=' to options which require args
67                 if require_arg:
68                     opt_label += '='
69                 print(opt_label)
70         # Exit code of the bash completion function is never passed back to
71         # the user, so it's safe to always exit with 0.
72         # For more details see #25420.
73         sys.exit(0)

在run_from_argv中

 1     def run_from_argv(self, argv):
 2         """
 3         Set up any environment changes requested (e.g., Python path
 4         and Django settings), then run this command. If the
 5         command raises a ``CommandError``, intercept it and print it sensibly
 6         to stderr. If the ``--traceback`` option is present or the raised
 7         ``Exception`` is not ``CommandError``, raise it.
 8         """
 9         self._called_from_command_line = True
10         parser = self.create_parser(argv[0], argv[1])  #创建命令行解析器
11 
12         options = parser.parse_args(argv[2:]) #获取命令行后续参数[2:]
13         cmd_options = vars(options)
14         # Move positional args out of options to mimic legacy optparse
15         args = cmd_options.pop('args', ())
16         handle_default_options(options)
17         try:
18             self.execute(*args, **cmd_options) #调用BaseCommand的execute方法
19         except Exception as e:
20             if options.traceback or not isinstance(e, CommandError):
21                 raise
22 
23             # SystemCheckError takes care of its own formatting.
24             if isinstance(e, SystemCheckError):
25                 self.stderr.write(str(e), lambda x: x)
26             else:
27                 self.stderr.write('%s: %s' % (e.__class__.__name__, e))
28             sys.exit(1)
29         finally:
30             try:
31                 connections.close_all()
32             except ImproperlyConfigured:
33                 # Ignore if connections aren't setup at this point (e.g. no
34                 # configured settings).
35                 pass

在BaseCommand的execute方法

 1     def execute(self, *args, **options):
 2         """
 3         Try to execute this command, performing system checks if needed (as
 4         controlled by the ``requires_system_checks`` attribute, except if
 5         force-skipped).
 6         """
 7         if options['no_color']:  #做一些系统检查
 8             self.style = no_style()
 9             self.stderr.style_func = None
10         if options.get('stdout'):
11             self.stdout = OutputWrapper(options['stdout'])
12         if options.get('stderr'):
13             self.stderr = OutputWrapper(options['stderr'], self.stderr.style_func)
14 
15         if self.requires_system_checks and not options.get('skip_checks'):
16             self.check()
17         if self.requires_migrations_checks:
18             self.check_migrations()
19         output = self.handle(*args, **options) #在这里需要调用django.core.management.commands.runserver.Command.handle方法
20         if output:
21             if self.output_transaction:
22                 connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
23                 output = '%s\n%s\n%s' % (
24                     self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),
25                     output,
26                     self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
27                 )
28             self.stdout.write(output)
29         return output

在django.core.management.commands.runserver.Command.handle方法中

 1     def handle(self, *args, **options):
 2         if not settings.DEBUG and not settings.ALLOWED_HOSTS:
 3             raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')
 4 
 5         self.use_ipv6 = options['use_ipv6']
 6         if self.use_ipv6 and not socket.has_ipv6:
 7             raise CommandError('Your Python does not support IPv6.')
 8         self._raw_ipv6 = False
 9         if not options['addrport']:
10             self.addr = ''
11             self.port = self.default_port
12         else:
13             m = re.match(naiveip_re, options['addrport'])
14             if m is None:
15                 raise CommandError('"%s" is not a valid port number '
16                                    'or address:port pair.' % options['addrport'])
17             self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
18             if not self.port.isdigit():
19                 raise CommandError("%r is not a valid port number." % self.port)
20             if self.addr:
21                 if _ipv6:
22                     self.addr = self.addr[1:-1]
23                     self.use_ipv6 = True
24                     self._raw_ipv6 = True
25                 elif self.use_ipv6 and not _fqdn:
26                     raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
27         if not self.addr:
28             self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
29             self._raw_ipv6 = self.use_ipv6
30         self.run(**options)

在django.core.management.commands.runserver.Command.run方法中

1     def run(self, **options):
2         """Run the server, using the autoreloader if needed."""
3         use_reloader = options['use_reloader']
4 
5         if use_reloader:
6             autoreload.main(self.inner_run, None, options)  #这里会进入main函数
7         else:
8             self.inner_run(None, **options)

这里会进入autoreload.main方法中调用python_reloader函数

 1 def python_reloader(main_func, args, kwargs):   #在这里环境里面的RUN_MAIN默认是false的所以会先进入restart_with_reloader,在里面把环境设置为true,再执行subprocess.call函数重新运行命令行参数
 2     if os.environ.get("RUN_MAIN") == "true":  
 3         _thread.start_new_thread(main_func, args, kwargs)
 4         try:
 5             reloader_thread()
 6         except KeyboardInterrupt:
 7             pass
 8     else:
 9         try:
10             exit_code = restart_with_reloader()
11             if exit_code < 0:
12                 os.kill(os.getpid(), -exit_code)
13             else:
14                 sys.exit(exit_code)
15         except KeyboardInterrupt:
16             pass

 

在django.core.management.commands.runserver.Command.inner_run方法中

 1     def inner_run(self, *args, **options):
 2         # If an exception was silenced in ManagementUtility.execute in order
 3         # to be raised in the child process, raise it now.
 4         autoreload.raise_last_exception()
 5 
 6         threading = options['use_threading']
 7         # 'shutdown_message' is a stealth option.
 8         shutdown_message = options.get('shutdown_message', '')
 9         quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'
10 
11         self.stdout.write("Performing system checks...\n\n")
12         self.check(display_num_errors=True)
13         # Need to check migrations here, so can't use the
14         # requires_migrations_check attribute.
15         self.check_migrations()  #检查数据迁移
16         now = datetime.now().strftime('%B %d, %Y - %X')
17         self.stdout.write(now)
18         self.stdout.write((
19             "Django version %(version)s, using settings %(settings)r\n"
20             "Starting development server at %(protocol)s://%(addr)s:%(port)s/\n"
21             "Quit the server with %(quit_command)s.\n"
22         ) % {
23             "version": self.get_version(),
24             "settings": settings.SETTINGS_MODULE,
25             "protocol": self.protocol,
26             "addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
27             "port": self.port,
28             "quit_command": quit_command,
29         })
30 
31         try:
32             handler = self.get_handler(*args, **options) #在这个方法里调用get_internal_wsgi_application方法
33             run(self.addr, int(self.port), handler,
34                 ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
35         except socket.error as e:
36             # Use helpful error messages instead of ugly tracebacks.
37             ERRORS = {
38                 errno.EACCES: "You don't have permission to access that port.",
39                 errno.EADDRINUSE: "That port is already in use.",
40                 errno.EADDRNOTAVAIL: "That IP address can't be assigned to.",
41             }
42             try:
43                 error_text = ERRORS[e.errno]
44             except KeyError:
45                 error_text = e
46             self.stderr.write("Error: %s" % error_text)
47             # Need to use an OS exit because sys.exit doesn't work in a thread
48             os._exit(1)
49         except KeyboardInterrupt:
50             if shutdown_message:
51                 self.stdout.write(shutdown_message)
52             sys.exit(0)

在get_internal_wsgi_application中

 1 def get_internal_wsgi_application():
 2     """
 3     Load and return the WSGI application as configured by the user in
 4     ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
 5     this will be the ``application`` object in ``projectname/wsgi.py``.
 6 
 7     This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
 8     for Django's internal server (runserver); external WSGI servers should just
 9     be configured to point to the correct application object directly.
10 
11     If settings.WSGI_APPLICATION is not set (is ``None``), return
12     whatever ``django.core.wsgi.get_wsgi_application`` returns.
13     """
14     from django.conf import settings
15     app_path = getattr(settings, 'WSGI_APPLICATION') #返回settings里面设置的路径
16     if app_path is None:
17         return get_wsgi_application()
18 
19     try:
20         return import_string(app_path)  #获取WSGI对象
21     except ImportError as err:
22         raise ImproperlyConfigured(
23             "WSGI application '%s' could not be loaded; "
24             "Error importing module." % app_path
25         ) from err

在run函数中启动django web

 1 def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
 2     server_address = (addr, port)
 3     if threading:
 4         httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
 5     else:
 6         httpd_cls = server_cls
 7     httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
 8     if threading:
 9         # ThreadingMixIn.daemon_threads indicates how threads will behave on an
10         # abrupt shutdown; like quitting the server by the user or restarting
11         # by the auto-reloader. True means the server will not wait for thread
12         # termination before it quits. This will make auto-reloader faster
13         # and will prevent the need to kill the server manually if a thread
14         # isn't terminating correctly.
15         httpd.daemon_threads = True
16     httpd.set_app(wsgi_handler)
17     httpd.serve_forever()   #相当于socket的recv方法,等待客户端的连接

 

posted @ 2019-02-10 16:36 arrowxiang 阅读(...) 评论(...) 编辑 收藏