RedisSessionInterface源码分析
先了解下 请求到来之前,获取session的方式
请求到来之前通过RequestContex 获取session, 由下图看出,open_session 调用session_interface,而session_interface,是SecureCookieSessionInterface()的对象。
而 SecureCookieSessionInterface(),提供了open,和save的方法,所以,可以使用 RedisSessionInterface 替换 SecureCookieSessionInterface,关键就是在配置文件中设置 session_interface指向哪个类
RedisSessionInterface
class RedisSessionInterface(SessionInterface): """Uses the Redis key-value store as a session backend. .. versionadded:: 0.2 The `use_signer` parameter was added. :param redis: A ``redis.Redis`` instance. :param key_prefix: A prefix that is added to all Redis store keys. :param use_signer: Whether to sign the session id cookie or not. :param permanent: Whether to use permanent session or not. """ serializer = pickle #使用pickel方式保存 session_class = RedisSession def __init__(self, redis, key_prefix, use_signer=False, permanent=True): if redis is None: from redis import Redis redis = Redis() self.redis = redis self.key_prefix = key_prefix self.use_signer = use_signer self.permanent = permanent def open_session(self, app, request): # 从cookie中获取session sid = request.cookies.get(app.session_cookie_name) # 首次访问如没有获取到session ID if not sid: # 设置一个随机字符串,使用uuid sid = self._generate_sid() #返回特殊字典 <RedisSession {‘_permanent‘: True}> return self.session_class(sid=sid, permanent=self.permanent) #session_class = RedisSession() 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‘) val = self.redis.get(self.key_prefix + sid) if val is not None: try: data = self.serializer.loads(val) 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) def save_session(self, app, session, response): domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) if not session: if session.modified: self.redis.delete(self.key_prefix + session.sid) response.delete_cookie(app.session_cookie_name, domain=domain, path=path) return # Modification case. There are upsides and downsides to # emitting a set-cookie header each request. The behavior # is controlled by the :meth:`should_set_cookie` method # which performs a quick check to figure out if the cookie # should be set or not. This is controlled by the # SESSION_REFRESH_EACH_REQUEST config flag as well as # the permanent flag on the session itself. # 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) #用户设置了seesion 后序列化session val = self.serializer.dumps(dict(session)) # key_prefix :用户设置前缀,val 是序列化之后的结果,存入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 # 生成的随机字符串uuid #写入session response.set_cookie(app.session_cookie_name, session_id, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
view 中配置RedisSessionInterface 方式一
from flask_session import RedisSessionInterface # from redis import Redis # app.session_interface = RedisSessionInterface(redis=Redis(host=‘127.0.0.1‘,port=6379),key_prefix=‘luffi‘)
方式二
from flask.ext.session import Session app.config[‘SESSION_TYPE‘] = ‘redis‘ from redis import Redis app.config[‘SESSION_REDIS‘] = Redis(host=‘192.168.0.94‘,port=‘6379‘) Session(app)
下面为Session代码:
class Session(object): def __init__(self, app=None): self.app = app if app is not None: self.init_app(app) def init_app(self, app): """This is used to set up session for your app object. :param app: the Flask app object with proper configuration. """ app.session_interface = self._get_interface(app) #这里设置了每次保存/打开session 时都会调用这个self._get_interface(app) def _get_interface(self, app): config = app.config.copy() config.setdefault(‘SESSION_TYPE‘, ‘null‘) config.setdefault(‘SESSION_PERMANENT‘, True) config.setdefault(‘SESSION_USE_SIGNER‘, False) config.setdefault(‘SESSION_KEY_PREFIX‘, ‘session:‘) config.setdefault(‘SESSION_REDIS‘, None) config.setdefault(‘SESSION_MEMCACHED‘, None) config.setdefault(‘SESSION_FILE_DIR‘, os.path.join(os.getcwd(), ‘flask_session‘)) config.setdefault(‘SESSION_FILE_THRESHOLD‘, 500) config.setdefault(‘SESSION_FILE_MODE‘, 384) config.setdefault(‘SESSION_MONGODB‘, None) config.setdefault(‘SESSION_MONGODB_DB‘, ‘flask_session‘) config.setdefault(‘SESSION_MONGODB_COLLECT‘, ‘sessions‘) config.setdefault(‘SESSION_SQLALCHEMY‘, None) config.setdefault(‘SESSION_SQLALCHEMY_TABLE‘, ‘sessions‘) 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‘]) elif config[‘SESSION_TYPE‘] == ‘filesystem‘: session_interface = FileSystemSessionInterface( config[‘SESSION_FILE_DIR‘], config[‘SESSION_FILE_THRESHOLD‘], config[‘SESSION_FILE_MODE‘], config[‘SESSION_KEY_PREFIX‘], config[‘SESSION_USE_SIGNER‘], config[‘SESSION_PERMANENT‘]) elif config[‘SESSION_TYPE‘] == ‘mongodb‘: session_interface = MongoDBSessionInterface( config[‘SESSION_MONGODB‘], config[‘SESSION_MONGODB_DB‘], config[‘SESSION_MONGODB_COLLECT‘], config[‘SESSION_KEY_PREFIX‘], config[‘SESSION_USE_SIGNER‘], config[‘SESSION_PERMANENT‘]) elif config[‘SESSION_TYPE‘] == ‘sqlalchemy‘: session_interface = SqlAlchemySessionInterface( app, config[‘SESSION_SQLALCHEMY‘], config[‘SESSION_SQLALCHEMY_TABLE‘], config[‘SESSION_KEY_PREFIX‘], config[‘SESSION_USE_SIGNER‘], config[‘SESSION_PERMANENT‘]) else: session_interface = NullSessionInterface() return session_interface
SecureCookieSessionInterface —— modified
用户等刚开始登陆时,获取cookie,没有获取到调用session_class ,而session_class 等同于SecureCookieSession。 SecureCookieSession 是一个特殊的字典,继承CallbackDict, SessionMixin,而CallbackDict 继承 UpdateDictMixin, dict ,下面看下UpdateDictMixin代码
"""Makes dicts call `self.on_update` on modifications. .. versionadded:: 0.5 :private: """ on_update = None def calls_update(name): def oncall(self, *args, **kw): rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw) if self.on_update is not None: self.on_update(self) return rv oncall.__name__ = name return oncall def setdefault(self, key, default=None): modified = key not in self rv = super(UpdateDictMixin, self).setdefault(key, default) if modified and self.on_update is not None: self.on_update(self) return rv def pop(self, key, default=_missing): modified = key in self if default is _missing: rv = super(UpdateDictMixin, self).pop(key) else: rv = super(UpdateDictMixin, self).pop(key, default) if modified and self.on_update is not None: self.on_update(self) return rv __setitem__ = calls_update(‘__setitem__‘) __delitem__ = calls_update(‘__delitem__‘) clear = calls_update(‘clear‘) popitem = calls_update(‘popitem‘) update = calls_update(‘update‘) del calls_update
UpdateDictMixin设置了__setitem__ ,__delitem__,所以当用户设置session时会触发 __setitem__ 方法,调用 calls_updata 方法
calls_update代码如下
def calls_update(name): def oncall(self, *args, **kw): rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw) if self.on_update is not None: self.on_update(self) return rv oncall.__name__ = name return oncall
calls_update。调用了on_update方法
class SecureCookieSession(CallbackDict, SessionMixin): """Base class for sessions based on signed cookies.""" def __init__(self, initial=None): def on_update(self): self.modified = True #调用后设置为True CallbackDict.__init__(self, initial, on_update) self.modified = False
所以当请求结束前,保存session 时执行if not self.should_set_cookie(app, session):
def save_session(self, app, session, response): 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)) response.set_cookie(app.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
should_set_cookie
def should_set_cookie(self, app, session): """Indicates whether a cookie should be set now or not. This is used by session backends to figure out if they should emit a set-cookie header or not. The default behavior is controlled by the ``SESSION_REFRESH_EACH_REQUEST`` config variable. If it‘s set to ``False`` then a cookie is only set if the session is modified, if set to ``True`` it‘s always set if the session is permanent. This check is usually skipped if sessions get deleted. .. versionadded:: 0.11 """ if session.modified: #如果modifed 为True ,则 if not self.should_set_cookie(app, session):就不会return ,继续执行。这样就会更新session return True save_each = app.config[‘SESSION_REFRESH_EACH_REQUEST‘] #每次请求都回会修改session return save_each and session.permanent
根据上面的总结,我们可以确认,前端,如下修改就会更新session
session[‘user_info‘] = {‘k1‘:1,‘k2‘:2}
如下图这样修改,则不会更改session
session[‘user_info‘][‘k1‘] = 99999
设置 session["modified"] =True 就会更新数值,但是不推荐使用这种方式
session["modified"] =True
推荐使用 ,在settiing中配置
SESSION_REFRESH_EACH_REQUEST= True注意:使用上面方法需要在初始登录的时候设置 session.permanent = True
@account.route(‘/login‘,methods=[‘GET‘,"POST"]) def login(): if request.method == ‘GET‘: form = LoginForm() return render_template(‘login.html‘,form=form) form = LoginForm(formdata=request.form) if not form.validate(): return render_template(‘login.html‘, form=form) obj = SQLHelper.fetch_one("select id,name from users where name=%(user)s and pwd=%(pwd)s", form.data) if obj: session.permanent = True session[‘user_info‘] = {‘id‘:obj[‘id‘], ‘name‘:obj[‘name‘]} return redirect(‘/index‘)
使用配置文件进行配置redis
from datetime import timedelta from redis import Redis import pymysql from DBUtils.PooledDB import PooledDB, SharedDBConnection class Config(object): DEBUG = True SECRET_KEY = "umsuldfsdflskjdf" PERMANENT_SESSION_LIFETIME = timedelta(minutes=20) SESSION_REFRESH_EACH_REQUEST= True SESSION_TYPE = "redis" class ProductionConfig(Config): SESSION_REDIS = Redis(host=‘192.168.0.94‘, port=‘6379‘) class DevelopmentConfig(Config): SESSION_REDIS = Redis(host=‘127.0.0.1‘, port=‘6379‘) class TestingConfig(Config): pass
setting
from s8pro_flask import create_app app = create_app() if __name__ == ‘__main__‘: app.run()
manage.py
from flask import Flask from flask_session import Session from .views import account from .views import home def create_app(): app = Flask(__name__) app.config.from_object(‘settings.DevelopmentConfig‘) app.register_blueprint(account.account) app.register_blueprint(home.home) # 将session替换成redis session Session(app) return app
__init__.py
原文地址:https://www.cnblogs.com/huyangblog/p/8971353.html