首頁 > 軟體

flask route對協定作用及設計思路

2022-07-05 18:00:07

引言

本文主要梳理了flask原始碼中route的設計思路。

首先,從WSGI協定的角度介紹flask route的作用;

其次,詳細講解如何藉助werkzeug庫的MapRule實現route

最後,梳理了一次完整的http請求中route的完整流程。

flask route 設計思路

原始碼版本說明

本文參考的是flask 0.5版本的程式碼。

flask 0.1版本的程式碼非常短,只有600多行,但是這個版本缺少blueprint機制。

因此,我參考的是0.5版本。

flask route範例

直接使用flask官方檔案中的例子

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'Hello World!'
@app.route('/post/')
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/' : show_post
}

這樣做的效果為:
當http請求的url為'/'時,flask會呼叫hello_world函數;
當http請求的url為'/post/<某整數值>'(例如/post/32)時,flask會呼叫show_post函數;

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位於請求鏈的最後一級,其作用就是生成最終的響應。

 http伺服器(比如,nginx)--&gt; WSGI server(比如gunicorn,SimpleHttpServer)--&gt;middleware--&gt;
 middleware--&gt; ... --&gt;application

如果接觸過Java Web 開發的人可能會立刻發現,這與servlet中的middleware機制是完全一致的。

特別重要的:

在上一小節的範例中app = Flask(__name__)建立了一個middleware
而這個middleware的核心作用是進行請求轉發(request dispatch)。

上面這句話非常重要,請在心裡重複一百遍。
上面這句話非常重要,請在心裡重複一百遍。
上面這句話非常重要,請在心裡重複一百遍。

進行請求轉發的前提就是能夠建立url與處理常式之間的對映關係,即route功能。
因此,在flask中,route是Flask類的一個裝飾器。

flask route的實現思路

通過上一小節,我們知道以下兩點:

  • 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中找到對應的處理常式,並呼叫

至此, 一個簡單的Flaskmiddleware的骨架就完成了。
上面的Flask類主要功能包括:

  • 符合WSGI協定的middleware:可被呼叫,並且可以呼叫application
  • 能夠儲存url與處理常式的對映資訊
  • 能夠根據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均為字串,並且預設情況下:

  • 如果是通過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/', endpoint='downloads/show')
           ])

在flask的原始碼中

class Flask(object):
    def __init__(self):
        self.url_map = Map()  # url_map為儲存所有Rule關係的容器Map
        self.view_functions = {} # view_functions儲存endpoint--&gt;function
  • 成員變數url_map儲存所有的(url, endpoint, method)關係
  • 成員變數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

總結

flaskFlask類是WSGIdispatch middleware

Flaskurl_map儲存所有的(url, endpoint, method)對映關係;

Flaskview_functions儲存所有的{endpoint: function}對映關係;

dispath request就是根據url找到endpoint,再根據endpoint找到function,最後呼叫function的過程

以上就是flask route對協定作用及設計思路的詳細內容,更多關於flask route協定設計的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com