1. 程式人生 > >flask 原始碼解析:路由

flask 原始碼解析:路由

轉載於:http://cizixs.com/2017/01/12/flask-insight-routing

構建路由規則

一個 web 應用不同的路徑會有不同的處理函式,路由就是根據請求的 URL 找到對應處理函式的過程。

在執行查詢之前,需要有一個規則列表,它儲存了 url 和處理函式的對應關係。最容易想到的解決方案就是定義一個字典,key 是 url,value 是對應的處理函式。如果 url 都是靜態的(url 路徑都是實現確定的,沒有變數和正則匹配),那麼路由的過程就是從字典中通過 url 這個 key ,找到並返回對應的 value;如果沒有找到,就報 404 錯誤。而對於動態路由,還需要更復雜的匹配邏輯。flask 中的路由過程是這樣的嗎?這篇文章就來分析分析。

在分析路由匹配過程之前,我們先來看看 flask

 中,構建這個路由規則的兩種方法:

  1. 通過 @app.route() decorator,比如文章開頭給出的 hello world 例子
  2. 通過 app.add_url_rule,這個方法的簽名為 add_url_rule(self, rule, endpoint=None, view_func=None, **options),引數的含義如下:
    • rule: url 規則字串,可以是靜態的 /path,也可以包含 /
    • endpoint:要註冊規則的 endpoint,預設是 view_func 的名字
    • view_func:對應 url 的處理函式,也被稱為檢視函式

這兩種方法是等價的,也就是說:

@app.route('/')
defhello():
    return "hello, world!"

也可以寫成

defhello():
    return "hello, world!"

app.add_url_rule('/', 'hello', hello)

NOTE: 其實,還有一種方法來構建路由規則——直接操作 app.url_map 這個資料結構。不過這種方法並不是很常用,因此就不展開了。

註冊路由規則的時候,flask 內部做了哪些東西呢?我們來看看 route 方法:

defroute(self,rule,**options):
    """A decorator that is used to register a view function for a
    given URL rule.  This does the same thing as :meth:`add_url_rule`
    but is intended for decorator usage.
    """
defdecorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator

route 方法內部也是呼叫 add_url_rule,只不過在外面包了一層裝飾器的邏輯,這也驗證了上面兩種方法等價的說法。

defadd_url_rule(self,rule,endpoint=None,view_func=None,**options):
    """Connects a URL rule.  Works exactly like the :meth:`route`
    decorator.  If a view_func is provided it will be registered with the
    endpoint.
    """

    methods = options.pop('methods', None)

    rule = self.url_rule_class(rule, methods=methods, **options)
    self.url_map.add(rule)
    if view_func is not None:
        old_func = self.view_functions.get(endpoint)
        if old_func is not None and old_func != view_func:
            raise AssertionError('View function mapping is overwriting an '
                                 'existing endpoint function: %s' % endpoint)
        self.view_functions[endpoint] = view_func

上面這段程式碼省略了處理 endpoint 和構建 methods 的部分邏輯,可以看到它主要做的事情就是更新 self.url_map 和 self.view_functions 兩個變數。找到變數的定義,發現 url_map 是 werkzeug.routeing:Map 類的物件,rule 是 werkzeug.routing:Rule 類的物件,view_functions 就是一個字典。這和我們之前預想的並不一樣,這裡增加了 Rule 和 Map 的封裝,還把 url 和 view_func 儲存到了不同的地方。

需要注意的是:每個檢視函式的 endpoint 必須是不同的,否則會報 AssertionError

werkzeug 路由邏輯

事實上,flask 核心的路由邏輯是在 werkzeug 中實現的。所以在繼續分析之前,我們先看一下 werkzeug 提供的路由功能

>>>m = Map([
...    Rule('/', endpoint='index'),
...    Rule('/downloads/', endpoint='downloads/index'),
...    Rule('/downloads/<int:id>', endpoint='downloads/show')
...])
>>>urls = m.bind("example.com", "/")
>>>urls.match("/", "GET")
('index', {})
>>>urls.match("/downloads/42")
('downloads/show', {'id': 42})

>>>urls.match("/downloads")
Traceback (most recent call last):
  ...
RequestRedirect: http://example.com/downloads/
>>>urls.match("/missing")
Traceback (most recent call last):
  ...
NotFound: 404 Not Found

上面的程式碼演示了 werkzeug 最核心的路由功能:新增路由規則(也可以使用 m.add),把路由表繫結到特定的環境(m.bind),匹配url(urls.match)。正常情況下返回對應的 endpoint 名字和引數字典,可能報重定向或者 404 異常。

可以發現,endpoint 在路由過程中非常重要werkzeug 的路由過程,其實是 url 到 endpoint 的轉換:通過 url 找到處理該 url 的 endpoint。至於 endpoint 和 view function 之間的匹配關係,werkzeug 是不管的,而上面也看到 flask 是把這個存放到字典中的。

flask 路由實現

好,有了這些基礎知識,我們回頭看 dispatch_request,繼續探尋路由匹配的邏輯:

defdispatch_request(self):
    """Does the request dispatching.  Matches the URL and returns the
    return value of the view or error handler.  This does not have to
    be a response object.  In order to convert the return value to a
    proper response object, call :func:`make_response`.
    """

    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule

    # dispatch to the handler for that endpoint
    return self.view_functions[rule.endpoint](**req.view_args)

這個方法做的事情就是找到請求物件 request,獲取它的 endpoint,然後從 view_functions 找到對應 endpoint 的 view_func ,把請求引數傳遞過去,進行處理並返回。view_functions 中的內容,我們已經看到,是在構建路由規則的時候儲存進去的;那請求中 req.url_rule 是什麼儲存進去的呢?它的格式又是什麼?

我們可以先這樣理解:_request_ctx_stack.top.request 儲存著當前請求的資訊,在每次請求過來的時候,flask 會把當前請求的資訊儲存進去,這樣我們就能在整個請求處理過程中使用它。至於怎麼做到併發情況下資訊不會相互干擾錯亂,我們將在下一篇文章介紹。

_request_ctx_stack 中儲存的是 RequestContext 物件,它出現在 flask/ctx.py 檔案中,和路由相關的邏輯如下:

classRequestContext(object):
	def__init__(self,app,environ,request=None):
        self.app = app
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.match_request()

    defmatch_request(self):
        """Can be overridden by a subclass to hook into the matching
        of the request.
        """
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e


classFlask(_PackageBoundObject):
    defcreate_url_adapter(self,request):
        """Creates a URL adapter for the given request.  The URL adapter
        is created at a point where the request context is not yet set up
        so the request is passed explicitly.
        """
        if request is not None:
            return self.url_map.bind_to_environ(request.environ,
                server_name=self.config['SERVER_NAME'])

在初始化的時候,會呼叫 app.create_url_adapter 方法,把 app 的 url_map 繫結到 WSGI environ 變數上(bind_to_environ 和之前的 bind 方法作用相同)。最後會呼叫 match_request 方法,這個方式呼叫了 url_adapter.match 方法,進行實際的匹配工作,返回匹配的 url rule。而我們之前使用的 url_rule.endpoint 就是匹配的 endpoint 值。

整個 flask 的路由過程就結束了,總結一下大致的流程:

  • 通過 @app.route 或者 app.add_url_rule 註冊應用 url 對應的處理函式
  • 每次請求過來的時候,會事先呼叫路由匹配的邏輯,把路由結果儲存起來
  • dispatch_request 根據儲存的路由結果,呼叫對應的檢視函式

match 實現

雖然講完了 flask 的路由流程,但是還沒有講到最核心的問題:werkzeug 中是怎麼實現match 方法的。Map 儲存了 Rule 列表,match 的時候會依次呼叫其中的 rule.match 方法,如果匹配就找到了 match。Rule.match 方法的程式碼如下:

defmatch(self,path):
        """Check if the rule matches a given path. Path is a string in the
        form ``"subdomain|/path(method)"`` and is assembled by the map.  If
        the map is doing host matching the subdomain part will be the host
        instead.

        If the rule matches a dict with the converted values is returned,
        otherwise the return value is `None`.
        """
        if not self.build_only:
            m = self._regex.search(path)
            if m is not None:
                groups = m.groupdict()

                result = {}
                for name, value in iteritems(groups):
                    try:
                        value = self._converters[name].to_python(value)
                    except ValidationError:
                        return
                    result[str(name)] = value
                if self.defaults:
                    result.update(self.defaults)

                return result

它的邏輯是這樣的:用實現 compile 的正則表示式去匹配給出的真實路徑資訊,把所有的匹配元件轉換成對應的值,儲存在字典中(這就是傳遞給檢視函式的引數列表)並返回。

相關推薦

flask 原始碼解析路由

轉載於:http://cizixs.com/2017/01/12/flask-insight-routing 構建路由規則 一個 web 應用不同的路徑會有不同的處理函式,路由就是根據請求的 URL 找到對應處理函式的過程。 在執行查詢之前,需要有一個規則列表,它儲存

flask 原始碼解析響應

轉載於:http://cizixs.com/2017/01/22/flask-insight-response response 簡介 在 flask 應用中,我們只需要編寫 view 函式,並不需要直接和響應(response)打交道,flask 會自動生成響應返回

Redis原始碼解析15Resis主從複製之從節點流程

Redis原始碼解析:15Resis主從複製之從節點流程   版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/gqtcgq/article/details/51172085        

Java集合類原始碼解析AbstractMap

目錄 引言 原始碼解析 抽象函式entrySet() 兩個集合檢視 操作方法 兩個子類 參考: 引言 今天學習一個Java集合的一個抽象類 AbstractMap ,AbstractMap 是Map介面的 實現類之一,也是HashMap、T

Java集合類原始碼解析HashMap (基於JDK1.8)

目錄 前言 HashMap的資料結構 深入原始碼 兩個引數 成員變數 四個構造方法 插入資料的方法:put() 雜湊函式:hash() 動態擴容:resize() 節點樹化、紅黑樹的拆分 節點樹化

Java集合類原始碼解析Vector

引言 之前的文章我們學習了一個集合類 ArrayList,今天講它的一個兄弟 Vector。 為什麼說是它兄弟呢?因為從容器的構造來說,Vector 簡直就是 ArrayList 的翻版,也是基於陣列的資料結構,不同的是,Vector的每個方法都加了 synchronized 修飾符,是執行緒安全的。 類

jQuery原始碼解析變數與函式

 //原始碼剖析都基於jQuery-2.0.3版本,主要考慮到相容IE 2行:jQuery javaScript Library v2.0.3——jQuery版本 3行:http://jQuery.com——官網 5~6行:Includes Sizzle.js;http://sizzlejs.

Flask原始碼解析:字串方法endswith與os.path.splittext()

1、字串方法endswith   endswith方法: def endswith(self, suffix, start=None, end=None): # real signature unknown; restored from __doc__ """ S.endswith(suffix

Spark2.2.2原始碼解析 3.啟動worker節點啟動流程分析

本文啟動worker節點啟動流程分析   啟動命令: ${SPARK_HOME}/sbin/start-slave.sh spark://sysadmindeMacBook-Pro.local:7077   檢視start-slave.sh  

Spark2.2.2原始碼解析 2.啟動master節點流程分析

本文主要說明在啟動master節點的時候,程式碼的流程走向。   授予檔案執行許可權 chmod755  兩個目錄裡的檔案: /workspace/spark-2.2.2/bin  --所有檔案 /workspace/spark-2.2.2/sb

Spring4原始碼解析BeanDefinition架構及實現

一、架構圖 首先共同看下總體的 Java Class Diagrams 圖: 二、具體類實現 2.1 AttributeAccessor 介面定義了一個通用的可對任意物件獲取、修改等操作元資料的附加契約。主要方法如下: public interface AttributeAcce

Tomcat原始碼解析Container中的Pipeline和Valve

前言:     我們在上一篇部落格 中分析了關於tomcat處理請求的全過程,在最後的時候交給了當前Engine的pipeline去處理。     Engine.pipeline獲取了first_valve,然後執行其invoke方法,即完成了請求

Tomcat原始碼解析Jsp檔案的編譯、實現

1.Jsp簡介     jsp(java server page),其根本是一個簡化的Servlet技術,是一種動態網頁技術標準。     它是在傳統的網頁HTML頁面中插入java程式碼段,從而形成jsp檔案,字尾為.jsp。  

Tomcat原始碼解析Web請求處理過程

前言:     Catalina是Tomcat提供的Servlet容器實現,它負責處理來自客戶端的請求並處理響應。     但是僅有Servlet容器伺服器是無法對外提供服務的,還需要由聯結器接收來自客戶端的請求,並按照既定協議進行解析,然後交由S

Tomcat原始碼解析Catalina原始碼解析

1.Catalina     對於Tomcat來說,Catalina是其核心元件,所有基於JSP/Servlet的Java Web應用均需要依託Servlet容器執行並對外提供服務。     4.0版本後,Tomcat完全重新設計了其Servlet

Spark2.3.2原始碼解析 6. SparkContext原始碼分析(一) SparkEnv

    SparkContext 是通往 Spark 叢集的唯一入口,可以用來在 Spark 叢集中建立 RDDs 、 累加器( Accumulators )和廣播變數( Broadcast Variables ) 。 SparkContext 也是整個 Spark 應用程式(

Spark2.3.2原始碼解析 5. RDD 依賴關係寬依賴與窄依賴

    Spark中RDD的高效與DAG(有向無環圖)有很大的關係,在DAG排程中需要對計算的過程劃分Stage,劃分的依據就是RDD之間的依賴關係。RDD之間的依賴關係分為兩種,寬依賴(wide dependency/shuffle dependency)和窄依賴(narrow

Spark2.3.2原始碼解析 5. SparkConf原始碼分析

  在執行程式碼的時候,首先要宣告:SparkConf,本文以SparkConf進行分析,逐步展開。 val conf = new SparkConf()       類中的方法(org.a

caffe原始碼解析層(layer)的註冊與管理

caffe中所有的layer都是類的結構,它們的構造相關的函式都註冊在一個全域性變數g_registry_ 中。 首先這個變數的型別 CreatorRegistry是一個map定義, public: typedef shared_ptr<Layer<Dt

Flume 原始碼解析HDFS Sink

Apache Flume 資料流程的最後一部分是 Sink,它會將上游抽取並轉換好的資料輸送到外部儲存中去,如本地檔案、HDFS、ElasticSearch 等。本文將通過分析原始碼來展現 HDFS Sink 的工作流程。 Sink 元件的生命週期 在上一篇文章