# 信号
Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。比如:在存储数据库之前,对每个请求的数据进行写入一条数据
# 准备工作
安装bliker第三方模块
pip3 install blinker
原因
from flask import Flask,signals #导入signals #点击signals源码中,需要导入blinker模块 #signals.py中 try: from blinker import Namespace signals_available = True except ImportError: signals_available = False ... template_rendered = _signals.signal("template-rendered") before_render_template = _signals.signal("before-render-template") request_started = _signals.signal("request-started") request_finished = _signals.signal("request-finished") request_tearing_down = _signals.signal("request-tearing-down") got_request_exception = _signals.signal("got-request-exception") appcontext_tearing_down = _signals.signal("appcontext-tearing-down") appcontext_pushed = _signals.signal("appcontext-pushed") appcontext_popped = _signals.signal("appcontext-popped") message_flashed = _signals.signal("message-flashed")
# 内置信号
request_started = _signals.signal('request-started') # 请求到来前执行
request_finished = _signals.signal('request-finished') # 请求结束后执行
before_render_template = _signals.signal('before-render-template') # 模板渲染前执行
template_rendered = _signals.signal('template-rendered') # 模板渲染后执行
got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行
request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 应用上下文执行完毕后自动执行(无论成功与否)
appcontext_pushed = _signals.signal('appcontext-pushed') # 应用上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped') # 应用上下文pop时执行
message_flashed = _signals.signal('message-flashed') # 调用flask在其中添加数据时,自动触发
# 信号使用简单演示
from flask import Flask,signals
app = Flask('app01')
app.debug = True
def func(*args, **kwargs):
print('触发信号', args, kwargs)
signals.request_started.connect(func) # 使用connect将函数注册到信号中,也就是说请求进来就执行,send()是执行信号中的函数
@app.route('/',methods=['GET'])
def index():
print('视图')
return 'index'
if __name__ == '__main__':
app.run()
# 运行结果
触发信号 (<Flask 'app01'>,) {}
视图
小提示
1、.connect(func) #是注册函数进信号 2、.send() #是执行信号中的函数
# 信号在哪触发?怎么触发?源码分析
依旧查看
app.__call__
中的wsgi_app函数def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) #将请求相关数据封装到RequestContext的对象ctx中,包括ctx.app、ctx.request、ctx.session error = None try: try: ctx.push() #将ctx、app_ctx通过LocalStack添加到local中,上下文管理 response = self.full_dispatch_request() #寻找试图函数执行,将返回值给用户 except Exception as e: error = e response = self.handle_exception(e) #将出错的响应给用户 except: # noqa: B001 error = sys.exc_info()[1] raise #对返回值进行处理 return response(environ, start_response) finally: if self.should_ignore_error(error): error = None ctx.auto_pop(error)
之前我们对ctx、ctx.push进行了详细的分析,接着我们将继续往下进行分析,对
full_dispatch_request()
进行分析,点击查看源码def full_dispatch_request(self): self.try_trigger_before_first_request_functions() #用于处理before_first_request函数 try: # 执行before_first_request request_started.send(self) #触发request_started信号里面的注册的函数(connect) #预处理请求,调用before_request函数,包括蓝图的before_request函数 rv = self.preprocess_request() if rv is None: #如果为空才执行视图函数的分发 rv = self.dispatch_request() #执行试图函数,以及模板渲染 except Exception as e: rv = self.handle_user_exception(e) return self.finalize_request(rv) #最后返回返回值
点击
try_trigger_before_first_request_functions()
函数def try_trigger_before_first_request_functions(self): if self._got_first_request: #默认为Flase,第二次进来就为True了,因为已经改了 return with self._before_request_lock: #添加了一个锁,是列表安全 if self._got_first_request: return for func in self.before_first_request_funcs: #循环添加的列表,并执行 func() self._got_first_request = True #关门,第二次进行了就直接return
点击查看
self.preprocess_request()
函数的源码def preprocess_request(self): bp = _request_ctx_stack.top.request.blueprint funcs = self.url_value_preprocessors.get(None, ()) #处理url相关的 if bp is not None and bp in self.url_value_preprocessors: funcs = chain(funcs, self.url_value_preprocessors[bp]) for func in funcs: func(request.endpoint, request.view_args) # self.before_request_funcs = {} 是全局的一个字典 funcs = self.before_request_funcs.get(None, ()) #向全局before_request_func字典获取函数 if bp is not None and bp in self.before_request_funcs:# 注意:默认app存入before_request_funcs字典的的key是None,而蓝图存入的带有子的蓝图名字 funcs = chain(funcs, self.before_request_funcs[bp]) #如果app和蓝图都有before_request函数,就是用chain链式函数,将函数进行排序,输出 for func in funcs: #循环每个before_request函数 rv = func() #如果函数有返回值,就返回返回值 if rv is not None: return rv
dispatch_request()
函数源码,该函数是之执行试图函数def dispatch_request(self): req = _request_ctx_stack.top.request #获得ctx中的request属性 if req.routing_exception is not None: self.raise_routing_exception(req) rule = req.url_rule # if we provide automatic options for this URL and the # request came with the OPTIONS method, reply automatically if ( getattr(rule, "provide_automatic_options", False) and req.method == "OPTIONS" ): return self.make_default_options_response() # otherwise dispatch to the handler for that endpoint,通过别名找到视图函数并执行 return self.view_functions[rule.endpoint](**req.view_args)
如果我们使用了render_template() 函数,查看源码
def render_template(template_name_or_list, **context): #模板的参数,模板渲染 ctx = _app_ctx_stack.top # 获取到app_ctx中的的AppContext对象 ctx.app.update_template_context(context) #将传入的参数给模板中的响应的自定义函数 return _render( #调用_render函数 ctx.app.jinja_env.get_or_select_template(template_name_or_list), context, ctx.app, ) 2、点击update_template_context(),为模板中的使用的一些自定义函数,传递参数 2、查看_render函数源码: def _render(template, context, app): #如果我们写的时候,谢了before_render_template.connect() 函数,就会执行下面一句 before_render_template.send(app, template=template, context=context) rv = template.render(context) #将参数传入,进行模板渲染 template_rendered.send(app, template=template, context=context) #模板渲染后执行,需要我们自己定义的template_rendered.connect()函数 return rv
response(environ, start_response)
函数源码分析,该函数实则是执行了self.finalize_request(rv) (表示正常处理结果,不会被异常接获)、handle_exception() 函数捕获一异常返回的finalize_request(server_error, from_error_handler=True)
函数,打开finalize_request()函数源码def finalize_request(self, rv, from_error_handler=False): response = self.make_response(rv) try: response = self.process_response(response) #处理after_request函数,session_save函数 request_finished.send(self, response=response) #执行请求结束执行的函数,需要我们自己使用connect注册 except Exception: if not from_error_handler: raise self.logger.exception( "Request finalizing failed with an error while handling an error" ) return response
# before_request 和 before_first_request 请求扩展源码分析
简单实例
from flask import Flask,signals app = Flask('app01') app.debug = True def func(*args, **kwargs): print('触发信号', args, kwargs) signals.request_started.connect(func) # 使用connect将函数注册到信号中,也就是说请求进来就执行 @app.before_first_request def before_request(): pass @app.before_first_request # 向全局before_first_request_funcs列表添加两个函数 def before_request2(): pass @app.before_request # 向全局before_first_request_funcs列表添加两个函数 def before_request3(): pass @app.route('/',methods=['GET']) def index(): print('视图') return 'index' if __name__ == '__main__': app.__call__ app.run()
# 源码分析
点击before_reqeust装饰器函数,查看源码
@setupmethod def before_first_request(self, f): self.before_first_request_funcs.append(f) #将装饰的函数添加到全局的一个列表中 return f #最后将函数返回,没有inner函数
点击before_request装饰器函数,查看源码
@setupmethod def before_request(self, f): self.before_request_funcs.setdefault(None, []).append(f) #向全局的self.before_request_funcs字典中添加,添加空列表 #得到的值字典:{None: ['1']} return f
# 梳理Flask信号流程
# 总结以上信号流程
1、请求进来执行:before_first_request 2、触发 reqeust_started 信号 3、before_request函数 4、如果视图函数中有模板渲染: 渲染前的信号:before_render_template.send rv = template.render() #模板渲染 渲染后的信号:template_rendered.send() 5、after_requset 函数 6、session.save_session() 7、触发request.finished信号 8、如果触发异常 9、触发错误处理的信号:执行handle_exception()函数中的got_request_exception.send()函数 10、在执行auto_pop()函数中的do_teardown_request()函数中: 执行request_teardown.send()
# 自定义Flask信号
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from flask import Flask, current_app, flash, render_template
from flask.signals import _signals
app = Flask(import_name=__name__)
# 自定义信号
xxxxx = _signals.signal('xxxxx')
def func(sender, *args, **kwargs):
print(sender)
# 自定义信号中注册函数
xxxxx.connect(func)
@app.route("/x")
def index():
# 触发信号
xxxxx.send('123123', k1='v1')
return 'Index'
if __name__ == '__main__':
app.run()