flask_session——RedisSessionInterface 使用

Posted hpython

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flask_session——RedisSessionInterface 使用相关的知识,希望对你有一定的参考价值。

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

 

以上是关于flask_session——RedisSessionInterface 使用的主要内容,如果未能解决你的问题,请参考以下文章

Flask_Session插件

flask flask_session,WTForms

Flask快速入门(17) — flask_session

flask_session连接redis

flask_session连接redis

flask_session——RedisSessionInterface 使用