1. 前言
上一篇中梳理了django中中间件的加载和调用逻辑,其实也是django应用的大部分逻辑了。其中,视图函数在process_view()
钩子函数调用且没有返回HttpResponse
对象后接着调用。那么我们怎么知道要调用哪个视图函数呢?这就需要从urls.py
中找到与URL对应的路由,根据路由找到应该调用的视图函数返回HttpResponse
响应。
2. URL解析
URL解析的工作是在WSGIHandler
类中进行的,该类是BaseHandler
的子类。
# https://github.com/django/django/blob/master/django/core/handlers/base.py
class BaseHandler:
......省略部分代码......
def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template_response middleware. This method is everything that happens
inside the request/response middleware.
"""
response = None
if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver() # URLResolver实例对象
resolver_match = resolver.resolve(request.path_info) # 将请求的URL转换为视图函数
callback, callback_args, callback_kwargs = resolver_match # 视图函数,位置参数,关键字参数
request.resolver_match = resolver_match
......省略部分代码......
return response
首先,让我们看看看看get_resolver()
。get_resolver()
的返回值是通过调用_get_cached_resolver()
方法得到的,是一个URLResolver
实例对象。
# https://github.com/django/django/blob/master/django/urls/resolvers.py
def get_resolver(urlconf=None):
if urlconf is None:
urlconf = settings.ROOT_URLCONF # 配置文件中ROOT_URLCONF,如'myweb.urls'
return _get_cached_resolver(urlconf)
@functools.lru_cache(maxsize=None)
def _get_cached_resolver(urlconf=None):
return URLResolver(RegexPattern(r'^/'), urlconf)
接下来让我们看看URLResolver
中的resolve()
方法。
# https://github.com/django/django/blob/master/django/urls/resolvers.py
def resolve(self, path):
path = str(path) # path may be a reverse_lazy object
tried = []
match = self.pattern.match(path) # self.pattern是RegexPattern实例对象
if match:
new_path, args, kwargs = match
for pattern in self.url_patterns: # 调用url_patterns属性方法
try:
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:
# 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
current_route = '' if isinstance(pattern, URLPattern) else str(pattern.pattern)
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,
self._join_route(current_route, sub_match.route),
)
tried.append([pattern])
raise Resolver404({'tried': tried, 'path': new_path})
raise Resolver404({'path': path})
首先,通过self.pattern.match()
从URL中解析出URL中传递的位置参数和关键字参数。
然后,调用self.url_patterns()
方法获得对应的urls.py中的路由,也就是urlpatterns
变量的值(URLPattern
对象构成的list)。
# https://github.com/django/django/blob/master/django/urls/resolvers.py
@cached_property
def urlconf_module(self):
if isinstance(self.urlconf_name, str):
return import_module(self.urlconf_name)
else:
return self.urlconf_name
@cached_property
def url_patterns(self):
# urlconf_module might be a valid set of patterns, so we default to it
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
try:
iter(patterns)
except TypeError:
msg = (
"The included URLconf '{name}' does not appear to have any "
"patterns in it. If you see valid patterns in the file then "
"the issue is probably caused by a circular import."
)
raise ImproperlyConfigured(msg.format(name=self.urlconf_name))
return patterns
最后,获得urls.py
中urlpatterns变量中URLPattern
实例对象,依次调用URLPattern
实例对象的resolve()
方法去匹配URL,直到第一次匹配成功,返回值是ResolverMatch
实例对象。ResolverMatch
实例对象包含了URL所匹配到的视图函数,位置参数,关键字参数等信息。
# https://github.com/django/django/blob/master/django/urls/resolvers.py
def resolve(self, path):
match = self.pattern.match(path)
if match:
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, route=str(self.pattern))
疑问?
但是,我有个疑问,从源码中能够看到
callback, callback_args, callback_kwargs = resolver_match
其中的resolver_match
是一个ResolverMatch
的实例对象。这是如何解包的呢?
自问自答,答案就是__getitem__()
方法了。
# https://github.com/django/django/blob/master/django/urls/resolvers.py
# ResolverMatch类中的__getitem__()方法
def __getitem__(self, index):
return (self.func, self.args, self.kwargs)[index]
3. 总结
WSGIHandler
的实例对象其实就是一个WSGI接口,就像廖雪峰老师的博客中所说的application
(web应用),功能就是根据request生成response。
解析URL用到了URLResolver
实例对象,调用URLResolver
实例对象的resolve解析URL。从对应的urls.py
文件中获得urlpatterns
中的那些路由,根据这些路由依次去匹配request中的URL,将匹配到的第一个ResolverMatch
实例对象返回给WSGIHandler
。
WSGIHandler
从返回的ResolverMatch
实例对象中获得该URL所对应的视图函数、URL中匹配出的位置参数、关键字参数,然后就是调用process_view()
钩子,视图函数,process_template_response()
钩子,process_exception()
钩子,process_response()
钩子balabala,最后生成response响应,并传递给WSGI服务器。