flask 源码解析:请求

2024-09-02 12:58
文章标签 源码 请求 解析 flask

本文主要是介绍flask 源码解析:请求,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载于:http://cizixs.com/2017/01/18/flask-insight-request

简介

对于物理链路来说,请求只是不同电压信号,它根本不知道也不需要知道请求格式和内容到底是怎样的; 对于 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 andprovides all of the attributes Werkzeug defines plus a few Flaskspecific 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    @propertydef max_content_length(self):"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""ctx = _request_ctx_stack.topif ctx is not None:return ctx.app.config['MAX_CONTENT_LENGTH']    @propertydef endpoint(self):"""The endpoint that matched the request.  This in combination with:attr:`view_args` can be used to reconstruct the same or amodified URL.  If an exception happened when matching, this willbe ``None``."""if self.url_rule is not None:return self.url_rule.endpoint    @propertydef 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]    @propertydef is_json(self):mt = self.mimetypeif mt == 'application/json':return Trueif mt.startswith('application/') and mt.endswith('+json'):return Truereturn 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 = environif populate_request and not shallow:self.environ['werkzeug.request'] = selfself.shallow = shallow

能看到实例化需要的唯一变量是 environ,它只是简单地把变量保存下来,并没有做进一步的处理。Request 的内容很多,其中相当一部分是被 @cached_property 装饰的方法,比如下面这种:

    @cached_propertydef 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_propertydef stream(self):"""The stream to read incoming data from.  Unlike :attr:`input_stream`this stream is properly guarded that you can't accidentally read pastthe length of the input.  Werkzeug will internally always refer tothis stream to read data which makes it possible to wrap thisobject with a stream that does filtering."""_assert_not_shallow(self)return get_input_stream(self.environ)    @cached_propertydef form(self):"""The form parameters."""self._load_form_data()return self.form    @cached_propertydef 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_propertydef 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.  Thefunction wrapped is called the first time to retrieve the resultand then that calculated result is used the next time you accessthe value.The class has to have a `__dict__` in order for this property towork."""# 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 = funcdef __set__(self, obj, value):obj.__dict__[self.__name__] = valuedef __get__(self, obj, type=None):if obj is None:return selfvalue = obj.__dict__.get(self.__name__, _missing)if value is _missing:value = self.func(obj)obj.__dict__[self.__name__] = valuereturn value

这个装饰器同时也是实现了 __set__ 和 __get__ 方法的描述器。 访问它装饰的属性,就会调用 __get__ 方法,这个方法先在 obj.__dict__ 中寻找是否已经存在对应的值。如果存在,就直接返回;如果不存在,调用底层的函数 self.func,并把得到的值保存起来,再返回。这也是它能实现缓存的原因:因为它会把函数的值作为属性保存到对象中。

关于 Request 内部各种属性的实现,就不分析了,因为它们每个具体的实现都不太一样,也不复杂,无外乎对 environ 字典中某些字段做一些处理和计算。 接下来回过头来看看 Mixin,这里只用 AcceptMixin 作为例子:

class AcceptMixin(object):    @cached_propertydef accept_mimetypes(self):return parse_accept_header(self.environ.get('HTTP_ACCEPT'), MIMEAccept)    @cached_propertydef accept_charsets(self):return parse_accept_header(self.environ.get('HTTP_ACCEPT_CHARSET'),CharsetAccept)    @cached_propertydef accept_encodings(self):return parse_accept_header(self.environ.get('HTTP_ACCEPT_ENCODING'))    @cached_propertydef accept_languages(self):return parse_accept_header(self.environ.get('HTTP_ACCEPT_LANGUAGE'),LanguageAccept)

AcceptMixin 实现了请求内容协商的部分,比如请求接受的语言、编码格式、相应内容等。 它也是定义了很多 @cached_property 方法,虽然自己没有 __init__ 方法,但是也直接使用了 self.environ,因此它并不能直接使用,只能和 BaseRequest 一起出现。

参考资料

  • Flask official docs

这篇关于flask 源码解析:请求的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130015

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [