开始之前建议先参考一下这篇文章:https://blog.csdn.net/qq_33339479/article/details/78862156
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
leave_locale_alone = True default_port = '8000' # 默认启动服务监听的端口 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', default=False,
help='Tells Django to use an IPv6 address.',
)
parser.add_argument(
'--nothreading', action='store_false', dest='use_threading', default=True,
help='Tells Django to NOT use threading.',
)
parser.add_argument(
'--noreload', action='store_false', dest='use_reloader', default=True,
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[str("DJANGO_COLORS")] = str("nocolor")
super(Command, self).execute(*args, **options) # 调用父类的执行方法 def get_handler(self, *args, **options):
"""
Returns the default WSGI handler for the runner.
"""
return get_internal_wsgi_application() def handle(self, *args, **options): # 调用处理方法
from django.conf import settings # 导入配置文件 if not settings.DEBUG and not settings.ALLOWED_HOSTS: # 检查是否是debug模式,如果不是则ALLOWED_HOSTS不能为空
raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.') self.use_ipv6 = options['use_ipv6']
if self.use_ipv6 and not socket.has_ipv6: # 检查输入参数中是否是ipv6格式,检查当前python是否支持ipv6
raise CommandError('Your Python does not support IPv6.')
self._raw_ipv6 = False
if not options['addrport']: # 如果输入参数中没有输入端口则使用默认的端口
self.addr = ''
self.port = self.default_port
else:
m = re.match(naiveip_re, options['addrport']) # 检查匹配的ip格式
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)
if self.addr: # 检查解析出的地址是否合法的ipv6地址
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)
if not self.addr: # 如果没有输入ip地址则使用默认的地址
self.addr = '::1' if self.use_ipv6 else '127.0.0.1'
self._raw_ipv6 = self.use_ipv6
self.run(**options) # 运行 def run(self, **options):
"""
Runs the server, using the autoreloader if needed
"""
use_reloader = options['use_reloader'] # 根据配置是否自动加载,如果没有输入则default=True if use_reloader:
autoreload.main(self.inner_run, None, 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.
self.check_migrations() # 检查是否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) # 获取信息处理的handler,默认返回wsgi
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)

该command主要进行了检查端口是否正确,是否多线程开启,是否开启了文件监控自动重启功能,如果开启了自动重启功能则,

   autoreload.main(self.inner_run, None, options) 

调用django/utils/autoreload.py中的mian函数处理,如下所示:

def main(main_func, args=None, kwargs=None):
if args is None:
args = ()
if kwargs is None:
kwargs = {}
if sys.platform.startswith('java'): # 获取当前reloader的处理函数
reloader = jython_reloader
else:
reloader = python_reloader wrapped_main_func = check_errors(main_func) # 添加对man_func的出错处理方法
reloader(wrapped_main_func, args, kwargs) # 运行重启导入函数

目前电脑运行的环境reloader为python_reloader,查看代码为:

def python_reloader(main_func, args, kwargs):
if os.environ.get("RUN_MAIN") == "true": # 获取环境变量是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

第一次运行时,RUN_MAIN没有设置,此时会运行restart_with_reloader函数

def restart_with_reloader():
while True:
args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv # 获取当前运行程序的执行文件
if sys.platform == "win32":
args = ['"%s"' % arg for arg in args]
new_environ = os.environ.copy() # 拷贝当前的系统环境变量
new_environ["RUN_MAIN"] = 'true' # 设置RUN_MAIN为true
exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ) # 启动新进程执行当前代码,如果进程主动退出返回退出状态吗
if exit_code != 3: # 如果返回状态码等于3则是因为监控到文件有变化退出,否则是其他错误,就结束循环退出
return exit_code

该函数主要是对当前执行的可执行文件进行重启,并且设置环境变量RUN_MAIN为true,此时再重新运行该程序时,此时再python_reloader中执行:

    if os.environ.get("RUN_MAIN") == "true":                           # 获取环境变量是RUN_MAIN是否为"true"
thread.start_new_thread(main_func, args, kwargs) # 开启子线程运行服务程序
try:
reloader_thread() # 调用监控函数
except KeyboardInterrupt:
pass

开启一个子线程执行运行服务的主函数,然后重启进程执行reloader_thread函数:

def reloader_thread():
ensure_echo_on()
if USE_INOTIFY: # 如果能够导入pyinotify模块
fn = inotify_code_changed # 使用基于pyinotify的文件监控机制
else:
fn = code_changed # 使用基于对所有文件修改时间的判断来判断是否进行文件的更新
while RUN_RELOADER:
change = fn() # 获取监控返回值
if change == FILE_MODIFIED: # 如果监控到文件修改则重启服务运行进程
sys.exit(3) # force reload
elif change == I18N_MODIFIED: # 监控是否是本地字符集的修改
reset_translations()
time.sleep(1) # 休眠1秒钟

此时主进程会一直循环执行该函数,间隔一秒调用代码变化监控函数执行,如果安装了pyinotify,则使用该模块监控代码是否变化,否则就使用django自己实现的文件监控,在这里就分析一下django自己实现的代码是否变化检测函数:

def code_changed():
global _mtimes, _win
for filename in gen_filenames():
stat = os.stat(filename) # 获取每个文件的状态属性
mtime = stat.st_mtime # 获取数据最后的修改时间
if _win:
mtime -= stat.st_ctime
if filename not in _mtimes:
_mtimes[filename] = mtime # 如果全局变量中没有改文件则存入,该文件的最后修改时间
continue
if mtime != _mtimes[filename]: # 如果已经存入的文件的最后修改时间与当前获取文件的最后修改时间不一致则重置保存最后修改时间变量
_mtimes = {}
try:
del _error_files[_error_files.index(filename)]
except ValueError:
pass
return I18N_MODIFIED if filename.endswith('.mo') else FILE_MODIFIED # 如果修改的文件是.mo结尾则是local模块更改,否则就是项目文件修改需要重启服务
return False

首先,调用gen_filenames函数,该函数主要是获取要监控项目的所有文件,然后将所有文件的最后编辑时间保存起来,当第二次检查时比较是否有新文件添加,旧文件的最后编辑时间是否已经改变,如果改变则重新加载:

def gen_filenames(only_new=False):
"""
Returns a list of filenames referenced in sys.modules and translation
files.
"""
# N.B. ``list(...)`` is needed, because this runs in parallel with
# application code which might be mutating ``sys.modules``, and this will
# fail with RuntimeError: cannot mutate dictionary while iterating
global _cached_modules, _cached_filenames
module_values = set(sys.modules.values()) # 获取模块的所有文件路径
_cached_filenames = clean_files(_cached_filenames) # 检查缓存的文件列表
if _cached_modules == module_values: # 判断所有模块是否与缓存一致
# No changes in module list, short-circuit the function
if only_new:
return []
else:
return _cached_filenames + clean_files(_error_files) new_modules = module_values - _cached_modules
new_filenames = clean_files(
[filename.__file__ for filename in new_modules
if hasattr(filename, '__file__')]) # 检查获取的文件是否存在,如果存在就添加到文件中 if not _cached_filenames and settings.USE_I18N:
# Add the names of the .mo files that can be generated
# by compilemessages management command to the list of files watched.
basedirs = [os.path.join(os.path.dirname(os.path.dirname(__file__)),
'conf', 'locale'),
'locale']
for app_config in reversed(list(apps.get_app_configs())):
basedirs.append(os.path.join(npath(app_config.path), 'locale')) # 添加项目目录下的locale
basedirs.extend(settings.LOCALE_PATHS)
basedirs = [os.path.abspath(basedir) for basedir in basedirs
if os.path.isdir(basedir)] # 如果现在的文件都是文件夹,将文件夹添加到路径中去
for basedir in basedirs:
for dirpath, dirnames, locale_filenames in os.walk(basedir):
for filename in locale_filenames:
if filename.endswith('.mo'):
new_filenames.append(os.path.join(dirpath, filename)) _cached_modules = _cached_modules.union(new_modules) # 添加新增的模块文件
_cached_filenames += new_filenames # 将新增的文件添加到缓存文件中
if only_new:
return new_filenames + clean_files(_error_files)
else:
return _cached_filenames + clean_files(_error_files) def clean_files(filelist):
filenames = []
for filename in filelist: # 所有文件的全路径集合
if not filename:
continue
if filename.endswith(".pyc") or filename.endswith(".pyo"): # 监控新的文件名
filename = filename[:-1]
if filename.endswith("$py.class"):
filename = filename[:-9] + ".py"
if os.path.exists(filename): # 检查文件是否存在,如果存在就添加到列表中
filenames.append(filename)
return filenames

至此,django框架的自动加载功能介绍完成,主要实现思路是, 
1.第一次启动时,执行到restart_with_reloader时,自动重头执行加载 
2.第二次执行时,会执行python_reloader中的RUN_MAIN为true的代码 
3.此时开启一个子线程执行服务运行程序,主进程进行循环,监控文件是否发生变化,如果发生变化则sys.exit(3),此时循环程序会继续重启,依次重复步骤2 
4.如果进程的退出code不为3,则终止整个循环

当监控运行完成后继续执行self.inner_run函数,当执行到

            handler = self.get_handler(*args, **options)                                        # 获取信息处理的handler,默认返回wsgi
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading) # 调用运行函数

获取wsgi处理handler,然后调用django/core/servers/basshttp.py中的run方法

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
server_address = (addr, port)
if threading:
httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
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()
 

调用标准库中的wsgi处理,django标准库的wsgi处理再次就不再赘述(在gunicorn源码分析中已经分析过),所以本地调试的server依赖于标准库,django并没有提供高性能的server端来处理连接,所以不建议使用该命令部署到生产环境中。

django源码解读——runserver分析的更多相关文章

  1. jQuery源码解读-事件分析

    最原始的事件注册 addEventListener方法大家应该都很熟悉,它是Html元素注册事件最原始的方法.先看下addEventListener方法签名: element.addEventList ...

  2. Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读

    本文主要说明Webpack模块构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack的基础构建原理. 本文使用的W ...

  3. Django 源码阅读笔记(基础视图)

    django源码解读之 View View. ContextMixin.TemplateResponseMixin.TemplateView.RedirectView View class View( ...

  4. django源码分析 python manage.py runserver

    django是一个快速开发web应用的框架, 笔者也在django框架上开发不少web应用,闲来无事,就想探究一下django底层到底是如何实现的,本文记录了笔者对django源码的分析过程 I be ...

  5. Django源码分析之启动wsgi发生的事

    前言 ​ 好多人对技术的理解都停留在懂得使用即可,因而只会用而不会灵活用,俗话说好奇害死猫,不然我也不会在凌晨1.48的时候决定写这篇博客,好吧不啰嗦了 ​ 继续上一篇文章,后我有个问题(上文:&qu ...

  6. 2、Django源码分析之启动wsgi发生了哪些事

    一 前言 Django是如何通过网络socket层接收数据并将请求转发给Django的urls层? 有的人张口就来:就是通过wsgi(Web Server Gateway Interface)啊! D ...

  7. fastclick.js源码解读分析

    阅读优秀的js插件和库源码,可以加深我们对web开发的理解和提高js能力,本人能力有限,只能粗略读懂一些小型插件,这里带来对fastclick源码的解读,望各位大神不吝指教~! fastclick诞生 ...

  8. DRF(1) - REST、DRF(View源码解读、APIView源码解读)

    一.REST 1.什么是编程? 数据结构和算法的结合. 2.什么是REST? 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下: /books/ /get_all_books/ 访问所 ...

  9. Restful 1 -- REST、DRF(View源码解读、APIView源码解读)及框架实现

    一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? - url用来唯一定位资源,http请求方式来区分用户行为 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下 ...

随机推荐

  1. python连接mysql中文数据编码

    系统是win7 x64 Python 2.7.6的site.py里面编码设定为 utf-8 py文件首行指定 #coding:utf-8 MySQL 5.5.38安装时指定代码为utf-8 peewe ...

  2. 牛客练习赛61 相似的子串(二分+Hash)

    题面在此 题解:将字符串分成k部分,然后求最长前缀,所以我们只关注前缀部分就好了,公共前缀后边的是啥不用管,那么问题就转化成了是否存在k个不相交的字符串的最长公共前缀问题.首先用Hash来记录一下字符 ...

  3. C - Dr. Evil Underscores CodeForces - 1285D 二进制

    题目大意:n个数,任意整数x对这n个数取异或值,然后使最大值最小. 思路:数据范围最大为pow(2,30);所以考虑二进制的话,最多有30位.对于某一位d,然后考虑数组v中每一个元素的d为是0还是1, ...

  4. Treasure Island DFS +存图

    All of us love treasures, right? That's why young Vasya is heading for a Treasure Island. Treasure I ...

  5. adb命令查看手机应用内存使用情况

    adb shell回车 一.procrank VSS >= RSS >= PSS >= USSVSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)是单个 ...

  6. 数值计算方法实验之Newton 多项式插值(MATLAB代码)

    一.实验目的 在己知f(x),x∈[a,b]的表达式,但函数值不便计算或不知f(x),x∈[a,b]而又需要给出其在[a,b]上的值时,按插值原则f(xi)=yi (i=0,1,……, n)求出简单函 ...

  7. python 携程asyncio 实现高并发示例2

    https://www.bilibili.com/video/BV1g7411k7MD?from=search&seid=13649975876676293013 import asyncio ...

  8. Spring Security 是如何在 Servlet 应用中执行的?

    Spring Security 是一个强大的认证和授权框架,它的使用方式也非常简单,但是要想真正理解它就需要花一时间来学习了,最近在学习 Spring Security 时有一些新的理解,特意记录下来 ...

  9. Java 自动拆箱 装箱 包装类的缓存问题--结合源码分析

    都0202 了 java 1.8 已经是主流 自动装箱 .拆箱已经很普遍使用了,那么有时候是不是会遇到坑呢? 我们先来看一段代码: public class TestWraperClass { pub ...

  10. Android-网页解析-gson的使用

    相对于较为传统的Json解析来说,google共享的开源Gson在解析速度和所使用的内存在有着明显的优势,虽然说阿里巴巴也提供了fastgson包,但是它跟Gson的处理速度大同小异,只是底层实现的原 ...