如何自定義Flask中的響應(yīng)類

有翻譯或理解不對(duì)的地方,望大家指正盟劫!

如何自定義Flask中的響應(yīng)類

Flask框架中的響應(yīng)類阴幌,命名很貼切,叫Response议忽。不過(guò)Flask應(yīng)用中很少直接調(diào)用這個(gè)類织阅。而是將其作為路由函數(shù)所返回響應(yīng)數(shù)據(jù)的內(nèi)部容器,容器里還包含了用于創(chuàng)建HTTP響應(yīng)的其他信息。

但是沒(méi)多少人知道,F(xiàn)lask框架其實(shí)允許應(yīng)用將默認(rèn)的響應(yīng)類碉纺,替換為自定義類态贤。這就給了我們研究小竅門的機(jī)會(huì)箱吕。在本文中,我將展示如何利用Flask的這個(gè)特性强饮,簡(jiǎn)化你的代碼。

Flask中的響應(yīng)類是如何工作的队贱?

大部分應(yīng)用并不直接使用Flask中的響應(yīng)類(Response class),但這并不是說(shuō)這個(gè)類沒(méi)有用武之地潭袱;實(shí)際上柱嫌,F(xiàn)lask會(huì)為每個(gè)請(qǐng)求創(chuàng)建響應(yīng)對(duì)象。那么屯换,它是如何實(shí)現(xiàn)的呢编丘?

Flask用來(lái)處理請(qǐng)求的函數(shù)返回時(shí),響應(yīng)周期就開始了彤悔。在網(wǎng)絡(luò)應(yīng)用中嘉抓,路由通常最后會(huì)調(diào)用render_template函數(shù),渲染引用的模板文件晕窑,將其作為字符串返回:

@app.route('/index')
def index():
    # ...
    return render_template('index.html')

但是掌眠,你可能也知道,F(xiàn)lask的路由函數(shù)可以選擇額外返回兩個(gè)值幕屹,這兩個(gè)值將被分別設(shè)為HTTP狀態(tài)碼和自定義的HTTP響應(yīng)標(biāo)頭:

@app.route('/data')
def index():
    # ...
    return render_template('data.json'), 201, {'Content-Type': 'application/json'}

在上面的例子中,狀態(tài)碼被設(shè)為201级遭,取代了Flask默認(rèn)的200望拖,即請(qǐng)求被成功處理的狀態(tài)碼。這個(gè)例子還定義了內(nèi)容類型標(biāo)頭(Content-Type header)挫鸽,表明HTTP響應(yīng)中包含JSON數(shù)據(jù)说敏,因?yàn)槿绻悴幻鞔_設(shè)置內(nèi)容類型的話,F(xiàn)lask會(huì)默認(rèn)設(shè)置為HTML丢郊。

上面的例子介紹了HTTP響應(yīng)的三個(gè)基本組成部分盔沫,即數(shù)據(jù)或正文、狀態(tài)碼和標(biāo)頭枫匾。Flask的應(yīng)用實(shí)例擁有一個(gè)make_response函數(shù)架诞,可以接受路由函數(shù)的返回值(可以是單個(gè)值,也可以是有1-3個(gè)值的元組)干茉,并將其填入響應(yīng)對(duì)象(Response object)中谴忧。

你可以通過(guò)Python控制臺(tái)會(huì)話(console session),看看整個(gè)過(guò)程角虫。首先創(chuàng)建一個(gè)虛擬環(huán)境沾谓,并安裝Flask,然后開啟Python會(huì)話戳鹅,并輸入下面的代碼:

>>> from flask import Flask
>>> app = Flask(__name__)
>>> app.make_response('Hello, World')
<Response 12 bytes [200 OK]>
>>> app.make_response(('Hello, World', 201))
<Response 12 bytes [201 CREATED]>

這里均驶,我創(chuàng)建了一個(gè)簡(jiǎn)單的Flask應(yīng)用實(shí)例,之后調(diào)用了make_response()方法創(chuàng)建響應(yīng)類對(duì)象枫虏。第一次調(diào)用時(shí)妇穴,我傳了一個(gè)字符串作為參數(shù)爬虱,所以響應(yīng)對(duì)象中使用了默認(rèn)的狀態(tài)碼和標(biāo)頭。第二次調(diào)用時(shí)伟骨,我傳入了有兩個(gè)值的元組饮潦,強(qiáng)制返回了非默認(rèn)的狀態(tài)碼。注意携狭,第二次調(diào)用時(shí)使用了兩個(gè)括號(hào)继蜡,里層的括號(hào)將字符串和狀態(tài)碼包在了元組中。由于make_response()函數(shù)只接受一個(gè)參數(shù)逛腿,所以必須要這樣做稀并。

Flask在創(chuàng)建了代表路由函數(shù)返回值的響應(yīng)對(duì)象(Response object)之后,還會(huì)做一些處理单默。包括將響應(yīng)對(duì)象傳入自定義的after_request處理程序(handlers)碘举,在這一步,應(yīng)用還有有機(jī)會(huì)插入或修改標(biāo)頭搁廓、更改正文或狀態(tài)碼引颈,如果愿意的話,甚至是啟用嶄新的的響應(yīng)對(duì)象取而代之境蜕。最后蝙场,F(xiàn)lask會(huì)獲取最終的響應(yīng)對(duì)象,渲染成HTTP響應(yīng)粱年,并發(fā)送給客戶端售滤。

Flask中的響應(yīng)類

我們來(lái)看看響應(yīng)類中最有趣的特性。下面的類定義台诗,展示了我眼中這個(gè)類所具備的靈活屬性和方法:

class Response:
    charset = 'utf-8'
    default_status = 200
    default_mimetype = 'text/html'

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

    @classmethod
    def force_type(cls, response, environ=None):
        pass

注意完箩,如果你去翻閱Flask的源碼,是找不到上述類定義的拉队。Flask中的Response類弊知,實(shí)際上衍生自Werkzeug庫(kù)中的一個(gè)同名類。而Werzeug中的Response類繼承的是BaseResponse類粱快,這個(gè)類中就包含了上述定義吉捶。

charsetdefault_statusdefault_mimetype這三個(gè)類屬性定義了相應(yīng)的默認(rèn)值皆尔。如果任何一個(gè)默認(rèn)值不適用你的應(yīng)用呐舔,那么你可以創(chuàng)建Response類的子類,定義你自己的默認(rèn)值慷蠕,而不必在每一個(gè)響應(yīng)對(duì)象中設(shè)置自定義值珊拼。例如,如果你的應(yīng)用是一個(gè)所有的路由均返回XML格式數(shù)據(jù)的API接口流炕,你就可以在自定義的類中澎现,將default_mimetype改為application/xml仅胞,這樣Flask就會(huì)默認(rèn)返回XML響應(yīng)。稍后你會(huì)看到如何實(shí)現(xiàn)剑辫。

這里干旧,我不會(huì)詳細(xì)介紹__init__構(gòu)造函數(shù)(你可以閱讀Werkzeug的文檔),但請(qǐng)注意妹蔽,F(xiàn)lask響應(yīng)對(duì)象中的三個(gè)重要元素椎眯,即響應(yīng)正文、狀態(tài)碼和標(biāo)頭胳岂,是作為參數(shù)傳入的编整。在子類中,構(gòu)造函數(shù)可以改變創(chuàng)建響應(yīng)的相應(yīng)規(guī)則乳丰。

響應(yīng)類中的force_type()類方法掌测,是唯一比較復(fù)雜,但又很重要的元素产园。有時(shí)候汞斧,Werkzeug或是Flask需要自行創(chuàng)建響應(yīng)對(duì)象,比如出現(xiàn)應(yīng)用錯(cuò)誤什燕,并需要將其返回給客戶端時(shí)断箫。在這種情況下,響應(yīng)對(duì)象不是應(yīng)用提供的秋冰,而是由框架創(chuàng)建的。在使用自定義響應(yīng)類的應(yīng)用中婶熬,F(xiàn)lask和Werkzeug無(wú)法知道自定義類的細(xì)節(jié)剑勾,所以它們使用標(biāo)準(zhǔn)響應(yīng)類來(lái)創(chuàng)建響應(yīng)。響應(yīng)類中的force_type()方法赵颅,被設(shè)計(jì)為可以接受不同響應(yīng)類的實(shí)例虽另,并會(huì)將其轉(zhuǎn)換成自身的格式。

我敢肯定饺谬,你一定被force_type()方法的描述搞糊涂了捂刺。說(shuō)白了,就是如果Flask碰到了一個(gè)不是其期望的響應(yīng)對(duì)象募寨,就會(huì)使用該方法進(jìn)行轉(zhuǎn)換族展。我下面要講的第三個(gè)使用場(chǎng)景,就利用了這個(gè)特點(diǎn)拔鹰,讓Flask的路由函數(shù)返回諸如字典仪缸、列表或者是其他任何自定義對(duì)象,作為請(qǐng)求的響應(yīng)對(duì)象列肢。

好了恰画,理論就講這么多了宾茂。接下來(lái),我來(lái)告訴大家如何應(yīng)用上面有關(guān)響應(yīng)類的小技巧拴还。準(zhǔn)備好了嗎跨晴?

使用自定義的響應(yīng)類

到現(xiàn)在為止,我確定你也會(huì)認(rèn)為:在部分有趣的場(chǎng)景下片林,使用自定義的響應(yīng)類是有利的端盆。在給出實(shí)際例子之前,我想告訴你在Flask中設(shè)置并使用自定義的響應(yīng)類是多么的簡(jiǎn)單拇厢。請(qǐng)看下面的這個(gè)例子:

from flask import Flask, Response

class MyResponse(Response):
    pass

app = Flask(__name__)
app.response_class = MyResponse

# ...

在上面的代碼中爱谁,我定義了一個(gè)名叫MyResponse的自定義響應(yīng)類。通常孝偎,自定義響應(yīng)類會(huì)增加或修改默認(rèn)類的行為访敌,所以一般都會(huì)通過(guò)創(chuàng)建Flask中Response類的子類來(lái)實(shí)現(xiàn)。要想讓Flask使用自定義類衣盾,我只需要設(shè)置app.response_class即可寺旺。

Flask類中的response_class是一個(gè)類屬性,所以我們可以稍微修改上面的例子势决,創(chuàng)建一個(gè)設(shè)置了自定義響應(yīng)類的Flask子類:

from flask import Flask, Response

class MyResponse(Response):
    pass

class MyFlask(Flask)
    response_class = MyResponse

app = MyFlask(__name__)

# ...

例1:更改響應(yīng)對(duì)象的默認(rèn)值

第一個(gè)例子極其簡(jiǎn)單阻塑。假設(shè)你的應(yīng)用中大部分或全部端點(diǎn)(endpoints)都返回的是XML。對(duì)于這樣的應(yīng)用果复,將默認(rèn)的內(nèi)容類型設(shè)置為application/xml是合理的陈莽。可以通過(guò)下面這個(gè)僅有兩行代碼的響應(yīng)類輕松實(shí)現(xiàn):

class MyResponse(Response):
    default_mimetype = 'application/xml'

容易虽抄,對(duì)吧走搁?如果將其設(shè)為應(yīng)用的默認(rèn)響應(yīng)類,那么你在編寫返回XML的函數(shù)時(shí)迈窟,就不用擔(dān)心忘記設(shè)置內(nèi)容類型了私植。舉個(gè)例子:

@app.route('/data')
def get_data():
    return '''<?xml version="1.0" encoding="UTF-8"?>
<person>
    <name>John Smith</name>
</person>
'''

上面這個(gè)路由使用的是默認(rèn)響應(yīng)類,其內(nèi)容類型會(huì)被設(shè)置為text/html车酣,因?yàn)槟鞘悄J(rèn)類型曲稼。使用自定義響應(yīng)類,可以免去你在所有XML路由的返回語(yǔ)句中湖员,額外加上標(biāo)頭的麻煩贫悄。另外,如果有的路由需要其他的內(nèi)容類型娘摔,你仍可以替換掉默認(rèn)值清女,就像對(duì)待一般的響應(yīng)類一樣。

@app.route('/')
def index():
    return '<h1>Hello, World!</h1>', {'Content-Type': 'text/html'}

例2:自動(dòng)決定內(nèi)容類型

下一個(gè)例子更復(fù)雜一點(diǎn)晰筛。假設(shè)我們的應(yīng)用中HTML路由與XML路由的數(shù)量差不多嫡丙,所以按照第一個(gè)例子的做法就不行了拴袭,因?yàn)椴还苣氵x用哪種默認(rèn)類型,都會(huì)有一半的路由需要替換內(nèi)容類型曙博。

更好的解決辦法拥刻,則是創(chuàng)建一個(gè)能夠通過(guò)分析響應(yīng)文本,決定正確的內(nèi)容類型的響應(yīng)類父泳。下面的這個(gè)類實(shí)現(xiàn)了該功能:

class MyResponse(Response):
    def __init__(self, response, **kwargs):
        if 'mimetype' not in kwargs and 'contenttype' not in kwargs:
            if response.startswith('<?xml'):
                kwargs['mimetype'] = 'application/xml'
        return super(MyResponse, self).__init__(response, **kwargs)

在這個(gè)簡(jiǎn)單的例子中般哼,我首先確保響應(yīng)對(duì)象中沒(méi)有明確設(shè)置內(nèi)容類型。然后惠窄,我檢查響應(yīng)的正文是否以<?xml開頭蒸眠,是的話就意味著數(shù)據(jù)是XML文檔格式。如果兩個(gè)條件同時(shí)成立杆融,我會(huì)在傳入父類構(gòu)造函數(shù)的參數(shù)中楞卡,插入XML內(nèi)容類型。

有了這個(gè)自定義響應(yīng)類脾歇,任何滿足XML格式要求的文檔都會(huì)自動(dòng)被標(biāo)記為XML內(nèi)容類型蒋腮,而其他響應(yīng)則會(huì)繼續(xù)獲得默認(rèn)的內(nèi)容類型。而且藕各,在所有的類中池摧,我仍然可以在必要時(shí)聲明內(nèi)容類型。

例3:自動(dòng)返回JSON響應(yīng)

最后一個(gè)例子激况,針對(duì)的是利用Flask創(chuàng)建API接口時(shí)常見(jiàn)的一個(gè)小問(wèn)題作彤。API接口通常返回的是JSON凈負(fù)荷(JSON Payload,這就要求你使用jsonify()函數(shù)將Python字典類型轉(zhuǎn)換成JSON數(shù)據(jù)乌逐,并且還得在響應(yīng)對(duì)象中將內(nèi)容類型設(shè)置為JSON內(nèi)容類型竭讳。請(qǐng)看下面這個(gè)例子:

@app.route('/data')
def get_data():
    return jsonify({'foo': 'bar'})

問(wèn)題是,每個(gè)返回JSON的路由都需要這樣處理黔帕,那么對(duì)接口數(shù)量眾多的的API來(lái)說(shuō),你就得大量重復(fù)調(diào)用jsonify()函數(shù)蹈丸。從代碼可讀性角度來(lái)講成黄,你按照下面的方式處理是不是更好?

@app.route('/data')
def get_data():
    return {'foo': 'bar'}

下面是一個(gè)支持使用上述語(yǔ)法的自定義響應(yīng)類逻杖,它不會(huì)影響應(yīng)用中使用其他內(nèi)容類型的路由正常工作:

class MyResponse(Response):
    @classmethod
    def force_type(cls, rv, environ=None):
        if isinstance(rv, dict):
            rv = jsonify(rv)
        return super(MyResponse, cls).force_type(rv, environ)

這個(gè)例子需要稍微解釋一下奋岁,因?yàn)楸容^復(fù)雜。Flask僅認(rèn)可一小部分的類型荸百,作為路由函數(shù)能夠返回的有效響應(yīng)類型闻伶。基本上够话,你可以返回任意與字符串和二進(jìn)制相關(guān)的類型(str蓝翰、unicode光绕、bytesbytearray)畜份。如果你喜歡诞帐,甚至可以返回一個(gè)已經(jīng)創(chuàng)建好的響應(yīng)對(duì)象。如果你返回的是字符串或二進(jìn)制類型爆雹,F(xiàn)lask會(huì)發(fā)現(xiàn)這些是響應(yīng)類知道如何處理的類型停蕉,并會(huì)將你返回的數(shù)據(jù)直接傳入響應(yīng)類的構(gòu)造函數(shù)。

但是钙态,如果你返回的是不支持的類型慧起,比如說(shuō)上述例子中的字典,會(huì)發(fā)生什么情況册倒?如果返回的響應(yīng)類型不是Flask預(yù)期的蚓挤,那么Flask就會(huì)默認(rèn)它是未知響應(yīng)對(duì)象,不會(huì)以其為參數(shù)創(chuàng)建響應(yīng)對(duì)象了剩失,而是使用響應(yīng)類的force_type()類方法屈尼,強(qiáng)制轉(zhuǎn)換未知類型。上面的例子中拴孤,響應(yīng)子類替換了該方法脾歧,但僅僅是通過(guò)調(diào)用jsonify()進(jìn)行轉(zhuǎn)換,之后就會(huì)讓基類接手處理演熟,就好像什么都沒(méi)發(fā)生一樣鞭执。

是個(gè)很好的竅門吧?尤其是這樣做不會(huì)影響其他響應(yīng)的正常工作芒粹。對(duì)于返回正常響應(yīng)類型的路由兄纺,該子類不會(huì)做任何處理,所有的調(diào)用請(qǐng)求會(huì)全部傳入父類中化漆。

結(jié)語(yǔ)

我希望本文能夠幫助大家更好地理解FLask中響應(yīng)對(duì)象的工作原理估脆。如果你知道其他使用Flask響應(yīng)類的小竅門,請(qǐng)務(wù)必與我分享座云!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疙赠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子朦拖,更是在濱河造成了極大的恐慌圃阳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璧帝,死亡現(xiàn)場(chǎng)離奇詭異捍岳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門锣夹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)页徐,“玉大人,你說(shuō)我怎么就攤上這事晕城∨⑻梗” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵砖顷,是天一觀的道長(zhǎng)贰锁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)滤蝠,這世上最難降的妖魔是什么豌熄? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮物咳,結(jié)果婚禮上锣险,老公的妹妹穿的比我還像新娘。我一直安慰自己览闰,他們只是感情好芯肤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著压鉴,像睡著了一般崖咨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上油吭,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天击蹲,我揣著相機(jī)與錄音,去河邊找鬼婉宰。 笑死歌豺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的心包。 我是一名探鬼主播类咧,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蟹腾!你這毒婦竟也來(lái)了痕惋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤岭佳,失蹤者是張志新(化名)和其女友劉穎血巍,沒(méi)想到半個(gè)月后萧锉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珊随,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了叶洞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鲫凶。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖衩辟,靈堂內(nèi)的尸體忽然破棺而出螟炫,到底是詐尸還是另有隱情,我是刑警寧澤艺晴,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布昼钻,位于F島的核電站,受9級(jí)特大地震影響封寞,放射性物質(zhì)發(fā)生泄漏然评。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一狈究、第九天 我趴在偏房一處隱蔽的房頂上張望碗淌。 院中可真熱鬧,春花似錦抖锥、人聲如沸亿眠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)纳像。三九已至,卻和暖如春还蹲,著一層夾襖步出監(jiān)牢的瞬間爹耗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工谜喊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留潭兽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓斗遏,卻偏偏與公主長(zhǎng)得像山卦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诵次,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理账蓉,服務(wù)發(fā)現(xiàn),斷路器逾一,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 22年12月更新:個(gè)人網(wǎng)站關(guān)停铸本,如果仍舊對(duì)舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,184評(píng)論 22 257
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 10,983評(píng)論 6 13
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 我記得, 曾經(jīng)有只小飛機(jī)遵堵, 它愛(ài)飛箱玷, 它愛(ài)飛在我的哭鬧中怨规, 愛(ài)飛到我的搖籃里。 我記得锡足, 曾經(jīng)有塊糖在搖籃里波丰, 它...
    安藝微閱讀 292評(píng)論 0 0