flask 核心 之 应用上下文 及 请求上下文
Werkzeugs 是 Flask 的底层WSGI库。
什么是WSGI?
一段简单的app:
def dispath_request(self, request): return Response('Hello World!') def wsgi_app(self, environ, start_response): request = Request(environ) response = self.dispath_request(request) return response(environ, start_response)
environ: 包含全部的HTTP请求信息的字典,由WSGI Server
解包 HTTP 请求生成。
start_response:一个WSGI Server
提供的函数,调用可以返回相应的状态吗和HTTP报文头,函数在返回前必须调用一次。
Flask的上下文对象
Flask有两种Context(上下文),分别是
RequestContext 请求上下文;
- Request 请求的对象,封装了Http请求(environ)的内容,生命周期请求处理完就结束了;
- Session 根据请求中的cookie,重新载入该访问者相关的会话信息;
AppContext 应用上下文;
- g 处理请求时用作临时存储的对象。每次请求都会重设这个变量, 生命周期请求处理完就结束了;
- current_app 当前激活程序的程序实例,只要当前程序还在运行就不会失效。
flask 处理请求和响应的流程:
在 'flask/globals.py' 代码中:
# context locals _request_ctx_stack = LocalStack() # LocalStack 是由werkzeug提供的栈结构类提供了push、pop等方法 # 并且Local对象是werkzeug开发的类似 thinking.local(用于隔离不同线程间的全局变量) 对象,实现了在同一个协程中数据的隔离和全局性,具体怎么实现看源代码,暂时没看明白 _app_ctx_stack = LocalStack() current_app = LocalProxy(_find_app) # partial(_lookup_req_object, 'request') 总是返回 LocalStack 栈顶对象的 request 属性 # LocalProxy 用于代理Local对象和LocalStack对象,至于为什么使用代理。。 request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session')) g = LocalProxy(partial(_lookup_app_object, 'g'))
flask local https://www.jianshu.com/p/3f38b777a621
为什么要使用 LocalProxy 而不直接使用 Local 和 LocalStack
# AppContext 应用上下文 # # 在flask/app.py下: # def app_context(self): # self 就是app对象 return AppContext(self) # # 在 `flask/ctx.py` 代码中 # 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() # Like request context, app contexts can be pushed multiple times # but there a basic "refcount" is enough to track them. self._refcnt = 0 def push(self): # 这个方法就是把应用上下文push到LocalStack,AppContext类有__enter__方法 """Binds the app context to the current context.""" self._refcnt += 1 if hasattr(sys, 'exc_clear'): sys.exc_clear() _app_ctx_stack.push(self) appcontext_pushed.send(self.app) # # 在 flask/cli.py 中有 # def with_appcontext(f): @click.pass_context def decorator(__ctx, *args, **kwargs): # decorator 被装饰器后 _ctx 参数是 threading 的 local() 对象 with __ctx.ensure_object(ScriptInfo).load_app().app_context(): # 在这里就把 应用上下文push到了LocalStack return __ctx.invoke(f, *args, **kwargs) return update_wrapper(decorator, f) # RequestContext 请求上下文 # #在flask/app.py下 # def request_context(self, environ): # 一次请求的环境变量 return RequestContext(self, environ) # # 在flask/ctx.py下: # 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): ... _request_ctx_stack.push(self) # #在flask/app.py下 # def wsgi_app(self, environ, start_response): # 这里类似上面的那小段简单 Werkzeugs app ctx = self.request_context(environ) error = None try: try: ctx.push() 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)
到此从 AppContext 到 RequestContext 梳理完
app.py
源码中有这么几个装饰器:
before_request(self, f)
# 注册一个在请求到达前要执行的函数before_first_request(self, f)
# 注册在整个应用实例第一个请求到达前要执行的函数after_request(self, f)
# 注册一个在请求处理后的函数teardown_request(self, f)
# 注册在请求上下文栈弹出后要执行的函数teardown_appcontext(self, f)
# 注册在应用上下文结束时要执行的函数context_processor(self, f)
# 向模板上下文中注入变量和方法,这个f
必须返回一个字典, 在渲染的模板中使用url_defaults(self, f)
# 为应用程序中的所有视图函数 注册URL值预处理器函数。 这些函数将在:before_request
函数之前调用。url_value_preprocessor(self, f)
# 注册一个 ‘在请求的url匹配后视图函数执行前,把环境中的某些变量换个位置存储的’ 函数
# url_defaults(self, f) 和 url_value_preprocessor(self, f) 的使用 from flask import Blueprint, render_template profile = Blueprint('profile', __name__, url_prefix='/<user_url_slug>') @profile.url_defaults def add_user_url_slug(endpoint, values): values.setdefault('user_url_slug', g.user_url_slug) @profile.url_value_preprocessor def pull_user_url_slug(endpoint, values): g.user_url_slug = values.pop('user_url_slug') query = User.query.filter_by(url_slug=g.user_url_slug) g.profile_owner = query.first_or_404() @profile.route('/') def timeline(): return render_template('profile/timeline.html') @profile.route('/photos') def photos(): return render_template('profile/photos.html') @profile.route('/about') def about(): return render_template('profile/about.html')
方法:
dispatch_request(self)
: 匹配路由,返回视图函数或者错误处理函数的返回值,并且检测是否为option
请求,如果是则构造默认的 ‘options response’ 响应。构造过程首先是Request
uri 所支持的方法集(get、post、等),然后更新Response
中allow
属性(set类型),最后返回Response
对象,若不是option
请求则执行视图函数;make_response(self, rv)
:rv
是视图函数的返回值,在python3中,rv可以使一个元组(body、status、headers)、Response
类对象、 或者一个返回Response
类对象的回调函数。这个函数的功能就是把视图函数返回的 status headers 绑定到 Response;create_url_adapter(self, request)
:url_map 适配器。对werkzeug的Map的bind
和bind_to_environ
两个方法进行了封装。bind
: 绑定一个主机地址,并返回MapAdapter对象 ;bind_to_environ
( 将MAP绑定到WSGI环境中,并返回MapAdapter对象(参数script_name在进行重定向时会用到);try_trigger_before_first_request_functions(self)
:在该实例第一个请求到达时把当前线程加锁,然后依次执行被before_first_request(self, f)
装饰过得函数,然后释放锁并把_got_first_request
置为True,再次就直接return;preprocess_request(self)
:该函数就是执行被url_value_preprocessor
和before_request
装饰过的函数;full_dispatch_request(self)
: 先执行try_trigger_before_first_request_functions
,然后执行preprocess_request
,若before_request
中的函数有返回值则为其构造Response
,然后跳过排在此函数后边的函数包括视图函数,若before_request
的函数都返回的None或没有函数就执行dispatch_request(self)
;inject_url_defaults(self, error, endpoint, values)
: 执行被'url_defaults' 装饰的函数process_response(self, response)
: 同preprocess_request(self)
通过查看源码了解到的几个工具:
click 包 把python代码打包成命令行工具
mimetypes 包 查看文件类型
itsdangerous 签名 序列化字符串
参考资料:
Flask的核心机制!关于请求处理流程和上下文
Werkzeug(Flask)之Local、LocalStack和LocalProxy
相关推荐
wpfeitian 2020-09-28
wangrui0 2020-07-05
qiximiao 2020-07-05
CSSEIKOCS 2020-06-25
邓博学习笔记 2020-06-15
fenxinzi 2020-06-15
bluecarrot 2020-06-15
onlykg 2020-06-14
cwgxiaoguizi 2020-06-14
atb 2020-06-14
Attend 2020-06-14
咏月东南 2020-06-14
citic 2020-06-14
NeverAgain 2020-06-14
heheeheh 2020-06-14
hickwu 2020-06-14
pointfish 2020-06-14
YoungkingWang 2020-06-13