zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    DLL文件的原理
    OD使用教程7(上) 调试篇07|解密系列
    复制独立数组的方法
    [转载 js] js正则表达式
    中国雅虎ued成员
    复制独立数组的方法
    [转载 js] js正则表达式
    以前做的flash相册
    编程的幽默
    我最早的全flash站
  • 原文地址:https://www.cnblogs.com/huyangblog/p/8971353.html
Copyright © 2011-2022 走看看