django源码分析

本文环境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 = Truedefault_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是否支持ipv6raise CommandError('Your Python does not support IPv6.')self._raw_ipv6 = Falseif not options['addrport']:                                                             # 如果输入参数中没有输入端口则使用默认的端口self.addr = ''self.port = self.default_portelse: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 = Trueself._raw_ipv6 = Trueelif 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_ipv6self.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,默认返回wsgirun(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 threados._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_reloaderelse: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:passelse: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为trueexit_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 reloadelif change == I18N_MODIFIED:                                       # 监控是否是本地字符集的修改reset_translations()time.sleep(1)                                                       # 休眠1秒钟

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

def code_changed():global _mtimes, _winfor filename in gen_filenames():stat = os.stat(filename)                                                        # 获取每个文件的状态属性mtime = stat.st_mtime                                                           # 获取数据最后的修改时间if _win:mtime -= stat.st_ctimeif filename not in _mtimes:_mtimes[filename] = mtime                                                   # 如果全局变量中没有改文件则存入,该文件的最后修改时间continueif mtime != _mtimes[filename]:                                                  # 如果已经存入的文件的最后修改时间与当前获取文件的最后修改时间不一致则重置保存最后修改时间变量_mtimes = {}try:del _error_files[_error_files.index(filename)]except ValueError:passreturn 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 translationfiles."""# 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 iteratingglobal _cached_modules, _cached_filenamesmodule_values = set(sys.modules.values())                                       # 获取模块的所有文件路径_cached_filenames = clean_files(_cached_filenames)                              # 检查缓存的文件列表if _cached_modules == module_values:                                            # 判断所有模块是否与缓存一致# No changes in module list, short-circuit the functionif only_new:return []else:return _cached_filenames + clean_files(_error_files)new_modules = module_values - _cached_modulesnew_filenames = clean_files([filename.__file__ for filename in new_modulesif 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'))         # 添加项目目录下的localebasedirs.extend(settings.LOCALE_PATHS)basedirs = [os.path.abspath(basedir) for basedir in basedirsif 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:                                                            continueif 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,默认返回wsgirun(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 = WSGIServerhttpd = 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)                                                                 # 设置服务类的处理handlerhttpd.serve_forever()  

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

Django源码分析2:本地运行runserver分析相关推荐

  1. Django源码分析7:migrate命令的浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-migrate命令分析 Django项目中提供了,通过migrations操作数据库的结构的命 ...

  2. Django源码分析3:处理请求wsgi分析与视图View

    django源码分析 本文环境python3.5.2,django1.10.x系列 根据前上一篇runserver的博文,已经分析了本地调试服务器的大致流程,现在我们来分析一下当runserver运行 ...

  3. Django源码分析4:staticfiles静态文件处理中间件分析

    django源码分析 本文环境python3.5.2,django1.10.x系列1.在上一篇文章中已经分析过handler的处理过程,其中load_middleware就是将配置的中间件进行初始化, ...

  4. Django源码分析9:model.py表结构的初始化概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-model概述 Django项目中提供了内置的orm框架,只需要在models.py文件中添加 ...

  5. Django源码分析8:单元测试test命令浅析

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-test命令分析 Django项目中提供了,test命令行命令来执行django的单元测试,该 ...

  6. Django源码分析10:makemigrations命令概述

    django源码分析 本文环境python3.5.2,django1.10.x系列 django源码分析-makemigrations命令概述 Django项目中的数据库管理命令就是通过makemig ...

  7. Django源码分析6:auth认证及登陆保持

    django源码分析 本文环境python3.5.2,django1.10.x系列 1.这次分析django框架中登陆认证与接口权限检查. 2.在后端开发中,难免会对接口进行权限验证,其中对于接口是否 ...

  8. Django源码分析5:session会话中间件分析

    django源码分析 本文环境python3.5.2,django1.10.x系列 1.这次分析django框架中的会话中间件. 2.会话保持是目前框架都支持的一个功能,因为http是无状态协议,无法 ...

  9. django源码简析——后台程序入口

    django源码简析--后台程序入口 这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结.工作中主 ...

最新文章

  1. java关闭inputstream_关闭Java InputStreams
  2. 批创建清账凭证,POSTING_INTERFACE_CLEARING
  3. vmware--查看链接克隆依赖关系
  4. 高效能码农的自我修养:5本书教你怎样科学学习,拒绝无用功
  5. C# 联合查询_c# 之linq——小白入门级
  6. python 括号 垃圾_Python 为什么抛弃累赘的花括号,使用缩进来划分代码块?
  7. mac m1 nvm 安装node版本失败
  8. 分布式唯一id生成器的想法
  9. '_thread._local' object has no attribute 'value'
  10. 都柏林理工学院计算机,入爱尔兰都柏林理工大学,硕博连读全免费
  11. Jtopo对象属性大全
  12. 大学生计算机应用大赛广告设计,第11届全国大学生计算机应用能力与信息素养大赛 “平面视觉设计” 赛项圆满结束...
  13. 银行简历计算机等级填错了,填错这几点,银行实习简历通过率几乎为0!
  14. Java8中关于LocalDateTime转换方法总结
  15. 有了这个抠图滤镜,设计师再也不怕扣头发婚纱了!
  16. IDEA连接数据库自动生成model(get set方法)
  17. SeleniumWebDriver运行数据库测试?
  18. java liferay 框架_liferay6.2-生成布局模板
  19. SQL Server 数据库之收缩数据库和文件
  20. 虚电路与数据报网络学习小记

热门文章

  1. 机器人越像人越好?被机器人盯着会变『蠢』
  2. 自动驾驶中实时车道检测和警报
  3. 别找了,Thonny 才是 Python 小白的最理想的 IDE
  4. 今日头条李磊等最新论文:用于文本生成的核化贝叶斯Softmax
  5. 知乎算法团队负责人孙付伟:Graph Embedding在知乎的应用实践
  6. Java 11 正式发布!
  7. 开发者转型AI看过来,这是一场汇聚中美顶尖专家的AI盛会
  8. 技术干货 | 如何选择上班路线最省时间?从A/B测试数学原理说起
  9. 同事乱用 Redis 卡爆,我真是醉了...
  10. 面试官问:如果MySQL的自增 ID 用完了,怎么办?