本文主要梳理了flask
源码中route
的设计思路。
首先,从WSGI
协议的角度介绍flask route
的做用;
其次,详细讲解如何借助werkzeug
库的Map
、Rule
实现route
;
最后,梳理了一次完整的http请求中route
的完整流程。python
本文参考的是flask 0.5
版本的代码。flask 0.1
版本的代码很是短,只有600多行,可是这个版本缺乏blueprint
机制。
所以,我参考的是0.5版本。nginx
直接使用flask
官方文档中的例子flask
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/post/<int:post_id>') def show_post(post_id): # show the post with the given id, the id is an integer return 'Post %d' % post_id if __name__ == '__main__': app.run()
此例中,使用app.route
装饰器,完成了如下两个url
与处理函数的route
:服务器
{ '/': hello_world, '/post/<int:post_id>' : show_post }
这样作的效果为:
当http请求的url为'/'时,flask会调用hello_world函数;
当http请求的url为'/post/<某整数值>'(例如/post/32)时,flask会调用show_post函数;app
从上面的示例中其实能够明白:flask route的做用就是创建url与处理函数的映射。框架
WSGI协议将处理请求的组件按照功能及调用关系分红了三种:server, middleware, application。
其中,server能够调用middleware和application,middleware能够调用application。函数
符合WSGI的框架对于一次http请求的完整处理过程为:
server读取解析请求,生成environ和start_response,而后调用middleware;
middleware完成本身的处理部分后,能够继续调用下一个middleware或application,造成一个完整的请求链;
application位于请求链的最后一级,其做用就是生成最终的响应。post
http服务器(好比,nginx)--> WSGI server(好比gunicorn,SimpleHttpServer)-->middleware--> middleware--> ... -->application
若是接触过Java Web 开发的人可能会马上发现,这与servlet中的middleware机制是彻底一致的。url
特别重要的:spa
在上一小节的示例中
app = Flask(__name__)
建立了一个middleware,
而这个middleware的核心做用是进行请求转发(request dispatch)。
上面这句话很是重要,请在内心重复一百遍。
上面这句话很是重要,请在内心重复一百遍。
上面这句话很是重要,请在内心重复一百遍。
进行请求转发的前提就是可以创建url与处理函数之间的映射关系,即route
功能。
所以,在flask
中,route是Flask类的一个装饰器。
经过上一小节,咱们知道如下两点:
flask route
是url与处理函数的映射关系;
在http请求时,Flask
这个middleware
负责完成对url对应的处理函数的调用;
那么,若是是咱们本身来实现route
,思路也很简单:
创建一个类Flask
,这个类是一个middleware,而且有一个字典型的成员变量url_map
;
url_map = {url : function}
当http请求时,进行request dispatch:根据url,从url_map中找到function
,而后调用function;
调用后续的middleware或application,并把function的结果传递下去。
flask的实现思路也是这样的。
class Flask(object): def __init__(self): self.url_map = {} # 此处定义保存url与处理函数的映射关系 def __call__(self, environ, start_response): # 根据WSGI协议,middleware必须是可调用对象 self.dispatch_request() # Flask的核心功能 request dispatch return application(environ, start_response) #最后调用下一级的application def route(self, rule): # Flask使用装饰器来完成url与处理函数的映射关系创建 def decorator(f): # 简单,侵入小,优雅 self.url_map[rule] = f return f return decorator def dispath_request(self): url = get_url_from_environ() #解析environ得到url return self.url_map[url]() #从url_map中找到对应的处理函数,并调用
至此, 一个简单的Flask
middleware的骨架就完成了。
上面的Flask
类主要功能包括:
符合WSGI协议的middleware:可被调用,而且能够调用application
可以保存url与处理函数的映射信息
可以根据url找处处理函数并调用(即,request dispatch)
固然,在实际中,不可能这么简单,可是基本思路是一致的。
须要指出,上面实现的最简单的Flask
类仍是有不少问题的。
好比,HTTP请求中相同的url,不一样的请求方法,好比GET,POST若是对应不一样的处理函数,该如何处理?
flask使用了werkzeug
库中的Map
和Rule
来管理url与处理函数映射关系。
首先须要简单了解一下Map
和Rule
的做用:
在werkzeug
中,Rule
的主要做用是保存了一组url
,endpoint
,methods
关系:
每一个(url, endpoint, methods)都有一个对应的Rule对象:
其实现以下:
class Rule(object): def __init__(self, url, endpoint, methods): self.rule = url self.endpoint = endpoint self.methods = methods
这里须要解释一下endpoint
:
前面说过:url与其处理函数可使用一个字典来实现:{url: function}
flask
在实现的时候,在中间加了一个中介endpoint
,因而,url与处理函数的映射变成了这样:
url-->endpoint-->function #一个url对应一个endpoint,一个endpoint对应一个function {url: endpoint} # 保存url与endpoint之间的关系 {endpoint: function} #保存endpoint与function之间的关系
因而,刚才咱们实现的简单的flask
骨架中{url: function}
的字典,就变成了{endpoint: function}
,
而{url: endpoint}
这个映射关系就须要借助Map
和Rule
这两个类来完成。
能够发现:endpoint
就是url和处理函数映射关系中的一个中介,因此,它能够是任何能够用做字典键的值,好比字符串。
可是在实际使用中endpoint
,通常endpoint
均为字符串,而且默认状况下:
若是是经过Flask.route
装饰器创建的映射关系,那么endpoint
就是处理函数的函数名;
若是是经过blueprint
创建的映射关系,那么endpoint
是blueprint名.处理函数名;
由于,每创建一个url-->endpoint-->function
关系就会建立一个Rule
对象,因此,会有不少Rule
对象存在。Map
的做用则是保存全部Rule
对象。
因此,通常状况下Map
的用法以下:
m = Map([ Rule('/', endpoint='index'), Rule('/downloads/', endpoint='downloads/index'), Rule('/downloads/<int:id>', endpoint='downloads/show') ])
在flask的源码中
class Flask(object): def __init__(self): self.url_map = Map() # url_map为保存全部Rule关系的容器Map self.view_functions = {} # view_functions保存endpoint-->function
成员变量url_map
保存全部的(url, endpoint, method)
关系
成员变量view_functions
保存全部的{endpoint, function}关系
因此,对于一个url,只要能找到(url,endpoint,method)
,就能根据endpoint
找到对应的function
。
首先,创建Flask
对象:
app = Flask(__name__)
而后,创建url
与function
之间的映射关系:
@app.route('/') def hello_world(): return 'Hello World!'
在装饰器route
中,建立(url, endpoint, method)
和{endpoint: function}
两组映射关系:
if endpoint is None: endpoint = view_func.__name__ # 默认使用响应函数名做为endpoint self.url_map.add(Rule(url, endpoint, method)) # 保存(url, endpoint, method)映射关系 self.view_functions[endpoint] = view_func # 保存{endpoint: function}映射关系
这样,就完成了对url和响应函数的映射关系。
下一步,调用WSGI server响应http请求,在文章开始的示例中使用:
app.run()
调用python
标准库提供的WSGI server,在实际使用时,多是gunicorn
或uwsgi
。
不论server是什么,最终都会调用Flask.__call__
函数。这个函数完成request dispatch的任务。
对于request dispatch而言,首先根据请求,解析environ,获得url,
而后调用Map.match
函数,这个函数会最终找到预先保存的(url, endpoint, method)
映射,
而后返回(endpoint, url请求参数),
因为获得了endpoint,而后,能够从Flask.view_functions
中直接取到对应的响应函数,
因此,能够直接进行函数调用
self.view_functions[endpoint](url请求参数)
至此,就完成了完整的route
。
flask
的Flask
类是WSGI
的dispatch middleware
;
Flask
的url_map
保存全部的(url, endpoint, method)映射关系;
Flask
的view_functions
保存全部的{endpoint: function}映射关系;
dispath request
就是根据url找到endpoint,再根据endpoint找到function,最后调用function的过程