Django系列之URL解析-03

本文深入探讨了Django框架中URL解析的过程,包括WSGIHandler如何利用URLResolver实例解析URL,从urls.py文件中获取路由并匹配请求路径,最终调用相应的视图函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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服务器。

4. 参考文献

[1] 【Django】Django架构流程分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值