原理
一个 web 应用中,不同的路径会有不同的处理函数,路由就是根据请求的 URL 找到对应处理函数的过程。
在下面的例子中,就是根据”/“找到hello_world
的过程
1 | from flask import Flask, request |
我们很容易想到用字典去做路由,key
是 url
,value
是对应的处理函数或者叫视图函数
1 | {"/":hello_world} |
但是对于动态路由,这样做就不好实现了
Flask
是用Map
和Rule
这两种数据结构来实现的,Map
的结构类似字典,但能处理更复杂的情况,我们姑且认为Map
就是字典
大概像这样
1 | {'/': "11"} |
value
不是视图函数名,而是一个字符串,我们叫它endpoint
,除非手动指定,它一般是函数名的字符串形式
endpoint
和视图函数是对应的,这个字典存储在Flask
的view_functions
属性中,它是一个字典
它的结构类似这样
1 | {'11':hello_world} |
以上就是在执行 @flask_app.route('/')
的时候发生的事情,Map
和view_functions
会形成上面的样子
匹配
flask
有一个url_map
属性,这个属性就是Map
实例,你可以认为是一个空字典。route
的作用就是在项目启动的时候往里面添加Rule
对象,就是url
规则
当一个请求来的时候,Flask
会根据url
在Map
找到对应的Rule
,再由Rule
获取endpoint
,再根据endpoint
找到对应的function
,然后执行 function()
为什么要endpoint
?
直接用视图函数名不好吗?
有时候我们需要根据视图函数的名字来获取这个视图函数的url
Flask
内置了url_for
函数,参数就是endpoint
的名字,例如 url_for("11")
返回的就是 "/"
如果没有endpoint
而使用函数名的字符串hellow_world
,例如url_for("hello_world")
,万一你的同事把函数名给改了,你的url_for
就要报错了,而endpoint
一般不会去改,谁改带他周末去爬山
参考What is an ‘endpoint’ in Flask?
实现
Rule和Map
Rule
和Map
都定义在 werkzeug/routing.py
中
测试以下代码
1 | from werkzeug.routing import Map, Rule |
我们可以知道
Map
中的元素是Rule
对象,Rule
对象其实就是URL
规则和endpoint
的封装对象Map
要绑定到某个域名下,实际上也可以绑定到environ
,毕竟environ
中有域名信息Map
的bind
方法返回MapAdapter
对象,MapAdapter
对象执行实际的匹配工作,它可以根据请求的URL匹配出(Rule,请求参数)
,也就是match
方法做的事情
route方法
route
方法做的事情就是向Map
里面add
Rule对象
我们看route
方法执行这一句 @flask_app.route('/',endpoint="11")
1 | def route(self, rule, **options): |
再看 add_url_rule
1 | def add_url_rule( |
就是为了第4、5两步
匹配
匹配的过程就是根据请求的URL
去match
出Rule
对象,再根据Rule
对象找到视图函数
full_dispatch_request->dispatch_request->dispatch_request
1 | def wsgi_app(self, environ, start_response): |
1 | def full_dispatch_request(self): |
重点看这个方法,请求到这里之后
1 | def dispatch_request(self): |
我们知道req
是从LocalStack
中取出的 RequestContext
的request
属性,保存着请求的信息
request
有一个url_rule
属性,他就是Rule
对象,我们从Rule
对象中拿到endpoint
,再从 view_functions
中根据endpoint
拿到视图函数,并传入请求参数,执行之后返回结果就结束了
一个疑问
那么req
是什么时候有的Rule
属性的呢???
是在 RequestContext
对象的push
方法中,我们知道请求来了第一步就是push
我还是删去了一些无关代码
1 | def push(self): |
而match_request
做的事情就是用 MapAdapter对象match
出当前请求的Rule
和请求参数 view_args
,然后绑定到request
上,这样request
就有了url_rule
属性
流程图
图随手画的,有什么好的画图工具可以推荐一下
url_adapter
就是MapAdapter
对象
1 | def match_request(self): |
至于match
方法为什么不需要传入当前请求的URL,那是因为url_adapter
已经包含了当前请求的信息了
在RequestContext
的 __init__
方法中我们可以看到, self.url_adapter = app.create_url_adapter(self.request)
1 | def __init__(self, app, environ, request=None, session=None): |
再看 create_url_adapter
方法,会用Map
对象 bind_to_environ
1 | def create_url_adapter(self, request): |
做的事情就是把Map
表绑定到environ
上,毕竟你这个Map
也就是路由表,要属于某个域名
再看 bind_to_environ
这个方法,没必要都看明白,需要的时候,再断点调试就好了
1 | def bind_to_environ(self, environ, server_name=None, subdomain=None): |
我们看到,这个方法把从envirion
中取出来的path_info
和query_args
传到了bind
方法中,然后返回MapAdapter
对象,接着就可以用MapAdapter
对象match
出Rule
了
也就是说我们从 Map
构造MapAdapter
,然后就可以直接用match
方法匹配出当前请求的Rule
,再根据Rule
获取endpoint
,再由此获取视图函数然后调用就好了