看源码,找到程序的入口是第一步,很简单,我们怎么启动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服务而做准备,大致内容如下
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
的实例化发现加载了中间件self.load_middleware()
,至此我们完成分析如何从wsgi
服务到将url
请求信息转交给django,剩下的就是django的内部流程啦,我们有机会再继续剖析吧
另外补充:可以用同样的手法查看envion
变量,该变量非常重要,http
协议的请求信息都被放入了environ
变量中。我们分析流程中的WSGIServer
类主要用于处理socket
请求和对接WSGIRequestHandler
,WSGIRequestHandler
类主要针对environ
进行预处理和对接WSGIServerHandler
,而ServerHandler
类则主要用于执行应用程序(application
)和返回响应给WSGIServer