全职技术开发外包2023年终复盘(二)django的启动流程是怎样的?

本文主要是介绍全职技术开发外包2023年终复盘(二)django的启动流程是怎样的?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文是2023年技术复盘文章之一。

复盘带来思考,通过思考取其精华,精进自我,提升赚钱能力。

平时对django的源码饶有兴趣,时不时翻看源码。本篇总结重在梳理django启动流程,了解在该过程中,到底发生了哪些事,用了哪些值得学习的方法。

注:生产环境和开发环境,仅入口文件及前面的某些流程有别而已,所以就以开发服务器启动的流程来分析。另外,我制作了一张启动的流程图,如想获取,移步文末看获取的方法。

第一步:入口文件启动

在上一篇django是如何加载settings的复盘中已有描述,manage.py会启动一个命令行管理工具类,命令行工具名为MangemenUtility类,执行它的execute方法会进入到启动流程。

django.core.management.__init__.py

def execute_from_command_line(argv=None):"""Run a ManagementUtility."""utility = ManagementUtility(argv)utility.execute()

第二步 :载入配置文件、载入django模块

这里不再赘述,可看上一篇文章django是如何加载settings的:

在载入配置文件后,会继续载入django的各模块,加载内置的和开发者自定义的模型类:

django.core.management.__init__.py

class ManagementUtility:# 省略其他代码def exexute(self):# 省略其他代码django.setup()# 省略其他代码# 省略其他代码

django.setup()感兴趣的可自行查看源码。

只要运行了django.setup(),就能在当前进程中使用django的模块了,这也是我们要在子进程中使用django时,需要使用该方法的原因。如:

在这里插入图片描述

当然,这个步骤还有不少琐碎之事,如:

  • 解析命令行参数和选项
  • 热重载时语法检查(闭包函数实现)

第三步:调用Command类运行命令

需要先行注明的是,该环节有点绕,涉及到三个类,分别是:

1、当前Command类(该步骤所说的当前类均指该类),位于:

django.contrib.staticfiles.management.commands.runserver文件中

2、父类Command类(子类和它同名,😳,该步骤所说的父类均指该类),位于:

django.core.management.commands.runserver文件中

3、祖父类BaseCommand该步骤所说的当祖父类均指该类),位于:

django.core.management.base文件中

走入这个绕脑袋的流程,就从self.fetch_command(subcommand).run_from_argv(self.argv)开始

class ManagementUtility:# 省略其他代码def exexute(self):# 省略其他代码django.setup()# 省略其他代码self.fetch_command(subcommand).run_from_argv(self.argv)# 省略其他代码

subcommand就是一个当前的Command类实例,由它去调用run_from_argv方法,这个方法,在祖父类中才有:

1、跳转到祖父类中的run_from_argv方法,在该方法中又执行了execute方法;

def run_from_argv(self, argv):# 省略其他代码try:self.execute(*args, **cmd_options)except CommandError as e:if options.traceback:raise# 省略其他代码

2、当前类中并没有execute方法,其父类中有,但也没干什么特别的事,又super().execute()跑回祖父类的execute方法了,

def execute(self, *args, **options):# 省略其他代码# 一灯注:检查迁移文件if self.requires_migrations_checks:self.check_migrations()# 一灯注:handle是个非常重要的节点的入口output = self.handle(*args, **options)# 一灯注:连接数据库if output:if self.output_transaction:connection = connections[options.get("database", DEFAULT_DB_ALIAS)]output = "%s\n%s\n%s" % (self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),output,self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),)self.stdout.write(output)return output

3、self.handle是个非常重要的节点入口,父类中声明了该方法必须在子类中重写,当前类并无handle方法,那么它一定在父类中实现了:

def handle(self, *args, **options):if not settings.DEBUG and not settings.ALLOWED_HOSTS:raise CommandError("You must set settings.ALLOWED_HOSTS if DEBUG is False.")# 省略其他代码self.run(**options)

4、self.run方法在当前类中也没有,在父类中有,self.run又迅速跳转到self.run_inner方法,同样的,该方法依然在父类中:

def inner_run(self, *args, **options):# 省略其他代码# 一灯注:检查迁移文件,和上面的检查迁移文件是二选一,即总会有一个节点是要检查的self.check_migrations()try:handler = self.get_handler(*args, **options)# 一灯注 run函数入参,传入了handlerrun(self.addr,int(self.port),handler,ipv6=self.use_ipv6,threading=threading,on_bind=self.on_bind,server_cls=self.server_cls,)except OSError as e:# 省略其他代码pass

这里是一个重要的分水岭,在该代码中,handler = self.get_handler(*args, **options)能获取到一个WSGIHandler的实例,它专门用来处理请求和响应的,即让请求和响应进入到django的内部中。既然是分水岭,那它到底分了什么呢?重点如下:

self.get_handler方法在当前类和父类都有,我们是通过当前类进来的,那么它当然会使用当前类的get_handler方法:

def get_handler(self, *args, **options):"""Return the static files serving handler wrapping the default handler,if static files should be served. Otherwise return the default handler."""handler = super().get_handler(*args, **options)use_static_handler = options["use_static_handler"]insecure_serving = options["insecure_serving"]if use_static_handler and (settings.DEBUG or insecure_serving):return StaticFilesHandler(handler)return handler

StaticFilesHandler的源码就不贴了,它的功能是,在开发服务器环境下,包装WSGIHandler类,增加了静态文件处理的方法。故当我们使用python manage.py runserver 启动开发服务器时,之所以能处理静态文件,关键点就在这了。请注意,在上面 run函数中,传入了StaticFilesHandler类实例,形参为:handler

再来说说,父类的get_handler方法,比当前类的更简单,它直接获取了一个WSGIHandler的实例,DEBUG = False时,会使用父类的get_handler,我们在开发服务器上设置DEBUG = False时会无法处理静态文件,样式文件js文件统统丢失。

注:

1、WSGIHandler__call__方法,上面的流程,获取到的都是它的类实例,这个类实例最后会交给底层服务器,由底层服务器调用实例,也就是调用__call__方法;

2、它在实例化时,自动加载一次中间件,并把所有中间件可调用的方法组合成一个中间件链(middleware_chain),供后面所使用。

再回头看看handler = self.get_handler(*args, **options)下方的run函数,它由外部文件引入,运行run方法后,启动流程就基本结束了。

第五步:运行run函数

该方法在django的服务器模块中:

def run(addr,port,wsgi_handler,ipv6=False,threading=False,on_bind=None,server_cls=WSGIServer,
):server_address = (addr, port)if threading:httpd_cls = type("WSGIServer", (socketserver.ThreadingMixIn, server_cls), {})else:httpd_cls = server_clshttpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)if on_bind is not None:on_bind(getattr(httpd, "server_port", port))if threading:httpd.daemon_threads = Truehttpd.set_app(wsgi_handler)httpd.serve_forever()

这一步,我们只关注重点:

1、httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6),启动了一个WSGIServer服务器,再往下看就追溯到底层了:socketserver服务;

2、httpd.set_app(wsgi_handler)WSGIHandler或子类StaticFilesHandler设置为底层服务socketserverapplication

3、在底层中,如果有请求,就会调用applicationapplicaiton(),把请求转交给WSGIHandler或子类StaticFilesHandler__call__方法;

4、在httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)中,入参WSGIRequestHandler,它继承与wsgiref,看源码功推测应该是把原始的请求 包装成django的规范,以便请求进入到WSGIHandler或子类StaticFilesHandler__call__方法时,就可以直接使用了,也就是我们在视图中经常接触到的HttpReqeustHttpReponse对象。

至此,整个启动流程就结束了。

题外话

WSGIHandler或子类StaticFilesHandler__call__方法是django处理请求响应的开始,从这里开始,流程如下:

  • 加载中间件
  • 控制请求逐一经过请求中间件
  • 解析url,获取视图
  • 请求经过视图,生成响应
  • 控制响应逐一经过中间件
  • 响应传递给底层服务器

这里就不展开了,内容真的很多。

总结

1、整个流程似乎没看到什么异步操作,毕竟上下文联系得很紧凑,完成一步才能走到下一步流程

2、OOP的继承特性发挥到极致了,但也带来一些问题,阅读源码真的不容易,除了看不懂的,思路跟着流程在各个子类、父类、祖父类之间上蹿下跳也是一个大挑战;

3、不过继承特性也有好处,至少在django的启动流程中,开发服务器的启动和生产服务器走的流程,使用继承特性来满足分支需求显得非常容易;

4、会想起刚接触编程没多久,强行将各种不同功能函数揉成一个类,而各种教程也非常喜欢使用PersonCar之类的案例来讲述类的作用,随着经验见长,不断地阅读优秀的代码之后,对类才有了一些体会,在上面的PersonCar的基础上,增加一点个人拙见:

一个类,旨在完成一项工作,一项工作需要很多细节,如果几项工作的细节雷同,那么可以抽象一个基类了,N项工作都能继承该基类,如果方式相同,但细节不同,也可以抽象基类,子类可实现各自的细节。

问题

WSGIhandler中,为何要设计一个__call__来处理请求和响应呢?普通类成员方法也是可调用的,只能靠猜测了:

1、可能底层服务要求入参的handler必须是一个类实例

2、该类实例必须可调用

最后

个人水平有限,不当之处还请指出,感激不尽。
另外,我也将上面的流程绘制成了流程图,需要的可在评论区留言。

这篇关于全职技术开发外包2023年终复盘(二)django的启动流程是怎样的?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/637158

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

SpringBoot项目是如何启动

启动步骤 概念 运行main方法,初始化SpringApplication 从spring.factories读取listener ApplicationContentInitializer运行run方法读取环境变量,配置信息创建SpringApplication上下文预初始化上下文,将启动类作为配置类进行读取调用 refresh 加载 IOC容器,加载所有的自动配置类,创建容器在这个过程