werkzeug的Request探究
werkzeug提供了Request類封裝請求。位于werkzeug.wrappers子產品裡。
class Request(
BaseRequest,
AcceptMixin,
ETagRequestMixin,
UserAgentMixin,
AuthorizationMixin,
CommonRequestDescriptorsMixin,
):
"""Full featured request object implementing the following mixins:
- :class:`AcceptMixin` for accept header parsing
- :class:`ETagRequestMixin` for etag and cache control handling
- :class:`UserAgentMixin` for user agent introspection
- :class:`AuthorizationMixin` for http auth handling
- :class:`CommonRequestDescriptorsMixin` for common headers
"""
請求對象有以下規則:
- 請求對象是不可變對象,除非修改内部資料結構。
- 請求對象是線程共享的,不是線程安全的。如果在多線程中使用清加鎖。這個規則實際上是針對請求對象作為全局變量而言的,如flask,是以flask實作了request線程隔離。
- 請求對象不能被序列化。
可以看出,這個類是通過繼承其他類類實作功能的。基本功能由BaseRequest類提供(意味着如果不需要這些混入類的功能,Request類和BaseRequest類功能一樣),其他功能由Mixin類提供。從繼承的類可以看出,我們也可以通過繼承新的Mixin類來增加功能。預設下繼承了5個Mixin類。
- AcceptMixin提供了解析請求頭功能。
- ETagResponseMixin提供了etag和cache處理功能。
- UserAgentMixin提供了使用者代理内省功能。
- AuthorizationMixin提供了HTTP認證處理。
- CommonRequestDescriptorsMixin提供了各種HTTP請求頭字段描述器。
分析
先分析出現較多的兩個描述器:
@cached_property
和
environ_property
。
@cached_property
class cached_property(property):
def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __set__(self, obj, value):
obj.__dict__[self.__name__] = value
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
這個描述器的作用是把函數轉變為懶惰屬性。懶惰屬性就是隻有當真正調用時才會執行計算且當第一次調用時會計算一次,之後直接擷取結果的屬性。
cached_property
類繼承
property
類,重寫了__init__、set、和__get__方法。如:
@cached_property
def host(self):
return get_host(self.environ, trusted_hosts=self.trusted_hosts)
當第一次調用request.host時,會把request對象作為obj,request的類作為type傳入__get__,是以request的字典沒有host這個屬性,是以執行這條語句
value = obj.__dict__.get(self.__name__, _missing)
,value得到的是_missing。然後進入if語句,真正執行函數調用,并把這個結果作為屬性寫到request的字典裡。那麼之後執行這條語句
value = obj.__dict__.get(self.__name__, _missing)
時得到的不再是_mising,而是第一次執行得到的結果,是以不會再次執行函數調用了,這也是為什麼叫懶惰屬性的原因。
environ_property
class environ_property(_DictAccessorProperty):
read_only = True
def lookup(self, obj):
return obj.environ
class _DictAccessorProperty(object):
"""Baseclass for `environ_property` and `header_property`."""
read_only = False
def __init__(
self,
name,
default=None,
load_func=None,
dump_func=None,
read_only=None,
doc=None,
):
self.name = name
self.default = default
self.load_func = load_func
self.dump_func = dump_func
if read_only is not None:
self.read_only = read_only
self.__doc__ = doc
def __get__(self, obj, type=None):
if obj is None:
return self
storage = self.lookup(obj) # 得到的就是environ
if self.name not in storage:
return self.default
rv = storage[self.name]
if self.load_func is not None: # load_func可以對屬性進一步處理
try:
rv = self.load_func(rv)
except (ValueError, TypeError):
rv = self.default
return rv
def __set__(self, obj, value):
if self.read_only:
raise AttributeError("read only property")
if self.dump_func is not None:
value = self.dump_func(value)
self.lookup(obj)[self.name] = value
def __delete__(self, obj):
if self.read_only:
raise AttributeError("read only property")
self.lookup(obj).pop(self.name, None)
def __repr__(self):
return "<%s %s>" % (self.__class__.__name__, self.name)
将請求屬性映射到環境變量。實際上就是從environ中擷取所需的屬性。如查詢字元串,直接把"QUERY_STRING"作為參數傳給environ_property即可,它把從environ映射查詢字元串屬性。environ不一定是請求環境,隻要提供了environ這個屬性即可。如果需要映射的屬性不存在,傳回default(預設為None)。這些映射的屬性都是read-only,除非指定read_only為False。
BaseRequest類就是對environ進一步封裝,提供更友好的接口處理請求。如:
- 提供headers懶惰屬性,這個屬性可以擷取所有有關請求頭的資訊。
- 提供cookies懶惰屬性,這個屬性解析了請求的cookie。
- 提供files懶惰屬性,這個屬性擷取上傳的二進制檔案。
- 提供form懶惰屬性,這個屬性解析了請求表格資料。
- 提供了args懶惰屬性,這個屬性解析了查詢參數。
headers
@cached_property
def headers(self):
return EnvironHeaders(self.environ)
EnvironHeaders實際上還是Headers類,隻是限制了headers是隻讀的。Headers提供了接口和dict基本相同。EnvironHeaders重寫了Headers的__getitem__方法,最核心的地方也是這個方法:
def __getitem__(self, key, _get_mode=False):
if not isinstance(key, string_types):
raise KeyError(key)
key = key.upper().replace("-", "_")
if key in ("CONTENT_TYPE", "CONTENT_LENGTH"):
return _unicodify_header_value(self.environ[key])
return _unicodify_header_value(self.environ["HTTP_" + key])
__getitem__就是把environ的請求頭部資料提取出來。
cookies
@cached_property
def cookies(self):
return parse_cookie(
self.environ,
self.charset,
self.encoding_errors,
cls=self.dict_storage_class,
)
實際上就是解析environ中的cookie請求頭部字段然後存儲到
self.dict_storage_class
中。
其他的files、form等處理和上面類似,可自行了解。