Django源码之路由匹配(下)——图解逐步分析底层源码

目录

[1. 前言](#1. 前言)

[2. 路由匹配全过程分析](#2. 路由匹配全过程分析)

[2.1 请求的入口](#2.1 请求的入口)

[2.2 request的封装](#2.2 request的封装)

[2.3 response的源头](#2.3 response的源头)

[2.4 handler的获取](#2.4 handler的获取)

[2.5 获取resolver对象](#2.5 获取resolver对象)

[2.6 路由进行匹配](#2.6 路由进行匹配)

[3. 小结](#3. 小结)


1. 前言

在上一篇文章中,我们谈到了路由的定义,通过URLPattern路由路径对象和RoutePattern路径匹配对象完成的。

将路由路径转化为RoutePattern对象,再将RoutePattern和视图函数转化为URLPattern对象

在这篇文章中,我们依旧会使用到URLPattern和RoutePattern的一些方法,来完成整个路由匹配的过程

2. 路由匹配全过程分析

2.1 请求的入口

如果我们使用的是同步请求(不涉及到异步),那么,请求的入口就是WSGI,也就是wsgi.py

WSGI 是 Python Web 应用程序和 Web 服务器之间的标准接口,它定义了一个简单而通用的方式来协调 Python Web 应用程序与 Web 服务器之间的通信。WSGI 文件通常作为 Web 应用程序的入口点,它负责将来自 Web 服务器的请求传递给应用程序,并将应用程序的响应返回给服务器。

复制代码
application = get_wsgi_application()

这个语句便是WSGI的开始,用于处理HTTP的请求,我们ctrl+左键点击查看源码

python 复制代码
def get_wsgi_application():
    django.setup(set_prefix=False)   # 初始化环境,启动django程序
    return WSGIHandler()

返回了一个WSGIHandler对象,我们进入其中:

python 复制代码
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = "%d %s" % (response.status_code, response.reason_phrase)
        response_headers = [
            *response.items(),
            *(("Set-Cookie", c.output(header="")) for c in response.cookies.values()),
        ]
        start_response(status, response_headers)
        if getattr(response, "file_to_stream", None) is not None and environ.get(
            "wsgi.file_wrapper"
        ):
            # If `wsgi.file_wrapper` is used the WSGI server does not call
            # .close on the response, but on the file wrapper. Patch it to use
            # response.close instead which takes care of closing all files.
            response.file_to_stream.close = response.close
            response = environ["wsgi.file_wrapper"](
                response.file_to_stream, response.block_size
            )
        return response

2.2 request的封装

我们可以先去除一部分暂时性不需要的

这里做了一些前置处理:

  • 设置URL的前缀(URL中与Django相关的部分),保证能找到相对应的视图。
  • 发送Django内置信号

现在,代码简洁很多了:

python 复制代码
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)
        response = self.get_response(request)
        return response

ok,我们现在来分析request:

environ: HTTP请求的各种环境信息,包括请求头、请求参数、请求方式等

复制代码
self.request_class(environ):其实就是对environ进行了再次封装,封装为了request对象,以便后续使用更加方便

我们可以先来看看request_class的内部实现:

request_class就是一个WSGIRequest对象

所以,他的内部:

python 复制代码
class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        # If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
        # trailing slash), operate as if '/' was requested.
        path_info = get_path_info(environ) or "/"
        self.environ = environ
        self.path_info = path_info
        # be careful to only replace the first slash in the path because of
        # http://test/something and http://test//something being different as
        # stated in RFC 3986.
        self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1))
        self.META = environ
        self.META["PATH_INFO"] = path_info
        self.META["SCRIPT_NAME"] = script_name
        self.method = environ["REQUEST_METHOD"].upper()
        # Set content_type, content_params, and encoding.
        self._set_content_type_params(environ)
        try:
            content_length = int(environ.get("CONTENT_LENGTH"))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ["wsgi.input"], content_length)
        self._read_started = False
        self.resolver_match = None

简单来讲,就是将environ的一些信息做了处理,然后重新封装给request对象,后续调用更加方便

2.3 response的源头

ok,现在是最重要的一步了

我们传递了请求到来的request参数,通过get_response进行处理,最后返回响应,说明在get_response中就已经做好了路由匹配。

现在,我们详细看看get_response里面的源码内容:

python 复制代码
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._resource_closers.append(request.close)
    if response.status_code >= 400:
        log_response(
            "%s: %s",
            response.reason_phrase,
            request.path,
            response=response,
            request=request,
        )
    return response
  • 先来看第一句

这就是urls的路径,设置好路径,后续方便寻找urlpatterns

  • 最关键的就是这句,这个函数最终返回了response

所以,我们剔除其他:

python 复制代码
def get_response(self, request):
    set_urlconf(settings.ROOT_URLCONF)
    response = self._middleware_chain(request)
    return response

2.4 handler的获取

我们点击查看_middleware_chain

可以看到如下源码:

python 复制代码
def load_middleware(self, is_async=False):
    """
    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 = []

    get_response = self._get_response_async if is_async else self._get_response
    handler = convert_exception_to_response(get_response)
    handler_is_async = is_async
    for middleware_path in reversed(settings.MIDDLEWARE):
        middleware = import_string(middleware_path)
        middleware_can_sync = getattr(middleware, "sync_capable", True)
        middleware_can_async = getattr(middleware, "async_capable", False)
        if not middleware_can_sync and not middleware_can_async:
            raise RuntimeError(
                "Middleware %s must have at least one of "
                "sync_capable/async_capable set to True." % middleware_path
            )
        elif not handler_is_async and middleware_can_sync:
            middleware_is_async = False
        else:
            middleware_is_async = middleware_can_async
        try:
            # Adapt handler, if needed.
            adapted_handler = self.adapt_method_mode(
                middleware_is_async,
                handler,
                handler_is_async,
                debug=settings.DEBUG,
                name="middleware %s" % middleware_path,
            )
            mw_instance = middleware(adapted_handler)
        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
        else:
            handler = adapted_handler

        if mw_instance is None:
            raise ImproperlyConfigured(
                "Middleware factory %s returned None." % middleware_path
            )

        if hasattr(mw_instance, "process_view"):
            self._view_middleware.insert(
                0,
                self.adapt_method_mode(is_async, mw_instance.process_view),
            )
        if hasattr(mw_instance, "process_template_response"):
            self._template_response_middleware.append(
                self.adapt_method_mode(
                    is_async, mw_instance.process_template_response
                ),
            )
        if hasattr(mw_instance, "process_exception"):
            # The exception-handling stack is still always synchronous for
            # now, so adapt that way.
            self._exception_middleware.append(
                self.adapt_method_mode(False, mw_instance.process_exception),
            )

        handler = convert_exception_to_response(mw_instance)
        handler_is_async = middleware_is_async

    # Adapt the top of the stack, if needed.
    handler = self.adapt_method_mode(is_async, handler, handler_is_async)
    # We only assign to this when initialization is complete as it is used
    # as a flag for initialization being complete.
    self._middleware_chain = handler

实际上,最后所执行的就是handler函数

我们自底向上看:

源码中,有一些对异步请求进行了判断,目前我们并不涉及,所以并不需要

我们需要知道在哪儿进行的路由匹配,肯定要从response入手,于是我们直接查看_get_response


这是简化后的的代码:

python 复制代码
def load_middleware(self, is_async=False):
    get_response = self._get_response
    handler = convert_exception_to_response(get_response)
    self._middleware_chain = handler

2.5 获取resolver对象

我们继续开始,以下是_get_response的代码

python 复制代码
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
    callback, callback_args, callback_kwargs = self.resolve_request(request)

    # 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)
        # If it is an asynchronous view, run it in a subthread.
        if iscoroutinefunction(wrapped_callback):
            wrapped_callback = async_to_sync(wrapped_callback)
        try:
            response = wrapped_callback(request, *callback_args, **callback_kwargs)
        except Exception as e:
            response = self.process_exception_by_middleware(e, request)
            if response is None:
                raise

    # Complain if the view returned None (a common error).
    self.check_response(response, callback)

    # If the response supports deferred rendering, apply template
    # response middleware and then render the response
    if 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).
            self.check_response(
                response,
                middleware_method,
                name="%s.process_template_response"
                % (middleware_method.__self__.__class__.__name__,),
            )
        try:
            response = response.render()
        except Exception as e:
            response = self.process_exception_by_middleware(e, request)
            if response is None:
                raise

    return response

其实有用的,也就一句:

复制代码
callback, callback_args, callback_kwargs = self.resolve_request(request)

因为这个地方,返回了一个callback,其实就是最后匹配的视图函数


复制代码
resolve_request

这个方法,才是重点,我们开始逐步分析:

python 复制代码
    def resolve_request(self, request):
        """
        Retrieve/set the urlconf for the request. Return the view resolved,
        with its args and kwargs.
        """
        # Work out the resolver.
        if hasattr(request, "urlconf"):
            urlconf = request.urlconf
            set_urlconf(urlconf)
            resolver = get_resolver(urlconf)
        else:
            resolver = get_resolver()
        # Resolve the view, and assign the match object back to the request.
        resolver_match = resolver.resolve(request.path_info)
        request.resolver_match = resolver_match
        return resolver_match

我们这里执行的是: get_resolver()

简化后:

python 复制代码
def resolve_request(self, request):
    resolver = get_resolver()
    # Resolve the view, and assign the match object back to the request.
    resolver_match = resolver.resolve(request.path_info)
    request.resolver_match = resolver_match
    return resolver_match

我们接着往下看,通过get_resolver , 成功返回了URLResolver的一个对象

所以,我们小结一下:

这里的resolver对象就是:URLResolver

2.6 路由进行匹配

这一句代码便是最终的匹配结果,最终返回了匹配结果对象,并保存给request

我们可以先打印看看:

python 复制代码
ResolverMatch(func=app01.views.test, args=(), kwargs={}, url_name=None, app_names=[], namespaces=[], route='test/')

可以看到,最后的匹配结果对象是一个ResolverMatch对象

这里开始进行匹配,path_info就是路由路径:比如login/

调用URLResolver的resolve方法:

python 复制代码
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:
                self._extend_tried(tried, pattern, e.args[0].get("tried"))
            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)
                    )
                    self._extend_tried(tried, pattern, sub_match.tried)
                    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,
                        captured_kwargs=sub_match.captured_kwargs,
                        extra_kwargs={
                            **self.default_kwargs,
                            **sub_match.extra_kwargs,
                        },
                    )
                tried.append([pattern])
        raise Resolver404({"tried": tried, "path": new_path})
    raise Resolver404({"path": path})

最重要的其实就是这个for循环了,可以看到我们遍历的是urls.py里面的我们提前定义url_patterns

下面这个地方,就是我在上一篇文章中聊到的路由定义了,每一个路由都是一个URLPattern对象,里面有一个resolve方法,通过不同的Pattern(常规、正则)来进行匹配

最后谁能够匹配成功,我们就返回ResolverMatch对象

这就是最后匹配的结果了,第一个func其实就是相对应的视图函数

3. 小结

大概的源码如下所示,其中删除了一些暂时不需要看的东西,会更加清楚明了

本篇文章,做了路由匹配的分析, 结合上一篇文章路由的本质,我们更加清楚路由是如何进行匹配的。

学习路由匹配,可以更好的掌握Django框架,学习源码编写的思想,可以帮助我们自己写出更健硕、更容易维护和扩展的代码。

相关推荐
oyguyteggytrrwwwrt24 分钟前
3dgs渲染部分详细注释
python
阿祖zu25 分钟前
芝士就是力量!开源私有化部署与 GitHub 双向同步的个人知识笔记 App
前端·后端·ios
不才不才不不才34 分钟前
Spring 源码系列(16): doDispatch 全流程——一次请求的主干链路
java·后端·spring
IT_陈寒41 分钟前
SpringBoot自动配置失效?这个隐藏配置坑了我一整晚
前端·人工智能·后端
搜狐技术产品小编20231 小时前
告别“黑盒”与误判:如何用“多智能体对抗辩论”重构内容安全审核系统
后端·多agent
DantyWei1 小时前
controller注册及调用时机
后端
edwarddamon2 小时前
Spring Cloud 配置热更新与 Bean 代理机制梳理
java·后端
码事漫谈2 小时前
我用 Seed Evolving 做了个 AI 小说写作工具
后端
Raas1002 小时前
MAIGateway,魔芋企业级AI网关的FinAPI成本竞争力设计
java·后端·网关·api网关·魔芋ai·finapi·mai gateway
天天爱吃肉82182 小时前
【重磅发布:拿下新超仁达代理权 】
大数据·人工智能·python·功能测试·汽车