当前位置:网站首页>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并执行即可。
边栏推荐
- MIT science and technology review article: AgI hype around Gato and other models may make people ignore the really important issues
- pom. XML configuration file label: differences between dependencies and dependencymanagement
- 力扣 1037.有效的回旋镖
- Force buckle 599 Minimum index sum of two lists
- JVM GC garbage collection brief
- EasyGBS级联时,上级平台重启导致推流失败、画面卡住该如何解决?
- 数据孤岛是企业数字化转型遇到的第一道险关
- Vulnhub's funfox2
- 力扣 599. 两个列表的最小索引总和
- 使用高斯Redis实现二级索引
猜你喜欢
机器学习笔记 - 使用Streamlit探索对象检测数据集
网络原理(1)——基础原理概述
Force buckle 599 Minimum index sum of two lists
How to cooperate among multiple threads
Chapter 9 Yunji datacanvas was rated as 36 krypton "the hard core technology enterprise most concerned by investors"
About cv2 dnn. Readnetfromonnx (path) reports error during processing node with 3 inputs and 1 outputs [exclusive release]
【mysql篇-基础篇】事务
mock. JS returns an array from the optional data in the object array
vulnhub之tre1
Force buckle 2319 Judge whether the matrix is an X matrix
随机推荐
Force buckle 989 Integer addition in array form
Precautions for cjson memory leakage
Chapter 20 using work queue manager (3)
php 获取图片信息的方法
Read PG in data warehouse in one article_ stat
九度 1201 -二叉排序数遍历- 二叉排序树「建议收藏」
pom. Brief introduction of XML configuration file label function
关于cv2.dnn.readNetFromONNX(path)就报ERROR during processing node with 3 inputs and 1 outputs的解决过程【独家发布】
微服务远程Debug,Nocalhost + Rainbond微服务开发第二弹
深度学习模型压缩与加速技术(七):混合方式
【哲思与实战】程序设计之道
解决/bin/sh进去的容器运行可执行文件报not found的问题
Gorilla official: sample code for golang to open websocket client
torch. nn. functional. Pad (input, pad, mode= 'constant', value=none) record
Micro service remote debug, nocalhost + rainbow micro service development second bullet
Mrs offline data analysis: process OBS data through Flink job
How to implement safety practice in software development stage
力扣 643. 子数组最大平均数 I
力扣 1232.缀点成线
Vulnhub tre1