flask基础之请求处理核心机制(五)
前言
总结一下flask框架的请求处理流程。
系列文章
WSGI协议
一般来说http服务器和框架需要进行解耦,http专门负责接受HTTP请求、解析HTTP请求、发送HTTP,响应请求等;而web框架负责处理请求的逻辑,和数据库的交互等等,那么它们之间需要约定一套接口使得http服务器能够调用web框架的处理逻辑,这个协议就是WSGI协议。
WSGI协议要求http服务器接收到http请求后经过处理得到两个参数,一个是请求数据封装的字典environ,另一个是需要框架回调的方法start_response。
在flask框架中,服务器对每个请求调用一次app的wsgi_app方法返回结果,而wsgi_app方法的执行过程就是请求的处理流程。
class Flask(object):
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
第一步:服务器启动
服务器启动后,假设服务器是基于线程的,此时app对象被创建,加载了相关的初始化参数,这时代理对象如current_app、g、session、request等会被创建,但是它们目前并没有代理任何的对象,如果此时使用它们会报错,需要在第一次接收到请求后才会真正地代理上下文。那么服务器启动究竟干了什么事呢?
详细请参考:flask之app初始化
第二步:接收请求,创建上下文,入栈
服务器收到一个http请求后,使用app上下文和请求数据创建一个线程,调用app的request_context(self, environ)方法,将解包后封装的http请求数据当做environ参数传入,返回一个RequestContext实例对象,每一个请求都有一个RequestContext实例对象,同时他们都拥有各自的app上下文,也就是说在本线程中的app应用是服务器初始化app的一个引,因此我们可以动态修改app的属性。
将RequestContext对象push进_request_ctx_stack里面,_request_ctx_stack是一个栈对象,此时代理对象request指向栈顶的RequestContext对象的request属性,该request是一个Request对象,而session此时指向栈顶的RequestContext对象的session属性。
判断_app_ctx_stack栈顶是否存在应用上下文对象AppContext,不存在就创建,同时将AppContext推送到_app_ctx_stack栈对象中,此时current_app指向栈顶AppContext对象的app属性,而g变量指向栈顶AppContext对象的g属性,本质上是一个_AppCtxGlobals对象,数据结构是一个字典。
- 应用上下文和请求上下文存放的栈对象
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
- 动态修改app的属性
from flask import Flask
app = Flask(__name__)
@app.route('/test1')
def test1():
"""
动态添加一个视图函数
"""
@app.route('/test2')
def test2():
return 'test2'
return 'OK'
- 应用上下文和请求上下文源码分析
class Flask(object):
def app_context(self):
return AppContext(self)
def request_context(self, environ):
return RequestContext(self, environ)
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()
def push(self):
self._refcnt += 1
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_app_ctx_stack.push(self) # 将自己推送到栈中
appcontext_pushed.send(self.app)
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
def push(self):
pass
第三步:请求分派
分发请求并执行处理逻辑的函数为full_dispatch_request,其返回一个Response对象。处理的过程为:
先执行app对象before_first_request_funcs列表中的所有方法,这是针对app的第一次请求需要的预处理方法,执行该列表中的所有方法是一个原子操作,被加了线程锁,如果不是第一次请求就跳过;
然后执行app对象的url_value_preprocessors字典中对应蓝图的列表中的所有方法,对所有的URL进行预处理;
执行app对象的before_request_funcs列表中的所有方法,其会按照加载的顺序链执行,并且如果中间有任何一个方法返回的结果不是None,那么执行中断,直接返回结果,不再执行视图函数。这是针对app所有的请求都会执行的方法,当然也可以通过蓝图来进行管理;
通过request对象的url_rule(Rule)找到app中的url_map中对应的视图函数执行,返回一个元组的结果rv,就是我们平时写视图函数时返回的元组;
调用make_response函数,以返回的结果rv作为参数构建一个Response对象;
执行app对象中的after_request_funcs列表的所有方法,以构建的Response对象作为参数,每个方法必须都返回Response类型的对象,最后调用session保存本次的状态信息;
第四步:出栈
先执行app对象的teardown_request_funcs列表中的所有的方法,其方法和after_request_funcs中的一样,只不过是在出栈前才触发,这意味着即使处理逻辑的部分出错,这里方法也会执行,然后从_request_ctx_stack中弹出RequestContext请求上下文,然后执行app对象中的teardown_appcontext_funcs列表的所有方法,最后从_app_ctx_stack中弹出AppContext应用上下文。
class AppContext:
def pop(self, exc=_sentinel):
app_ctx = self._implicit_app_ctx_stack.pop()
try:
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
self._preserved_exc = None
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc) # 调用请求钩子
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
request_close = getattr(self.request, 'close', None)
if request_close is not None:
request_close()
clear_request = True
finally:
rv = _request_ctx_stack.pop() # 弹出请求上下文
if clear_request:
rv.request.environ['werkzeug.request'] = None
if app_ctx is not None:
app_ctx.pop(exc) # 弹出应用上下文
flask请求处理最简代码模型
假设服务器使用的是多进程模式。
from multiprocessing import Process, Pool
class Flask(object):
def __call__(self, environ, start_response):
"""定义app对请求的处理过程"""
pass
def listen_port():
"""假设这是端口监听并解析http请求的方法"""
pass
def run_web():
"""假设这是程序主循环"""
app = Flask() # 创建一个app,这是app初始化做的
pool = Pool(10)
while True:
# 获取一个http请求的数据
environ, start_response = listen_port()
# 调用app处理请求
pool.apply_async(app, args=(environ, start_response))
if __name__ == '__main__':
run_web()
总结
无论是gunicorn服务器还是uwsgi服务器,其启动后加载了app对象;
当收到http请求后,按照http协议解析数据,将数据打包成一个字典,将其和响应函数一起作为参数调用app对象的wsgi_app方法;
wsgi_app方法按照接收请求,创建上下文,入栈,请求分发,出栈的步骤处理完业务逻辑返回响应数据;
参考
flask基础之请求处理核心机制(五)的更多相关文章
- flask基础之AppContext应用上下文和RequestContext请求上下文(六)
前言 应用上下文和请求上下文存在的目的,官方文档讲的很清楚,可参考: http://www.pythondoc.com/flask/appcontext.html 应用上下文对象在没有请求的时候是可以 ...
- JAVA基础之两种核心机制
突然之间需要学习Java,学校里学的东西早就忘记了,得用最短的时间把Java知识理顺,重点还是J2EE,毕竟所有的ava项目中95%都是J2EE,还是先从基础的J2SE学起吧....... 首先是了解 ...
- Flask核心机制--上下文源码剖析
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
- 1.Java基础_Java核心机制简介
Java的两种核心机制 Java虚拟机机制 Java垃圾回收机制 解释名词 J2SDK&JRE: J2SDK=JDK=Software Development Kit(软件开发包) JRE=J ...
- flask基础之app初始化(四)
前言 flask的核心对象是Flask,它定义了flask框架对于http请求的整个处理逻辑.随着服务器被启动,app被创建并初始化,那么具体的过程是这样的呢? 系列文章 flask基础之安装和使用入 ...
- Flask基础全套
Flask简介 Flask是主流PythonWeb三大框架之一,其特点是短小精悍以及功能强大从而获得众多Pythoner的追捧,相比于Django它更加简单更易上手,Flask拥有非常强大的三方库,提 ...
- Qt核心机制与原理
转: https://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★ ...
- Qt核心机制和原理
转:http://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★了解Q ...
- 前端工程化系列[06]-Yeoman脚手架核心机制
在前端工程化系列[05] Yeoman脚手架使用入门这边文章中,对Yeoman的使用做了简单的入门介绍,这篇文章我们将接着探讨Yeoman这个脚手架工具内部的核心机制,主要包括以下内容 ❏ Yeoma ...
随机推荐
- ios开发之 -- xib关联自定义view
在xib下使用自定义的view,因为很多时候,可能幸亏自顶一个view,然后在view里面填充控件,但是需要重写很多无用的 代码,而且很容易出错不说,还很好工作量,使用xib的话,分钟搞定一个view ...
- 一些基于jQuery开发的控件
基于jQuery开发,非常简单的水平方向折叠控件.主页:http://letmehaveblog.blogspot.com/2007/10/haccordion-simple-horizontal-a ...
- BZOJ1226 SDOI2009学校食堂(状压dp)
由于Bi<=7,考虑状压. 如果考虑前i个位置的话,状态里需要压入前7个人后7个人,显然是跑不动的. 那么改成考虑前i个人.于是设f[i][j][k]表示前i个人都已吃完饭,i+1后面7个人的吃 ...
- boost::asio 同步&异步例子
同步客户端: using boost::asio; io_service service; ip::tcp::endpoint ep( ip::address::from_string(); ip:: ...
- Tensorflow Object_Detection 目标检测 笔记
Tensorflow models Code:https://github.com/tensorflow/models 编写时间:2017.7 记录在使用Object_Detection 中遇到的问题 ...
- 实用 zsh 插件
zsh 命令补全插件 zsh-users/zsh-autosuggestions laravel5(使用前提:安装了 oh-my-zsh) 使用方法,修改 ~/.zshrc,在 plugins 里面加 ...
- egg.js路由的优雅改造
引言 在使用express,koa, 或者是egg.js进行node server开发的过程中,我们的路由基本上都是定义在controller层的,框架对于 node 原生路由都会进行一层封装,一版都 ...
- Linux Wget 命令实例讲解
Linux wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,尤其对于网络管理员,经常要下载一些软件或从远程服务器恢复备份到本地服务器.如果我们使用虚拟主机,处理这样的 ...
- Ansible2:主机清单
目录 Hosts and Groups(主机与组) 简单的主机和组 端口与别名 指定主机范围 使用主机变量 组内变量 组的包含与组内变量 Patterns(主机与组正则匹配部分) 1. 表示所有的主机 ...
- GO_01:Mac之Go语言Idea环境配置
声明:本人所使用的是Mac Pro 安装开始 1. 首先将 GO 基础组件安装好,就好似 java 中的 jdk.当然,安装的时候需要到官网去下载,这一步难倒了好多无法FQ的同学们,故这里我将我这边下 ...