flask一個基本的http響應流程


前置技能   —   WSGI

在具體讀源碼之前,這裏先需要說一個概念,什麼是WSGI


WSGI,全稱 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是爲 Python 語言定義的 Web 服務器和 Web 應用程序或框架之間的一種簡單而通用的接口。自從 WSGI 被開發出來以後,許多其它語言中也出現了類似接口。

WSGI 的官方定義是,the Python Web Server Gateway Interface。從名字就可以看出來,這東西是一個Gateway,也就是網關。網關的作用就是在協議之間進行轉換。

WSGI 是作爲 Web 服務器與 Web 應用程序或應用框架之間的一種低級別的接口,以提升可移植 Web 應用開發的共同點。WSGI 是基於現存的 CGI 標準而設計的。

很多框架都自帶了 WSGI server ,比如 Flask,webpy,Django、CherryPy等等。當然性能都不好,自帶的 web server 更多的是測試用途,發佈時則使用生產環境的 WSGI server或者是聯合 nginx 做 uwsgi 。


在網上搜過WSGI的人應該會看到一個圖,左邊是Server,右邊是APP,中間有一個連接紐帶是WSGI。

不過,我看了源碼以後的理解和這個有些不同,我個人覺得,實際上不應該單獨寫一個APP,因爲,這個WSGI的使用方法實際上也是包含在APP裏面的,最右端的app實際上應該指的是邏輯功能,包括URL和view function的對應關係。

所以我個人的理解如下自己畫的圖

WSGI其實是作爲一個接口,來接受Server傳遞過來的信息, 然後通過這個接口調用後臺app裏的view function進行響應。





WSGI具體的功能

上面講到了WSGI可以起到一個接口的功能,前面對接服務器,後面對接app的具體功能

我們先來看看最簡單的一個wsgi_app的實現

  1. def application(environ, start_response):               #一個符合wsgi協議的應用程序寫法應該接受2個參數  
  2.     start_response(’200 OK’, [(‘Content-Type’‘text/html’)])  #environ爲http的相關信息,如請求頭等 start_response則是響應信息  
  3.     return [b‘<h1>Hello, web!</h1>’]        #return出來是響應內容  
def application(environ, start_response):               #一個符合wsgi協議的應用程序寫法應該接受2個參數
    start_response('200 OK', [('Content-Type', 'text/html')])  #environ爲http的相關信息,如請求頭等 start_response則是響應信息
    return [b'<h1>Hello, web!</h1>']        #return出來是響應內容

但是,作爲app本身,你就算啓動了程序,你也沒辦法給application傳遞參數?

所以,實際上,調用application傳遞2個參數的動作,是服務器來進行的,比如Gunicorn.

而這個叫做application的東西,在Flask框架內部的名字,叫做wsgi_app,請看下面章節的源碼。



Flask和WSGI

生成Flask實例

我們再來看下生成flask應用的操作寫法,用過的人肯定都非常熟悉。

  1. from flask import Flask  
  2.   
  3. app = Flask(__name__)         #生成app實例  
  4.  
  5. @app.route(‘/’)  
  6. def index():  
  7.         return ‘Hello World’  
from flask import Flask

app = Flask(__name__)         #生成app實例

@app.route('/')
def index():
        return 'Hello World'


這樣,一個flask app就生成了

但是這裏有一個概念必須要搞清楚,就是當你的gunicorn收到http請求,去調用app的時候,他實際上是用了Flask 的 __call__方法,這點非常重要!!!

因爲__call__方法怎麼寫,決定了你整個流程從哪裏開始。


那我們來看下Flask類的__call__方法的源碼

  1. class Flask(_PackageBoundObject):        #Flask類  
  2.   
  3. #中間省略一些代碼  
  4.   
  5.     def __call__(self, environ, start_response):    #Flask實例的__call__方法  
  6.         ”“”Shortcut for :attr:`wsgi_app`.”“”  
  7.         return self.wsgi_app(environ, start_response)  #注意他的return,他返回的時候,實際上是調用了wsgi_app這個功能  
class Flask(_PackageBoundObject):        #Flask類





中間省略一些代碼

def __call__(self, environ, start_response):    #Flask實例的__call__方法
    """Shortcut for :attr:`wsgi_app`."""
    return self.wsgi_app(environ, start_response)  #注意他的return,他返回的時候,實際上是調用了wsgi_app這個功能</pre>


如此一來,我們便知道,當http請求從server發送過來的時候,他會啓動__call__功能,最終實際是調用了wsgi_app功能並傳入environ和start_response


Flask的wsgi_app定義

  1. class Flask(_PackageBoundObject):  
  2.   
  3. #中間省略一些代碼  
  4.                                 #請注意函數的說明,說得非常準確,這個wsgi_app是一個真正的WSGI應用  
  5.     def wsgi_app(self, environ, start_response):    #他扮演的是一箇中間角色  
  6.         ”“”The actual WSGI application.  This is not implemented in 
  7.         `__call__` so that middlewares can be applied without losing a 
  8.         reference to the class.  So instead of doing this:: 
  9.  
  10.             app = MyMiddleware(app) 
  11.  
  12.         It’s a better idea to do this instead:: 
  13.  
  14.             app.wsgi_app = MyMiddleware(app.wsgi_app) 
  15.  
  16.         Then you still have the original application object around and 
  17.         can continue to call methods on it. 
  18.  
  19.         :param environ: a WSGI environment 
  20.         :param start_response: a callable accepting a status code, 
  21.                                a list of headers and an optional 
  22.                                exception context to start the response 
  23.         ”“”  
  24.         ctx = self.request_context(environ)  
  25.         ctx.push()  
  26.         error = None  
  27.         try:  
  28.             try:  
  29.                 response = self.full_dispatch_request()    #full_dispatch_request起到了預處理和錯誤處理以及分發請求的作用  
  30.             except Exception as e:  
  31.                 error = e  
  32.                 response = self.make_response(self.handle_exception(e))  #如果有錯誤發生,則生成錯誤響應  
  33.             return response(environ, start_response)       #如果沒有錯誤發生,則正常響應請求,返回響應內容  
  34.         finally:  
  35.             if self.should_ignore_error(error):  
  36.                 error = None  
  37.             ctx.auto_pop(error)  
class Flask(_PackageBoundObject):





中間省略一些代碼

                            #請注意函數的說明,說得非常準確,這個wsgi_app是一個真正的WSGI應用
def wsgi_app(self, environ, start_response):    #他扮演的是一箇中間角色
    """The actual WSGI application.  This is not implemented in
    `__call__` so that middlewares can be applied without losing a
    reference to the class.  So instead of doing this::

        app = MyMiddleware(app)

    It's a better idea to do this instead::

        app.wsgi_app = MyMiddleware(app.wsgi_app)

    Then you still have the original application object around and
    can continue to call methods on it.

    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code,
                           a list of headers and an optional
                           exception context to start the response
    """
    ctx = self.request_context(environ)
    ctx.push()
    error = None
    try:
        try:
            response = self.full_dispatch_request()    #full_dispatch_request起到了預處理和錯誤處理以及分發請求的作用
        except Exception as e:
            error = e
            response = self.make_response(self.handle_exception(e))  #如果有錯誤發生,則生成錯誤響應
        return response(environ, start_response)       #如果沒有錯誤發生,則正常響應請求,返回響應內容
    finally:
        if self.should_ignore_error(error):
            error = None
        ctx.auto_pop(error)</pre>


Ok, 這個wsgi_app的函數定義,基本上包含了整個流程的功能




WSGI_APP的內部流程


第一步:生成request請求對象和請求上下文環境

首先,你會看到ctx = self.request_context(environ)的語句,這個涉及到Flask使用了請求上下文應用上下文的概念,結構爲棧結構,這部分比較難,後面第二篇會單獨寫。

這裏只需要理解爲,上面語句產生的所用是生成了一個request請求對象以及包含請求信息在內的request context




第二部:請求進入預處理,錯誤處理及請求轉發到響應的過程

進入wsgi_app的函數內部,生成了request對象和上下文環境之後,進入到try

  1. response = self.full_dispatch_request()      
response = self.full_dispatch_request()    


我們看到,響應被賦值成了full_dispatch_request()方法的返回內容,所以我們來看一下full_dispatch_request方法

  1. class Flask(_PackageBoundObject):  
  2.   
  3. #此處省略一些代碼  
  4.   
  5.     def full_dispatch_request(self):  
  6.         ”“”Dispatches the request and on top of that performs request 
  7.         pre and postprocessing as well as HTTP exception catching and 
  8.         error handling. 
  9.  
  10.         .. versionadded:: 0.7 
  11.         ”“”  
  12.         self.try_trigger_before_first_request_functions()  #進行發生真實請求前的處理  
  13.         try:  
  14.             request_started.send(self)                     #socket部分的操作  
  15.             rv = self.preprocess_request()                 #進行請求的預處理  
  16.             if rv is None:  
  17.                 rv = self.dispatch_request()  
  18.         except Exception as e:  
  19.             rv = self.handle_user_exception(e)  
  20.         response = self.make_response(rv)  
  21.         response = self.process_response(response)  
  22.         request_finished.send(self, response=response)  
  23.         return response  
class Flask(_PackageBoundObject):





此處省略一些代碼

def full_dispatch_request(self):
    """Dispatches the request and on top of that performs request
    pre and postprocessing as well as HTTP exception catching and
    error handling.

    .. versionadded:: 0.7
    """
    self.try_trigger_before_first_request_functions()  #進行發生真實請求前的處理
    try:
        request_started.send(self)                     #socket部分的操作
        rv = self.preprocess_request()                 #進行請求的預處理
        if rv is None:
            rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
    response = self.make_response(rv)
    response = self.process_response(response)
    request_finished.send(self, response=response)
    return response</pre><br><p></p>

他首先會觸發 try_trigger_before_first_request_function()方法

                      在方法內部 ———->會觸發 _got_first_request 屬性,這個屬性的返回值是True或者False.  True的話就代表了程序開始處理請求了.


來看看 try_trigger_before_first_request_function()的代碼,他的目的是,最後將_got_first_request屬性置爲True.

  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略一些代碼  
  4.   
  5.     def try_trigger_before_first_request_functions(self):  
  6.         ”“”Called before each request and will ensure that it triggers 
  7.         the :attr:`before_first_request_funcs` and only exactly once per 
  8.         application instance (which means process usually). 
  9.  
  10.         :internal: 
  11.         ”“”  
  12.         if self._got_first_request:  
  13.             return  
  14.         with self._before_request_lock:  
  15.             if self._got_first_request:  
  16.                 return  
  17.             for func in self.before_first_request_funcs:  
  18.                 func()  
  19.             self._got_first_request = True  
class Flask(_PackageBoundObject):





省略一些代碼

def try_trigger_before_first_request_functions(self):
    """Called before each request and will ensure that it triggers
    the :attr:`before_first_request_funcs` and only exactly once per
    application instance (which means process usually).

    :internal:
    """
    if self._got_first_request:
        return
    with self._before_request_lock:
        if self._got_first_request:
            return
        for func in self.before_first_request_funcs:
            func()
        self._got_first_request = True</pre>



再來看看_got_first_request 的定義,他的默認值是False

他的定義中可以明顯看到, if the application started,this attribute is set to True.

  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略一些代碼  
  4.  
  5.     @property  
  6.     def got_first_request(self):  
  7.         ”“”This attribute is set to “True“ if the application started 
  8.         handling the first request. 
  9.  
  10.         .. versionadded:: 0.8 
  11.         ”“”  
  12.         return self._got_first_request  
class Flask(_PackageBoundObject):





省略一些代碼

@property
def got_first_request(self):
    """This attribute is set to ``True`` if the application started
    handling the first request.

    .. versionadded:: 0.8
    """
    return self._got_first_request</pre>


接着,當_got_first_request 屬性被設置完以後,我們就需要再次回到 full_dispatch_request函數內部,繼續往下走

下面一段代碼是request_started.send(),他是繼承自signal模塊,大致作用是進行socket部分的功能,暫時不詳細追溯。

preprocess_request()方法的話,主要是進行flask的hook鉤子, before_request功能的實現,也就是在真正發生請求之前,有些事情需要提前做

Flask一共有4個hook鉤子,另外再寫吧


隨後,繼續往下走,來到了一個至  關  重  要的功能  dispatch_request()

  1. try:  
  2.     request_started.send(self)  
  3.     rv = self.preprocess_request()  
  4.     if rv is None:  
  5.         rv = self.dispatch_request()  
        try:
            request_started.send(self)
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()


爲什麼說至關重要,因爲一個http請求到了這裏,實際上已經完成了從wsgi部分的過渡,進入到了尋找響應的階段了,一個請求通過url進來以後,app怎麼知道要如何響應呢?

就是通過dispatch_request方法來進行請求判定和分發。


第三步:請求分發  dispatch_request

來看源碼

  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略一些代碼  
  4.   
  5.     def dispatch_request(self):   #看函數定義,matches the URL and returns the value of the view or error.  
  6.         ”“”Does the request dispatching.  Matches the URL and returns the 
  7.         return value of the view or error handler.  This does not have to 
  8.         be a response object.  In order to convert the return value to a 
  9.         proper response object, call :func:`make_response`. 
  10.  
  11.         .. versionchanged:: 0.7 
  12.            This no longer does the exception handling, this code was 
  13.            moved to the new :meth:`full_dispatch_request`. 
  14.         ”“”  
  15.         req = _request_ctx_stack.top.request  
  16.         if req.routing_exception is not None:  
  17.             self.raise_routing_exception(req)  
  18.         rule = req.url_rule  
  19.         # if we provide automatic options for this URL and the  
  20.         # request came with the OPTIONS method, reply automatically  
  21.         if getattr(rule, ‘provide_automatic_options’False) \  
  22.            and req.method == ‘OPTIONS’:  
  23.             return self.make_default_options_response()  
  24.         # otherwise dispatch to the handler for that endpoint  
  25.         return self.view_functions[rule.endpoint](**req.view_args)   #最終進入view_functions,取出url對應的視圖函數的返回值  
class Flask(_PackageBoundObject):





省略一些代碼

def dispatch_request(self):   #看函數定義,matches the URL and returns the value of the view or error.
    """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`.

    .. versionchanged:: 0.7
       This no longer does the exception handling, this code was
       moved to the new :meth:`full_dispatch_request`.
    """
    req = _request_ctx_stack.top.request
    if req.routing_exception is not None:
        self.raise_routing_exception(req)
    rule = req.url_rule
    # if we provide automatic options for this URL and the
    # request came with the OPTIONS method, reply automatically
    if getattr(rule, 'provide_automatic_options', False) \
       and req.method == 'OPTIONS':
        return self.make_default_options_response()
    # otherwise dispatch to the handler for that endpoint
    return self.view_functions[rule.endpoint](**req.view_args)   #最終進入view_functions,取出url對應的視圖函數的返回值</pre>

中間不需要過多考慮,req = _request_ctx_stack.top.request 可以暫時理解爲,將請求對象賦值給req

這裏先簡單講下,每個url進來以後,他都會對應一個view_function

比如下面的一個簡單視圖函數,路徑  ‘/’ 對應的是index函數

但是,實際上當中是分2步走的,第一步是’/’對應的endpoint爲’index’ ,第二部是endpoint  ‘index’ 對應到index()視圖函數

這個也是放在第二篇文章裏面具體寫flask 路由的實現,這裏暫時可以忽略中間步驟,只要知道URL———–>VIEW FUNCTION的邏輯步驟就ok


  1. @app.route(‘/’)  
  2. def index():  
  3.     return ‘Hello world’  
@app.route('/')
def index():
    return 'Hello world'


另外說下view_functions 是一個字典形式,他的key和value的關係是endpoint ——> view function

所以每個有效的URL進來,都能找到他對應的視圖函數view function,取得返回值並賦值給  rv

比如上面簡單的index,他取得的就是 ‘Hello world’ 值



請求分發完成後,已經取得了返回的值,再看下一步是如何做

我們再次回到  full_dispatch_request方法內往下走

  1. response = self.make_response(rv)  
  2. response = self.process_response(response)  
  3. request_finished.send(self, response=response)  
  4. return response  
        response = self.make_response(rv)
        response = self.process_response(response)
        request_finished.send(self, response=response)
        return response


這時候,通過make_response函數,將剛纔取得的 rv 生成響應,重新賦值response

再通過process_response功能主要是處理一個after_request的功能,比如你在請求後,要把數據庫連接關閉等動作,和上面提到的before_request對應和類似。

之後再進行request_finished.send的處理,也是和socket處理有關,暫時不詳細深入。

之後返回新的response對象

這裏特別需要注意的是,make_response函數是一個非常重要的函數,他的作用是返回一個response_class的實例對象,也就是可以接受environ和start_reponse兩個參數的對象


非   常   重   要!!!

Converts the return value from a view function to a real response object that is an instance of :attr:`response_class

  1. class Flask(_PackageBoundObject):   #注意函數說明,converts the return value from view function to a real response object  
  2.   
  3.     #省略一部分代碼  
  4.   
  5.     def make_response(self, rv):  
  6.         ”“”Converts the return value from a view function to a real 
  7.         response object that is an instance of :attr:`response_class`. 
  8.  
  9.         The following types are allowed for `rv`: 
  10.  
  11.         .. tabularcolumns:: |p{3.5cm}|p{9.5cm}| 
  12.  
  13.         ======================= =========================================== 
  14.         :attr:`response_class`  the object is returned unchanged 
  15.         :class:`str`            a response object is created with the 
  16.                                 string as body 
  17.         :class:`unicode`        a response object is created with the 
  18.                                 string encoded to utf-8 as body 
  19.         a WSGI function         the function is called as WSGI application 
  20.                                 and buffered as response object 
  21.         :class:`tuple`          A tuple in the form “(response, status, 
  22.                                 headers)“ or “(response, headers)“ 
  23.                                 where `response` is any of the 
  24.                                 types defined here, `status` is a string 
  25.                                 or an integer and `headers` is a list or 
  26.                                 a dictionary with header values. 
  27.         ======================= =========================================== 
  28.  
  29.         :param rv: the return value from the view function 
  30.  
  31.         .. versionchanged:: 0.9 
  32.            Previously a tuple was interpreted as the arguments for the 
  33.            response object. 
  34.         ”“”  
  35.         status_or_headers = headers = None  
  36.         if isinstance(rv, tuple):  
  37.             rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))  
  38.   
  39.         if rv is None:  
  40.             raise ValueError(‘View function did not return a response’)  
  41.   
  42.         if isinstance(status_or_headers, (dict, list)):  
  43.             headers, status_or_headers = status_or_headers, None  
  44.   
  45.         if not isinstance(rv, self.response_class):  
  46.             # When we create a response object directly, we let the constructor  
  47.             # set the headers and status.  We do this because there can be  
  48.             # some extra logic involved when creating these objects with  
  49.             # specific values (like default content type selection).  
  50.             if isinstance(rv, (text_type, bytes, bytearray)):  
  51.                 rv = self.response_class(rv, headers=headers,  
  52.                                          status=status_or_headers)  
  53.                 headers = status_or_headers = None  
  54.             else:  
  55.                 rv = self.response_class.force_type(rv, request.environ)  
  56.   
  57.         if status_or_headers is not None:  
  58.             if isinstance(status_or_headers, string_types):  
  59.                 rv.status = status_or_headers  
  60.             else:  
  61.                 rv.status_code = status_or_headers  
  62.         if headers:  
  63.             rv.headers.extend(headers)  
  64.   
  65.         return rv  
class Flask(_PackageBoundObject):   #注意函數說明,converts the return value from view function to a real response object

    #省略一部分代碼

    def make_response(self, rv):
        """Converts the return value from a view function to a real
        response object that is an instance of :attr:`response_class`.

        The following types are allowed for `rv`:

        .. tabularcolumns:: |p{3.5cm}|p{9.5cm}|

        ======================= ===========================================
        :attr:`response_class`  the object is returned unchanged
        :class:`str`            a response object is created with the
                                string as body
        :class:`unicode`        a response object is created with the
                                string encoded to utf-8 as body
        a WSGI function         the function is called as WSGI application
                                and buffered as response object
        :class:`tuple`          A tuple in the form ``(response, status,
                                headers)`` or ``(response, headers)``
                                where `response` is any of the
                                types defined here, `status` is a string
                                or an integer and `headers` is a list or
                                a dictionary with header values.
        ======================= ===========================================

        :param rv: the return value from the view function

        .. versionchanged:: 0.9
           Previously a tuple was interpreted as the arguments for the
           response object.
        """
        status_or_headers = headers = None
        if isinstance(rv, tuple):
            rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))

        if rv is None:
            raise ValueError('View function did not return a response')

        if isinstance(status_or_headers, (dict, list)):
            headers, status_or_headers = status_or_headers, None

        if not isinstance(rv, self.response_class):
            # When we create a response object directly, we let the constructor
            # set the headers and status.  We do this because there can be
            # some extra logic involved when creating these objects with
            # specific values (like default content type selection).
            if isinstance(rv, (text_type, bytes, bytearray)):
                rv = self.response_class(rv, headers=headers,
                                         status=status_or_headers)
                headers = status_or_headers = None
            else:
                rv = self.response_class.force_type(rv, request.environ)

        if status_or_headers is not None:
            if isinstance(status_or_headers, string_types):
                rv.status = status_or_headers
            else:
                rv.status_code = status_or_headers
        if headers:
            rv.headers.extend(headers)

        return rv





第四步:返回到wsgi_app內部

終於快進行到了最後一步,流程走回到了wsgi_app的內部

下面這段是wsgi_app內部的代碼

  1. try:  
  2.     try:  
  3.         response = self.full_dispatch_request()  
  4.     except Exception as e:  
  5.         error = e  
  6.         response = self.make_response(self.handle_exception(e))  
  7.     return response(environ, start_response)  
  8. finally:  
  9.     if self.should_ignore_error(error):  
  10.         error = None  
  11.     ctx.auto_pop(error)  
        try:
            try:
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.make_response(self.handle_exception(e))
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)


當response從剛剛的full_dispatch_request功能返回之後,函數會對這個response加上environ, start_response的參數並返回給Gunicorn


至此,一個HTTP從請求到響應的流程就完畢了.


總的來說,一個流程的關鍵步驟可以簡單歸結如下:




後一篇,將會記錄一下flask的route實現,裏面的url如何和endpoint對應起來,endpoint和view function又是如何對應起來




本篇參考資料:

http://docs.jinkan.org/docs/flask/api.html?highlight=wsgi_app#flask.Flask.wsgi_app

https://segmentfault.com/a/1190000004223296

http://docs.gunicorn.org/en/stable/run.html






                </div>
發佈了47 篇原創文章 · 獲贊 74 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章