本文环境python3.5.2,django1.10.x系列

1.根据上一篇文章分析了,django-admin startproject与startapp的分析流程后,根据django的官方实例此时编写好了基本的路由和相应的处理函数,此时需要调试我们写的接口此时本地调试,django框架提供了python manage.py runserver 命令来本地调试。
2.runserver的特点是启用多线程处理请求,并可以监控当文件修改后自动重启服务,以达到服务重启极大方便了本地的调试,根据官方文档该命令只用于本地调试,不简易部署到生成环境。
3.当生成项目后,找到manage.py文件,其实代码与django-admin代码一样,都是调用django注册的方法。
我们直接分析runserver的命令位于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
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_address = (addr, port) # 服务监听的地址和端口
if threading: # 如果是多线程运行
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, 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) # 设置服务类的处理handler
httpd.serve_forever()

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

django源码分析——本地runserver分析的更多相关文章

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

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

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

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

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

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

  4. ArrayList源码和多线程安全问题分析

    1.ArrayList源码和多线程安全问题分析 在分析ArrayList线程安全问题之前,我们线对此类的源码进行分析,找出可能出现线程安全问题的地方,然后代码进行验证和分析. 1.1 数据结构 Arr ...

  5. Okhttp3源码解析(3)-Call分析(整体流程)

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  6. Okhttp3源码解析(2)-Request分析

    ### 前言 前面我们讲了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源码解析(1)-OkHttpClient分析]( ...

  7. Spring mvc之源码 handlerMapping和handlerAdapter分析

    Spring mvc之源码 handlerMapping和handlerAdapter分析 本篇并不是具体分析Spring mvc,所以好多细节都是一笔带过,主要是带大家梳理一下整个Spring mv ...

  8. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  9. ThreadLocal源码及相关问题分析

    前言 在高并发的环境下,当我们使用一个公共的变量时如果不加锁会出现并发问题,例如SimpleDateFormat,但是加锁的话会影响性能,对于这种情况我们可以使用ThreadLocal.ThreadL ...

随机推荐

  1. matplotlib中的基本概念

    有外语基础的朋友看这里: matplotlib官方文档 Figure(图像): 组成部分

  2. Redis学习一:Redis两种持久化机制

    申明 本文章首发自本人公众号:壹枝花算不算浪漫,如若转载请标明来源! 感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫 22.jpg 前言 Redis是基于内存来实现的NO SQL数据库,但是我么你都 ...

  3. AJ整理问题之:copy,对象自定义copy 什么是property

    AJ分享,必须精品 copy copy的正目的 copy 目的:建立一个副本,彼此修改,各不干扰 Copy(不可变)和MutableCopy(可变)针对Foundation框架的数据类型. 对于自定义 ...

  4. stand up meeting 12/4/2015 -12/6/2015

    part 组员 今日工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云        ------  --- 数据库 朱玉影  等待张葳出关        0  foxit reader  6 ...

  5. F - What Is Your Grade?

    “Point, point, life of student!” This is a ballad(歌谣)well known in colleges, and you must care about ...

  6. mongodb的远程连接和配置(阿里ECS)

    1.) 首先安装mongodb 2.)配置mongodb.conf bind_ip = 0.0.0.0 port= dbpath=/root/mongodb/mongodb-linux-x86_64- ...

  7. SSL 3.0 POODLE攻击信息泄露漏洞_CVE-2014-3566

    0x01 SSL3.0简介 我们知道最开始HTTP协议传输数据的时候,数据是不加密的,不安全的,网景公司针对此,推出了SSL(secure socket layer)安全套接层.SSL3.0时,IET ...

  8. GIT生成ssh(window7系统)——git工具篇

    由于本地git仓库和github仓库直接的传递需要ssh加密的,所以必须要生成ssh,下面是具体的操作步骤: 1.打开GIT命令行,输入命令:ssh-keygen -t rsa -C "yo ...

  9. TensorFlow keras dropout层

    # 建立神经网络模型 model = keras.Sequential([ keras.layers.Flatten(input_shape=(28, 28)), # 将输入数据的形状进行修改成神经网 ...

  10. Asp.Net Core 3.1 学习3、Web Api 中基于JWT的token验证及Swagger使用

    1.初始JWT 1.1.JWT原理 JWT(JSON Web Token)是目前最流行的跨域身份验证解决方案,他的优势就在于服务器不用存token便于分布式开发,给APP提供数据用于前后端分离的项目. ...