【譯】如何自定義Flask中的響應類
- 原文連結:Flask Web Development作者的部落格
- 譯文連結:程式設計派
有翻譯或理解不對的地方,望大家指正!
Flask框架中的響應類,命名很貼切,叫Response
。不過Flask應用中很少直接呼叫這個類。而是將其作為路由函式所返回響應資料的內部容器,容器裡還包含了用於建立HTTP響應的其他資訊。
但是沒多少人知道,Flask框架其實允許應用將預設的響應類,替換為自定義類。這就給了我們研究小竅門的機會。在本文中,我將展示如何利用Flask的這個特性,簡化你的程式碼。
Flask中的響應類是如何工作的?
大部分應用並不直接使用Flask中的響應類(Response
class),但這並不是說這個類沒有用武之地;實際上,Flask會為每個請求建立響應物件。那麼,它是如何實現的呢?
Flask用來處理請求的函式返回時,響應週期就開始了。在網路應用中,路由通常最後會呼叫render_template
函式,渲染引用的模板檔案,將其作為字串返回:
@app.route('/index')
def index():
# ...
return render_template('index.html')
但是,你可能也知道,Flask的路由函式可以選擇額外返回兩個值,這兩個值將被分別設為HTTP狀態碼和自定義的HTTP響應標頭:
@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響應,併傳送給客戶端。
Flask中的響應類
我們來看看響應類中最有趣的特性。下面的類定義,展示了我眼中這個類所具備的靈活屬性和方法:
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__)
# ...
例1:更改響應物件的預設值
第一個例子極其簡單。假設你的應用中大部分或全部端點(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'}
例2:自動決定內容型別
下一個例子更復雜一點。假設我們的應用中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內容型別,而其他響應則會繼續獲得預設的內容型別。而且,在所有的類中,我仍然可以在必要時宣告內容型別。
例3:自動返回JSON響應
最後一個例子,針對的是利用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響應類的小竅門,請務必與我分享!
相關文章
- .NET Core授權失敗如何自定義響應資訊?
- 自定義響應資料結構資料結構
- [Flutter翻譯]如何在Backendless Flutter SDK中使用自定義類Flutter
- [譯] Part 31: Golang 中的自定義ErrorGolangError
- BeetleX之webapi自定義響應內容WebAPI
- 由自定義事件到vue資料響應事件Vue
- TDengine 3.0 中如何編譯、建立和使用自定義函式編譯函式
- flask 原始碼解析:響應Flask原始碼
- C#中自定義異常類C#
- MFC vc++ 中CTreeContrl如何自定義實現滑鼠單擊或雙擊響應事件 ,即重寫類似於控制元件的響應事件或訊息C++事件控制元件
- SpringBoot中,如何把自定義的yml配置檔案中的內容和對應類的bean物件取出Spring BootBean物件
- Flutter自定義View以及響應式UI框架原理FlutterViewUI框架
- 如何在Flask中整合Dash應用Flask
- java中如何自定義註解Java
- 在Laravel 中如何自定義servicesLaravel
- ETL中如何自定義規則
- Flask_restful 之 自定義錯誤資訊FlaskREST
- 【譯】vue 自定義指令的魅力Vue
- 【譯】移動應用開發:如何建立自定義Android程式碼模板Android
- Java的自定義異常類Java
- 自定義異常類
- Canvas類的最全面詳解 - 自定義View應用系列CanvasView
- 如何優雅的定義統一響應物件物件
- [譯] 教你如何用 Flutter 的 GestureDetector 構建自定義滑塊Flutter
- 如何用Flask中的Blueprints構建大型Web應用FlaskWeb
- python中什麼時候使用自定義類Python
- JumpList中Recent類別和自定義型別薦型別
- 聊聊自定義SPI如何使用自定義標籤注入到spring容器中Spring
- python Flask框架學習——Response響應PythonFlask框架
- Flask 應用如何部署Flask
- 如何在 pyqt 中自定義工具提示 ToolTipQT
- objc系列譯文(12.2):Layer中自定義屬性的動畫OBJ動畫
- VC自定義訊息postmessage用法(訊息響應函式)函式
- motorola手機中在主類中響應其它類中的按鈕訊息 (轉)
- [譯] 使用自定義檔案模板加快你的應用開發速度
- 【譯】CSS 自定義屬性的策略指南CSS
- SpringBoot應用使用自定義的ApplicationContext實現類Spring BootAPPContext
- 在VC++中建立自定義資料庫類 (轉)C++資料庫