上一节,我们分析了Django的网络服务的启动流程,以及它如何通过网络层接收到数并将请求转发给Django Application。那么这一节,我们主要分析,当Request到达Django Application之后,Django内部的路由规则是如何将其导入到正确的View(视图)方法中的呢?
1、准备工作
- Python 3.5.2
- Django 2.1.2
- PyCharm 2018.2.1 (Professional Edition)
- 启动项目
[min:] ~/Desktop/python/Demo$ python manage.py runserver 0.0.0.0:8000
2、分析流程
django.core.handlers.base.BaseHandler#get_response
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request) # 重点关注!!
response._closable_objects.append(request)
# If the exception handler returns a TemplateResponse that has not
# been rendered, force it to be rendered.
if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
response = response.render()
if response.status_code >= 400:
log_response(
'%s: %s', response.reason_phrase, request.path,
response=response,
request=request,
)
return response
- 从Django源码分析--服务启动中,我们知道了当请求到达时,最后会调用到WSGIHandler的
__call__
方法,然后会执行response = self.get_response(request)
,即调用父类中的get_reponse
方法; - 在此方法中我们重点关注一下
response = self._middleware_chain(request)
,发现该属性来自于load_middleware
方法。
django.core.handlers.base.BaseHandler#load_middleware
def load_middleware(self):
"""
Populate middleware lists from settings.MIDDLEWARE.
Must be called after the environment is fixed (see __call__ in subclasses).
"""
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
handler = convert_exception_to_response(self._get_response)
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path) # 通过类似反射的方法获得对象实例;
try:
mw_instance = middleware(handler) # 调用MiddlewareMixin的__init__方法;
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue
########省 略######
handler = convert_exception_to_response(mw_instance)
# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler
class MiddlewareMixin:
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
# 这步比较关键,决定了会按照MIDDLEWARE中的顺序依次倒序执行;
response = response or self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
-
这个方法就很重要,如果大家有看过我们的上一个章节,就会知道这个方法是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
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
# 获得Django App的Root URLResolver
if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver()
# 根据URL完成匹配,返回一个ResolverMatch示例
resolver_match = resolver.resolve(request.path_info)
# 分解ResolverMatch示例,得到对应的view方法,即callback
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
if response is None:
wrapped_callback = self.make_view_atomic(callback)
try:
# 重要,在此处完成了对view方法对执行;
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
response = self.process_exception_by_middleware(e, request)
# Complain if the view returned None (a common error).
if response is None:
if isinstance(callback, types.FunctionType): # FBV
view_name = callback.__name__
else: # CBV
view_name = callback.__class__.__name__ + '.__call__'
raise ValueError(
"The view %s.%s didn't return an HttpResponse object. It "
"returned None instead." % (callback.__module__, view_name)
)
# If the response supports deferred rendering, apply template
# response middleware and then render the response
elif hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# Complain if the template response middleware returned None (a common error).
if response is None:
raise ValueError(
"%s.process_template_response didn't return an "
"HttpResponse object. It returned None instead."
% (middleware_method.__self__.__class__.__name__)
)
try:
response = response.render()
except Exception as e:
response = self.process_exception_by_middleware(e, request)
return response
- 这个方法是整个路由分发系统的核心,可以看到就是在这个方法,request对应的视图方法被匹配到,执行并返回结果,即URLResolver根据path info匹配到正确的ResolverMatch,然后调用其中的view方法,并返回respone,具体的过程看上述代码注释,接下来我们进入详细分析;
- 还有一个需要说明的就是在这个方法中判断respone中有没有render,有就调用;
@functools.lru_cache(maxsize=None)
def get_resolver(urlconf=None):
if urlconf is None
urlconf = settings.ROOT_URLCONF
return URLResolver(RegexPattern(r'^/'), urlconf)
tutorial/settings.py
ROOT_URLCONF = 'tutorial.urls'
tutorial/urls.py
urlpatterns = [
url(r'^', include('snippets.urls')),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
url(r'^schema/$', schema_view),
url(r'^docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION))
]
- 通过
get_resolver
方法,完成了对settings.ROOT_URLCONF关联的整个路由系统的加载,返回一个URLResolver
实例; - urlpatterns其实就是一个URLPattern和URLResolver的集合列表,包含inclue的为URLResolver,直接跟着一个view方法的则是URLPattern;
django.urls.conf._path
def _path(route, view, kwargs=None, name=None, Pattern=None):
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,
)
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().')
- 这个方法其实就是
url(r'^schema/$', schema_view)
追溯过去的,解释了上面的问题:什么情况是URLPattern和什么情况下是URLResolver;
class URLPattern:
def __init__(self, pattern, callback, default_args=None, name=None):
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): # 负责完成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)
- 每个URLPattern都需要指定如下几个内容:
- 一个正则表达式字符串。
- 一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串。
- 可选的要传递给视图函数的默认参数(字典形式)。
- 一个可选的name参数。
- 如果有include,则递归生成上面的模式;
-
resolve(self, path)
负责完成path的匹配,并返回匹配的视图方法;
class URLResolver:
def __init__(self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
self.pattern = pattern
# urlconf_name is the dotted Python path to the module defining
# urlpatterns. It may also be an object with an urlpatterns attribute
# or urlpatterns itself.
self.urlconf_name = urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
self.namespace = namespace
self.app_name = app_name
self._reverse_dict = {}
self._namespace_dict = {}
self._app_dict = {}
# set of dotted paths to all functions and classes that are used in
# urlpatterns
self._callback_strs = set()
self._populated = False
self._local = threading.local()
################################省 略################################
def resolve(self, path):
path = str(path) # path may be a reverse_lazy object
tried = []
match = self.pattern.match(path)
if match:
new_path, args, kwargs = match
for pattern in self.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
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})
@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
-
一个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
方法
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,
)匹配结果(Resolver Match)
-
匹配结果是指当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。
网友评论