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

目录

1. 前言

2. 路由匹配全过程分析

2.1 请求的入口

2.2 request的封装

2.3 response的源头

2.4 handler的获取

2.5 获取resolver对象

 2.6 路由进行匹配

3. 小结


1. 前言

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

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

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

2. 路由匹配全过程分析

2.1 请求的入口

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

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

application = get_wsgi_application()

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

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

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

class WSGIHandler(base.BaseHandler):request_class = WSGIRequestdef __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.closeresponse = environ["wsgi.file_wrapper"](response.file_to_stream, response.block_size)return response

2.2 request的封装

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

这里做了一些前置处理:

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

现在,代码简洁很多了:

class WSGIHandler(base.BaseHandler):request_class = WSGIRequestdef __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对象

所以,他的内部:

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 = environself.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 = environself.META["PATH_INFO"] = path_infoself.META["SCRIPT_NAME"] = script_nameself.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 = 0self._stream = LimitedStream(self.environ["wsgi.input"], content_length)self._read_started = Falseself.resolver_match = None

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

2.3 response的源头

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

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

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

def get_response(self, request):"""Return an HttpResponse object for the given HttpRequest."""# Setup default url resolver for this threadset_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

所以,我们剔除其他:

def get_response(self, request):set_urlconf(settings.ROOT_URLCONF)response = self._middleware_chain(request)return response

2.4 handler的获取

我们点击查看_middleware_chain

可以看到如下源码:

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_responsehandler = convert_exception_to_response(get_response)handler_is_async = is_asyncfor 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 = Falseelse:middleware_is_async = middleware_can_asynctry:# 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)continueelse:handler = adapted_handlerif 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


这是简化后的的代码:

def load_middleware(self, is_async=False):get_response = self._get_responsehandler = convert_exception_to_response(get_response)self._middleware_chain = handler

2.5 获取resolver对象

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

def _get_response(self, request):"""Resolve and call the view, then apply view, exception, andtemplate_response middleware. This method is everything that happensinside the request/response middleware."""response = Nonecallback, callback_args, callback_kwargs = self.resolve_request(request)# Apply view middlewarefor middleware_method in self._view_middleware:response = middleware_method(request, callback, callback_args, callback_kwargs)if response:breakif 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 responseif 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:raisereturn response

其实有用的,也就一句:

callback, callback_args, callback_kwargs = self.resolve_request(request)

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


resolve_request

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

    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.urlconfset_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_matchreturn resolver_match

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

 简化后:

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_matchreturn resolver_match

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

所以,我们小结一下:

这里的resolver对象就是:URLResolver

 2.6 路由进行匹配

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

我们可以先打印看看:

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

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

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

调用URLResolverresolve方法:

def resolve(self, path):path = str(path)  # path may be a reverse_lazy objecttried = []match = self.pattern.match(path)if match:new_path, args, kwargs = matchfor 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 submatchsub_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.argsif not sub_match_dict:sub_match_args = args + sub_match.argscurrent_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框架,学习源码编写的思想,可以帮助我们自己写出更健硕、更容易维护和扩展的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/794671.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(一)小案例银行家应用程序-介绍

案例示例如下所示: 登录之后就会出现下面所示: 项目案例流程图如下 ● 首先我们建立四个账号对象,用于登录 const account1 {owner: ItShare,movements: [200, 450, -400, 3000, -650, -130, 70, 1300],interestRate: 1.2, // %pin: 11…

注意力机制篇 | YOLOv8改进之添加多尺度全局注意力机制DilateFormer(MSDA)| 即插即用

前言:Hello大家好,我是小哥谈。多尺度全局注意力机制DilateFormer是一种用图像识别任务的深度学习模型。它是在Transformer模型的基础上进行改进的,旨在提高模型对图像中不同尺度信息的感知能力。DilateFormer引入了多尺度卷积和全局注意力机制来实现多尺度感知。具体来说,…

Rancher介绍

1.什么是Rancher Rancher是一套容器管理平台,专门用于部署和管理容器化应用。以下是关于Rancher的详细介绍: 容器编排与管理:Rancher是一个开源的企业级容器管理平台,它支持Kubernetes作为其容器编排引擎。Rancher可以帮助用户在…

java线程的几种状态

目录 正文: 1.JConsole 2.新建状态(New) 3.运行状态(Runnable) 4.阻塞状态(Blocked) 5.等待状态(Waiting) 6.计时等待状态(Timed Waiting) 7.终止状态(Terminated) 总结: 正文: 1.JConsole JConsole是Java监控和管理控制台工具&…

ABAP 读取EXCEL 文件内容,函数 TEXT_CONVERT_XLS_TO_SAP

EXCEL 内容: 读取内容: 代码: TYPES: BEGIN OF ITAB, FL1(50) TYPE C, FL2(50) TYPE C, FL3(50) TYPE C, FL4(50) TYPE C, FL5(50) TYPE C, FL6(50) TYPE C, END OF ITAB. DATA: T_ITEM TYPE TABLE OF ITAB WITH HEADER LINE. TYPE…

Netty经典32连问

文章目录 1、Netty是什么,它的主要特点是什么?2、Netty 应用场景了解么?3、Netty 核心组件有哪些?分别有什么作用?4、Netty的线程模型是怎样的?如何优化性能?5、EventloopGroup了解么?和 Event…

PWM方式读取AS5600磁编码器数据

PWM方式读取AS5600磁编码器获取角度例程 📍相关篇《STM32 软件I2C方式读取AS5600磁编码器获取角度例程》📌《HAL STM32 硬件I2C方式读取AS5600磁编码器获取角度例程》🎉本例程包含:Arduino测试代码、STM32标准库代码、HAL STM32代…

Mac删除软件,动一动手指,几秒就彻底删除 mac删除软件删不掉的解决方法 mac删除软件后怎么删除软件数据

当你入职新公司,接手前任员工使用的Mac电脑时,很可能会遇到一个非常普遍的问题:电脑中装有大量你不需要的软件。这些软件不仅占用宝贵的硬盘空间,还可能影响电脑的运行速度和效率。为了获得一个干净、清爽的使用体验,删…

Java 关键字 this 使用详解(通俗易懂)

this关键字主要有以下三个地方使用 在方法体中引用当前对象,即其方法被调用的对象,以便将当前对象的实例变量或当前对象作为参数传递给其他方法。 ① t this.x; 要在方法中引用当前对象,可以使用关键字 this。 ② return this; 作为当前…

[java]网络编程

网络编程概述 计算机网络: 把分布在不同地理区域的具有独立功能的计算机,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统。 Java是 Internet 上的语言,它从语言级上提供了对网络应用程序的支持,程序…

题库管理系统-基于Springboot实现JAVA+SQL离散数学题库管理系统(完整源码+LW+翻译)

基于Springboot实现JAVASQL离散数学题库管理系统(完整源码LW翻译) 概述: 本系统具体完成的功能如下: 1题库的管理与维护:新题的录入,修改,删除等功能。 2生成试卷:包括自动生成与手工改动,要…

使用msf进行有防火墙限制的3389端口转发

使用msf进行有防火墙限制的3389端口转发 这里主要是针对在内网中遇到需要开启3389的时候,发现存在防火墙,就没有办法直接远程连接,这个时候就可以使用端口转发使用msf,使用前记得先初始化,连接好数据库这里先使用msf进…

二.音视频编辑-媒体组合-播放

引言 当涉及到音视频编辑时,媒体资源的提取和组合是至关重要的环节。在iOS平台上,AVFoundation框架提供了丰富而强大的功能,使得媒体资源的操作变得轻松而高效。从原始的媒体中提取片段,然后将它们巧妙地组合成一个完整的作品&am…

51之定时器与中断系统

目录 1.定时器与中断系统简介 1.1中断系统 1.2定时器 1.2.1定时器简介 1.2.2定时器大致原理及其配置 1.2.3定时器所需的所有配置总介 2.定时器0实现LED闪烁 3.使用软件生成定时器初始化程序 1.定时器与中断系统简介 1.1中断系统 首先,我们需要来了解一下什么…

深入浅出 -- 系统架构之单体到分布式架构的演变

一、传统模式的技术改革 在很多年以前,其实没有严格意义上的前后端工程师之分,每个后端就是前端,同理,前端也可以是后端,即Ajax、jQuery技术未盛行前的年代。 起初,大部分前端界面很简单,显示的…

AcWing1402.星空之夜

【题目链接】1402. 星空之夜 - AcWing题库 夜空深处,闪亮的星星以星群的形式出现在人们眼中,形态万千。 一个星群是指一组非空的在水平,垂直或对角线方向相邻的星星的集合。 一个星群不能是一个更大星群的一部分。 星群可能是相似的。 如…

【蓝桥杯】GCD与LCM

一.概述 最大公约数(GCD)和最小公倍数(Least Common Multiple,LCM) 在C中,可以使用 std::__gcd(a, b)来计算最大公约数 1.欧几里德算法/辗转相除法 int gcd(int a,int b){return b?gcd(b, a%b):a; } 2…

Tensorboard以及Transforms初步学习

一.前情提要 1.本文是代码结合知识点,注释即为知识点 2.主要详细讲解Tensorboard以及Transforms代码以及基础知识 3.若想深入学习,建议阅读 P3. Python学习中的两大法宝函数(当然也可以用在PyTorch)_哔哩哔哩_bilibili 二.简述…

抖音运营技巧

1、视频时长 抖音的作品是否能够继续被推荐,取决于综合数据,包括完播率、点赞率、评论率、转发率和收藏率等。其中,完播率是最容易控制的因素。对于新号来说,在没有粉丝的初期,发布过长的视频可能会导致无人观看。因此…

金田金业带你一文了解现货黄金开户注意事项

现货黄金作为一种受欢迎的投资品种,吸引了越来越多的投资者。然而,在进行现货黄金开户时,有一些重要的注意事项需要牢记。本文将为您详细介绍现货黄金开户的九大关键要点,确保您能够在投资过程中做出明智的决策。 第一&#xff0c…