Flask中的session处理机制
- 请求刚到来,获取随机字符串,存在则去“数据库”中获取原来的个人数据,否则创建一个空的容器-->内存:对象(随机字符串 ,存放数据容器)
- 视图:操作 内存中的对象(随机字符串 ,存放数据容器)
- 响应:把内存对象数据保存到数据库中,并把随机字符串更新到用户的cookie中
之前我们对flask源码进行分析过,在RequestContext中不仅封装了request的数据,还是封装了session,只不过此时为none
不过在执行ctx.push()方法中,执行到_request_ctx_stack.push(self),也是LocalStack对象把RequestContext对象放到local中...之后,还有这么一段代码
self.session = self.app.open_session(self.request) if self.session is None: self.session = self.app.make_null_session()
似乎上面这个过程就对ctx里session进行重新赋值,那这个session到底是一个什么对象呢?
上面self.app就是flask对象,在open_session返回这么一玩意
self.session_interface.open_session(self, request)
这self.session_interface又是个什么鬼?是SecureCookieSessionInterface()对象,并调用它里面的open_session方法,还是传入了request的,还在这个方法中,最终执行了return self.session_class(data),其实就是一个SecureCookieSession对象,而在SecureCookieSession这个类的一个父类是继承了字典,所以SecureCookieSession对象是一个特殊的字典,RequestContext对象封装的session也就是一个特殊的字典
class CallbackDict(UpdateDictMixin, dict):
再次回到open_session的源码中
def open_session(self, app, request): #s就相当于加密规则,里面有涉及到secret_key s = self.get_signing_serializer(app) if s is None: return None #去用户请求的cookie中获取原来给你的随机字符串 #去cookie中获取key为session的值 val = request.cookies.get(app.session_cookie_name) if not val: #如果没有,就创建一个特殊的空字典 return self.session_class() max_age = total_seconds(app.permanent_session_lifetime) try: #把从cookie里获取的session值反序列化回字典 data = s.loads(val, max_age=max_age) return self.session_class(data) except BadSignature: return self.session_class()
最后我们会发现在ctx.push()里,请求刚进来时,会给封装在RequestContext对象下的session创建一个特殊的空字典
请求处理中,比如进行session['xxx'] = 123操作,会执行session的__setitem__方法,注意这个session是全局导入的,所以它是LocalProxy对象,流程又是这么熟悉,执行偏函数,获取session对象(这里就是特殊空字典),self._get_current_object()就是这个特殊的空字典,也是SecureCookieSession对象
self._get_current_object()[key] = value
[key] = value这代码又会执行SecureCookieSession里的__setitem__方法,它本身没有实现该方法,所以它调用父类字典中的方法
响应时,在response = self.full_dispatch_request()中,最后会执行self.finalize_request(rv),并在这里面执行的response = self.process_response(response),返回response前还执行了如下代码
if not self.session_interface.is_null_session(ctx.session): #ctx.session为那个特殊字典,执行视图后,里面现在是有值的 self.save_session(ctx.session, response)
save_session中,执行了self.session_interface.save_session,而这个session_interface就是SecureCookieSessionInterface对象,执行它里面save_session方法
def save_session(self, app, session, response): #这里self是配置文件对象,这些get操作是从配置文件读取session的相关的一些配置 domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) if not session: if session.modified: response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return if not self.should_set_cookie(app, session): return httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) #把特殊字典转成字典,并进行序列化 val = self.get_signing_serializer(app).dumps(dict(session)) #写入到相应的cookie中,但是这里没有涉及写入"数据库"相关的操作 response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
so,flask内置的session,是将session保存在加密的cookie中实现
但是放在cookie会有两个方面的考虑:长度和安全,所以还需要了解第三方session
安装 pip3 install Flask-Session,这个 组件里支持了session的各种存储方法:Redis,Memcached,文件,数据库(mongodb,sqlalchemy)
上面源码分析过,在self.session_interface.save_session中,self.session_interface就是SecureCookieSessionInterface(),在它里面直接是把序列化后的session写入到cookie中,没有进行服务端的存储,而我们希望就是把session随机字符串写入cookie中,其他的值存储到我们指定的地方去,所以我们需要把SecureCookieSessionInterface这个类替换成第三方session提供的类
from flask import Flask,sessionapp = Flask(__name__)app.secret_key = 'suijksdfsd'# 指定session_interface 类from redis import Redisfrom flask_session import RedisSessionInterfaceconn = Redis() #ip 端口连接#permanent为true时,关闭浏览器,cookie就失效app.session_interface = RedisSessionInterface(conn,key_prefix='__',use_signer=False,permanent=True) @app.route('/')def index(): session['xxx'] = 123 return 'Index'if __name__ == '__main__': app.run()
从源码执行情况看,指定为RedisSessionInterface后,它会执行RedisSessionInterface的open_session和save_session方法
def open_session(self, app, request): #去cookie中获取键为session的值 sid = request.cookies.get(app.session_cookie_name) if not sid: #刚开始没有的时候,生成一个随机字符串 sid = self._generate_sid() #session_class = RedisSession,RedisSession也是一个特殊字典 return self.session_class(sid=sid, permanent=self.permanent) if self.use_signer: #签名加密 signer = self._get_signer(app) if signer is None: return None try: sid_as_bytes = signer.unsign(sid) sid = sid_as_bytes.decode() except BadSignature: sid = self._generate_sid() return self.session_class(sid=sid, permanent=self.permanent) if not PY2 and not isinstance(sid, text_type): sid = sid.decode('utf-8', 'strict') #到redis里获取 前缀+随机字符串 的值 val = self.redis.get(self.key_prefix + sid) if val is not None: try: #有值就进行反序列化,得到字典 data = self.serializer.loads(val) #生成redis特殊字典,返回 return self.session_class(data, sid=sid) except: return self.session_class(sid=sid, permanent=self.permanent) return self.session_class(sid=sid, permanent=self.permanent)
在close_session中,其他似曾相识的地方就不看,就看不同的吧
httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) #字典化后,在进行序列化 val = self.serializer.dumps(dict(session)) #把序列化后的字符串写入redis,并设置超时时间 self.redis.setex(name=self.key_prefix + session.sid, value=val, time=total_seconds(app.permanent_session_lifetime)) if self.use_signer: session_id = self._get_signer(app).sign(want_bytes(session.sid)) else: session_id = session.sid #最终只是把随机字符串写入到cookie中 response.set_cookie(app.session_cookie_name, session_id, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
除了上述直接指定session_interface类的方式外,还可以通过配置文件的方式进行指定
# 通过配置的方式进行from redis import Redisfrom flask_session import Sessionapp.config['SESSION_TYPE'] = 'redis'app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379')Session(app)
我可以看在Session(app)做了一件什么事,实例化对象,执行__init__方法
def __init__(self, app=None): self.app = app if app is not None: self.init_app(app)
在这里面执行了init_app方法,对session_interface开始操作
app.session_interface = self._get_interface(app)
我们大概都能猜到在get_interface(app)里根据配置来为session_interface执行类
if config['SESSION_TYPE'] == 'redis': session_interface = RedisSessionInterface( config['SESSION_REDIS'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT']) elif config['SESSION_TYPE'] == 'memcached': session_interface = MemcachedSessionInterface( config['SESSION_MEMCACHED'], config['SESSION_KEY_PREFIX'], config['SESSION_USE_SIGNER'], config['SESSION_PERMANENT'])
最后返回session_interface对象给app.session_interface,过程和第一方式一样的,只不过就多做了一层封装