一.请求上下文源码分析
第一阶段:将ctx(request,session)放到Local对象上 第二阶段:视图函数导入:request/session request.method -LocalProxy对象.method,执行getattr方法,getattr(self._get_current_object(), name) -self._get_current_object()返回return self.__local(),self.__local(),在LocakProxy实例化的时候,object.__setattr__(self, '_LocalProxy__local', local),此处local就是:partial(_lookup_req_object, 'request') -def _lookup_req_object(name): top = _request_ctx_stack.top #_request_ctx_stack 就是LocalStack()对象,top方法把ctx取出来 if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name)#获取ctx中的request或session对象 第三阶段:请求处理完毕 - 获取session并保存到cookie - 将ctx删除
请求上下文的代码示例
from flask import Flask,request app=Flask(__name__) @app.route("/") def index(): print(request.form) return "1" if __name__ == '__main__': # self,是app,app(),--->Flask对象,Flask的__call__ # app.__call__ # 源码入口 app.run()
源码分析过程
''' 1 app.__call__ 2 wsgi_app(environ, start_response) 2.1 ctx = self.request_context(environ) 2.1.1 return RequestContext(self, environ) 这里的self是app,environ请求相关 2.1.2 return RequestContext(self, environ) 得到了RequestContext的对象,而且有request属性 2.2 2.1中的ctx就是RequestContext的对象 2.3 ctx.push()执行这个,就是RequestContext的对象的push方法 2.3.1 #执行这个,self-->ctx _request_ctx_stack.push(self) 2.3.1.1 我们发现_request_ctx_stack = LocalStack() 他的push方法的源码: def push(self, obj): rv = getattr(self._local, "stack", None) if rv is None: # self._local=>stack-->storage['线程id']['stack']=[ctx,] self._local.stack = rv = [] rv.append(obj) return rv 3在请求中获取request.form 3.1 request是LocalProxy的对象,当获取属性的时候会走__getattr__ def __getattr__(self, name): if name == "__members__": return dir(self._get_current_object()) #name-->form, #self._get_current_object()===>ctx.request,form #_get_current_object()---》self.__local() return getattr(self._get_current_object(), name) 3.1.1 self._get_current_object():源码:最终:partial(_lookup_req_object, "request") def _get_current_object(self): if not hasattr(self.__local, "__release_local__"): #local==>partial(_lookup_req_object, "request") #def __init__(self, local, name=None): # object.__setattr__(self, "_LocalProxy__local", local) #self.__local()===>local() return self.__local() try: return getattr(self.__local, self.__name__) except AttributeError: raise RuntimeError("no object bound to %s" % self.__name__) 4 partial(_lookup_req_object, "request")偏函数的源码 def _lookup_req_object(name): #name是request #ctx top = _request_ctx_stack.top if top is None: raise RuntimeError(_request_ctx_err_msg) #ctx-->request return getattr(top, name) 4.1中_request_ctx_stack.top @property def top(self): try: return self._local.stack[-1] except (AttributeError, IndexError): return None '''
二.蓝图
蓝图可以对程序进行目录结构划分
当我们不使用蓝图,自己分文件
目录结构:
-templates -views -__init__.py -user.py -order.py -app.py
app.py
from views import app if __name__ == '__main__': app.run()
init.py
from flask import Flask,request app = Flask(__name__) #不导入这个不行 from . import account from . import order from . import user
user.py
from . import app @app.route('/user') def user(): return 'user'
order.py
from . import app @app.route('/order') def order(): return 'order'
使用蓝图之中小型系统
目录结构:
-flask_pro -flask_test -__init__.py -static -templates -views -order.py -user.py -manage.py
__init__.py
from flask import Flask app=Flask(__name__) from flask_test.views import user from flask_test.views import order app.register_blueprint(user.us) app.register_blueprint(order.ord)
manage.py
from flask_test import app if __name__ == '__main__': app.run(port=8008)
user.py
from flask import Blueprint us=Blueprint('user',__name__) @us.route('/login') def login(): return 'login'
order.py
from flask import Blueprint ord=Blueprint('order',__name__) @ord.route('/test') def test(): return 'order test'
使用蓝图之大型系统
目录结构
-flask_pro -flask_test -__init__.py -admin -templates -static -views -__init__.py -web -templates -static -views -__init__.py -manage.py
manage.py
from pro_flask import app if __name__ == '__main__': app.run()
__init__.py
from flask import Flask from .admin import admin from .web import web app = Flask(__name__) app.debug = True app.register_blueprint(admin, url_prefix='/admin') app.register_blueprint(web)
-admin.-views.py
from . import admin @admin.route('/index') def index(): return 'Admin.Index'
-admin.-__init__.py
from flask import Blueprint admin = Blueprint( 'admin', __name__, template_folder='templates', static_folder='static' ) from . import views
-web.-views.py
from flask import Blueprint web = Blueprint( 'web', __name__, template_folder='templates', static_folder='static' ) from . import views
-web.-__init__.py
from . import web @web.route('/index') def index(): return 'Web.Index'
总结:
1.xxx = Blueprint('account',name,url_prefix='/xxx'):url_prefix='/xxx'表示的是蓝图URL前缀,在该蓝图下所有url都加前缀
2.xxx = Blueprint('account', name,url_prefix='/xxx',template_folder='tpls'):给当前蓝图单独使用templates,向上查找,当前找不到,会找总templates
3.蓝图的before_request,只对当前蓝图有效,如果想要在每个蓝图中加,可以在根目录的app前添加before_request
4.大型项目,可以模拟出类似于django中app的概念
三.g对象
专门用来存储用户信息的g对象,g的全称是global
g对象存储的信息在一次请求中的所有地方都是可用的,但是后面的请求就不可用了
g对象和session的区别
session对象是可以跨request的,只要session还未失效,不同的request的请求会获取到同一个session,但是g对象不是,g对象不需要管过期时间,请求一次就g对象就改变了一次,或者重新赋值了一次
代码示例
from flask import Flask, g app = Flask(__name__) def setg(): g.name = 'sxc' @app.route('/index') def index(): setg() print(g.name) return 'ok' @app.route('/login') def login(): print(g.name) return 'login ok' if __name__ == '__main__': app.run()
四.信号
Flask框架中的信号基于blinker,主要就是让开发者可以在flask请求过程中定制一些用户行为
安装:pip3 install blinker
内置信号
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,render_template app = Flask(__name__) # 往信号中注册函数 def func(*args,**kwargs): print('触发型号',args,kwargs) # 信号类.内置信号执行时间.connect(绑定的信号) signals.request_started.connect(func) # 触发信号: signals.request_started.send() @app.before_first_request def before_first1(*args,**kwargs): print('我是请求前的第一次') @app.before_first_request def before_first2(*args,**kwargs): print('我是请求前') @app.route('/',methods=['GET',"POST"]) def index(): print('视图') return 'ok' if __name__ == '__main__': # app.wsgi_app app.run()
调用信号是执行:内置信号.send(函数名)
# 源码分析 # 这是请求之前第一次执行的函数 self.try_trigger_before_first_request_functions() try: request_started.send(self) # 这是请求之前执行的函数 rv = self.preprocess_request()
分析源码猜想我们的内置信号request_started,是在两个请求扩展的函数之间执行的
代码验证如下,猜想正确
一个流程中的信号触发点(了解)
a. before_first_request b. 触发 request_started 信号 c. before_request d. 模板渲染 渲染前的信号 before_render_template.send(app, template=template, context=context) rv = template.render(context) # 模板渲染 渲染后的信号 template_rendered.send(app, template=template, context=context) e. after_request f. session.save_session() g. 触发 request_finished信号 如果上述过程出错: 触发错误处理信号 got_request_exception.send(self, exception=e) h. 触发信号 request_tearing_down
自定义信号
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): # 第一个参数sender不管是否传都必须接收,否则报错 print(sender) # 自定义信号中注册函数 xxxxx.connect(func) @app.route("/x") def index(): # 触发信号 # 第一个参数sender可以使用位置传参,后面的参数都只能使用关键字传参 xxxxx.send('123123', k1='v1') return 'Index' if __name__ == '__main__': app.run()
注意:
1.自定义信号的函数定义时第一个参数sender不管是否传都必须接收,否则报错
2.在触发信号xxxxx.send('123123', k1='v1')时,第一个参数sender可以使用位置传参,后面的参数都只能使用关键字传参
五.flask_session
作用:将默认保存的签名cookie中的值 保存到 redis/memcached/file/Mongodb/SQLAlchemy
安装:pip3 install flask-session
第一种使用方式
from flask import Flask,session from flask_session import RedisSessionInterface import redis app = Flask(__name__) conn=redis.Redis(host='127.0.0.1',port=6379) # key_prefix='sxc'是session存储的前缀 # use_signer是否对key签名 app.secret_key = 'sxfafasf' # permanent=True表示是否关闭浏览器清除cookie app.session_interface=RedisSessionInterface(conn,key_prefix='sxc', use_signer=True, permanent=False) @app.route('/') def hello_world(): session['name']='sxc' return 'Hello World!' @app.route('/index') def index(): print(session['name']) return 'index World!' if __name__ == '__main__': app.run()
第二种使用方式
from redis import Redis from flask_session import Session from flask import Flask,session app = Flask(__name__) app.config['SESSION_TYPE'] = 'redis' app.config['SESSION_REDIS'] = Redis(host='127.0.0.1',port='6379') app.config['SESSION_KEY_PREFIX'] = 'sxc' # 设置session_key的前缀 app.config['SESSION_USE_SIGNER'] = True # 是否对key签名 app.config['SESSION_PERMANENT'] = False # 表示是否关闭浏览器清除cookie app.secret_key = 'sadgweqw' Session(app) @app.route('/') def hello_world(): session['name']='sxc' return 'Hello World!' @app.route('/index') def index(): print(session['name']) return 'index World!' if __name__ == '__main__': app.run()
问题:设置cookie时,如何设定关闭浏览器则cookie失效。
response.set_cookie('k','v',exipre=None)#这样设置即可 #在session中设置 app.session_interface=RedisSessionInterface(conn,key_prefix='lqz',permanent=False) #一般不用,我们一般都设置超时时间,多长时间后失效
问题:cookie默认超时时间是多少?如何设置超时时间
#源码expires = self.get_expiration_time(app, session) 'PERMANENT_SESSION_LIFETIME': timedelta(days=31),#这个配置文件控制
91