摘要:我們知道響應(yīng)分為三個部分狀態(tài)欄版本狀態(tài)碼和說明頭部以冒號隔開的字符對,用于各種控制和協(xié)商服務(wù)端返回的數(shù)據(jù)。
這是 flask 源碼解析系列文章的其中一篇,本系列所有文章列表:
flask 源碼解析:簡介
flask 源碼解析:應(yīng)用啟動流程
flask 源碼解析:路由
flask 源碼解析:上下文
flask 源碼解析:請求
flask 源碼解析:響應(yīng)
response 簡介在 flask 應(yīng)用中,我們只需要編寫 view 函數(shù),并不需要直接和響應(yīng)(response)打交道,flask 會自動生成響應(yīng)返回給客戶端。
The return value from a view function is automatically converted into a response object for you.
—— Flask docs
我們知道 HTTP 響應(yīng)分為三個部分:
狀態(tài)欄(HTTP 版本、狀態(tài)碼和說明)、頭部(以冒號隔開的字符對,用于各種控制和協(xié)商)、body(服務(wù)端返回的數(shù)據(jù))。比如下面訪問博客首頁的響應(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
flask 自然也會提供所有這些數(shù)據(jù)的操作,視圖函數(shù)就支持返回三個值:第一個是返回的數(shù)據(jù),第二個是狀態(tài)碼,第三個是頭部字典。比如:
@app.route("/") def hello_world(): return "Hello, World!", 201, {"X-Foo": "bar"}
這篇文章就講講這背后的魔法。
flask 響應(yīng)(response)在 flask 源碼解析:應(yīng)用啟動流程 的最后,我們講到 full_dsipatch_request 在調(diào)用路由到視圖函數(shù)之后,會調(diào)用 finalize_request 進行最后的處理,在這個方法里就包含了 response 對象的生成和處理邏輯。
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
里面有兩個方法調(diào)用:make_response 根據(jù)視圖函數(shù)的返回值生成 response 對象,process_response 對 response 做一些后續(xù)的處理(比如執(zhí)行 hooks 函數(shù))。我們先來看看 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ù)能返回多個不同數(shù)量和類型值的關(guān)鍵,因為它能處理這些情況,統(tǒng)一把它們轉(zhuǎn)換成 response。
如果返回值本身就是 Response 實例,就直接使用它;如果返回值是字符串類型,就把它作為響應(yīng)的 body,并自動設(shè)置狀態(tài)碼和頭部信息;
如果返回值是 tuple,會嘗試用 (response, status, headers) 或者 (response, headers) 去解析。
NOTE:因為視圖函數(shù)可以返回 Response 對象,因此我們可以直接操作 Response。
不管視圖函數(shù)返回的是什么,最終都會變成 Response 對象,那么我們就來看看 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 類非常簡單,它只是繼承了 werkzeug.wrappers:Response,然后設(shè)置默認(rèn)返回類型為 html。
不過從注釋中,我們得到兩條很有用的信息:
一般情況下不要直接操作 Response 對象,而是使用 make_response 方法來生成它
如果需要使用自定義的響應(yīng)對象,可以覆蓋 flask app 對象的 response_class 屬性。
繼續(xù),下面就要分析 werkzeug 對應(yīng)的代碼了。
werkzeug responsewerkzeug 實現(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 """
和我們在 flask 請求分析的 Request 類一樣,這里使用了 Mixin 機制。BaseResponse 精簡后的大概框架如下:
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ù)有:
response: 字符串或者其他 iterable 對象,作為響應(yīng)的 body
status: 狀態(tài)碼,可以是整數(shù),也可以是字符串
headers: 響應(yīng)的頭部,可以是個列表,也可以是 werkzeug.datastructures.Headers 對象
mimetype: mimetype 類型,告訴客戶端響應(yīng) body 的格式,默認(rèn)是文本格式
content_type: 響應(yīng)頭部的 Content-Type 內(nèi)容
所有這些參數(shù)都是可選的,默認(rèn)情況下會生成一個狀態(tài)碼為 200,沒有任何 body 的響應(yīng)。status、status_code 作為 Response 的屬性,可以直接讀取和修改。body 數(shù)據(jù)在內(nèi)部保存為 iterable 的類型,
但是對外也提供了直接讀寫的接口 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 字符的編碼和長度都是自動設(shè)置的,用戶不需要手動處理。
至于頭部的存儲,werkzeug 使用的是類似于字典的 werkzeug.datastructures:Headers 類。在flask 源碼解析:請求這篇文章中,我們沒有詳細(xì)
解釋頭部的存儲,那么這篇文章就具體分析一下吧。
Headers 這個類的提供了很多和字典相同的接口:keys、values、iterms,但是和字典的區(qū)別在于它保存的值是有序的,而且允許相同 key 的值存在。
為什么這么設(shè)計呢?因為著更符合 HTTP 頭部的特性。先來看看有序,在 HTTP 傳送的過程中,如果頭部各個 key-value 鍵值對順序發(fā)生變化,有些代理或者客戶端等組件會認(rèn)為請求被篡改而丟棄或者拒絕請求的處理,所以最好把頭部設(shè)置為有序的,用戶按照什么順序設(shè)置的,就按照什么順序存儲;再說說相同 key 的問題,這是因為 HTTP 頭部同一個 key 可能有多個 value(比如 Accept、SetCookie頭部)。那么這個看起比較特殊的字典是怎么實現(xiàn)的呢?來看代碼:
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)部存儲為二元組構(gòu)成的列表,這樣就能同時保證它的有序性和重復(fù)性。一個核心的方法是 __getitem__,它定義了如何獲取頭部中的信息:
通過下標(biāo) header[3],直接返回對應(yīng)未知存儲的鍵值對元組
通過 key,返回 value header["Accept"],返回匹配的第一個 value 值
通過 slice header[3:7],返回另外一個 Headers 對象,保存了 slice 中所有的數(shù)據(jù)
然后實現(xiàn) keys()、items()、pop()、setdefault() 等方法讓它表現(xiàn)出來字典的特性,除此之外還有 add()、extend()、add_header() 等和字典無關(guān)的方法方便操作。
自定義 response如果需要擴展 flask Response 的功能,或者干脆把它替換掉,只要修改 flask app 的 response_class 屬性就可以了,比如:
from flask import Flask, Response class MyResponse(Response): pass app = Flask(__name__) app.response_class = MyResponse
更多可能的用法,可以參考文章末尾的參考資料。
參考資料customizing the flask response class
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/38446.html
摘要:引言本文主要梳理了源碼中的設(shè)計思路。協(xié)議將處理請求的組件按照功能及調(diào)用關(guān)系分成了三種。不論是什么,最終都會調(diào)用函數(shù)。 引言 本文主要梳理了flask源碼中route的設(shè)計思路。首先,從WSGI協(xié)議的角度介紹flask route的作用;其次,詳細(xì)講解如何借助werkzeug庫的Map、Rule實現(xiàn)route;最后,梳理了一次完整的http請求中route的完整流程。 flask rou...
摘要:簡介官網(wǎng)上對它的定位是一個微開發(fā)框架。另外一個必須理解的概念是,簡單來說就是一套和框架應(yīng)用之間的協(xié)議。功能比較豐富,支持解析自動防止攻擊繼承變量過濾器流程邏輯支持代碼邏輯集成等等。那么,從下一篇文章,我們就正式開始源碼之旅了 文章屬于作者原創(chuàng),原文發(fā)布在個人博客。 flask 簡介 Flask 官網(wǎng)上對它的定位是一個微 python web 開發(fā)框架。 Flask is a micro...
摘要:可以看到,雖然是同樣的請求數(shù)據(jù),在不同的階段和不同組件看來,是完全不同的形式。請求還有一個不那么明顯的特性它不能被應(yīng)用修改,應(yīng)用只能讀取請求的數(shù)據(jù)。 這是 flask 源碼解析系列文章的其中一篇,本系列所有文章列表: flask 源碼解析:簡介 flask 源碼解析:應(yīng)用啟動流程 flask 源碼解析:路由 flask 源碼解析:上下文 flask 源碼解析:請求 flask 源碼解...
摘要:中有一個非常重要的概念每個應(yīng)用都是一個可調(diào)用的對象。它規(guī)定了的接口,會調(diào)用,并傳給它兩個參數(shù)包含了請求的所有信息,是處理完之后需要調(diào)用的函數(shù),參數(shù)是狀態(tài)碼響應(yīng)頭部還有錯誤信息。一般來說,嵌套的最后一層是業(yè)務(wù)應(yīng)用,中間就是。 文章屬于作者原創(chuàng),原文發(fā)布在個人博客。 WSGI 所有的 python web 框架都要遵循 WSGI 協(xié)議,如果對 WSGI 不清楚,可以查看我之前的介紹文章。 ...
閱讀 1193·2021-11-15 18:00
閱讀 1789·2021-10-08 10:15
閱讀 752·2021-09-04 16:48
閱讀 2373·2021-09-04 16:48
閱讀 1313·2019-08-29 18:40
閱讀 965·2019-08-29 13:08
閱讀 2987·2019-08-26 14:06
閱讀 1111·2019-08-26 13:35