原理
一个 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,再由此获取视图函数然后调用就好了