上一篇分析了wsgi应用的实现以及中间件加载及调用顺序,wsgi应用在处理request时还涉及了路由以及视图处理等还未分析,今天分析一下路由以及视图相关的代码
class BaseHandler: _view_middleware = None _template_response_middleware = None _exception_middleware = None _middleware_chain = Nonedef 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 response.status_code >= 400: log_response( ‘%s: %s‘, response.reason_phrase, request.path, response=response, request=request, ) return 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 # 我的测试环境request对象没有urlconf属性,所以走的是else分支,当然我们可以在中间件中给urlconf赋值,那么此处将走if分支.暂时还不知道会有什么影响 if hasattr(request, ‘urlconf‘): urlconf = request.urlconf set_urlconf(urlconf) resolver = get_resolver(urlconf) else:
# 获取URLResolver对象,可以理解为路由解析器 resolver = get_resolver() # 解析请求路径,下面有具体分析,返回ResolverMatch对象,包含了视图函数,请求参数等信息 resolver_match = resolver.resolve(request.path_info)
# 分别为视图函数,args,kwargs 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: 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
@functools.lru_cache(maxsize=None) def get_resolver(urlconf=None): if urlconf is None:
# 这里urlconf为None 所以会从配置文件中读取我们配置的ROOT_URLCONF urlconf = settings.ROOT_URLCONF return URLResolver(RegexPattern(r‘^/‘), urlconf)
解析请求路径相关代码,以如下配置为例
urlpatterns = [
path(‘admin/‘, admin.site.urls),
path(‘user/‘, include(‘user.urls‘)),
path(‘goods/‘, include(‘goods.urls‘)),
path(‘cart/‘,include(‘cart.urls‘)),
path(‘order/‘,include(‘order.urls‘)),
path(‘‘,views.IndexView.as_view()),
]
goods.urls
urlpatterns = [
path(‘‘, views.IndexView.as_view(), name=‘index‘),
path(‘detail/<int:pk>/‘, views.DetailView.as_view(), name=‘detail‘),
path(‘list/<int:pk>/‘, views.ListView.as_view(), name=‘list‘),
]
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 = []
# 以path=‘/goods/detail/4/‘为例,此处match为(‘goods/detail/4/‘,(),{})
# 匹配到后会把匹配到的字符删去,以便做进一步的匹配 match = self.pattern.match(path) if match: new_path, args, kwargs = match
# 迭代项目urls.py中urlpatterns列表中的对象,通过查看源码可知pattern是URLResolver对象或者URLPattern对象,path第二个参数是list或tuple则是URLResolver,
# 如果是可调用对象则是URLPattern for pattern in self.url_patterns: try:
# 这里遍历到path(‘goods/‘,include(‘goods.urls‘))能够匹配到,接着调用URLRrsolver.resolve方法,匹配到了‘goods/‘,match为(‘detail/4/‘,(),{}),接着遍历goods app中urls的
# urlpatterns,遍历到path(‘detail/<int:pk>/‘,views.DetailView.as_view(),name=‘detail‘),能够匹配到,调用URLPattern.resolve方法,返回ResolverMatcher
# 包含了视图函数,请求参数等信息 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}) @cached_property def urlconf_module(self):
# 导入对应的urls模块 if isinstance(self.urlconf_name, str): return import_module(self.urlconf_name) else: return self.urlconf_name @cached_property def url_patterns(self):
# 获取urls中的urlpatterns # 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
URLPattern相关代码
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): 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, route=str(self.pattern))
path和include相关代码,
以path(‘goods/‘,include(‘goods.urls‘))
goods.urls中path(‘detail/<int:pk>/‘,views.DetailView.as_view(),name=‘detail‘)
def include(arg, namespace=None):
# arg=‘goods.urls‘ app_name = None if isinstance(arg, tuple): # Callable returning a namespace hint. try: urlconf_module, app_name = arg except ValueError: if namespace: raise ImproperlyConfigured( ‘Cannot override the namespace for a dynamic module that ‘ ‘provides a namespace.‘ ) raise ImproperlyConfigured( ‘Passing a %d-tuple to include() is not supported. Pass a ‘ ‘2-tuple containing the list of patterns and app_name, and ‘ ‘provide the namespace argument to include() instead.‘ % len(arg) ) else: # No namespace hint - use manually provided namespace. urlconf_module = arg # 导入goods.urls模块 if isinstance(urlconf_module, str): urlconf_module = import_module(urlconf_module)
# patterns为goods.urls.patterns,app_name为goods.urls.app_name patterns = getattr(urlconf_module, ‘urlpatterns‘, urlconf_module) app_name = getattr(urlconf_module, ‘app_name‘, app_name) if namespace and not app_name: raise ImproperlyConfigured( ‘Specifying a namespace in include() without providing an app_name ‘ ‘is not supported. Set the app_name attribute in the included ‘ ‘module, or pass a 2-tuple containing the list of patterns and ‘ ‘app_name instead.‘, ) namespace = namespace or app_name # Make sure the patterns can be iterated through (without this, some # testcases will break). if isinstance(patterns, (list, tuple)): for url_pattern in patterns: pattern = getattr(url_pattern, ‘pattern‘, None) if isinstance(pattern, LocalePrefixPattern): raise ImproperlyConfigured( ‘Using i18n_patterns in an included URLconf is not allowed.‘ ) return (urlconf_module, app_name, namespace) def _path(route, view, kwargs=None, name=None, Pattern=None): if isinstance(view, (list, tuple)):
# path(‘goods/‘,include(‘goods.urls‘))会走这个分支 # 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):
# path(‘detail/4/‘,views.DetailView.as_view(),name=‘detail‘)会走这个分支 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) re_path = partial(_path, Pattern=RegexPattern)
小结:
到这里路由解析有了一个大致的了解
当一个请求到达后,django根据settings中配置的ROOT_URLCONF,加载根路由模块,获取urlpatterns,将请求路由与urlpatterns进行匹配,如果匹配到后还有子路由,将进行进一步的匹配
直至找到最终的路由,最后返回ResolverMatch对象,该对象包含了路由对应的视图函数,参数等信息
下一篇分析视图相关代码
第一次分析源码,还有许多不足之处,欢迎指正
原文:https://www.cnblogs.com/zhoubin50/p/14388795.html