上一节,我们分析了Django的网络服务的启动流程,以及它如何通过网络层接收到数并将请求转发给Django Application。那么这一节,我们主要分析,当Request到达Django Application之后,Django内部的路由规则是如何将其导入到正确的View(视图)方法中的呢?
1、准备工作
- Python 3.5.2
- Django 2.1.2
- PyCharm 2018.2.1 (Professional Edition)
- 启动项目
1 | [min:] ~/Desktop/python/Demo$ python manage.py runserver 0.0.0.0:8000 |
2、分析流程
django.core.handlers.base.BaseHandler#get_response
1 | def get_response(self, request): |
- 从Django源码分析–服务启动中,我们知道了当请求到达时,最后会调用到WSGIHandler的
__call__
方法,然后会执行response = self.get_response(request)
,即调用父类中的get_reponse
方法; - 在此方法中我们重点关注一下
response = self._middleware_chain(request)
,发现该属性来自于load_middleware
方法。
django.core.handlers.base.BaseHandler#load_middleware
1 | def load_middleware(self): |
1 | class MiddlewareMixin: |
这个方法就很重要,如果大家有看过我们的上一个章节,就会知道这个方法是WSGIHandler的
__init__
方法中调用的,也就是在执行runserver命令的时候就执行的;convert_exception_to_response
是一个装饰器,通过这装饰器和MiddlewareMixin方法结合,使得最后的handler实例可以依次倒序执行 settings.MIDDLEWARE中配置的所有的中间件方法,最后执行
_get_response
方法!备注:这块比较考察对装饰器的理解,需要好好琢磨一下,关键点在于理解在于MiddlewareMixin的get_response对应的就是类似于SecurityMiddleware的实例,所以每次
__call__
方法中response = response or self.get_response(request)
就相当于重新调用了新的Middleware的__call__
;
django.core.handlers.base.BaseHandler#_get_response
1 | def _get_response(self, request): |
- 这个方法是整个路由分发系统的核心,可以看到就是在这个方法,request对应的视图方法被匹配到,执行并返回结果,即URLResolver根据path info匹配到正确的ResolverMatch,然后调用其中的view方法,并返回respone,具体的过程看上述代码注释,接下来我们进入详细分析;
- 还有一个需要说明的就是在这个方法中判断respone中有没有render,有就调用;
1
2
3
4
5
def get_resolver(urlconf=None):
if urlconf is None
urlconf = settings.ROOT_URLCONF
return URLResolver(RegexPattern(r'^/'), urlconf)
tutorial/settings.py
1 | ROOT_URLCONF = 'tutorial.urls' |
tutorial/urls.py
1 | urlpatterns = [ |
- 通过
get_resolver
方法,完成了对settings.ROOT_URLCONF关联的整个路由系统的加载,返回一个URLResolver
实例; - urlpatterns其实就是一个URLPattern和URLResolver的集合列表,包含inclue的为URLResolver,直接跟着一个view方法的则是URLPattern;
django.urls.conf._path
1 | def _path(route, view, kwargs=None, name=None, Pattern=None): |
- 这个方法其实就是
url(r'^schema/$', schema_view)
追溯过去的,解释了上面的问题:什么情况是URLPattern和什么情况下是URLResolver;
1 | class URLPattern: |
- 每个URLPattern都需要指定如下几个内容:
- 一个正则表达式字符串。
- 一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串。
- 可选的要传递给视图函数的默认参数(字典形式)。
- 一个可选的name参数。
- 如果有include,则递归生成上面的模式;
resolve(self, path)
负责完成path的匹配,并返回匹配的视图方法;
1 | class URLResolver: |
一个URL Resolve可以包含多个URL Pattern,也可以包含多个其他URL Resolve。 通过这种包含结构设计,实现Django对URL的层级解析,也是Django实现app与项目解耦的关键。通常由include方法操作的URL配置模块,最终会被解释成为URL分解器。
每个URL分解器都包含两个重要的变量:
- 一个正则表达式字符串。URL开始部分是否匹配正则表达式,如匹配,去除成功匹配部分后余下部分匹配包含的URL模式和URL分解器。
- URL配置模块名或URL配置模块的引用。
resolve(self, path)
在这个方法开始遍历URLResolve,直到获得正确匹配的URLPattern,并调用其resolve
方法
1 | class ResolverMatch: |
匹配结果是指当URL被正确匹配时,需返回的匹配结果,匹配结果需指定以下几个内容:
- 一个可调用对象。通常是视图函数
- 视图函数参数。通常是URL模式中正则表达式命名组匹配的值
- 视图函数关键字参数。通常是url方法中设置传递给视图函数的参数(字典形式)
- 可选的URL名称参数。
- 可选的APP名称参数。
- 可选的命名空间参数。
用来表示匹配结果。ResolverMatch类实现了
__getitem__
方法,可以同元组操作一样,获取视图函数引用与视图函数参数,从而具备调用视图函数的条件。
3、总结
上面所有的过程都是Django的服务启动后,一个新的request达到后,经过怎么的流程到达对应view方法的步骤,简单做一个总结;
- 在启动Django服务的时候,会调用
load_middleware
来加载所有的中间键到BaseHandler的_middleware_chain
变量中,可以等同一个多层的装饰器; - 当一个新的请求到达后,会调用WSGIHandler的
__call__
方法,最终执行到BaseHandler中的get_reponse
方法; - 在
get_reponse
方法中调用了_middleware_chain
方法,即依次倒序执行中间件中的方法,最后执行到_get_response
方法; - 在这个方法,request对应的视图方法被匹配到,执行并返回结果,即URLResolver根据path info匹配到正确的ResolverMatch,然后调用其中的view方法,并返回respone。