1 django的url路由分发原理 作用:解析请求的url,匹配找到对应的view函数来处理。
2 分析 2.1 项目默认会带有setting文件,它会有ROOT_URLCONF,而ROOT_URLCONF会指向一个默认的url配置文件。 样例:
urlpatterns = [ path('admin/', admin.site.urls), path('blog/', views.page), ]
2.2 源码 # django/urls/conf.py
def _path(route, view, kwargs=None, name=None, Pattern=None): # 这里做了分支处理,如果是一个list或者tuple,就用URLResolver处理 if isinstance(view, (list, tuple)): # For include(...) processing. pattern = Pattern(route, is_endpoint=False) urlconf_module, app_name, namespace = view return URLResolver( pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace, ) # 如果是一个正常的可调用的view函数,则用URLPattern处理 elif callable(view): pattern = Pattern(route, name=name, is_endpoint=True) return URLPattern(pattern, view, kwargs, name) else: raise TypeError('view must be a callable or a list/tuple in the case of include().')
path = partial(_path, Pattern=RoutePattern)
分析: 当是一个正常的可调用的view函数的时候,就会当成URLPattern处理;但是如果是一个list或者tuple,则会当成URLResolver继续处理。那么继续看下URLPattern和URLResolver
2.3 # django/urls/resolvers.py
class URLPattern: def __init__(self, pattern, callback, default_args=None, name=None): # 需要匹配的urlpattern self.pattern = pattern # 对应的回调函数 self.callback = callback # the view # 参数等 self.default_args = default_args or {} self.name = name
def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.pattern.describe())
...
def resolve(self, path): match = self.pattern.match(path) if match: # 如果匹配成功,则返回一个ResolverMatch new_path, args, kwargs = match # Pass any extra_kwargs as **kwargs. kwargs.update(self.default_args) return ResolverMatch(self.callback, args, kwargs, self.pattern.name)
分析: URLPattern包括根据正则表达式字符串生成的pattern对象,一个可调用的对象,以及一些参数。所以很明显,对应的就是url配置文件里面配置了可调用view函数或者对象的那些记录。这里面当匹配成功后,会返回ResolverMatch,定义如下:
2.4 # django/urls/resolvers.py
class ResolverMatch: def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None): self.func = func self.args = args self.kwargs = kwargs self.url_name = url_name
# If a URLRegexResolver doesn't have a namespace or app_name, it passes # in an empty value. self.app_names = [x for x in app_names if x] if app_names else [] self.app_name = ':'.join(self.app_names) self.namespaces = [x for x in namespaces if x] if namespaces else [] self.namespace = ':'.join(self.namespaces)
if not hasattr(func, '__name__'): # A class-based view self._func_path = func.__class__.__module__ + '.' + func.__class__.__name__ else: # A function-based view self._func_path = func.__module__ + '.' + func.__name__
view_path = url_name or self._func_path self.view_name = ':'.join(self.namespaces + [view_path])
# 可以获取回调函数,或者参数 def __getitem__(self, index): return (self.func, self.args, self.kwargs)[index]
def __repr__(self): return "ResolverMatch(func=%s, args=%s, kwargs=%s, url_name=%s, app_names=%s, namespaces=%s)" % ( self._func_path, self.args, self.kwargs, self.url_name, self.app_names, self.namespaces, ) 分析: ResolverMatch就是匹配成功后返回的结果。它包含了可调用对象。通常是视图函数;参数等信息。以及因为实现了__getitem__方法,所以可以很方便的回调函数参数等信息。 最后还剩下URLResolver
2.5 # django/urls/resolvers.py class URLResolver: ... def resolve(self, path): path = str(path) # path may be a reverse_lazy object tried = [] # 匹配path match = self.pattern.match(path) if match: new_path, args, kwargs = match # 如果匹配成功,则继续匹配它的url_patterns for pattern in self.url_patterns: try: # 这里比较关键,这个pattern可能是urlpattern,也可能是URLResolver;如果是urlpattern,匹配成功则返回ResolverMatch;如果是URLResolver,则会递归调用下去。 sub_match = pattern.resolve(new_path) except Resolver404 as e: sub_tried = e.args[0].get('tried') if sub_tried is not None: tried.extend([pattern] + t for t in sub_tried) else: tried.append([pattern]) else: if sub_match: # 匹配成功,生成ResolverMatch返回。 # Merge captured arguments in match with submatch sub_match_dict = {**kwargs, **self.default_kwargs} # Update the sub_match_dict with the kwargs from the sub_match. sub_match_dict.update(sub_match.kwargs) # If there are *any* named groups, ignore all non-named groups. # Otherwise, pass all non-named arguments as positional arguments. sub_match_args = sub_match.args if not sub_match_dict: sub_match_args = args + sub_match.args return ResolverMatch( 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, ) tried.append([pattern]) raise Resolver404({'tried': tried, 'path': new_path}) raise Resolver404({'path': path}) ... 分析: 最核心的就是看resolve方法。这里面在遍历url_patterns的时候,这里面的pattern可能是urlpattern,也可能是URLResolver;如果是urlpattern,匹配成功则返回ResolverMatch;如果是URLResolver,则会递归调用下去。所以这就可以解释为什么可以进行多级的url配置。
3 总结 ResolverMatch就是匹配结果,包含匹配成功后需要的信息; URLPattern是一条url映射信息的对象,包含了url映射对应的可调用对象等信息; URLResolver是实现url路由,解析url的关键的地方,它的url_patterns既可以是URLPattern也可以是URLResolver。正是因为这种设计, 实现了对URL的层级解析。
参考: https://blog.csdn.net/bbwangj/article/details/79935500
