1Django是如何处理请求的当用户请求Django站点的一个页面时,下面是Django系统用来决定执行哪些Python代码的算法:Django判断使用rootURLconf模块。通常,这是由ROOT_URLCONF设置的值(即设置中的ROOT_URLCONF),但如果传入的HttpRequest对象具有urlconf属性(由中间件设置),则将使用其值而不是ROOT_URLCONF设置。逻辑可以在django/core/handlers/base.py中找到。类BaseHandler:...def_get_response(self,request):...ifhasattr(request,'urlconf'):urlconf=request.urlconfset_urlconf(urlconf)resolver=get_resolver(urlconf)else:resolver=get_resolver()Django加载Python模块并查找可用的urlpatterns。它是一系列django.urls.path()和/或django.urls.re_path()实例。其实我们写的url.pyDjango会依次遍历每一个URL模式,然后在请求的URL匹配到第一个模式后停止,与path_info进行匹配。这是路由匹配的关键,相关逻辑在django/urls/resolvers.py中。其中有几个重要的概念,如RegexPattern、RoutePattern、URLPattern、URLResolver。其中,URLResolver有嵌套逻辑,下面会详细介绍。一旦URL匹配,Django导入并调用关联的视图,这是一个Python函数(或基于类的视图)。如果匹配成功,将返回一个ResolverMatch对象。如果没有匹配到URL,或者在匹配过程中出现异常,Django将调用适当的错误处理视图。本文详细介绍2和3,即urlpatterns的相关概念和路由匹配的过程。2URL配置文件在Django2之后通常使用path/re_path来设置路由,还需要一个特殊的方法include。path:fornormalpathre_path:forregularpathinclude:importasub-urlconfigurationfileasfollowingexample:urlpatterns=[path('index/',views.index),#normalpathre_path(r'^articles/([0-9]{4})/$',views.articles),#常规路径path("app01/",include("app01.urls")),]以上配置文件,设置了3个urlpatterns,都是普通的路径index/和视图函数views.index,常规路径^articles/([0-9]{4})/$绑定到视图函数views.articles。app01/绑定到app01.urls,app01.urls不是视图函数,而是子模块的urlpatterns。可以看到urlpattern可以绑定一个url到一个视图函数,或者绑定一个子urlpattern。2.1path、re_path在django/urls/conf.py中定义了几个设置路由的函数。definclude(arg,namespace=None):...return(urlconf_module,app_name,namespace)def_path(route,view,kwargs=None,name=None,Pattern=None):ifisinstance(view,(列表,元组)):#用于include(...)处理。pattern=Pattern(route,is_endpoint=False)urlconf_module,app_name,namespace=view返回URLResolver(pattern,urlconf_module,kwargs,app_name=app_name,namespace=namespace,)elifcallable(view):pattern=Pattern(route,name=name,is_endpoint=True)returnURLPattern(pattern,view,kwargs,name)else:raiseTypeError('viewmustbeacallableoralist/tupleinthecaseofinclude().')path=partial(_path,Pattern=RoutePattern)re_path=partial(_path,Pattern=RegexPattern)首先看path和re_path。这两个函数分别被functools下的partial封装。partial的作用很简单,就是固定一个函数的一些参数,返回一个新的函数。详细文档可以在部分文档中找到。这样一来,path和re_path就不难理解了,它们是绑定不同Pattern参数的_path函数。进一步查看_path的内部逻辑。如果第一个分支绑定到列表或元组,请使用URLResolver来解析它。其实这时候include就是用来定义urlpattern的。在另一种情况下,如果绑定视图是可调用的,则使用URLPattern来解析它。URLPattern中的pattern参数根据path/re_path方法分别对应RoutePattern/RegexPattern。2.2includedefinclude(arg,namespace=None):...ifisinstance(urlconf_module,str):urlconf_module=import_module(urlconf_module)patterns=getattr(urlconf_module,'urlpatterns',urlconf_module)app_name=getattr(urlconf_module,'app_name',app_name)...return(urlconf_module,app_name,namespace)include方法所做的就是通过import_module导入定义的url模块。返回由子urlconf模块、app_name、namespace命名空间组成的元组。回到上面_path中的第一个分支。将此元组中的参数代入URLResolver并返回。3URLPattern和URLResolver3.1上面提到的URLPattern,如果url定义中的binding是一个视图,可以直接调用。也就是直接使用URLPattern来解析。classURLPattern:def__init__(self,pattern,callback,default_args=None,name=None):#需要匹配的urlpattern,这里是根据path或re_path的RoutePattern或RegexPattern实例self.pattern=patternself.callback=callback#viewself.default_args=default_argsor{}self.name=name...defresolve(self,path):调用RoutePattern或RegexPattern实例中的match方法进行匹配(注意这里不是match在re模块中)match=self.pattern.match(path)ifmatch:new_path,args,kwargs=match#将任何extra_kwargs作为**kwargs.kwargs.update(self.default_args)#Match返回`ResolverMatch`returnResolverMatch(self.callback,args,kwargs,self.pattern.name,route=str(self.pattern))...初始化URLPattern时,pattern是用path/re_path分别对应RoutePattern或RegexPattern。其实就是指定匹配模式是普通路由还是常规路由。3.2URLResolverURLResolver源码的核心是resolve函数,就是传入一个路径进行匹配。classURLResolver:defresolve(self,path):path=str(path)#path可能是一个reverse_lazy对象tried=[]#匹配路径match=self.pattern.match(path)ifmatch:new_path,args,kwargs=match#如果匹配成功,继续匹配它的url_patternsforpatterninself.url_patterns:try:#这个pattern可能是URLPattern或者URLResolver;如果是URLPattern,匹配成功则返回ResolverMatch;如果是URLResolver,就会递归调用下来。sub_match=pattern.resolve(new_path)...else:ifsub_match:...#匹配成功返回ResolverMatchreturnResolverMatch(sub_match.func,sub_match_args,sub_match_dict,sub_match.url_name,[self.app_name]+sub_match.app_names,[self.namespace]+sub_match.namespaces,self._join_route(current_route,sub_match.route),)tried.append([pattern])raiseResolver404({'tried':tried,'path':new_path})raiseResolver404({'path':path})URLResolver的关键逻辑在模式匹配过程中。如果模式与URLPattern匹配成功,则直接返回ResolverMatch。如果是另一个URLResolver,则实现递归调用。Django通过这个URLResolver实现了多级URL配置。4总结Django路由匹配有几个核心概念path/re_path/include、RegexPattern/RoutePattern、URLPattern/URLResolver。首先用partial封装了_path,绑定了一个模式匹配模式(RegexPattern/RoutePattern),后面会多次使用。然后根据视图是元组还是可调用的视图函数,分别使用URLResolver和URLPattern进行解析。两个类解析后返回给ResolverMatch,ResolverMatch会回调匹配成功的结果(view和args等)。本文从全局的角度大致讲解了Django路由的匹配过程,后面会详细讲解一些关键点。
