深入探究 Flask 的应用和请求上下文

目标

读完本文后,您应该能够解释:

  1. 什么是上下文
  2. 哪些数据同时存储在应用程序和请求上下文中
  3. 在 Flask 中处理请求时,处理应用程序和请求上下文所需的步骤
  4. 如何使用应用程序和请求上下文的代理
  5. 如何在视图函数中使用current_app和代理request
  6. 什么是上下文本地

什么是上下文?

为了执行你编写的代码,需要处理数据。这些数据可能是配置数据、输入数据、数据库数据等等。

上下文用于跟踪代码需要执行的数据。

在 Flask 中,上下文用于提供处理请求和命令行界面 (CLI) 命令所需的数据。

虽然本文重点介绍处理请求,但所提出的概念也适用于 CLI 命令。

请求处理

让我们从高层的角度开始了解请求的处理方式:

Web 服务器、WSGI 服务器和 Flask 应用图

因此,浏览器会向 Web 服务器(如 Nginx 或 Apache)发送请求,以请求特定的 URL(上图中的“/”URL)。然后,Web 服务器会将此请求路由到 WSGI 服务器进行处理。

WSGI 代表 Web 服务器网关接口,是 Web 服务器和基于 Python 的 Web 应用程序之间的接口。它是必需的,因为 Web 服务器无法直接与 Python 应用程序通信。有关详细信息,请查看WSGI。

WSGI 服务器告诉 Flask 应用程序处理请求。

Flask 应用程序生成一个响应,该响应被发送回 WSGI 服务器,再发送回 Web 服务器,最终发送回 Web 浏览器。

这些步骤描述了请求-响应周期,这是通过 Web 服务器、WSGI 应用服务器和 Web 应用程序处理请求的关键功能。

Flask 中的上下文

当收到请求时,Flask 提供两个上下文:

语境描述可用对象
应用跟踪应用程序级数据(配置变量、记录器、数据库连接)current_appg
要求跟踪请求级数据(URL、HTTP 方法、标头、请求数据、会话信息)requestsession

值得注意的是,上述每个对象通常被称为“代理”。这只是意味着它们是对象全局风格的代理。我们稍后会深入探讨这一点。

Flask 在收到请求时处理这些上下文的创建。它们可能会造成混淆,因为您并不总是能够根据应用程序所处的状态访问特定对象。

概览图

下图说明了处理请求时如何处理上下文:

第 1 步 - Web 和 WSGI 服务器

当 Web 服务器收到请求时,一切就开始了:

Web 服务器的工作是将传入的 HTTP 请求路由到WSGI服务器。

Apache和Nginx是两种常见的 Web 服务器,而Gunicorn、uWSGI和mod_wsgi是流行的 WSGI 服务器。

值得注意的是,虽然Flask 开发服务器是一个 WSGI 服务器,但它并不适合用于生产。

步骤 2 - 工作者

为了处理该请求,WSGI 服务器会生成一个工作进程来处理该请求:


工作线程可以是线程、进程或协程。例如,如果您使用 Flask Development Server 的默认配置,则工作线程将是线程。

如果您有兴趣了解有关 Python 中线程、多处理和异步之间的更多区别,请查看使用并发、并行和异步加速 Python文章和Python 中的并发视频。

对于这个解释,工作者类型并不重要;关于工作者的关键点是它一次处理一个请求(因此需要多个工作者)。

步骤 3 - 上下文

一旦执行切换到 Flask 应用程序,Flask 就会创建应用程序和请求上下文并将它们推送到各自的堆栈上:

回顾一下,应用程序上下文存储应用程序级数据,例如配置变量、数据库连接和记录器。同时,请求上下文存储需要处理以生成响应的特定于请求的数据。

可能会令人惊讶地看到,但两个堆栈都是作为全局对象实现的(这将在下一节中变得更加清晰)。

步骤 4 - 代理

现在 Flask 应用程序已准备好处理数据(在视图函数中),并且数据已在应用程序和请求上下文堆栈中准备就绪,我们需要一种方法来连接这两部分......代理来救援!

视图函数使用代理来访问应用程序(存储在应用程序上下文堆栈中)和请求上下文(存储在请求上下文堆栈中):

  • current_app- 代理工作者的应用程序上下文
  • request- 代理工作者的请求上下文

乍一看,这个序列似乎令人困惑,因为视图函数似乎正在通过代理访问全局对象(应用程序和请求上下文堆栈)。如果是这样的话,这个操作就会有问题,因为它不是线程安全的。您可能还会认为这些堆栈(作为全局对象)可以被任何工作程序访问,这将是一个安全问题。

然而,这种设计是 Flask 的一大特色……堆栈被实现为上下文本地对象。

有关代理的更多信息,请查看Flask 文档中的代理注释和代理模式文章。

上下文局部变量

Python 有一个线程本地数据的概念,用于存储特定于线程的数据,它既是“线程安全的,又是线程唯一的”。换句话说,每个线程都能够以线程安全的方式访问数据,并且数据对于特定线程始终是唯一的。

Flask 实现了类似的行为(上下文本地),但是以更通用的方式允许工作者成为线程、进程或协程。

上下文局部变量实际上是在Werkzeug中实现的,它是 Flask 的关键包之一。为简单起见,我们在讨论上下文局部变量时将引用 Flask。

当数据存储在上下文本地对象中时,数据以只有一个工作进程可以检索的方式存储。因此,如果两个单独的工作进程访问上下文本地对象,它们将各自获取各自独有的特定数据。

下一节将介绍一个使用上下文本地对象的示例。

总而言之,每个视图函数中都有current_apprequest代理,它们用于从各自的堆栈访问上下文,这些堆栈存储为上下文本地对象。

在应用程序和请求上下文堆栈中使用“堆栈”使这个概念比原来更加令人困惑。这些“堆栈”通常存储的上下文不超过一个。

使用的数据结构是堆栈,因为存在非常高级的场景(例如,内部重定向)需要多于一个的元素。

Flask 中代理的好处

如果你要从头开始创建自己的 Web 框架,那么你可能会考虑将应用程序和请求上下文传递到每个视图函数中,如下所示:

@app.route('/add_item', methods=['GET', 'POST'])
def add_item(application_context, request_context):  # contexts passed in!if request_context.method == 'POST':# Save the form data to the database...application_context.logger.info(f"Added new item ({ request_context.form['item_name'] })!")...

事实上,许多 Web 框架都是这样工作的(包括Django)。

然而,Flask 提供了current_apprequest代理,它们最终看起来像视图函数的全局变量:

from flask import current_app, request@app.route('/add_item', methods=['GET', 'POST'])
def add_item():if request.method == 'POST':# Save the form data to the database...current_app.logger.info(f"Added new item ({ request.form['item_name'] })!")...

通过使用这种方法,视图函数不需要将上下文作为参数传入;这种方法简化了视图函数定义。但它可能会引起混淆,因为您并不总是能够访问current_apprequest代理,具体取决于您的应用程序所处的状态。

提醒:current_apprequest代理实际上不是全局变量;它们指向作为上下文本地实现的全局对象,因此代理对于每个工作者来说始终是唯一的。

第 5 步 - 清理

生成响应后,请求和应用程序上下文将从各自的堆栈中弹出:

此步骤清理堆栈。

然后将响应发送回 Web 浏览器,完成对该请求的处理。

上下文局部变量

上下文本地对象是使用本地对象实现的,可以像这样创建:

$ python>>> from werkzeug.local import Local
>>> data = Local()
>>> data.user = 'pkennedy@hey.com'

每个上下文(即上一节中讨论的“工作者”)都可以访问一个Local对象,用于上下文独有的数据存储。所访问的数据对于上下文来说是唯一的,并且只能由该上下文访问。

LocalStack对象与Local对象类似,但是保留一个对象堆栈以允许push()pop()操作。

LocalStack在上一节中,我们了解了在 Flask 中处理请求时如何使用应用程序上下文堆栈和请求上下文堆栈。这些堆栈在 Flask 中作为全局内存中的对象实现。

为了帮助巩固上下文局部变量的工作原理,让我们通过一个例子来说明如何LocalStack在全局内存中创建一个对象,然后让三个独立的线程访问它:

以下是该示例的完整脚本:

"""
Example script to illustrate how a global `LocalStack` object can be used
when working with multiple threads.
"""
import random
import threading
import timefrom werkzeug.local import LocalStack# Create a global LocalStack object for storing data about each thread
thread_data_stack = LocalStack()def long_running_function(thread_index: int):"""Simulates a long-running function by using time.sleep()."""thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})print(f'Starting thread #{thread_index}... {thread_data_stack}')time.sleep(random.randrange(1, 11))print(f'LocalStack contains: {thread_data_stack.top}')print(f'Finished thread #{thread_index}!')thread_data_stack.pop()if __name__ == "__main__":threads = []# Create and start 3 threads that each run long_running_function()for index in range(3):thread = threading.Thread(target=long_running_function, args=(index,))threads.append(thread)thread.start()# Wait until each thread terminates before the script exits by# 'join'ing each threadfor thread in threads:thread.join()print('Done!')

该文件创建一个LocalStack对象(thread_data_stack)用于存储将要创建的每个线程的数据。

thread_data_stack模仿 Flask 中的应用程序上下文堆栈或请求上下文堆栈。

long_running_function在每个线程中运行:

def long_running_function(thread_index: int):"""Simulates a long-running function by using time.sleep()."""thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})print(f'Starting thread #{thread_index}... {thread_data_stack}')time.sleep(random.randrange(1, 11))print(f'LocalStack contains: {thread_data_stack.top}')print(f'Finished thread #{thread_index}!')thread_data_stack.pop()

该函数将有关线程的数据推送到thread_data_stack全局内存中的对象:

thread_data_stack.push({'index': thread_index, 'thread_id': threading.get_native_id()})

此操作模仿将应用程序或请求上下文推送到其各自的堆栈。

函数完成后time.sleep(),将访问以下数据thread_data_stack

print(f'LocalStack contains: {thread_data_stack.top}')

此操作模仿使用app_contextrequest代理,因为这些代理访问其各自堆栈顶部的数据。

在函数的末尾,数据从中弹出thread_data_stack

thread_data_stack.pop()

此操作模拟从各自的堆栈中弹出应用程序或请求上下文。

脚本运行时会启动3个线程:

# Create and start 3 threads that each run long_running_function()
for index in range(3):thread = threading.Thread(target=long_running_function, args=(index,))threads.append(thread)thread.start()

并且join每个线程都等待,直到每个线程完成执行:

# Wait until each thread terminates before the script exits by
# 'join'ing each thread
for thread in threads:thread.join()

让我们运行这个脚本看看会发生什么:

$ python app.pyStarting thread #0... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #1... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #2... <werkzeug.local.LocalStack object at 0x109cebc40>
LocalStack contains: {'index': 0, 'thread_id': 320270}
Finished thread #0!
LocalStack contains: {'index': 1, 'thread_id': 320271}
Finished thread #1!
LocalStack contains: {'index': 2, 'thread_id': 320272}
Finished thread #2!
Done!

每个线程真正有趣的是它们都指向LocalStack内存中的同一个对象:

Starting thread #0... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #1... <werkzeug.local.LocalStack object at 0x109cebc40>
Starting thread #2... <werkzeug.local.LocalStack object at 0x109cebc40>

当每个线程访问 时thread_data_stack,访问都是该线程独有的LocalStack!这就是(和)的魔力Local——它们允许上下文独有的访问:

LocalStack contains: {'index': 0, 'thread_id': 320270}
LocalStack contains: {'index': 1, 'thread_id': 320271}
LocalStack contains: {'index': 2, 'thread_id': 320272}

与典型的全局内存访问不同,对的访问thread_data_stack也是线程安全的。

结论

Flask 的一个强大(但令人困惑)的方面是如何处理应用程序和请求上下文。希望本文能对这个主题提供一些澄清!

应用程序和请求上下文在处理请求或 CLI 命令时提供必要的数据。确保使用current_apprequest代理来访问应用程序上下文和请求上下文。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54510.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

并发编程多线程

1.线程和进程的区别&#xff1f; 进程是正在运行程序的实例&#xff0c;进程中包含了线程&#xff0c;每个线程执行不同的任务不同的进程使用不同的内存空间&#xff0c;在当前进程下的所有线程可以共享内存空间线程更轻量&#xff0c;线程上下文切换成本一般上要比进程上下文…

如何用3个月零基础入门网络安全?_网络安全零基础怎么学习

前 言 写这篇教程的初衷是很多朋友都想了解如何入门/转行网络安全&#xff0c;实现自己的“黑客梦”。文章的宗旨是&#xff1a; 1.指出一些自学的误区 2.提供客观可行的学习表 3.推荐我认为适合小白学习的资源.大佬绕道哈&#xff01; →点击获取网络安全资料攻略← 一、自学…

某省公共资源交易中心爬虫逆向分析

目标网站 aHR0cHM6Ly95Z3AuZ2R6d2Z3Lmdvdi5jbi8jLzQ0L3NjenQteHEvP3VzZXJJZD02NzM4OTg2MzkyNjA3NzAzMDQmcm93SWQ9NTI1MDYyMDI2ODg0NzE2NTQ0JnRpbWU9MjAwOC0xMS0yNiZjZXJ0aWZpY2F0ZU5vPTkxNDQwOTA0NjgyNDI2MzU4QyZjZXJ0aWZpY2F0ZVR5cGU9Mjg 一、抓包分析 请求头参数加密 二、…

校园社区服务系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;发布类型管理&#xff0c;互帮互助管理&#xff0c;物品分类管理&#xff0c;闲置交易管理&#xff0c;购买物品管理&#xff0c;反馈信息系统管理 微信端账号功能包括&#xff1…

高等代数笔记(2)————(弱/强)数学归纳法

数学归纳法的引入情景其实很简单&#xff0c;就是多米诺骨牌。 推倒所有多米诺骨牌的关键就是推倒第一块&#xff0c;以及确保第一块倒下后会带动第二块&#xff0c;第二块带动第三块&#xff0c;以此类推&#xff0c;也就是可以递推。由此我们可以归纳出所有的多米诺骨牌都可…

deepin桌面版连接windows远程桌面

在Linux系统中&#xff0c;要登录到Windows系统&#xff0c;通常可以使用远程桌面协议(RDP)。你需要在Linux系统上安装RDP客户端。 使用如下命令安装rdp协议&#xff1a; sudo apt-get install xrdp 安装成功后&#xff0c;启动rdp服务。 sudo systemctl start xrdp 有了r…

go解决引入私有包报错“Repository owner does not exist“的两种方式

当你写好引入的私有包,执行go mod tidy报错: Gogs: Repository owner does not exist fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. 目前我的两种解决方案: 一、拉群整个…

江协科技STM32学习- P14 示例程序(定时器定时中断和定时器外部时钟)

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

传输层协议 —— TCP协议(上篇)

目录 1.认识TCP 2.TCP协议段格式 3.可靠性保证的机制 确认应答机制 超时重传机制 连接管理机制 三次握手 四次挥手 1.认识TCP 在网络通信模型中&#xff0c;传输层有两个经典的协议&#xff0c;分别是UDP协议和TCP协议。其中TCP协议全称为传输控制协议&#xff08;Tra…

后台数据管理系统 - 项目架构设计-Vue3+axios+Element-plus(0920)

十三、文章分类页面 - [element-plus 表格] Git仓库&#xff1a;https://gitee.com/msyycn/vue3-hei-ma.git 基本架子 - PageContainer 功能需求说明&#xff1a; 基本架子-PageContainer封装文章分类渲染 & loading处理文章分类添加编辑[element-plus弹层]文章分类删除…

pg入门3—详解tablespaces—下

pg默认的tablespace的location为空&#xff0c;那么如果表设置了默认的tablespace&#xff0c;数据实际上是存哪个目录的呢? 在 PostgreSQL 中&#xff0c;如果你创建了一个表并且没有显式指定表空间&#xff08;tablespace&#xff09;&#xff0c;或者表空间的 location 为…

OpenCV运动分析和目标跟踪(4)创建汉宁窗函数createHanningWindow()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 此函数计算二维的汉宁窗系数。 createHanningWindow是OpenCV中的一个函数&#xff0c;用于创建汉宁窗&#xff08;Hann window&#xff09;。汉宁…

肾癌的多模态预测模型-临床-组织学-基因组

目录 摘要 技术路线 ① lncRNA的预测模型 ②病理 WSI 的分类器 ③临床病理分类器 模型结果 与别的模型比较 同行评审学习 1&#xff09;使用lncRNA的原因 2&#xff09;模型临床使用意义 3&#xff09;关于截止值的使用 摘要 A multi-classifier system integrated…

STC89C52定时器与中断 详细介绍 0基础入门

STC89C52定时器与中断 前言定时器/计数器定时器/计数器 功能选择定时器/计数器 模式选择使用寄存器进行功能选择与模式选择 中断使用寄存器进行中断配置中断执行操作 总结完整程序 前言 对于定时器与中断&#xff0c;这是两个完全不同的概念&#xff0c;在单片机中它们也对应着…

【HTTP】认识 URL 和 URL encode

文章目录 认识 URLURL 基本格式**带层次的文件路径****查询字符串****片段标识符** URL encode 认识 URL 计算机中非常重要的概念&#xff0c;并不仅仅是在 HTTP 中使用。用来描述一个网络资源所处的位置&#xff0c;全称“唯一资源定位符” URI 是“唯一资源标识符“严格的说…

mac命令行分卷压缩与合并

对当前目录内的文件压缩的同时分卷 //语法:zip -r -s 1m 压缩文件名.zip 当前路径 zip -r -s 1m split.zip . //解压 zip -s 0 split.zip --out unsplit.zip unzip unsplit.zip 将一个zip文件进行分卷 一个900k的压缩包名为hello.zip,将其分割为每500K一个zip zip - hello.…

Microsoft Edge 五个好用的插件

&#x1f423;个人主页 可惜已不在 &#x1f424;这篇在这个专栏 插件_可惜已不在的博客-CSDN博客 &#x1f425;有用的话就留下一个三连吧&#x1f63c; 目录 Microsoft Edge 一.安装游览器 ​编辑 二.找到插件商店 1.打开游览器后&#xff0c;点击右上角的设置&#…

第十四章:html和css做一个心在跳动,为你而动的表白动画

💖 让心跳加速,传递爱意 💖 在这个特别的时刻,让爱在跳动中绽放!🌟 无论是初次相遇的心动,还是陪伴多年的默契,我们的心总在为彼此跳动。就像这颗炙热的爱心,随着每一次的跳动,传递着满满的温暖与期待。 在这个浪漫的季节,让我们一同感受爱的律动!无论你是在…

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-19

计算机前沿技术-人工智能算法-大语言模型-最新论文阅读-2024-09-19 1. SAM4MLLM: Enhance Multi-Modal Large Language Model for Referring Expression Segmentation Authors: Yi-Chia Chen, Wei-Hua Li, Cheng Sun, Yu-Chiang Frank Wang, Chu-Song Chen SAM4MLLM: 增强多模…

防火墙详解(三)华为防火墙基础安全策略配置(命令行配置)

实验要求 根据实验要求配置防火墙&#xff1a; 合理部署防火墙安全策略以及安全区域实现内网用户可以访问外网用户&#xff0c;反之不能访问内网用户和外网用户均可以访问公司服务器 实验配置 步骤一&#xff1a;配置各个终端、防火墙端口IP地址 终端以服务器为例&#xff…