前言

总结一下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基础之请求处理核心机制(五)的更多相关文章

  1. flask基础之AppContext应用上下文和RequestContext请求上下文(六)

    前言 应用上下文和请求上下文存在的目的,官方文档讲的很清楚,可参考: http://www.pythondoc.com/flask/appcontext.html 应用上下文对象在没有请求的时候是可以 ...

  2. JAVA基础之两种核心机制

    突然之间需要学习Java,学校里学的东西早就忘记了,得用最短的时间把Java知识理顺,重点还是J2EE,毕竟所有的ava项目中95%都是J2EE,还是先从基础的J2SE学起吧....... 首先是了解 ...

  3. Flask核心机制--上下文源码剖析

    一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...

  4. 1.Java基础_Java核心机制简介

    Java的两种核心机制 Java虚拟机机制 Java垃圾回收机制 解释名词 J2SDK&JRE: J2SDK=JDK=Software Development Kit(软件开发包) JRE=J ...

  5. flask基础之app初始化(四)

    前言 flask的核心对象是Flask,它定义了flask框架对于http请求的整个处理逻辑.随着服务器被启动,app被创建并初始化,那么具体的过程是这样的呢? 系列文章 flask基础之安装和使用入 ...

  6. Flask基础全套

    Flask简介 Flask是主流PythonWeb三大框架之一,其特点是短小精悍以及功能强大从而获得众多Pythoner的追捧,相比于Django它更加简单更易上手,Flask拥有非常强大的三方库,提 ...

  7. Qt核心机制与原理

    转:  https://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★ ...

  8. Qt核心机制和原理

    转:http://blog.csdn.net/light_in_dark/article/details/64125085 ★了解Qt和C++的关系 ★掌握Qt的信号/槽机制的原理和使用方法 ★了解Q ...

  9. 前端工程化系列[06]-Yeoman脚手架核心机制

    在前端工程化系列[05] Yeoman脚手架使用入门这边文章中,对Yeoman的使用做了简单的入门介绍,这篇文章我们将接着探讨Yeoman这个脚手架工具内部的核心机制,主要包括以下内容 ❏ Yeoma ...

随机推荐

  1. JDBC数据库连接技术

    [学习笔记]JDBC数据库连接技术(Java Database Connectivity) 一.JDBC简介 Java是通过JDBC技术实现对各种数据库的访问的,JDBC是Java数据库连接技术的简称 ...

  2. PHP 字符串数组按照拼音排序的问题

    拼音排序的规则: 字符串包括特殊字符.数字.英文字符.中文字符等等,排序结果要求,特殊字符排在第一梯队,将其按照首个字符ascii码表进行排序,数字字符排在第二梯队,将首个字符数字按照数字大小排序,英 ...

  3. Alpha 冲刺 —— 十分之八

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭鸭鸭鸭鸭鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 多次测试软件运行 学习OPENMP ...

  4. 洛谷P4233 射命丸文的笔记 【多项式求逆】

    题目链接 洛谷P4233 题解 我们只需求出总的哈密顿回路个数和总的强联通竞赛图个数 对于每条哈密顿回路,我们统计其贡献 一条哈密顿回路就是一个圆排列,有\(\frac{n!}{n}\)种,剩余边随便 ...

  5. 20135239 益西拉姆 linux内核分析 进程的切换和系统的一般执行过程

    week 8 进程的切换和系统的一般执行过程 [ 20135239 原文请转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course ...

  6. 解题:BZOJ 3884 上帝与集合的正确用法

    题面 好久以前写的,发现自己居然一直没有写题解=.= 扩展欧拉定理:在$b>φ(p)$时有$a^b \equiv a^{b\%φ(p)+φ(p)}(mod$ $p)$ 然后每次递归那个$a^{b ...

  7. HYSBZ(BZOJ) 4300 绝世好题(位运算,递推)

    HYSBZ(BZOJ) 4300 绝世好题(位运算,递推) Description 给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<= ...

  8. supervisor "INFO spawnerr: unknown error making dispatchers for xxx" 错误

    查看 supervisor 的配置中,是否有受限的文件路径,如果有,则需要修改对应文件的权限

  9. QCon技术干货:个推基于Docker和Kubernetes的微服务实践

    2016年伊始,Docker无比兴盛,如今Kubernetes万人瞩目.在这个无比需要创新与速度的时代,由容器.微服务.DevOps构成的云原生席卷整个IT界.在近期举办的QCon全球软件开发大会上, ...

  10. 51 nod 1046 A^B Mod C

    1046 A^B Mod C 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 给出3个正整数A B C,求A^B Mod C.   例如,3 5 8,3^ ...