入口
看源碼,找到程序的入口是第一步,很簡單,我們怎麼啓動django來着
python manage.py runserver 8000
好了,就它manage.py,我們來看看它裏面都幹了些啥(讀源碼不必面面俱到,找到關鍵代碼即可)
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Winston_crm.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
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?"
)
raise
execute_from_command_line(sys.argv)
找到關鍵代碼execute_from_command_line(sys.argv)
,進入到裏面:
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
# 調用當前文件中的類ManagementUtility產生對象,這個類就在該函數的上方,一找就能找到
utility = ManagementUtility(argv)
# 調用類ManagementUtility中的方法execute
utility.execute()
關鍵代碼utility.execute()
,去類ManagementUtility
中可以找到,該方法特別長,就不列舉了,一連串if
條件就是判斷參數是否合法
進入到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.
"""
# 獲取命令行輸入第一個參數,如果沒有則爲help
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(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:Namespace(args=[],pythonpath=None,settings=None)
options, args = parser.parse_known_args(self.argv[2:])
# 如果options中的pythonpath或者settings有,則使用傳入的路徑與文件
handle_default_options(options)
except CommandError:
pass # Ignore any option errors at this point.
# 當是django-admin輸入時沒有配置文件此時會報錯,
# 如果是已經生產的項目則可以導入配置文件中已經配置的應用
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.
# 如果不是runserver並且沒有關閉自動重載功能,則執行以下函數
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
# 調用自動檢測文件是否修改如果修改則自動重新啓動Django服務
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
# 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環境
django.setup()
# 檢測是否是自動完成
self.autocomplete()
# 如果解析命令爲help
if subcommand == 'help':
if '--commands' in args:
# 打印出help命令
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']:
# 則輸出當前Django的版本
sys.stdout.write(django.get_version() + '\n')
# 如果輸入參數中包括了--help -h 則打印幫助信息
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)
首先使用命令行工具創建django項目和應用,此時使用的命令爲startproject,startapp,該兩個命令比較相似,實現的功能都是講django/conf目錄下的project_template和app_template兩個模板進行一定數據的渲染後生成一個完整的項目到指定目錄下。假如當前執行的參數爲django-admin startapp testapp,此時就會執行到self.fetch_command(subcommand).run_from_argv(self.argv)
,鏈式調用,我們一點一點來看; 先看fetch_command(subcommand)
,即fetch_command('runserver')
,就在類ManagementUtility
中往上翻可以找到該方法,進入其中:
def fetch_command(self, subcommand): # 執行命令行輸入的具體命令
"""
Try to fetch the given subcommand, printing a message with the
appropriate command called from the command line (usually
"django-admin" or "manage.py") if it can't be found.
"""
# Get commands outside of try block to prevent swallowing exceptions
# get_comands()會返回一個字典
commands = get_commands() # 獲取所有支持的命令
try:
# app_name = commands[subcommand]取值操作即app_name='django.core'
app_name = commands[subcommand] # 獲取命令名稱所在的路徑或者實例
except KeyError:
if os.environ.get('DJANGO_SETTINGS_MODULE'):
# If `subcommand` is missing due to misconfigured settings, the
# following line will retrigger an ImproperlyConfigured exception
# (get_commands() swallows the original one) so the user is
# informed about it.
settings.INSTALLED_APPS
else:
sys.stderr.write("No Django settings specified.\n")
possible_matches = get_close_matches(subcommand, commands)
sys.stderr.write('Unknown command: %r' % subcommand)
if possible_matches:
sys.stderr.write('. Did you mean %s?' % possible_matches[0])
sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
sys.exit(1)
if isinstance(app_name, BaseCommand):# 判斷app_name是否是基本命令的實例,還是命令的路徑
# If the command is already loaded, use it directly.
klass = app_name
else:
# klass = load_command_class(app_name, subcommand)
# 即klass = load_command_class('django.core' ,’runserver‘),
# 自己去看很簡單,klass=django.core.management.commands.runserver.Command類
klass = load_command_class(app_name, subcommand) # 如果是路徑則導入該命令
return klass # 將命令的實例化對象返回
self.fetch_command
是利用django內置的命令管理工具去匹配到具體的模塊,例如self.fetch_command(subcommand)
其實就相當於是self.fetch_command('runserver')
,它最終找到了==django.contrib.staticfiles.management.commands.runserver.Command
==這個命令工具。
django中的命令工具代碼組織採用的是策略模式+接口模式,也就是說django.core.management.commands
這個目錄下面存在各種命令工具,每個工具下面都有一個Command
接口,當匹配到'runserver'
時調用'runserver'
命令工具的Command
接口,當匹配到'migrate'
時調用'migrate'
命令工具的Command
接口。
好啦,此時我們得知self.fetch_command(subcommand)
得到的是類Command
,好多人就在這懵逼了,接下來鏈式調用應該去找run_from_argv(self.argv)
了,但是在Command
類中怎麼也找不到,傻了吧,去Command
的父類BaseCommand
裏找啊
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:
self.execute(*args, **cmd_options)# 調用execute方法
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
run_from_argv
的作用是初始化中間件、啓動服務,也就是拉起wsgi
(但實際上並不是由它來直接完成,而是由後續很多其他代碼來完成),直觀上看它應該是runserver.Command
對象的一個方法,但實際上要稍微更復雜一些,因爲沒有列出關聯代碼,所以在下一個代碼塊中進行說明。
關鍵代碼self.execute(*args, **cmd_options)
,注意了,這個execute
應該去Command
類裏找啊,因爲該self
是Command
類的對象啊,讓我們回到Command
類中,找execute
這裏有個坑,我找了好久都沒找到Command
類的入口,原來藏在runserver.py
中:
django/core/management/commands/runserver.py # Command類所在文件
class Command(BaseCommand):
help = "Starts a lightweight Web server for development."
# Validation is called explicitly each time the server is reloaded.
requires_system_checks = False
stealth_options = ('shutdown_message',)
default_addr = '127.0.0.1'
default_addr_ipv6 = '::1'
default_port = '8000'# 默認啓動服務監聽的端口
protocol = 'http'
server_cls = WSGIServer
def add_arguments(self, parser): # 創建幫助信息
parser.add_argument(
'addrport', nargs='?',
help='Optional port number, or ipaddr:port'
)
parser.add_argument(
'--ipv6', '-6', action='store_true', dest='use_ipv6',
help='Tells Django to use an IPv6 address.',
)
parser.add_argument(
'--nothreading', action='store_false', dest='use_threading',
help='Tells Django to NOT use threading.',
)
parser.add_argument(
'--noreload', action='store_false', dest='use_reloader',
help='Tells Django to NOT use the auto-reloader.',
)
def execute(self, *args, **options):# 調用處理方法
if options['no_color']:# 檢查是否需要更改文字顏色
# We rely on the environment because it's currently the only
# way to reach WSGIRequestHandler. This seems an acceptable
# compromise considering `runserver` runs indefinitely.
os.environ["DJANGO_COLORS"] = "nocolor"
# super().execute(*args, **options)會去父類BaseCommand中找到excute方法,
# 該方法中的關鍵代碼爲output = self.handle(*args, **options),
# 該self是Command類的對象,所以接着去Command類中找到handle方法
super().execute(*args, **options)# 調用父類的執行方法
def get_handler(self, *args, **options):
"""Return the default WSGI handler for the runner."""
return get_internal_wsgi_application()
def handle(self, *args, **options):# 調用處理方法
# 檢查是否是debug模式,如果不是則ALLOWED_HOSTS不能爲空
if not settings.DEBUG and not settings.ALLOWED_HOSTS:
raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')
self.use_ipv6 = options['use_ipv6']
# 檢查輸入參數中是否是ipv6格式,檢查當前python是否支持ipv6
if self.use_ipv6 and not socket.has_ipv6:
raise CommandError('Your Python does not support IPv6.')
self._raw_ipv6 = False
# 如果輸入參數中沒有輸入端口則使用默認的端口
if not options['addrport']:
self.addr = ''
self.port = self.default_port
# 檢查匹配的ip格式
else:
m = re.match(naiveip_re, options['addrport'])
if m is None:
raise CommandError('"%s" is not a valid port number '
'or address:port pair.' % options['addrport'])
# 找出匹配的數據
self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
# 檢查端口是否爲數字
if not self.port.isdigit():
raise CommandError("%r is not a valid port number." % self.port)
# 檢查解析出的地址是否合法的ipv6地址
if self.addr:
if _ipv6:
self.addr = self.addr[1:-1]
self.use_ipv6 = True
self._raw_ipv6 = True
elif self.use_ipv6 and not _fqdn:
raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
# 如果沒有輸入ip地址則使用默認的地址
if not self.addr:
self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
self._raw_ipv6 = self.use_ipv6
# 運行,調用run方法
self.run(**options)
def run(self, **options):
"""Run the server, using the autoreloader if needed."""
# 根據配置是否自動加載,如果沒有輸入則default=True
use_reloader = options['use_reloader']
# 當開啓了自動加載時,則調用自動啓動運行
if use_reloader:
autoreload.run_with_reloader(self.inner_run, **options)
# 如果沒有開啓文件更新自動重啓服務功能則直接運行
else:
self.inner_run(None, **options)
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.
# 檢查是否migrations是否與數據庫一致
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,默認返回wsgi
handler = self.get_handler(*args, **options)
# 調用run方法
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading, server_cls=self.server_cls)
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 = 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)
執行無異常的話,最終都要定位到一個run方法,該方法就在本文件開頭位置導入過
from django.core.servers.basehttp import (
WSGIServer, get_internal_wsgi_application, run,
)
截止到該部分,Command
類實際上就是一個初始化過程,全部都爲'runserver'
服務,雖然很多代碼我沒有列出來,但是它確實做了一些,例如參數解析、端口指定檢測、ipv4檢測、ipv6檢測、端口是否佔用、線程檢查、文件改動檢測自動重啓服務等工作。
接下來我把注意力放在django.core.servers.basehttp
下的run
函數上,代碼如下
# 形參wsgi_handler的值爲StaticFilesHandler
# run方法主要是知會各個對象啓動wsgi服務
def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
# 服務監聽的地址和端口
server_address = (addr, port)
# 如果是多線程運行
if threading:
# 調用內置元類type創建一個類WSGIServer,該類繼承了
#(socketserver.ThreadingMixIn, WSGIServer),去代碼塊WSGIServer類中查看它本身只繼承
# wsgiref.simple_server.WSGIServer、object這兩個類,通過type重新創建一下是給
#類WSGIServer強行添加了一個爹socketserver.ThreadingMixIn,這麼做的意義是每次調用類
#WSGIServer的時候都會單獨啓用一個線程來處理,說完了WSGIServer的第一個基類,
#我們再來說它的第二個基類WSGIServer完整的繼承家族
"""
django.core.servers.basehttp.WSGIServer
wsgiref.simple_server.WSGIServer、 socketserver.ThreadingMixIn
http.server.HTTPServer
socketserver.TCPServer
socketserver.BaseServer
object
"""
# httpd_cls這個變量被定義完成之後,由於大量的繼承關係,它其實已經不單純的屬於django,
# 它是一個傳統意義上的WSGI服務對象了。
httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
# 實例化該類,它是WSGI服務器與django之間相互通信的唯一樞紐通道,也就是說,
# 當WSGI服務對象收到socket請求後,會將這個請求傳遞給django的WSGIRequestHandler
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
# 是將django.contrib.staticfiles.handlers.StaticFilesHandler 傳遞給
# WSGIServer當作一個application,當WSGIServer收到網絡請求後,可以將數據分發給
# django.core.servers.basehttp.WSGIRequestHandler,最終由
# django.core.servers.basehttp.WSGIRequestHandler將數據傳遞給
# application(即:django.contrib.staticfiles.handlers.StaticFilesHandler)
httpd.set_app(wsgi_handler)
# 啓動非堵塞網絡監聽服務。
httpd.serve_forever()
總結:綜上所述其實都是在爲啓動django服務而做準備,大致內容如下
- 解析運行 python manage.py 所提供的參數,例如: runserver.
- 根據參數 找到相對應的 命令管理工具。
- 加載所有的app。
- 檢查端口、ipv4檢測、ipv6檢測、端口是否佔用、線程檢查、orm對象檢查(表是否創建)。
- 實例化WSGIRequestHandler,並且將它註冊到python Lib庫中的WSGIServer中。
- 最後啓動python Lib庫中的WSGIServer
httpd.serve_forever
調用的是socketserver.BaseServer.serve_forever
方法(關於socketserver
的源碼解析點擊這裏,下面我直接說流程,原理不再累述)。
-
socketserver.BaseServer.serve_forever
方法採用了selector
網絡模型進行等待數據,每0.5秒遍歷一次文件描述符,當有數據進來時,ready
變量會是一個socket
請求對象,這時會將後續工作轉交給self._handler_request_noblock
方法(即:socketserver.BaseServer._handler_request_noblock
)去處理。 -
socketserver.BaseServer._handler_request_noblock
方法基本沒做什麼事情(self.verify_request
壓根就沒有檢查任何東西),直接就把後續工作轉交給socketserver.BaseServer.process_request
方法。 -
socketserver.BaseServer.process_request
也沒做什麼事情,直接就將後續工作轉交給socketserver.BaseServer.finish_request
方法,只不過在最後加了一條關閉請求的命令。 -
socketserver.BaseServer.finish_request
也沒做什麼事情,直接就將後續工作轉交給socketserver.BaseServer.RequestHandlerClass
。 -
socketserver.BaseServer.RequestHandlerClass
是由上一節httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
傳遞過來的參數django.core.servers.basehttp.WSGIRequestHandler
。 也就是說當執行self.RequestHandler(request, client_address, self)
時等同於執行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)
。
serve_forever
就是開啓了一個while
來無限監聽網絡層的scoket
請求,當一條請求過來時,就層層轉交到django.core.servers.basehttp.WSGIRequestHandler
代碼如下
class WSGIRequestHandler(simple_server.WSGIRequestHandler):
protocol_version = 'HTTP/1.1'
def address_string(self):
# Short-circuit parent method to not call socket.getfqdn
return self.client_address[0]
def log_message(self, format, *args):
extra = {
'request': self.request,
'server_time': self.log_date_time_string(),
}
if args[1][0] == '4':
# 0x16 = Handshake, 0x03 = SSL 3.0 or TLS 1.x
if args[0].startswith('\x16\x03'):
extra['status_code'] = 500
logger.error(
"You're accessing the development server over HTTPS, but "
"it only supports HTTP.\n", extra=extra,
)
return
if args[1].isdigit() and len(args[1]) == 3:
status_code = int(args[1])
extra['status_code'] = status_code
if status_code >= 500:
level = logger.error
elif status_code >= 400:
level = logger.warning
else:
level = logger.info
else:
level = logger.info
level(format, *args, extra=extra)
def get_environ(self):
# Strip all headers with underscores in the name before constructing
# the WSGI environ. This prevents header-spoofing based on ambiguity
# between underscores and dashes both normalized to underscores in WSGI
# env vars. Nginx and Apache 2.4+ both do this as well.
for k in self.headers:
if '_' in k:
del self.headers[k]
return super().get_environ()
def handle(self): # 關鍵代碼
self.close_connection = True
self.handle_one_request()
while not self.close_connection:
self.handle_one_request()
try:
self.connection.shutdown(socket.SHUT_WR)
except (socket.error, AttributeError):
pass
def handle_one_request(self):
"""Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self # backpointer for logging & connection closing
handler.run(self.server.get_app())
方法handle
,至於如何調用到它,需要從WSGIRequestHandler
的實例化說起,上面我們提到當執行self.RequestHandler(request, client_address, self)
時等同於執行django.core.servers.basehttp.WSGIRequestHandler(request, client_address, self)
,而WSGIRequestHandler
的繼承的父類們如下:
1. django.core.servers.basehttp.WSGIRequestHandler
2. wsgiref.simple_server.WSGIRequestHandler
3. http.server.BaseHTTPRequestHandler
4. socketserver.StreamRequestHandler
5. socketserver.BaseRequestHandler
6. object
實例化類WSGIRequestHandler
時發現它並沒有__init__
和__call__
方法,需要去父類中找,最終在socketserver.BaseRequestHandler
中找到,它調用了self.hande
方法,注意self.handle
並不是直接調用BaseRequestHandler
中的handle
,根據對象屬性的查找關係,會去django.core.servers.basehttp.WSGIRequestHandler
類中找,找到了handle
,其實是相當於回調了handle
,代碼如下
def handle(self):
self.close_connection = True
self.handle_one_request()
while not self.close_connection:
self.handle_one_request() # 關鍵代碼
try:
self.connection.shutdown(socket.SHUT_WR)
except (socket.error, AttributeError):
pass
關鍵代碼:self.handle_one_request()
直接在當前類中找到,代碼如下
def handle_one_request(self):
"""Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
# 實例化了ServerHandler對象
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
# 將django.contrib.staticfiles.handlers.StaticFilesHandler轉交給ServerHandler去運行,
# 而ServerHandler對象並沒有run方法,去它的父類們中去找
handler.request_handler = self # backpointer for logging & connection closing
handler.run(self.server.get_app())
run
方法位置:
1、django.core.servers.basehttp.ServerHandler
2、wsgiref.simple_server.ServerHandler
3、wsgiref.handlers.SimpleHandler
4、wsgiref.handlers.BaseHandler # 在此處找到run方法
5、object
進入run
方法:
def run(self, application):
"""Invoke the application"""
# Note to self: don't move the close()! Asynchronous servers shouldn't
# call close() from finish_response(), so if you close() anywhere but
# the double-error branch here, you'll break asynchronous servers by
# prematurely closing. Async servers must return from 'run()' without
# closing if there might still be output to iterate over.
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)# 關鍵代碼
self.finish_response()
except:
try:
self.handle_error()
except:
# If we get an error handling an error, just give up already!
self.close()
raise # ...and let the actual server figure it out.
關鍵代碼:application(self.environ, self.start_response)
也就相當於是django.contrib.staticfiles.handlers.StaticFilesHandler.__call__(self.environ, lf.start_response)
。
進入到StaticFilesHandler
類中:
class StaticFilesHandler(WSGIHandler): # django專門用來處理靜態文件的類
"""
WSGI middleware that intercepts calls to the static files directory, as
defined by the STATIC_URL setting, and serves those files.
"""
# May be used to differentiate between handler types (e.g. in a
# request_finished signal)
handles_files = True
def __init__(self, application):
self.application = application
# 解析配置的靜態文件路徑
self.base_url = urlparse(self.get_base_url())
super().__init__()
def load_middleware(self):
# Middleware are already loaded for self.application; no need to reload
# them for self.
pass
def get_base_url(self):
# 檢查靜態文件相關配置是否正確
utils.check_settings()
# 返回配置中的靜態文件
return settings.STATIC_URL
def _should_handle(self, path):
"""
Check if the path should be handled. Ignore the path if:
* the host is provided as part of the base_url
* the request's path isn't under the media path (or equal)
"""
# 路徑是否以靜態路徑開頭,並且配置文件沒有給出靜態文件的Host
return path.startswith(self.base_url[2]) and not self.base_url[1]
def file_path(self, url):
"""
Return the relative path to the media file on disk for the given URL.
"""
# 獲取文件的相對路徑
relative_url = url[len(self.base_url[2]):]
return url2pathname(relative_url)
def serve(self, request):
"""Serve the request path."""
# 啓動server處理靜態文件
return serve(request, self.file_path(request.path), insecure=True)
def get_response(self, request):
from django.http import Http404
# 如果是靜態文件路徑則使用server處理
if self._should_handle(request.path):
try:
return self.serve(request)
except Http404 as e:
return response_for_exception(request, e)
return super().get_response(request)
def __call__(self, environ, start_response):
# 先判斷請求url是否是靜態文件路徑
if not self._should_handle(get_path_info(environ)):
# 如果不是靜態文件路徑,則正常處理
return self.application(environ, start_response) # 關鍵代碼
# 如果是靜態文件路徑則調用父方法處理
return super().__call__(environ, start_response)
self.application(environ, start_response)
,先說self.application
是個啥呢,可以看到在該類的__init__
方法中執行了一個self.application = application
,那它的值到底是啥呢?
讀源碼的竅門在於讀一點記錄一點,遇到看不懂的變量打印一下值看一下即可,最好不要重複回頭,那樣只會讓你更暈,例如我們用管理用戶(修改django源碼需要權限)修改文件django.contrib.staticfiles.handlers.StaticFilesHandler
加一行打印代碼
def __init__(self, application):
self.application = application
print('django源碼打印--->self.application值爲',self.application) # 打印
self.base_url = urlparse(self.get_base_url())
super().__init__()
然後重啓django可以看到self.application
的值爲<django.core.handlers.wsgi.WSGIHandler object at 0x106cf0278>
,去查看類django.core.handlers.wsgi.WSGIHandler
的實例化發現加載了中間件self.load_middleware()
,至此我們完成分析如何從wsgi
服務到將url
請求信息轉交給django,剩下的就是django的內部流程啦,我們有機會再繼續剖析吧
另外補充:可以用同樣的手法查看envion
變量,該變量非常重要,http
協議的請求信息都被放入了environ
變量中。我們分析流程中的WSGIServer
類主要用於處理socket
請求和對接WSGIRequestHandler
,WSGIRequestHandler
類主要針對environ
進行預處理和對接WSGIServerHandler
,而ServerHandler
類則主要用於執行應用程序(application
)和返回響應給WSGIServer