加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Python > 正文

flask 源码解析:请求

发布时间:2020-12-17 17:02:54 所属栏目:Python 来源:网络整理
导读:这是 flask 源码解析系列文章的其中一篇,本系列所有文章列表: flask 源码解析:简介 flask 源码解析:应用启动流程 flask 源码解析:路由 flask 源码解析:上下文 flask 源码解析:请求 flask 源码解析:响应 flask 源码解析:session 简介 对于物理链路来

这是 flask 源码解析系列文章的其中一篇,本系列所有文章列表:

  • flask 源码解析:简介

  • flask 源码解析:应用启动流程

  • flask 源码解析:路由

  • flask 源码解析:上下文

  • flask 源码解析:请求

  • flask 源码解析:响应

  • flask 源码解析:session

简介

对于物理链路来说,请求只是不同电压信号,它根本不知道也不需要知道请求格式和内容到底是怎样的;
对于 TCP 层来说,请求就是传输的数据(二进制的数据流),它只要发送给对应的应用程序就行了;
对于 HTTP 层的服务器来说,请求必须是符合 HTTP 协议的内容;
对于 WSGI server 来说,请求又变成了文件流,它要读取其中的内容,把 HTTP 请求包含的各种信息保存到一个字典中,调用 WSGI app;
对于 flask app 来说,请求就是一个对象,当需要某些信息的时候,只需要读取该对象的属性或者方法就行了。

可以看到,虽然是同样的请求数据,在不同的阶段和不同组件看来,是完全不同的形式。因为每个组件都有它本身的目的和功能,这和生活中的事情一个道理:对于同样的事情,不同的人或者同一个人不同人生阶段的理解是不一样的。

这篇文章呢,我们只考虑最后一个内容,flask 怎么看待请求。

请求

我们知道要访问 flask 的请求对象非常简单,只需要?from flask import request

from?flask?import?requestwith?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?RequestBaseclass?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?头部的请求,可以这样做:

但是不要滥用 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,?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)

参考资料

  • Flask official docs


(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读