当前位置:网站首页>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并执行即可。
边栏推荐
猜你喜欢
数据孤岛是企业数字化转型遇到的第一道险关
Implement secondary index with Gaussian redis
vulnhub之school 1
Cloud 组件发展升级
使用高斯Redis实现二级索引
VMWare中虚拟机网络配置
【STL】vector
php 获取图片信息的方法
Leetcode force buckle (Sword finger offer 36-39) 36 Binary search tree and bidirectional linked list 37 Serialize binary tree 38 Arrangement of strings 39 Numbers that appear more than half of the tim
机械臂速成小指南(十二):逆运动学分析
随机推荐
POJ 1742 Coins ( 单调队列解法 )「建议收藏」
CSDN syntax description
Yolov6:yolov6+win10--- train your own dataset
Force buckle 88 Merge two ordered arrays
php 获取图片信息的方法
841. String hash
力扣 1037.有效的回旋镖
EasyGBS级联时,上级平台重启导致推流失败、画面卡住该如何解决?
Try the tuiroom of Tencent cloud (there is an appointment in the evening, which will be continued...)
Force buckle 599 Minimum index sum of two lists
使用camunda做工作流设计,驳回操作
使用高斯Redis实现二级索引
Force buckle 674 Longest continuous increasing sequence
Vulnhub tre1
Gorilla official: sample code for golang to open websocket client
JVM GC garbage collection brief
Data island is the first danger encountered by enterprises in their digital transformation
How to cooperate among multiple threads
Version selection of boot and cloud
JVM 类加载机制