譯文連接:編程派html
原文連接:Flask Web Development做者的博客編程
有翻譯或理解不對的地方,望你們指正!json
Flask框架中的響應類,命名很貼切,叫Response
。不過Flask應用中不多直接調用這個類。而是將其做爲路由函數所返回響應數據的內部容器,容器裏還包含了用於建立HTTP響應的其餘信息。flask
可是沒多少人知道,Flask框架其實容許應用將默認的響應類,替換爲自定義類。這就給了咱們研究小竅門的機會。在本文中,我將展現如何利用Flask的這個特性,簡化你的代碼。網絡
大部分應用並不直接使用Flask中的響應類(Response
class),但這並非說這個類沒有用武之地;實際上,Flask會爲每一個請求建立響應對象。那麼,它是如何實現的呢?session
Flask用來處理請求的函數返回時,響應週期就開始了。在網絡應用中,路由一般最後會調用render_template
函數,渲染引用的模板文件,將其做爲字符串返回:併發
@app.route('/index') def index(): # ... return render_template('index.html')
可是,你可能也知道,Flask的路由函數能夠選擇額外返回兩個值,這兩個值將被分別設爲HTTP狀態碼和自定義的HTTP響應標頭:app
@app.route('/data') def index(): # ... return render_template('data.json'), 201, {'Content-Type': 'application/json'}
在上面的例子中,狀態碼被設爲201,取代了Flask默認的200,即請求被成功處理的狀態碼。這個例子還定義了內容類型標頭(Content-Type header
),代表HTTP響應中包含JSON數據,由於若是你不明確設置內容類型的話,Flask會默認設置爲HTML。框架
上面的例子介紹了HTTP響應的三個基本組成部分,即數據或正文、狀態碼和標頭。Flask的應用實例擁有一個make_response
函數,能夠接受路由函數的返回值(能夠是單個值,也能夠是有1-3個值的元組),並將其填入響應對象(Response object
)中。函數
你能夠經過Python控制檯會話(console session
),看看整個過程。首先建立一個虛擬環境,並安裝Flask,而後開啓Python會話,並輸入下面的代碼:
>>> 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]>
這裏,我建立了一個簡單的Flask應用實例,以後調用了make_response()
方法建立響應類對象。第一次調用時,我傳了一個字符串做爲參數,因此響應對象中使用了默認的狀態碼和標頭。第二次調用時,我傳入了有兩個值的元組,強制返回了非默認的狀態碼。注意,第二次調用時使用了兩個括號,裏層的括號將字符串和狀態碼包在了元組中。因爲make_response()
函數只接受一個參數,因此必需要這樣作。
Flask在建立了表明路由函數返回值的響應對象(Response object
)以後,還會作一些處理。包括將響應對象傳入自定義的after_request
處理程序(handlers),在這一步,應用還有有機會插入或修改標頭、更改正文或狀態碼,若是願意的話,甚至是啓用嶄新的的響應對象取而代之。最後,Flask會獲取最終的響應對象,渲染成HTTP響應,併發送給客戶端。
咱們來看看響應類中最有趣的特性。下面的類定義,展現了我眼中這個類所具有的靈活屬性和方法:
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
類,實際上衍生自Werkzeug庫中的一個同名類。而Werzeug中的Response
類繼承的是BaseResponse
類,這個類中就包含了上述定義。
charset
、default_status
和default_mimetype
這三個類屬性定義了相應的默認值。若是任何一個默認值不適用你的應用,那麼你能夠建立Response
類的子類,定義你本身的默認值,而沒必要在每個響應對象中設置自定義值。例如,若是你的應用是一個全部的路由均返回XML格式數據的API接口,你就能夠在自定義的類中,將default_mimetype
改成application/xml
,這樣Flask就會默認返回XML響應。稍後你會看到如何實現。
這裏,我不會詳細介紹__init__
構造函數(你能夠閱讀Werkzeug的文檔),但請注意,Flask響應對象中的三個重要元素,即響應正文、狀態碼和標頭,是做爲參數傳入的。在子類中,構造函數能夠改變建立響應的相應規則。
響應類中的force_type()
類方法,是惟一比較複雜,但又很重要的元素。有時候,Werkzeug或是Flask須要自行建立響應對象,好比出現應用錯誤,並須要將其返回給客戶端時。在這種狀況下,響應對象不是應用提供的,而是由框架建立的。在使用自定義響應類的應用中,Flask和Werkzeug沒法知道自定義類的細節,因此它們使用標準響應類來建立響應。響應類中的force_type()
方法,被設計爲能夠接受不一樣響應類的實例,並會將其轉換成自身的格式。
我敢確定,你必定被force_type()
方法的描述搞糊塗了。說白了,就是若是Flask碰到了一個不是其指望的響應對象,就會使用該方法進行轉換。我下面要講的第三個使用場景,就利用了這個特色,讓Flask的路由函數返回諸如字典、列表或者是其餘任何自定義對象,做爲請求的響應對象。
好了,理論就講這麼多了。接下來,我來告訴你們如何應用上面有關響應類的小技巧。準備好了嗎?
到如今爲止,我肯定你也會認爲:在部分有趣的場景下,使用自定義的響應類是有利的。在給出實際例子以前,我想告訴你在Flask中設置並使用自定義的響應類是多麼的簡單。請看下面的這個例子:
from flask import Flask, Response class MyResponse(Response): pass app = Flask(__name__) app.response_class = MyResponse # ...
在上面的代碼中,我定義了一個名叫MyResponse
的自定義響應類。一般,自定義響應類會增長或修改默認類的行爲,因此通常都會經過建立Flask中Response
類的子類來實現。要想讓Flask使用自定義類,我只須要設置app.response_class
便可。
Flask
類中的response_class
是一個類屬性,因此咱們能夠稍微修改上面的例子,建立一個設置了自定義響應類的Flask子類:
from flask import Flask, Response class MyResponse(Response): pass class MyFlask(Flask) response_class = MyResponse app = MyFlask(__name__) # ...
第一個例子極其簡單。假設你的應用中大部分或所有端點(endpoints)都返回的是XML。對於這樣的應用,將默認的內容類型設置爲application/xml
是合理的。能夠經過下面這個僅有兩行代碼的響應類輕鬆實現:
class MyResponse(Response): default_mimetype = 'application/xml'
容易,對吧?若是將其設爲應用的默認響應類,那麼你在編寫返回XML的函數時,就不用擔憂忘記設置內容類型了。舉個例子:
@app.route('/data') def get_data(): return '''<?xml version="1.0" encoding="UTF-8"?> <person> <name>John Smith</name> </person> '''
上面這個路由使用的是默認響應類,其內容類型會被設置爲text/html
,由於那是默認類型。使用自定義響應類,能夠免去你在全部XML路由的返回語句中,額外加上標頭的麻煩。另外,若是有的路由須要其餘的內容類型,你仍能夠替換掉默認值,就像對待通常的響應類同樣。
@app.route('/') def index(): return '<h1>Hello, World!</h1>', {'Content-Type': 'text/html'}
下一個例子更復雜一點。假設咱們的應用中HTML路由與XML路由的數量差很少,因此按照第一個例子的作法就不行了,由於無論你選用哪一種默認類型,都會有一半的路由須要替換內容類型。
更好的解決辦法,則是建立一個可以經過分析響應文本,決定正確的內容類型的響應類。下面的這個類實現了該功能:
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)
在這個簡單的例子中,我首先確保響應對象中沒有明確設置內容類型。而後,我檢查響應的正文是否以<?xml
開頭,是的話就意味着數據是XML文檔格式。若是兩個條件同時成立,我會在傳入父類構造函數的參數中,插入XML內容類型。
有了這個自定義響應類,任何知足XML格式要求的文檔都會自動被標記爲XML內容類型,而其餘響應則會繼續得到默認的內容類型。並且,在全部的類中,我仍然能夠在必要時聲明內容類型。
最後一個例子,針對的是利用Flask建立API接口時常見的一個小問題。API接口一般返回的是JSON淨負荷(JSON Payload,這就要求你使用jsonify()
函數將Python字典類型轉換成JSON數據,而且還得在響應對象中將內容類型設置爲JSON內容類型。請看下面這個例子:
@app.route('/data') def get_data(): return jsonify({'foo': 'bar'})
問題是,每一個返回JSON的路由都須要這樣處理,那麼對接口數量衆多的的API來講,你就得大量重複調用jsonify()
函數。從代碼可讀性角度來說,你按照下面的方式處理是否是更好?
@app.route('/data') def get_data(): return {'foo': 'bar'}
下面是一個支持使用上述語法的自定義響應類,它不會影響應用中使用其餘內容類型的路由正常工做:
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)
這個例子須要稍微解釋一下,由於比較複雜。Flask僅承認一小部分的類型,做爲路由函數可以返回的有效響應類型。基本上,你能夠返回任意與字符串和二進制相關的類型(str
、unicode
、bytes
、bytearray
)。若是你喜歡,甚至能夠返回一個已經建立好的響應對象。若是你返回的是字符串或二進制類型,Flask會發現這些是響應類知道如何處理的類型,並會將你返回的數據直接傳入響應類的構造函數。
可是,若是你返回的是不支持的類型,好比說上述例子中的字典,會發生什麼狀況?若是返回的響應類型不是Flask預期的,那麼Flask就會默認它是未知響應對象,不會以其爲參數建立響應對象了,而是使用響應類的force_type()
類方法,強制轉換未知類型。上面的例子中,響應子類替換了該方法,但僅僅是經過調用jsonify()
進行轉換,以後就會讓基類接手處理,就好像什麼都沒發生同樣。
是個很好的竅門吧?尤爲是這樣作不會影響其餘響應的正常工做。對於返回正常響應類型的路由,該子類不會作任何處理,全部的調用請求會所有傳入父類中。
我但願本文可以幫助你們更好地理解FLask中響應對象的工做原理。若是你知道其餘使用Flask響應類的小竅門,請務必與我分享!