当前位置:网站首页>Flask1.1.4 Werkzeug1.0.1 源码分析:路由
Flask1.1.4 Werkzeug1.0.1 源码分析:路由
2022-07-07 18:16:00 【某工程师$】
路由大致分为两部分:
- 路由绑定:将url_rule和view_func 绑定到falsk实例中,这个在之前的一篇 启动流程中已经讲过了
- 路由解析:当实际请求来时,根据environ去匹配到对应的Rule,并从environ中解析出参数,然后根据Rule.endpoint 去view_functions中找对应的view_function进行调用
此篇文章重点讲解第二部分,Flask的路由匹配其实基于 werkzeug.routing.Map 和 werkzeug.routing.Rule
我们可以先写个简单的demo,了解下这两个工具的使用方法。
# 构造map
url_map = Map([Rule("/a", endpoint='a'), Rule("/b", endpoint='b'), Rule("/c/<path_param>", endpoint='c')])
# 调用Map.bind() 返回 MapAdapter对象
urls = url_map.bind('eee.com', '/')
# MapAdapter.match() 进行请求匹配
print(urls.match("/a"))
print(urls.match("/c/123"))
print(urls.match("/dd"))
结果如下:
#匹配到了会返回 (endpoint, view_func_args)
('a', {
})
('c', {
'path_param': '123'})
#匹配不到 抛异常
Traceback (most recent call last):
File "/Users/panc/Documents/projects/PycharmProjects/grpc-client/pycode/flask_demo.py", line 9, in <module>
print(urls.match("/dd"))
File "/usr/local/Caskroom/miniconda/base/envs/python37/lib/python3.7/site-packages/werkzeug/routing.py", line 1945, in match
raise NotFound()
werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
ok,下面开始研究Flask的路由过程~
首先要注意下,Flask中并没有直接把URL和view_func关联起来,而是在其中添加了endpoint的概念。其实也好理解,URL很多时候并不是固定的,还包含了参数部分,不太适合作为key。另外,从处理请求的步骤来看,首先要根据请求url信息去匹配已经注册了的url_rule,然后再去找对应的view_func执行逻辑。这样分为两步的情况下,将url信息和view_func分开存储,然后通过endpoint来关联两者显得很合适。
启动流程一篇中已经讲过,HTTP格式数据转换为WSGI格式数据后,调用 flask_app(environ, start_response) 执行具体的处理逻辑
class Flask(_PackageBoundObject):
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
# 此处构建了 flask.ctx.RequestContext 对象
# 构建过程中包含 路由处理逻辑
ctx = self.request_context(environ)
error = None
try:
try:
# 此处也包含了路由逻辑 将当前请求上下文入栈
# 入栈是为了用非传参的方式将一些信息传递给后面的view_function
ctx.push()
# RequestContext 对象构建和push的过程中已经处理好了路由的逻辑
# 之后只需要获取当前请求的 RequestContext实例,用endpoint去获取并调用方法即可
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 请求处理完成后清理当前的上下文 即出栈
ctx.auto_pop(error)
def request_context(self, environ):
# 注意第一个参数为flask实例
return RequestContext(self, environ)
def create_url_adapter(self, request):
if request is not None:
subdomain = (
(self.url_map.default_subdomain or None)
if not self.subdomain_matching
else None
)
# 之前的demo里面有看过这个 bind_to_environ 和bind作用是一样的
# 返回一个 MapAdapter对象
return self.url_map.bind_to_environ(
request.environ,
server_name=self.config["SERVER_NAME"],
subdomain=subdomain,
)
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
# 执行请求
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
# 此处将view_function的返回 转换为 Response对象
return self.finalize_request(rv)
def dispatch_request(self):
# 取出当前的RequestContext对象 并获取其request属性
req = _request_ctx_stack.top.request
# 路由匹配失败 抛异常
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# 最终 Rule对象的endpoint去找到对应的view_function并用参数调用 执行具体逻辑
return self.view_functions[rule.endpoint](**req.view_args)
下面来看下路由处理的关键 RequestContext
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
# 此处保存了 flask app实例
self.app = app
if request is None:
# 使用environ 构建一个 Request对象
request = app.request_class(environ)
self.request = request
self.url_adapter = None
try:
# 此处 就是进行Map.bind 获得了一个 MapAdapter对象
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
self.flashes = None
self.session = session
def match_request(self):
try:
# 其实就是 MapAdapter.match() 此处没有传参因为之前是 Map.bind_to_environ()
result = self.url_adapter.match(return_rule=True)
# 拆包 url_rule即Rule对象包含endpoint信息 view_args即参数
# 也就是说 RequestContext对象push()时,已经完成了路由解析过程了
# 将解析结果绑定到 RequestContext对象的request属性上
self.request.url_rule, self.request.view_args = result
except HTTPException as e:
self.request.routing_exception = e
def push(self):
"""Binds the request context to the current context."""
# 将当前的请求上下文对象入栈 方便后续传递
_request_ctx_stack.push(self)
if self.url_adapter is not None:
# 关键
self.match_request()
ok,至此路由的解析过程就结束啦~
总结一下:
- 核心的核心其实还是 werkzeug的 Map、Rule、MapAdapter对象
- 路由的绑定走的是 flask_app.add_url_rule() 方式有多种,可以是 app.route() 或者用 blueprint,但是本质都是 flask_app.add_url_rule()
- 路由的解析,本质就是 map_adapter = url_map.bind_to_environ() -> rule, view_args = map_adapter.match() -> view_functions[rule.endpoint] (**view_args)
- 其中 url_map.bind_to_environ() 在 构建RequestContext对象时执行, match操作在 RequestContext.push()时执行。上下文对象准备完成并入栈之后,只需取出当前的请求上下文对象,然后用endpoint去找到对应的view_func并执行即可。
边栏推荐
- Machine learning notes - explore object detection datasets using streamlit
- Jenkins 用户权限管理
- How C language determines whether it is a 32-bit system or a 64 bit system
- c语言如何判定是32位系统还是64位系统
- [solution] package 'XXXX' is not in goroot
- Mrs offline data analysis: process OBS data through Flink job
- JVM class loading mechanism
- Opencv learning notes high dynamic range (HDR) imaging
- TS快速入门-泛型
- 力扣 2319. 判断矩阵是否是一个 X 矩阵
猜你喜欢
使用camunda做工作流设计,驳回操作
Data island is the first danger encountered by enterprises in their digital transformation
vulnhub之Funfox2
Cloud 组件发展升级
Some important knowledge of MySQL
CSDN syntax description
How to cooperate among multiple threads
LeetCode_ 7_ five
机器学习笔记 - 使用Streamlit探索对象检测数据集
AIRIOT助力城市管廊工程,智慧物联守护城市生命线
随机推荐
Creation of kubernetes mysql8
一键部署Redis任意版本
[MySQL - Basic] transactions
MIT science and technology review article: AgI hype around Gato and other models may make people ignore the really important issues
Vulnhub's funfox2
力扣 989. 数组形式的整数加法
【哲思与实战】程序设计之道
pom.xml 配置文件标签作用简述
微服务远程Debug,Nocalhost + Rainbond微服务开发第二弹
Mongodb由浅入深学习
【mysql篇-基础篇】事务
VMWare中虚拟机网络配置
EasyGBS级联时,上级平台重启导致推流失败、画面卡住该如何解决?
LeetCode_ 7_ five
Chapter 9 Yunji datacanvas company won the highest honor of the "fifth digital finance innovation competition"!
equals 方法
Chapter 20 using work queue manager (3)
vulnhub之school 1
JVM class loading mechanism
力扣 88.合并两个有序数组