這是 flask 源碼解析系列文章的其中一篇,本系列全部文章列表:python
flask 源碼解析:簡介json
flask 源碼解析:應用啓動流程flask
flask 源碼解析:請求cookie
對於物理鏈路來講,請求只是不一樣電壓信號,它根本不知道也不須要知道請求格式和內容究竟是怎樣的;
對於 TCP 層來講,請求就是傳輸的數據(二進制的數據流),它只要發送給對應的應用程序就好了;
對於 HTTP 層的服務器來講,請求必須是符合 HTTP 協議的內容;
對於 WSGI server 來講,請求又變成了文件流,它要讀取其中的內容,把 HTTP 請求包含的各類信息保存到一個字典中,調用 WSGI app;
對於 flask app 來講,請求就是一個對象,當須要某些信息的時候,只須要讀取該對象的屬性或者方法就好了。ide
能夠看到,雖然是一樣的請求數據,在不一樣的階段和不一樣組件看來,是徹底不一樣的形式。由於每一個組件都有它自己的目的和功能,這和生活中的事情一個道理:對於一樣的事情,不一樣的人或者同一我的不一樣人生階段的理解是不同的。函數
這篇文章呢,咱們只考慮最後一個內容,flask 怎麼看待請求。ui
咱們知道要訪問 flask 的請求對象很是簡單,只須要 from flask import request
:
from flask import request with app.request_context(environ): assert request.method == 'POST'
前面一篇文章 已經介紹了這個神奇的變量是怎麼工做的,它最後對應了 flask.wrappers:Request
類的對象。
這個類內部的實現雖然咱們還不清楚,可是咱們知道它接受 WSGI server 傳遞過來的 environ
字典變量,並提供了不少經常使用的屬性和方法可使用,好比請求的 method、path、args 等。
請求還有一個不那麼明顯的特性——它不能被應用修改,應用只能讀取請求的數據。
這個類的定義很簡單,它繼承了 werkzeug.wrappers:Request
,而後添加了一些屬性,這些屬性和 flask 的邏輯有關,好比 view_args、blueprint、json 處理等。它的代碼以下:
from werkzeug.wrappers import Request as RequestBase class Request(RequestBase): """ The request object is a :class:`~werkzeug.wrappers.Request` subclass and provides all of the attributes Werkzeug defines plus a few Flask specific ones. """ #: The internal URL rule that matched the request. This can be #: useful to inspect which methods are allowed for the URL from #: a before/after handler (``request.url_rule.methods``) etc. url_rule = None #: A dict of view arguments that matched the request. If an exception #: happened when matching, this will be ``None``. view_args = None @property def max_content_length(self): """Read-only view of the ``MAX_CONTENT_LENGTH`` config key.""" ctx = _request_ctx_stack.top if ctx is not None: return ctx.app.config['MAX_CONTENT_LENGTH'] @property def endpoint(self): """The endpoint that matched the request. This in combination with :attr:`view_args` can be used to reconstruct the same or a modified URL. If an exception happened when matching, this will be ``None``. """ if self.url_rule is not None: return self.url_rule.endpoint @property def blueprint(self): """The name of the current blueprint""" if self.url_rule and '.' in self.url_rule.endpoint: return self.url_rule.endpoint.rsplit('.', 1)[0] @property def is_json(self): mt = self.mimetype if mt == 'application/json': return True if mt.startswith('application/') and mt.endswith('+json'): return True return False
這段代碼沒有什難理解的地方,惟一須要說明的就是 @property
裝飾符可以把類的方法變成屬性,這是 python 中常常見到的用法。
接着咱們就要看 werkzeug.wrappers:Request
:
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 """
這個方法有一點比較特殊,它沒有任何的 body。可是有多個基類,第一個是 BaseRequest
,其餘的都是各類 Mixin
。
這裏要講一下 Mixin 機制,這是 python 多繼承的一種方式,若是你但願某個類能夠自行組合它的特性(好比這裏的狀況),或者但願某個特性用在多個類中,就可使用 Mixin。
若是咱們只須要能處理各類 Accept
頭部的請求,能夠這樣作:
class Request(BaseRequest, AcceptMixin) pass
可是不要濫用 Mixin,在大多數狀況下子類繼承了父類,而後實現須要的邏輯就能知足需求。
咱們先來看看 BaseRequest
:
class BaseRequest(object): def __init__(self, environ, populate_request=True, shallow=False): self.environ = environ if populate_request and not shallow: self.environ['werkzeug.request'] = self self.shallow = shallow
能看到實例化須要的惟一變量是 environ
,它只是簡單地把變量保存下來,並無作進一步的處理。Request
的內容不少,其中至關一部分是被 @cached_property
裝飾的方法,好比下面這種:
@cached_property def args(self): """The parsed URL parameters.""" return url_decode(wsgi_get_bytes(self.environ.get('QUERY_STRING', '')), self.url_charset, errors=self.encoding_errors, cls=self.parameter_storage_class) @cached_property def stream(self): """The stream to read incoming data from. Unlike :attr:`input_stream` this stream is properly guarded that you can't accidentally read past the length of the input. Werkzeug will internally always refer to this stream to read data which makes it possible to wrap this object with a stream that does filtering. """ _assert_not_shallow(self) return get_input_stream(self.environ) @cached_property def form(self): """The form parameters.""" self._load_form_data() return self.form @cached_property def cookies(self): """Read only access to the retrieved cookie values as dictionary.""" return parse_cookie(self.environ, self.charset, self.encoding_errors, cls=self.dict_storage_class) @cached_property def headers(self): """The headers from the WSGI environ as immutable :class:`~werkzeug.datastructures.EnvironHeaders`. """ return EnvironHeaders(self.environ)
@cached_property
從名字就能看出來,它是 @property
的升級版,添加了緩存功能。咱們知道@property
能把某個方法轉換成屬性,每次訪問屬性的時候,它都會執行底層的方法做爲結果返回。@cached_property
也同樣,區別是隻有第一次訪問的時候纔會調用底層的方法,後續的方法會直接使用以前返回的值。
那麼它是如何實現的呢?咱們能在 werkzeug.utils
找到它的定義:
class cached_property(property): """A decorator that converts a function into a lazy property. The function wrapped is called the first time to retrieve the result and then that calculated result is used the next time you access the value. The class has to have a `__dict__` in order for this property to work. """ # implementation detail: A subclass of python's builtin property # decorator, we override __get__ to check for a cached value. If one # choses to invoke __get__ by hand the property will still work as # expected because the lookup logic is replicated in __get__ for # manual invocation. 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
這個裝飾器同時也是實現了 __set__
和 __get__
方法的描述器。
訪問它裝飾的屬性,就會調用 __get__
方法,這個方法先在 obj.__dict__
中尋找是否已經存在對應的值。若是存在,就直接返回;若是不存在,調用底層的函數self.func
,並把獲得的值保存起來,再返回。這也是它能實現緩存的緣由:由於它會把函數的值做爲屬性保存到對象中。
關於 Request
內部各類屬性的實現,就不分析了,由於它們每一個具體的實現都不太同樣,也不復雜,無外乎對 environ
字典中某些字段作一些處理和計算。
接下來回過頭來看看 Mixin,這裏只用 AcceptMixin
做爲例子:
class AcceptMixin(object): @cached_property def accept_mimetypes(self): return parse_accept_header(self.environ.get('HTTP_ACCEPT'), MIMEAccept) @cached_property def accept_charsets(self): return parse_accept_header(self.environ.get('HTTP_ACCEPT_CHARSET'), CharsetAccept) @cached_property def accept_encodings(self): return parse_accept_header(self.environ.get('HTTP_ACCEPT_ENCODING')) @cached_property def accept_languages(self): return parse_accept_header(self.environ.get('HTTP_ACCEPT_LANGUAGE'), LanguageAccept)
AcceptMixin
實現了請求內容協商的部分,好比請求接受的語言、編碼格式、相應內容等。
它也是定義了不少 @cached_property
方法,雖然本身沒有 __init__
方法,可是也直接使用了self.environ
,所以它並不能直接使用,只能和 BaseRequest
一塊兒出現。