Flask框架知识系列之三

1,本文主要梳理了flask源码中route路由的设计思路。

首先,从WSGI协议的角度介绍flask route的作用;
其次,详细讲解如何借助werkzeug库的Map、Rule实现route;
最后,梳理了一次完整的http请求中route的完整流程。

2,源码版本说明

本文参考的是flask 0.5版本的代码。
flask 0.1版本的代码非常短,只有600多行,但是这个版本缺少blueprint机制。

3,flask route示例

直接使用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函数;

4,flask route的作用

从上面的示例中其实可以明白:flask route的作用就是建立url与处理函数的映射。

WSGI协议将处理请求的组件按照功能及调用关系分成了三种:server, middleware, application。

其中,server可以调用middleware和application,middleware可以调用application。

符合WSGI的框架对于一次http请求的完整处理过程为:

1,server读取解析请求,生成environ和start_response;
2,然后调用middleware,middleware完成自己的处理部分后,可以继续调用下一个middleware或application,形成一个完整的请求链;
3,application位于请求链的最后一级,其作用就是生成最终的响应。

 http服务器(比如,nginx)--> WSGI server(比如gunicorn,SimpleHttpServer)-->middleware-->
 middleware--> ... -->application

特别重要的:

在上节的示例中app = Flask(____name____)创建了一个middleware,
而这个middleware的核心作用是进行请求转发(request dispatch)。

进行请求转发的前提就是能够建立url与处理函数之间的映射关系,即route功能。

因此,在flask中,route是Flask类的一个装饰器。

5,flask route的实现思路

通过上一小节,我们知道以下两点:

1.flask route 是url与处理函数的映射关系;
2.在http请求时,Flask这个middleware负责完成对url对应的处理函数的调用;

那么,如果是我们自己来实现route,思路也很简单:

1,建立一个类Flask,这个类是一个middleware,并且有一个字典型的成员变量url_map;
2,url_map = {url : function}
3,当http请求时,进行request dispatch:根据url,从url_map中找到function,然后调用function;
4,调用后续的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中找到对应的处理函数,并调用

至此, 一个简单的Flaskmiddleware的骨架就完成了。

上面的Flask类主要功能包括:

1,符合WSGI协议的middleware:可被调用,并且可以调用application
2,能够保存url与处理函数的映射信息
3,能够根据url找到处理函数并调用(即,request dispatch)

当然,在实际中,不可能这么简单,但是基本思路是一致的。

6,werkzeug库中的Map与Rule在Flask中的应用

需要指出,上面实现的最简单的Flask类还是有很多问题的。
比如,HTTP请求中相同的url,不同的请求方法,比如GET,POST如果对应不同的处理函数,该如何处理?
flask使用了werkzeug库中的Map和Rule来管理url与处理函数映射关系。

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均为字符串,并且默认情况下:

1,如果是通过Flask.route装饰器建立的映射关系,那么endpoint就是处理函数的函数名;
2,如果是通过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

1,成员变量url_map保存所有的(url, endpoint, method)关系
2,成员变量view_functions保存所有的{endpoint, function}关系

所以,对于一个url,只要能找到(url,endpoint,method),就能根据endpoint找到对应的function。

7,route的完整流程

# 首先,建立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。

8,总结

1,flask的Flask类是WSGI的dispatch middleware;
2,Flask的url_map保存所有的(url, endpoint, method)映射关系;
3,Flask的view_functions保存所有的{endpoint: function}映射关系;
4,dispath request就是根据url找到endpoint,再根据endpoint找到function,最后调用function的过程

9,参考资料链接如下:

https://segmentfault.com/a/1190000004213652
http://www.cnblogs.com/DylanHooz/p/6389138.html
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容