flask route設計思路

引言

本文主要梳理了flask源碼中route的設計思路。
首先,從WSGI協議的角度介紹flask route的做用;
其次,詳細講解如何藉助werkzeug庫的MapRule實現route
最後,梳理了一次完整的http請求中route的完整流程。python

flask route 設計思路

源碼版本說明

本文參考的是flask 0.5版本的代碼。
flask 0.1版本的代碼很是短,只有600多行,可是這個版本缺乏blueprint機制。
所以,我參考的是0.5版本。nginx

flask route示例

直接使用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的做用

從上面的示例中其實能夠明白: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的實現思路

經過上一小節,咱們知道如下兩點:

  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)

固然,在實際中,不可能這麼簡單,可是基本思路是一致的。

werkzeug庫中的Map與Rule在Flask中的應用

須要指出,上面實現的最簡單的Flask類仍是有不少問題的。
好比,HTTP請求中相同的url,不一樣的請求方法,好比GET,POST若是對應不一樣的處理函數,該如何處理?

flask使用了werkzeug庫中的MapRule來管理url與處理函數映射關係。

首先須要簡單瞭解一下MapRule的做用:
werkzeug中,Rule的主要做用是保存了一組urlendpointmethods關係:
每一個(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}這個映射關係就須要藉助MapRule這兩個類來完成。

能夠發現: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

route的完整流程

首先,創建Flask對象:

app = Flask(__name__)

而後,創建urlfunction之間的映射關係:

@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,在實際使用時,多是gunicornuwsgi

不論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

總結

  1. flaskFlask類是WSGIdispatch middleware

  2. Flaskurl_map保存全部的(url, endpoint, method)映射關係;

  3. Flaskview_functions保存全部的{endpoint: function}映射關係;

  4. dispath request就是根據url找到endpoint,再根據endpoint找到function,最後調用function的過程

相關文章
相關標籤/搜索