当前位置:网站首页>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并执行即可。
边栏推荐
- When easygbs cascades, how to solve the streaming failure and screen jam caused by the restart of the superior platform?
- vulnhub之tre1
- 备份 TiDB 集群到持久卷
- 841. String hash
- Jenkins 用户权限管理
- Implement secondary index with Gaussian redis
- 《数字图像处理原理与实践(MATLAB版)》一书之代码Part2[通俗易懂]
- gorilla官方:golang开websocket client的示例代码
- pom.xml 配置文件标签:dependencies 和 dependencyManagement 区别
- 力扣 2319. 判断矩阵是否是一个 X 矩阵
猜你喜欢
Splicing and splitting of integer ints
Network principle (1) - overview of basic principles
php 获取图片信息的方法
Mongodb由浅入深学习
vulnhub之school 1
【STL】vector
微服务远程Debug,Nocalhost + Rainbond微服务开发第二弹
ASP.NET学习& asp‘s one word
让这个CRMEB单商户微信商城系统火起来,太好用了!
I wrote a markdown command line gadget, hoping to improve the efficiency of sending documents by garden friends!
随机推荐
sql 常用优化
[auto.js] automatic script
Force buckle 989 Integer addition in array form
Traversée des procédures stockées Oracle
Try the tuiroom of Tencent cloud (there is an appointment in the evening, which will be continued...)
School 1 of vulnhub
写了个 Markdown 命令行小工具,希望能提高园友们发文的效率!
Creation of kubernetes mysql8
ASP.NET学习& asp‘s one word
Cuda版本不一致,编译apex报错
MIT science and technology review article: AgI hype around Gato and other models may make people ignore the really important issues
php 获取图片信息的方法
大厂经典指针笔试题
equals 方法
LeetCode_ 7_ five
Traversal of Oracle stored procedures
EasyGBS级联时,上级平台重启导致推流失败、画面卡住该如何解决?
力扣 88.合并两个有序数组
ASP. Net learning & ASP's one word
《数字图像处理原理与实践(MATLAB版)》一书之代码Part2[通俗易懂]