应用上下文
前面的请求上下文是把请求相关的封装成ctx对象放到Local对象中,应用上下文是把整个应用app自己放到Local对象中。
和应用上下文有关的全局变量def _find_app(): top = _app_ctx_stack.top if top is None: raise RuntimeError(_app_ctx_err_msg) return top.app
def _lookup_app_object(name): # top 是, name 是 'g' top = _app_ctx_stack.top print(top, name) if top is None: raise RuntimeError(_app_ctx_err_msg) return getattr(top, name)
多app应用
flask的web开发中根据url的不同分发到不同app去处理,一个请求最终只会落到一个app的视图函数去处理
from flask import Flaskfrom werkzeug.serving import run_simplefrom werkzeug.wsgi import DispatcherMiddlewareapp1 = Flask('xx')app2 = Flask('oo')@app2.route('/index')def index(): return 'index'@app1.route('/login')def login(): return 'login'# http://127.0.0.1:5000/login 对应app1, # http://127.0.0.1:5000/backend/index 对应app2application = DispatcherMiddleware(app1, { 'backend',app2})if __name__ == '__main__': application.__call__ run_simple('localhost', 5000, application)
其中DispatcherMiddleware.__call__源码如下:
def __call__(self, environ, start_response): script = environ.get('PATH_INFO', '') path_info = '' while '/' in script: if script in self.mounts: app = self.mounts[script] break script, last_item = script.rsplit('/', 1) path_info = '/%s%s' % (last_item, path_info) else: app = self.mounts.get(script, self.app) original_script_name = environ.get('SCRIPT_NAME', '') environ['SCRIPT_NAME'] = original_script_name + script environ['PATH_INFO'] = path_info return app(environ, start_response)
其实就是解析url,根据url的不同调用不同的app去处理。其中app()就会调用app.__call__, 这样就走到单app的流程中了
两个问题
为什么flask要用列表去存储上下文对象?
通过之前的了解,我们知道在单线程中,请求是一个个进行处理,也就是一个请求结束才会处理另一个请求。请求来了往列表放上下文对象,请求走了pop掉对象。下次请求来了还是循环上面的操作。即使是多线程情况下,stack的列表永远都只有一个值,既然如此,为什么还需要用列表来存值呢,不能不用列表而是直接把对象裸露地放在外面吗?确实,flask在web开发中,不会存在app嵌套app的情况,所以永远遇不到stack对应的列表中存放多个值的情况,但是如果写一个离线脚本,这个脚本用于测试flask的一些功能,就会存在app套app的情况
from flask import Flask, _app_ctx_stack, current_appapp1 = Flask('xx')app2 = Flask('oo')with app1.app_context(): print(_app_ctx_stack.top) print(current_app) with app2.app_context(): print(_app_ctx_stack.top) print(current_app) print(_app_ctx_stack.top) print(current_app)
with 语法会在进入代码块的时候执行__enter__ ,出代码块的时候执行__exit__, 看看app1.app_context()对象的__enter__和__exit__做了些什么
g和session的区别
通过源码流程可以知道,g的生命周期是一次请求,下一次请求拿不到上一次请求g赋的值,因上一次请求结束的时候已经被清除了。而session不同,session是把数据要么放到浏览器,要么放到数据库,虽然session对象在请求结束会被清除,但是它事先找好藏身的地方了,下次请求来的时候再去那个藏身的地方把东西拿回来。g和全局变量也不同,全局变量在flask程序启动的时候只加载一次,而g却是不断创造和销毁。
使用:因为before_request和after_request这些钩子函数和视图函数同属于一次http请求周期,通过源码就能看到那段核心代码在处理视图函数的前后分别处理了钩子函数,所以要想使用g,可以在钩子函数中使用g。在一次请求的阶段中,有后面的阶段需要用到前面阶段的值,就可以使用g。例如,如果flask没有提供session的导入,那么我们就可以在before_request中拿到session,然后g.session = session,这样在视图函数中就可以使用g.session了,类似于django内置中间件把session赋值给request对象。