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) + `
`)
elif len(options.args) < 1:
sys.stdout.write(self.main_help_text() + `
`)
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() + `
`)
elif self.argv[1:] in ([`--help`], [`-h`]):
sys.stdout.write(self.main_help_text() + `
`)
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...
")
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
"
"Starting development server at http://%(addr)s:%(port)s/
"
"Quit the server with %(quit_command)s.
"
) % {
"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就開始接收請求了。
相關文章
- vue原始碼分析系列之入口檔案分析Vue原始碼
- 精盡MyBatis原始碼分析 - SqlSession 會話與 SQL 執行入口MyBatis原始碼SQLSession會話
- Java多執行緒之Thread原始碼分析Java執行緒thread原始碼
- 執行流程原始碼分析原始碼
- 執行緒池之ScheduledThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- 執行緒池之ThreadPoolExecutor執行緒池原始碼分析筆記執行緒thread原始碼筆記
- Netty原始碼分析之NioEventLoop(三)—NioEventLoop的執行Netty原始碼OOP
- Yii2原始碼分析(一):入口原始碼
- 執行緒池原始碼分析執行緒原始碼
- Mybatis執行流程原始碼分析MyBatis原始碼
- DRF之請求執行流程和APIView原始碼分析APIView原始碼
- Netty原始碼分析之Reactor執行緒模型詳解Netty原始碼React執行緒模型
- Django(55)GenericAPIView原始碼分析DjangoAPIView原始碼
- Django筆記四十之執行Django環境的python指令碼Django筆記Python指令碼
- EOS原始碼分析(2)EOS執行原始碼
- SpringMVC執行流程及原始碼分析SpringMVC原始碼
- Zookeeper原始碼分析(一) ----- 原始碼執行環境搭建原始碼
- Java併發之執行緒池ThreadPoolExecutor原始碼分析學習Java執行緒thread原始碼
- JUC之執行緒池基礎與簡單原始碼分析執行緒原始碼
- 精盡MyBatis原始碼分析 - SQL執行過程(一)之 ExecutorMyBatis原始碼SQL
- 精盡MyBatis原始碼分析 - SQL執行過程(二)之 StatementHandlerMyBatis原始碼SQL
- 精盡MyBatis原始碼分析 - SQL執行過程(三)之 ResultSetHandlerMyBatis原始碼SQL
- 原始碼分析OKHttp的執行過程原始碼HTTP
- 深入wepy原始碼:wepy執行原理分析原始碼
- hashCode()方法原始碼執行簡要分析原始碼
- Python執行緒池ThreadPoolExecutor原始碼分析Python執行緒thread原始碼
- Dubbo RPC執行緒模型 原始碼分析RPC執行緒模型原始碼
- dubbo原始碼分析之服務呼叫方發起呼叫(入口InvokerInvocationHandler.invoke)原始碼
- Tomcat詳解系列(3) - 原始碼分析準備和分析入口Tomcat原始碼
- netty原始碼分析之揭開reactor執行緒的面紗(三)Netty原始碼React執行緒
- netty原始碼分析之揭開reactor執行緒的面紗(二)Netty原始碼React執行緒
- netty原始碼分析之揭開reactor執行緒的面紗(一)Netty原始碼React執行緒
- 入口檔案開始,分析Vue原始碼實現Vue原始碼
- 以太坊原始碼分析(18)以太坊交易執行分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- Laravel 原始碼方法執行類詳細分析Laravel原始碼
- Springboot中mybatis執行邏輯原始碼分析Spring BootMyBatis原始碼
- Mybatis原始碼解析之執行SQL語句MyBatis原始碼SQL