开始之前建议先参考一下这篇文章: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. String 对象-->substring() 方法

    1.定义和用法 substring() 方法用于提取两个指定下标之间的字符. substring() 方法返回的子串包括 开始 处的字符,但不包括 结束 处的字符 语法: string.substri ...

  2. Linq 学习——将List集合作为筛选条件查询数据

    例: A表是一个List集合,B表也是一个List集合 .A与B有一个共同的字段 RecognitionCarCode B表通过RecognitionCarCode去重后拿到两个值{'1','2'}记 ...

  3. tf.nn.softmax 分类

    tf.nn.softmax(logits,axis=None,name=None,dim=None) 参数: logits:一个非空的Tensor.必须是下列类型之一:half, float32,fl ...

  4. ConcurrentHashMap 同步安全 的真正含义(stringbuff 是同步安全的,stringbutter 不安全)

    同步安全的集合,在多线程下用到这个map是安全的,但这个安全指的是什么?线程安全指的是指get.remove.put等操作时即同一对象,同一时间只有一个线程能在这几个方法上运行,也就是说线程安全是在这 ...

  5. CentOS7安装JAVA环境

    安装JAVA环境我常用的有两种形式 1.下载tar包安装 2.下载rpm包直接安装 本篇内容就写这两种形式的安装方法: JAVA程序的下载地址:https://www.oracle.com/java/ ...

  6. AJ学IOS(06)UI之iOS热门游戏_超级猜图

    AJ分享,必须精品 先看效果图 思路 需求分析 1,搭建界面 1>上半部分,固定的,用Storyboard直接连线(OK) 2>下半部分,根据题目的变化,不断变化和调整,用代码方式实现比较 ...

  7. 怎么高效学习python?其实只需要这个方法,快速掌握不叫事儿

    很多人想学python,并且希望能快速高效的学习python,但一直都没有找到合适的方法,下面谈一下我的方法. 首先,高效入门python 怎么高效学习python?想要高效,就要先搞清楚你这个阶段, ...

  8. Keepalived实现Nginx负载均衡高可用

    第一章:keepalived介绍 VRRP协议 目的就是为了解决静态路由单点故障问题的 第二章: keepalived工作原理 2.1 作为系统网络服务的高可用功能(failover) keepali ...

  9. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(四)之Operators

    At the lowest level, data in Java is manipulated using operators Using Java Operators An operator ta ...

  10. Delphi Unicode转中文

    function UniCode2GB(S : String):String;Var I: Integer;beginI := Length(S);while I >=4 do begintry ...