flask源码解析之上下文

引入

  对于flask而言,其请求过程与django有着截然不同的流程。在django中是将请求一步步封装最终传入视图函数的参数中,但是在flask中,视图函数中并没有请求参数,而是将请求通过上下文机制完成对请求的解析操作。

流程图镇楼:

源码解析

0. 请求入口

  1. if __name__ == '__main__':
  2. app.run()
  1. def run(self, host=None, port=None, debug=None,
  2. load_dotenv=True, **options):
  3. # Change this into a no-op if the server is invoked from the
  4. # command line. Have a look at cli.py for more information.
  5. if os.environ.get('FLASK_RUN_FROM_CLI') == 'true':
  6. from .debughelpers import explain_ignored_app_run
  7. explain_ignored_app_run()
  8. return
  9.  
  10. if get_load_dotenv(load_dotenv):
  11. cli.load_dotenv()
  12.  
  13. # if set, let env vars override previous values
  14. if 'FLASK_ENV' in os.environ:
  15. self.env = get_env()
  16. self.debug = get_debug_flag()
  17. elif 'FLASK_DEBUG' in os.environ:
  18. self.debug = get_debug_flag()
  19.  
  20. # debug passed to method overrides all other sources
  21. if debug is not None:
  22. self.debug = bool(debug)
  23.  
  24. _host = '127.0.0.1'
  25. _port = 5000
  26. server_name = self.config.get('SERVER_NAME')
  27. sn_host, sn_port = None, None
  28.  
  29. if server_name:
  30. sn_host, _, sn_port = server_name.partition(':')
  31.  
  32. host = host or sn_host or _host
  33. port = int(port or sn_port or _port)
  34.  
  35. options.setdefault('use_reloader', self.debug)
  36. options.setdefault('use_debugger', self.debug)
  37. options.setdefault('threaded', True)
  38.  
  39. cli.show_server_banner(self.env, self.debug, self.name, False)
  40.  
  41. from werkzeug.serving import run_simple
  42.  
  43. try:
  44. run_simple(host, port, self, **options)
  45. finally:
  46. # reset the first request information if the development server
  47. # reset normally. This makes it possible to restart the server
  48. # without reloader and that stuff from an interactive shell.
  49. self._got_first_request = False
  1. def __call__(self, environ, start_response):
  2. """The WSGI server calls the Flask application object as the
  3. WSGI application. This calls :meth:`wsgi_app` which can be
  4. wrapped to applying middleware."""
  5. return self.wsgi_app(environ, start_response)

对于每次请求进来之后,都会执行Flask类实例的__call__方法,至于为什么执行的是__call__方法请看博主之前的《web框架本质》文章,这里不再过多叙述。但是我们可以知道的是,只要是请求进来之后,__call__方法执行的 wsgi_app方法是对请求处理及响应的全部过程,对于每次请求上下文的创建和销毁也是在其内部完成,而上下文正是flask框架的核心,因此研究其内部的执行流程有着至关重要的作用。

首先我们要明白,在上下文中,要完成的操作是:

  1.对原生请求进行封装,生成视图函数可操作的request对象

  2.获取请求中的cookie信息,生成Session对象

  3.执行预处理函数和视图函数

  4.返回响应结果

以下为上下文源码,后续对各部分代码进行分别阐述

  1. def wsgi_app(self, environ, start_response):
  2. # 生成 ctx.request , request.session,请求上下文,即请求先关的数据都封装到了 ctx这个对象中去
  3. ctx = self.request_context(environ)
  4. error = None
  5. try:
  6. try:
  7. # 将ctx入栈,但是内部也将应用上下文入栈
  8. ctx.push()
  9. # 对请求的url进行视图函数的匹配,执行视图函数,返回响应信息(cookie)
  10. response = self.full_dispatch_request()
  11. except Exception as e:
  12. # 若发生错误将错误信息最为相应信息进行返回
  13. error = e
  14. response = self.handle_exception(e)
  15. except:
  16. error = sys.exc_info()[1]
  17. raise
  18. # 封装响应信息
  19. return response(environ, start_response)
  20. finally:
  21. if self.should_ignore_error(error):
  22. error = None
  23. # 出栈,删除本次请求的相关信息
  24. ctx.auto_pop(error)

1.请求上下文对象的创建

  1. # 生成 ctx.request , request.session,请求上下文,即请求先关的数据都封装到了 ctx这个对象中去
  2. ctx = self.request_context(environ)

生成RequestContext类实例,该实例包含了本次请求的request和Session信息

  1. def request_context(self, environ):
  2. return RequestContext(self, environ)

对生成的类实例进行初始化,并且将传入的原生请求信息environ封装值Request类实例中。此时,request为封装之后的Request实例,Session为None

  1. class RequestContext(object):
  2.  
  3. def __init__(self, app, environ, request=None):
  4. self.app = app
  5. if request is None:
  6. request = app.request_class(environ)
  7. self.request = request
  8. self.url_adapter = app.create_url_adapter(self.request)
  9. self.flashes = None
  10. self.session = None
  11.  
  12. self._after_request_functions = []
  13.  
  14. self.match_request()
  1. request_class = Request

 2. 将请求上下文和应用上下文入栈

  1. # 将ctx入栈,但是内部也将应用上下文入栈
  2. ctx.push()
  1. def push(self):
  2. # 获取到的 top == ctx
  3. top = _request_ctx_stack.top
  4. if top is not None and top.preserved:
  5. top.pop(top._preserved_exc)
  6.  
  7. # Before we push the request context we have to ensure that there
  8. # is an application context.
  9. """
  10. _request_ctx_stack 和 _app_ctx_stack 都是 Local 类的实例
  11. """
  12. # 获取 应用上下文的栈顶元素,得到 app_ctx
  13. app_ctx = _app_ctx_stack.top
  14. if app_ctx is None or app_ctx.app != self.app:
  15. # self.app == Fask()
  16. # 得到 一个 AppContext类的实例对象,得到一个 应用上下文对象 app_ctx,此时 app_ctx拥有以下属性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
  17. app_ctx = self.app.app_context()
  18. # 将 app_ctx 入栈,应用上下文入栈
  19. app_ctx.push()
  20. self._implicit_app_ctx_stack.append(app_ctx)
  21. else:
  22. self._implicit_app_ctx_stack.append(None)
  23.  
  24. if hasattr(sys, 'exc_clear'):
  25. sys.exc_clear()
  26.  
  27. # self 指的是 ctx,即将ctx入栈,即 _request_ctx_stack._local.stack = [ctx]。请求上下文入栈
  28. _request_ctx_stack.push(self)
  29. # 由于每次请求都会初始化创建你ctx,因此session都为None
  30. if self.session is None:
  31. # SecureCookieSessionInterface()
  32. # session_interface = SecureCookieSessionInterface(),即session_interface就是一个SecureCookieSessionInterface类的实例对象
  33. session_interface = self.app.session_interface
  34. # 第一次访问:生成一个 字典(容器) 返回至 self.session
  35. self.session = session_interface.open_session(
  36. self.app, self.request
  37. )
  38. if self.session is None:
  39. self.session = session_interface.make_null_session(self.app)

首先,应用上下文入栈,这里不多做解释说明,其执行流程与请求上下文相同,请参考下文对与请求上下文的入栈流程分析。

其次,请求上下文入栈。执行  _request_ctx_stack.push(self) ,我们先看看  _request_ctx_stack 是什么。由 _request_ctx_stack = LocalStack()  可知 _request_ctx_stack  是 LocalStack 类实例对象,进入 LocalStack 的构造方法中

  1. def __init__(self):
  2. self._local = Local()

即在类实例化过程中,为 _request_ctx_stack 实例对象创建  _local 属性,该属性的值是  Local 类实例,进入其构造方法中,在该方法中为每一个 Local 类实例创建  __storage__  和  __ident_func__ 属性:

  1. class Local(object):
  2. __slots__ = ('__storage__', '__ident_func__')
  3.  
  4. def __init__(self):
  5. object.__setattr__(self, '__storage__', {})
  6. object.__setattr__(self, '__ident_func__', get_ident)

至此,完成了对  _request_ctx_stack 实例对象创建的流程分析,但是需要注意的是,该实例对象并不是在每次请求之后才创建完成的,而是在flask项目启动之后就会被立即创建,该对象对于每次的请求都会调用该对象的push方法进行请求上下文的入栈,也就是说 _request_ctx_stack  是一个单例对象,该单例对象可以在任何的地方被调用,其他的单例对象还有:

  1. """
  2. 注意:
  3. 在项目启动之后,global里的代码就已经执行完毕,而且也只会执行一次,因此这里面的变量是针对所有请求所使用的,但是根据不同线程id用来存放各自的值
  4. """
  5. # 生成 请求上下文栈对象,将请求上下文对象 ctx 保存到 _request_ctx_stack._local.stack = [ctx]中
  6. _request_ctx_stack = LocalStack()
  7. # 生成应用上下文栈对象,将应用上下文对象 app_ctx 保存到 _app_ctx_stack._local.stack = [app_ctx]中
  8. _app_ctx_stack = LocalStack()
  9.  
  10. # current_app.__local = app
  11. current_app = LocalProxy(_find_app)
  12. # 获取ctx.request
  13. request = LocalProxy(partial(_lookup_req_object, 'request'))
  14. # 获取 ctx.session
  15. session = LocalProxy(partial(_lookup_req_object, 'session'))
  16. # 维护此次请求的一个全局变量,其实就是一个字典
  17. g = LocalProxy(partial(_lookup_app_object, 'g'))

对于以上的单例对象,在项目启动之后被创建,在项目停止后被销毁,与请求是否进来无任何关系。现在我们知道了 _request_ctx_stack 的创建流程,我们返回之前对请求上下文的入栈操作 _request_ctx_stack.push(self) (self指的是ctx),进入push方法:

  1. def push(self, obj):
  2. # obj == ctx
  3. """Pushes a new item to the stack"""
  4. rv = getattr(self._local, 'stack', None)
  5. if rv is None:
  6. self._local.stack = rv = []
  7. rv.append(obj)
  8. return rv

在上述流程中,首先使用反射获取  _request_ctx_stack._local.stack 的值,也就是获取请求栈的值。项目刚启动,在第一次请求进来之前,请求栈的为空,则代码继续向下执行将当前请求的ctx追加至请求栈中,并且返回请求栈的值。这里着重说一下入栈之前的流程和入栈之后的数据结构:执行 self._local.stack = rv = [] ,会调用 Local 类的 __setattr__ 方法

  1. def __setattr__(self, name, value):
  2. ident = self.__ident_func__()
  3. storage = self.__storage__
  4. try:
  5. storage[ident][name] = valueexcept KeyError:
  6. storage[ident] = {name: value}

self.__ident_func__() 为获取当前此次请求的协程id或者线程id, self.__storage__ 为一个字典对象,在项目启动后的第一个请求进来之后会发生 storage[ident][name] = value 的异常错误,抛出异常被下面捕获,因此执行 storage[ident] = {name: value} (以此次协程id或线程id为key,该key的value为一个字典,在字典中存储一个键值对"stack":[ctx]),即此数据结构为:

  1. _request_ctx_stack._local.stack={
  2. 线程id或协程id: {
  3. 'stack': [ctx]
  4. }
  5. }

同时, self._local.stack  = [ctx]。至此,完成请求上下文的入栈操作,应用上下文与请求上下文的入栈流程相同,这里不在赘述。至此完成了请求入栈的操作,我们需要知道在上述过程中使用到的四个类: RequestContext (请求上下文类,实例对象ctx中包含了request,Session两个属性)、 Request (对请求的元信息environ进行封装)、 LocalStack (使用该类实例对象 _request_ctx_stack ,维护请求上下文对象ctx的入栈和出栈操作,相当于请求上下文对象的管理者)、 Local (堆栈类,真正存放请求上下文的类),如果你还是对着几个类关系还是不明白,请看我为你准备的图:

返回 wsgi_app 函数,继续向下执行  response = self.full_dispatch_request() 函数:

  1. def full_dispatch_request(self):
  2. # 将 _got_first_request = True,依次执行定义的 before_first_request 函数
  3. self.try_trigger_before_first_request_functions()
  4. try:
  5. # 触发 request_started 信号
  6. request_started.send(self)
  7. # 执行钩子函数:before_request,before_first_request
  8. rv = self.preprocess_request()
  9. # 如果执行的before_request,before_first_request函数没有返回值,则继续执行视图函数。若有返回值,则不执行视图函数
  10. if rv is None:
  11. # 执行此url对应的别名的视图函数并执行该函数,返回视图函数的返回值,得到相应信息
  12. rv = self.dispatch_request()
  13. except Exception as e:
  14. # 如果发生错误,则将异常信息作为返回值进行返回
  15. rv = self.handle_user_exception(e)
  16. # 封装返回信息并返回,包括 session
  17. return self.finalize_request(rv)

在函数的内部首先执行预处理函数再执行视图函数,返回预处理函数或视图函数的返回值至浏览器。

返回  wsgi_app 函数中,继续向下执行  ctx.auto_pop(error) 函数,完成对请求上下文和应用上下文的出栈操作:

  1. def auto_pop(self, exc):
  2. if self.request.environ.get('flask._preserve_context') or \
  3. (exc is not None and self.app.preserve_context_on_exception):
  4. self.preserved = True
  5. self._preserved_exc = exc
  6. else:
  7. self.pop(exc)
  1. def pop(self, exc=_sentinel):
  2. """Pops the request context and unbinds it by doing that. This will
  3. also trigger the execution of functions registered by the
  4. :meth:`~flask.Flask.teardown_request` decorator.
  5.  
  6. .. versionchanged:: 0.9
  7. Added the `exc` argument.
  8. """
  9. app_ctx = self._implicit_app_ctx_stack.pop()
  10.  
  11. try:
  12. clear_request = False
  13. if not self._implicit_app_ctx_stack:
  14. self.preserved = False
  15. self._preserved_exc = None
  16. if exc is _sentinel:
  17. exc = sys.exc_info()[1]
  18. self.app.do_teardown_request(exc)
  19.  
  20. # If this interpreter supports clearing the exception information
  21. # we do that now. This will only go into effect on Python 2.x,
  22. # on 3.x it disappears automatically at the end of the exception
  23. # stack.
  24. if hasattr(sys, 'exc_clear'):
  25. sys.exc_clear()
  26.  
  27. request_close = getattr(self.request, 'close', None)
  28. if request_close is not None:
  29. request_close()
  30. clear_request = True
  31. finally:
  32. # 请求上下文出栈
  33. rv = _request_ctx_stack.pop()
  34.  
  35. # get rid of circular dependencies at the end of the request
  36. # so that we don't require the GC to be active.
  37. if clear_request:
  38. rv.request.environ['werkzeug.request'] = None
  39.  
  40. # Get rid of the app as well if necessary.
  41. if app_ctx is not None:
  42. # 应用上下文出栈
  43. app_ctx.pop(exc)
  44.  
  45. assert rv is self, 'Popped wrong request context. ' \
  46. '(%r instead of %r)' % (rv, self)
  1. def pop(self):
  2. """Removes the topmost item from the stack, will return the
  3. old value or `None` if the stack was already empty.
  4. """
  5. stack = getattr(self._local, 'stack', None)
  6. if stack is None:
  7. return None
  8. elif len(stack) == 1:
  9. release_local(self._local)
  10. return stack[-1]
  11. else:
  12. # 获取并删除列表中的第一个元素,同时返回该元素
  13. return stack.pop()

stack获取到的是请求栈或应用栈的列表,栈的长度为1,则进入 elif 控制语句中,首先执行  release_local(self._local) :

  1. def release_local(local):
  2.  
  3. local.__release_local__()

local=self._local ,即执行 Local 类的 __release_local__ 方法,进入该方法:

  1. def __release_local__(self):
  2. # 将 self.__storage__ 所维护的字典中删除当前协程或线程id为key的元素
  3. self.__storage__.pop(self.__ident_func__(), None)

从上面的语句中可以很明显看出,要执行的操作就是将以当前协程或线程id为key的元素从字典 self.__storage__ 中删除,返回至pop函数中的elif控制语句,最终将列表中的最后一个元素返回。注意,最终 _request_ctx_stack._local 的请求栈和应用栈列表中至少会存在一个元素。

 

flask上下文(new)的更多相关文章

  1. Flask上下文管理、session原理和全局g对象

    一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...

  2. flask上下文详解

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

  3. flask上下文全局变量,程序上下文、请求上下文、上下文钩子

    Flask上下文 Flask中有两种上下文,程序上下文(application context)和请求上下文(request context) 当客户端发来请求时,请求上下文就登场了.请求上下文里包含 ...

  4. Flask上下文管理

    一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...

  5. Flask 上下文(Context)原理解析

    :first-child { margin-top: 0; } blockquote > :last-child { margin-bottom: 0; } img { border: 0; m ...

  6. Flask上下文管理机制

    前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...

  7. 4.1 python类的特殊成员,偏函数,线程安全,栈,flask上下文

    目录 一. Python 类的特殊成员(部分) 二. Python偏函数 1. 描述 2. 实例一: 取余函数 3. 实例二: 求三个数的和 三. 线程安全 1. 实例一: 无线程,消耗时间过长 2. ...

  8. Flask 上下文机制和线程隔离

    1. 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决, 上下文机制就是这句话的体现. 2. 如果一次封装解决不了问题,那就再来一次 上下文:相当于一个容器,保存了Flask程序运行过程中 ...

  9. 详解Flask上下文

    上下文是在Flask开发中的一个核心概念,本文将通过阅读源码分享下其原理和实现. Flask系列文章: Flask开发初探 WSGI到底是什么 Flask源码分析一:服务启动 Flask路由内部实现原 ...

  10. 10.Flask上下文

    1.1.local线程隔离对象 不用local对象的情况 from threading import Thread request = ' class MyThread(Thread): def ru ...

随机推荐

  1. SpringCloud微服务负载均衡与网关

    1.使用ribbon实现负载均衡ribbon是一个负载均衡客户端 类似nginx反向代理,可以很好的控制htt和tcp的一些行为.Feign默认集成了ribbon. 启动两个会员服务工程,端口号分别为 ...

  2. gk888t打印机安装

    https://jingyan.baidu.com/article/948f5924090c7ad80ff5f9c5.html

  3. node 常用模块及方法fs,url,http,path

    http://www.cnblogs.com/mangoxin/p/5664615.html https://www.liaoxuefeng.com/wiki/001434446689867b2715 ...

  4. python+selenium,打开浏览器时报selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH

    有一年多没写web自动化了,今天搭建环境的时候报了一个常见错误,但是处理过程有点闹心,报错就是常见的找不到驱动<selenium.common.exceptions.WebDriverExcep ...

  5. ASP.NET Core 添加NLog日志支持(VS2015update3&VS2017)

    1.创建一个新的ASP.NET Core项目 2.添加项目依赖 NLog.Web.AspNetCore 3.在项目目录下添加nlog.config文件: <?xml version=" ...

  6. Python并发解决方案

    一.subprocess模块 call():执行命令,返回程序返回码(int) import subprocess print(subprocess.call("mspaint") ...

  7. jQuery基础方法:each(),map(),index(),is()

    jQuery的each()方法和forEach()的区别: each()返回调用自身的jQuery对象,可用于链式调用 $('div').each(function(idx){ //找到所有div元素 ...

  8. GUI学习之四——QWidget控件学习总结

    上一章将的QObject是PyQt里所有控件的基类,并不属于可视化的控件.这一章所讲的QWidget,是所有可视化控件的基类. QWidget包含下面几点特性 a.控件是用户界面的最小的元素 b.每个 ...

  9. JSON.parse()和eval()的区别

    json格式非常受欢迎,而解析json的方式通常用JSON.parse()但是eval()方法也可以解析,这两者之间有什么区别呢? JSON.parse()之可以解析json格式的数据,并且会对要解析 ...

  10. union: php/laravel command

    #########Laravel###############2018-01-09 16:46:26 # switch to maintenance mode php artisan down # s ...