flask 源碼解析:響應(yīng)

6.flask 源碼解析:響應(yīng)

response 簡(jiǎn)介

在 flask 應(yīng)用中剩辟,我們只需要編寫(xiě) view 函數(shù)端铛,并不需要直接和響應(yīng)(response)打交道鲸阻,flask 會(huì)自動(dòng)生成響應(yīng)返回給客戶端六荒。

The return value from a view function is automatically converted into a response object for you.
—— Flask docs

我們知道 HTTP 響應(yīng)分為三個(gè)部分:
狀態(tài)欄(HTTP 版本养距、狀態(tài)碼和說(shuō)明)、頭部(以冒號(hào)隔開(kāi)的字符對(duì)汇荐,用于各種控制和協(xié)商)洞就、body(服務(wù)端返回的數(shù)據(jù))。比如下面訪問(wèn)一個(gè)地址的響應(yīng):

HTTP/1.1 200 OK

Access-Control-Allow-Origin: *
Cache-Control: max-age=600
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 15 Feb 2017 07:50:41 GMT
Expires: Wed, 15 Feb 2017 08:00:41 GMT
Last-Modified: Wed, 15 Feb 2017 07:46:56 GMT
Server: GitHub.com
Transfer-Encoding: chunked
X-GitHub-Request-Id: D2A7:7B6B:33C0628:47C44B9:58A40851

<BODY>

flask 自然也會(huì)提供所有這些數(shù)據(jù)的操作掀淘,視圖函數(shù)就支持返回三個(gè)值:第一個(gè)是返回的數(shù)據(jù)旬蟋,第二個(gè)是狀態(tài)碼,第三個(gè)是頭部字典革娄。比如:

@app.route('/')
def hello_world():
    return 'Hello, World!', 201, {'X-Foo': 'bar'}

這篇文章就講講這背后的魔法倾贰。

flask 響應(yīng)(response)

flask 源碼解析:應(yīng)用啟動(dòng)流程 的最后,我們講到 full_dispatch_request 在調(diào)用路由的視圖函數(shù)之后拦惋,會(huì)調(diào)用 finalize_request 進(jìn)行最后的處理匆浙,在這個(gè)方法里就包含了 response 對(duì)象的生成和處理邏輯。

finalize_request 的代碼如下:

def finalize_request(self, rv, from_error_handler=False):
    """Given the return value from a view function this finalizes
    the request by converting it into a response and invoking the
    postprocessing functions.  This is invoked for both normal
    request dispatching as well as error handlers.
    """
    response = self.make_response(rv)
    try:
        response = self.process_response(response)
        request_finished.send(self, response=response)
    except Exception:
        if not from_error_handler:
            raise
        self.logger.exception('Request finalizing failed with an '
                              'error while handling an error')
    return response

里面有兩個(gè)方法調(diào)用:make_response 根據(jù)視圖函數(shù)的返回值生成 response 對(duì)象厕妖,process_response 對(duì) response 做一些后續(xù)的處理(比如執(zhí)行 hooks 函數(shù))首尼。我們先來(lái)看看 make_response

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`.
    """
    status_or_headers = headers = None
    if isinstance(rv, tuple):
        rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))

    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

    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

make_response 是視圖函數(shù)能返回多個(gè)不同數(shù)量和類型值的關(guān)鍵,因?yàn)樗芴幚磉@些情況,統(tǒng)一把它們轉(zhuǎn)換成 response软能。
如果返回值本身就是 Response 實(shí)例迎捺,就直接使用它;如果返回值是字符串類型查排,就把它作為響應(yīng)的 body凳枝,并自動(dòng)設(shè)置狀態(tài)碼和頭部信息;
如果返回值是 tuple雹嗦,會(huì)嘗試用 (response, status, headers) 或者 (response, headers) 去解析。

NOTE:因?yàn)橐晥D函數(shù)可以返回 Response 對(duì)象合是,因此我們可以直接操作 Response了罪。

不管視圖函數(shù)返回的是什么,最終都會(huì)變成 Response 對(duì)象聪全,那么我們就來(lái)看看 Response 的定義:

from werkzeug.wrappers import Response as ResponseBase


class Response(ResponseBase):
    """The response object that is used by default in Flask.  Works like the
    response object from Werkzeug but is set to have an HTML mimetype by
    default.  Quite often you don't have to create this object yourself because
    :meth:`~flask.Flask.make_response` will take care of that for you.

    If you want to replace the response object used you can subclass this and
    set :attr:`~flask.Flask.response_class` to your subclass.
    """
    default_mimetype = 'text/html'

Flask 的 Response 類非常簡(jiǎn)單泊藕,它只是繼承了 werkzeug.wrappers:Response,然后設(shè)置默認(rèn)返回類型為 html难礼。
不過(guò)從注釋中娃圆,我們得到兩條很有用的信息:

  1. 一般情況下不要直接操作 Response 對(duì)象,而是使用 make_response 方法來(lái)生成它
  2. 如果需要使用自定義的響應(yīng)對(duì)象蛾茉,可以覆蓋 flask app 對(duì)象的 response_class 屬性讼呢。

繼續(xù),下面就要分析 werkzeug 對(duì)應(yīng)的代碼了谦炬。

werkzeug response

werkzeug 實(shí)現(xiàn)的 response 定義在 werkzeug/wrappers.py 文件中:

class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,
               CommonResponseDescriptorsMixin,
               WWWAuthenticateMixin):

    """Full featured response object implementing the following mixins:

    - :class:`ETagResponseMixin` for etag and cache control handling
    - :class:`ResponseStreamMixin` to add support for the `stream` property
    - :class:`CommonResponseDescriptorsMixin` for various HTTP descriptors
    - :class:`WWWAuthenticateMixin` for HTTP authentication support
    """

和我們?cè)?flask 請(qǐng)求分析的 Request 類一樣悦屏,這里使用了 Mixin 機(jī)制。BaseResponse 精簡(jiǎn)后的大概框架如下:

class BaseResponse(object):
    """Base response class.  The most important fact about a response object
    is that it's a regular WSGI application.  It's initialized with a couple
    of response parameters (headers, body, status code etc.) and will start a
    valid WSGI response when called with the environ and start response
    callable.
    """

    charset = 'utf-8'
    default_status = 200
    default_mimetype = 'text/plain'
    automatically_set_content_length = True

    def __init__(self, response=None, status=None, headers=None,
                 mimetype=None, content_type=None, direct_passthrough=False):
        pass

BaseResponse 有一些類屬性键思,定義了默認(rèn)的值础爬,比如默認(rèn)字符編碼是 utf-8,默認(rèn)狀態(tài)碼是 200 等吼鳞。實(shí)例化的時(shí)候接受的參數(shù)有:

  • response: 字符串或者其他 iterable 對(duì)象看蚜,作為響應(yīng)的 body
  • status: 狀態(tài)碼,可以是整數(shù)赔桌,也可以是字符串
  • headers: 響應(yīng)的頭部供炎,可以是個(gè)列表,也可以是 werkzeug.datastructures.Headers對(duì)象
  • mimetype: mimetype 類型疾党,告訴客戶端響應(yīng) body 的格式碱茁,默認(rèn)是文本格式
  • content_type: 響應(yīng)頭部的 Content-Type 內(nèi)容

所有這些參數(shù)都是可選的,默認(rèn)情況下會(huì)生成一個(gè)狀態(tài)碼為 200仿贬,沒(méi)有任何 body 的響應(yīng)纽竣。status、status_code 作為 Response 的屬性,可以直接讀取和修改蜓氨。body 數(shù)據(jù)在內(nèi)部保存為 iterable 的類型聋袋,
但是對(duì)外也提供了直接讀寫(xiě)的接口 self.data

    def get_data(self, as_text=False):
        """The string representation of the request body.  Whenever you call
        this property the request iterable is encoded and flattened.
        """
        self._ensure_sequence()
        rv = b''.join(self.iter_encoded())
        if as_text:
            rv = rv.decode(self.charset)
        return rv

    def set_data(self, value):
        """Sets a new string as response.  The value set must either by a
        unicode or bytestring.
        """
        if isinstance(value, text_type):
            value = value.encode(self.charset)
        else:
            value = bytes(value)
        self.response = [value]
        if self.automatically_set_content_length:
            self.headers['Content-Length'] = str(len(value))

    data = property(get_data, set_data, doc='''
        A descriptor that calls :meth:`get_data` and :meth:`set_data`.  This
        should not be used and will eventually get deprecated.
        ''')

body 字符的編碼和長(zhǎng)度都是自動(dòng)設(shè)置的,用戶不需要手動(dòng)處理穴吹。

至于頭部的存儲(chǔ)幽勒,werkzeug 使用的是類似于字典的 werkzeug.datastructures:Headers 類。在flask 源碼解析:請(qǐng)求這篇文章中港令,我們沒(méi)有詳細(xì)
解釋頭部的存儲(chǔ)啥容,那么這篇文章就具體分析一下吧。

Headers 這個(gè)類的提供了很多和字典相同的接口:keys顷霹、values咪惠、iterms,但是和字典的區(qū)別在于它保存的值是有序的淋淀,而且允許相同 key 的值存在遥昧。
為什么這么設(shè)計(jì)呢?因?yàn)橹?HTTP 頭部的特性朵纷。先來(lái)看看有序炭臭,在 HTTP 傳送的過(guò)程中,如果頭部各個(gè) key-value 鍵值對(duì)順序發(fā)生變化袍辞,有些代理或者客戶端等組件會(huì)認(rèn)為請(qǐng)求被篡改而丟棄或者拒絕請(qǐng)求的處理鞋仍,所以最好把頭部設(shè)置為有序的,用戶按照什么順序設(shè)置的搅吁,就按照什么順序存儲(chǔ)凿试;再說(shuō)說(shuō)相同 key 的問(wèn)題,這是因?yàn)?HTTP 頭部同一個(gè) key 可能有多個(gè) value(比如 Accept似芝、SetCookie頭部)那婉。那么這個(gè)看起比較特殊的字典是怎么實(shí)現(xiàn)的呢?來(lái)看代碼:

class Headers(object):
    """An object that stores some headers.  It has a dict-like interface
    but is ordered and can store the same keys multiple times.
    """

    def __init__(self, defaults=None):
        self._list = []
        if defaults is not None:
            if isinstance(defaults, (list, Headers)):
                self._list.extend(defaults)
            else:
                self.extend(defaults)

    def __getitem__(self, key, _get_mode=False):
        if not _get_mode:
            if isinstance(key, integer_types):
                return self._list[key]
            elif isinstance(key, slice):
                return self.__class__(self._list[key])
        if not isinstance(key, string_types):
            raise exceptions.BadRequestKeyError(key)
        ikey = key.lower()
        for k, v in self._list:
            if k.lower() == ikey:
                return v
        if _get_mode:
            raise KeyError()
        raise exceptions.BadRequestKeyError(key)

可以看到党瓮,頭部信息在內(nèi)部存儲(chǔ)為二元組構(gòu)成的列表详炬,這樣就能同時(shí)保證它的有序性和重復(fù)性。一個(gè)核心的方法是 __getitem__寞奸,它定義了如何獲取頭部中的信息:

  • 通過(guò)下標(biāo) header[3]呛谜,直接返回對(duì)應(yīng)未知存儲(chǔ)的鍵值對(duì)元組
  • 通過(guò) key,返回 value header['Accept']枪萄,返回匹配的第一個(gè) value 值
  • 通過(guò) slice header[3:7]隐岛,返回另外一個(gè) Headers 對(duì)象,保存了 slice 中所有的數(shù)據(jù)

然后實(shí)現(xiàn) keys()瓷翻、items()聚凹、pop()割坠、setdefault() 等方法讓它表現(xiàn)出來(lái)字典的特性,除此之外還有 add()妒牙、extend()彼哼、add_header() 等和字典無(wú)關(guān)的方法方便操作。

自定義 response

如果需要擴(kuò)展 flask Response 的功能湘今,或者干脆把它替換掉敢朱,只要修改 flask app 的 response_class 屬性就可以了,比如:

from flask import Flask, Response

class MyResponse(Response):
    pass

app = Flask(__name__)
app.response_class = MyResponse
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摩瞎,一起剝皮案震驚了整個(gè)濱河市拴签,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旗们,老刑警劉巖蚓哩,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蚪拦,居然都是意外死亡杖剪,警方通過(guò)查閱死者的電腦和手機(jī)冻押,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門驰贷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人洛巢,你說(shuō)我怎么就攤上這事括袒。” “怎么了稿茉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵锹锰,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我漓库,道長(zhǎng)恃慧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任渺蒿,我火速辦了婚禮痢士,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茂装。我一直安慰自己怠蹂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布少态。 她就那樣靜靜地躺著城侧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪彼妻。 梳的紋絲不亂的頭發(fā)上嫌佑,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天豆茫,我揣著相機(jī)與錄音,去河邊找鬼歧强。 笑死澜薄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的摊册。 我是一名探鬼主播肤京,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼茅特!你這毒婦竟也來(lái)了忘分?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤白修,失蹤者是張志新(化名)和其女友劉穎妒峦,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體兵睛,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肯骇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祖很。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笛丙。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖假颇,靈堂內(nèi)的尸體忽然破棺而出胚鸯,到底是詐尸還是另有隱情,我是刑警寧澤笨鸡,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布姜钳,位于F島的核電站,受9級(jí)特大地震影響形耗,放射性物質(zhì)發(fā)生泄漏哥桥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一激涤、第九天 我趴在偏房一處隱蔽的房頂上張望拟糕。 院中可真熱鬧,春花似錦昔期、人聲如沸已卸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)累澡。三九已至,卻和暖如春般贼,著一層夾襖步出監(jiān)牢的瞬間愧哟,已是汗流浹背奥吩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕊梧,地道東北人霞赫。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肥矢,于是被迫代替她去往敵國(guó)和親端衰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • [TOC]一直想做源碼閱讀這件事甘改,總感覺(jué)難度太高時(shí)間太少旅东,可望不可見(jiàn)。最近正好時(shí)間充裕十艾,決定試試做一下抵代,并記錄一下...
    何柯君閱讀 7,174評(píng)論 3 98
  • ? ??在第1章,我們已經(jīng)了解了Flask的基本知識(shí)忘嫉,如果想要進(jìn)一步開(kāi)發(fā)更復(fù)雜的Flask應(yīng)用荤牍,我們就得了解F...
    懵懂_傻孩紙閱讀 2,939評(píng)論 0 4
  • 原文鏈接:Flask Web Development作者的博客譯文鏈接:編程派有翻譯或理解不對(duì)的地方,望大家指正庆冕!...
    EarlGrey閱讀 12,470評(píng)論 1 24
  • flask源碼分析 1. 前言 本文將基于flask 0.1版本(git checkout 8605cc3)來(lái)分析...
    甘尼克斯_閱讀 2,688評(píng)論 1 0
  • 《梨花賦》 清明將至康吵,又逢踏青之際。清閑散步至梨園愧杯,梨樹(shù)青紫微紅涎才,光潔溜滑鞋既,枝丫中已經(jīng)點(diǎn)綴出花...
    諸葛智叟閱讀 421評(píng)論 2 0