zoukankan      html  css  js  c++  java
  • Flask框架【七】—session组件详解

    一、flask session简介

    flask中session组件可分为内置的session组件还有第三方flask-session组件,内置的session组件缺点:

    • 功能单一

    • session是保存在浏览器中的cookie中,不安全,

    • 大小有限制

    而第三方插件flask-session可支持redis、memcached、文本等session的存储。

    二、内置session处理机制

    Cookie与Session

    我们回顾一下cookie和session知识

    Cookie

    Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。

    Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容

    Session

    Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了,实质上session就是保存在服务器端的键值对。

    如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。

    session流程

    1.第一次请求,session的创建过程

    flask上下文中介绍了,请求到flask框架会执行wsgi_app方法:

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)  # 实例化生成RequestContext对象
        error = None
        try:
            try:
                ctx.push()  # push上下文到LocalStack中
                response = self.full_dispatch_request()  # 执行视图函数过程
            except Exception as e:
                error = e
                response = self.handle_exception(e)  # 处理异常
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)  # 删除LocalStack中的数据

    在改方法中会生成一个ctx也就是RequestContext对象:

    class RequestContext(object):
        def __init__(self, app, environ, request=None):
            self.app = app  # app对象
            if request is None:
                request = app.request_class(environ)
            self.request = request  # 封装request
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None  # 一开始的session

    在这个对象中封装了session,最初为None。接着在wsgi_app中执行ctx.push:

    def push(self):
        app_ctx = _app_ctx_stack.top  # 获取app上下文
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()  # 将app上下文push到app_ctx对于的LocalStack中
            app_ctx.push()
            self._implicit_app_ctx_stack.append(app_ctx)
        else:
            self._implicit_app_ctx_stack.append(None)
        if hasattr(sys, "exc_clear"):
            sys.exc_clear()
        _request_ctx_stack.push(self)
        if self.session is None:  # 判断session是否为None,一开始为None
            session_interface = self.app.session_interface  # 获取操作session的对象
            self.session = session_interface.open_session(  # 调用open_session 创建session
                self.app, self.request
            )
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)

    这里我们主要关注session,前面的代码在上下文中已经进行了相关说明,这里有个判断session是否为None,刚开始RequestContext中的session为None,所以条件成立,此时执行以下语句:

    session_interface = self.app.session_interface
    self.session = session_interface.open_session(self.app, self.request)
    if self.session is None:
        self.session = session_interface.make_null_session(self.app)

    首先来看session_interface = self.app.session_interface,self.app.session_interface就是app中的session_interface属性:

    session_interface = SecureCookieSessionInterface()

    默认是一个SecureCookieSessionInterface()对象,该对象的内部主要实现了open_session和save_session用于使用和保存session。接着self.session被重新赋值为session_interface.open_session(self.app, self.request)方法返回的值,以下为open_session源码:

    def open_session(self, app, request):
        s = self.get_signing_serializer(app)  # 根据app.secret_key获取签名算法
        if s is None:
            return None
        # 根据配置中的session_cookie_name获取session对于的值
        val = request.cookies.get(app.session_cookie_name)  # 如果request.cookies为空,val为空
        if not val:
            return self.session_class()
        max_age = total_seconds(app.permanent_session_lifetime)
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()

    该方法返回self.session_class(),当请求第一次来时,request.cookies为None,所以val也为None,返回self.session_class(),而session_class又是SecureCookieSession:

    session_class = SecureCookieSession

    所以我们继续看SecureCookieSession:

    class SecureCookieSession(CallbackDict, SessionMixin):
        accessed = False
    
        def __init__(self, initial=None):
            def on_update(self):
                self.modified = True
                self.accessed = True
        
        super(SecureCookieSession, self).__init__(initial, on_update)
    
    
        def __getitem__(self, key):
            self.accessed = True
            return super(SecureCookieSession, self).__getitem__(key)
    
        def get(self, key, default=None):
            self.accessed = True
            return super(SecureCookieSession, self).get(key, default)
    
        def setdefault(self, key, default=None):
            self.accessed = True
            return super(SecureCookieSession, self).setdefault(key, default)

    该类继承了CallbackDict, SessionMixin我们继续来看看CallbackDict:

    class CallbackDict(UpdateDictMixin, dict):
        """A dict that calls a function passed every time something is changed.
        The function is passed the dict instance.
        """
        
        def __init__(self, initial=None, on_update=None):
            dict.__init__(self, initial or ())
            self.on_update = on_update
        
        def __repr__(self):
            return '<%s %s>' % (
                self.__class__.__name__,
                dict.__repr__(self)
            )

    也就是说SecureCookieSession继承了CallbackDict而CallbackDict继承了原生的dict,所以我们可以认为SecureCookieSession是一个特殊的字典,是调用了SecureCookieSessionInterface类中open_session返回的特殊字典,经过进一步分析self.session此时就是这个字典,这也意味着session在执行open_session方法时候被创建了,并保存在ctx中,也就是在RequestContext对象中,当我们使用session时候是通过全局变量session = LocalProxy(partial(_lookup_req_object, 'session'))由LocalProxy对象从ctx中获取到session。

    2.第二次请求

    开始我们知道session第一次请求来的时候是在open_session方法之后被创建,当第二次请求时,此时在open_session方法中,val已经不在是None,此时获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回,若cookie已经失效,则仍然返回'空字典',这样以来在第二次请求中就能获取到之前保存的session数据。

    session生命周期

    我们介绍了session创建时候是在ctx.push时候开始创建,也就是说在这之后我们就可以使用session,对它进行操作了,那么session什么时候保存呢?我们接下来继续看wsgi_app:

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        error = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

    生成session后,接着执行self.full_dispatch_request():

    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.
       ​
        .. versionadded:: 0.7
        """
        self.try_trigger_before_first_request_functions()  # 执行app.before_first_reques钩子函数
        try:
            request_started.send(self)  # 触发request_started信号
            rv = self.preprocess_request()  # 执行before_request钩子函数
            if rv is None:
                rv = self.dispatch_request()  # 执行视图函数
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)

    这一部分先执行钩子app.before_first_reques在触发request_started信号,再执行before_request钩子函数,然后在执行视图函数,rv是执行完视图函数的返回值,最后执行finalize_request,这里的session保存就发生在这里:

    def finalize_request(self, rv, from_error_handler=False):
        response = self.make_response(rv)
        try:
            response = self.process_response(response)
            request_finished.send(self, response=response)
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response

    注意这里的在最后会session判断是否为空,会执行save_session方法,也就是SecureCookieSessionInterface的save_session方法:

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
    
        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    app.session_cookie_name,
                    domain=domain,
                    path=path
                )
            return
        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add('Cookie')
        if not self.should_set_cookie(app, session):
            return
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(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,
            samesite=samesite
        )

    该方法最后保存的session调用的response.set_cookie,其实是将数据保存在cookie中,也就是在客户端的浏览器中,并非在服务端进行数据的保存,当请求完毕后会执行ctx.auto_pop(error)这时候会从上下文中将session和request删除,到此,session的生命周期结束。

    视图函数使用session

    在介绍flask的上下文中就已经对session进行过介绍,其本质也是通过LocalProxy操作上下文从而设置session,我们以session['username']='wd'作为列子,首先根据

    session = LocalProxy(partial(_lookup_req_object, 'session'))

    session是一个LocalProxy对象,执行session['username']=‘wd'则执行LocalProxy对象的setitem方法,而setitem方法中则是调用_get_current_object获取ctx中的session对象,而其对象本质是一个特殊的字典,相当于在字典中加一对key,value。

    小结

    flask内置session本质上依靠上下文,当请求到来时,调用session_interface中的open_session方法解密获取session的字典,并保存在RequestContext.session中,也就是上下文中,然后在视图函数执行完毕后调用session_interface的save_session方法,将session以加密的方式写入response的cookie中,浏览器再保存数据。而第三方的session组件原理就是基于是open_session方法和save方法,从而实现session更多的session保存方案。

    三、第三方组件flask-session

    flask-session支持多种数据库session保存方案如:redis、memchached、mongodb甚至文件系统等。官方文档: https://pythonhosted.org/Flask-Session/

    安装:

    pip3 install flask-session

    redis存储的配置

    import redis
    from flask import Flask, session
    from flask_session import Session
    from datetime import timedelta
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'adavafa'
    app.config['SESSION_TYPE'] = 'redis'  # session类型为redis
    app.config['SESSION_PERMANENT'] = True  # 如果设置为True,则关闭浏览器session就失效。
    app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=20)
    # 一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
    app.config['SESSION_USE_SIGNER'] = False  # 是否对发送到浏览器上session的cookie值进行加密,默认False 
    app.config['SESSION_KEY_PREFIX'] = 'flask-session'  # 保存到redis中的key的前缀
    app.config['SESSION_COOKIE_NAME'] = 'session_id'  # 保存在浏览器的cookie名称
    app.config['SESSION_REDIS'] = redis.Redis(host='10.1.210.33', port=6379,password='123123')  # 用于连接redis的配置
    # 其他配置,不经常使用
    app.config['SESSION_COOKIE_DOMAIN'] = '127.0.0.1'  # 设置cookie的域名,不建议设置默认为server_name
    app.config['SESSION_COOKIE_PATH'] = '/'  # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
    app.config['SESSION_COOKIE_HTTPONLY'] = True  # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
    Session(app)
    
    
    @app.route('/login')
    def index():
        session["username"] = "jack"
        return 'login'
    
    if __name__ == '__main__':
        app.run()

    Memchached存储的配置

    import memcache
    from flask import Flask, session
    from flask_session import Session
    from datetime import timedelta
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'adavafa'
    
    app.config['SESSION_TYPE'] = 'memcached'  # session类型为memcached 
    app.config['SESSION_PERMANENT'] = True  # 如果设置为True,则关闭浏览器session就失效。
    app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=20)
    # 一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
    app.config['SESSION_USE_SIGNER'] = False  # 是否对发送到浏览器上session的cookie值进行加密,默认False
    app.config['SESSION_KEY_PREFIX'] = 'flask-session'  # 保存到缓存中的key的前缀
    app.config['SESSION_COOKIE_NAME'] = 'session_id'  # 保存在浏览器的cookie名称
    app.config['SESSION_MEMCACHED'] = memcache.Client(['10.1.210.33:12000'])  # 连接
    
    # 其他配置,不经常使用
    app.config['SESSION_COOKIE_DOMAIN'] = '127.0.0.1'  # 设置cookie的域名,不建议设置默认为server_name
    app.config['SESSION_COOKIE_PATH'] = '/'  # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
    app.config['SESSION_COOKIE_HTTPONLY'] = True  # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
    
    Session(app)
    
    
    @app.route('/login')
    def index():
        session["username"] = "jack"
        return 'login'
    
    
    if __name__ == '__main__':
        app.run()

    Filesystem存储的配置

    from flask import Flask, session
    from flask_session import Session
    from datetime import timedelta
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'adavafa'
    
    app.config['SESSION_TYPE'] = 'filesystem'  # session类型为filesystem
    app.config['SESSION_FILE_DIR'] = '/opt/db'  # 文件保存目录
    app.config['SESSION_FILE_THRESHOLD'] = 300  # 存储session的个数如果大于这个值时,开始删除
    
    app.config['SESSION_PERMANENT'] = True  # 如果设置为True,则关闭浏览器session就失效。
    app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=20)
    # 一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
    app.config['SESSION_USE_SIGNER'] = False  # 是否对发送到浏览器上session的cookie值进行加密,默认False
    app.config['SESSION_KEY_PREFIX'] = 'flask-session'  # 保存到文件中的key的前缀
    app.config['SESSION_COOKIE_NAME'] = 'session_id'  # 保存在浏览器的cookie名称
    
    # 其他配置,不经常使用
    app.config['SESSION_COOKIE_DOMAIN'] = '127.0.0.1'  # 设置cookie的域名,不建议设置默认为server_name
    app.config['SESSION_COOKIE_PATH'] = '/'  # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
    app.config['SESSION_COOKIE_HTTPONLY'] = True  # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
    
    Session(app)
    
    @app.route('/login')
    def index():
        session["username"] = "jack"
        return 'login'
    
    if __name__ == '__main__':
        app.run()

    mongodb存储的配置

    import pymongo
    from flask import Flask, session
    from flask_session import Session
    from datetime import timedelta
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'adavafa'
    
    app.config['SESSION_TYPE'] = 'mongodb'  # session类型为mongodb
    app.config['SESSION_MONGODB'] = pymongo.MongoClient('localhost', 27017)
    app.config['SESSION_MONGODB_DB'] = '数据库名称'
    app.config['SESSION_MONGODB_COLLECT'] = '表名称'
    
    app.config['SESSION_PERMANENT'] = True  # 如果设置为True,则关闭浏览器session就失效。
    app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=20)
    # 一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
    app.config['SESSION_USE_SIGNER'] = False  # 是否对发送到浏览器上session的cookie值进行加密,默认False
    app.config['SESSION_KEY_PREFIX'] = 'flask-session'  # 保存的session的key的前缀
    app.config['SESSION_COOKIE_NAME'] = 'session_id'  # 保存在浏览器的cookie名称
    
    # 其他配置,不经常使用
    app.config['SESSION_COOKIE_DOMAIN'] = '127.0.0.1'  # 设置cookie的域名,不建议设置默认为server_name
    app.config['SESSION_COOKIE_PATH'] = '/'  # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
    app.config['SESSION_COOKIE_HTTPONLY'] = True  # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
    
    Session(app)
    
    
    @app.route('/login')
    def index():
        session["username"] = "jack"
        return 'login'
    
    
    if __name__ == '__main__':
        app.run()

    sqlalchemy存储的配置

    import redis
    from flask import Flask, session
    from flask_session import Session
    from flask_sqlalchemy import SQLAlchemy
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'adavafa'
    
    # 设置数据库链接
    app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:dev@127.0.0.1:3306/devops?charset=utf8'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
    # 实例化SQLAlchemy
    db = SQLAlchemy(app)
    app.config['SESSION_TYPE'] = 'sqlalchemy'  # session类型为sqlalchemy
    app.config['SESSION_SQLALCHEMY'] = db  # SQLAlchemy对象
    app.config['SESSION_SQLALCHEMY_TABLE'] = '表名'  # session要保存的表名称
    
    app.config['SESSION_PERMANENT'] = True  # 如果设置为True,则关闭浏览器session就失效。
    app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=20)
    # 一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
    app.config['SESSION_USE_SIGNER'] = False  # 是否对发送到浏览器上session的cookie值进行加密,默认False
    app.config['SESSION_KEY_PREFIX'] = 'flask-session'  # 保存的session的key的前缀
    app.config['SESSION_COOKIE_NAME'] = 'session_id'  # 保存在浏览器的cookie名称
    
    # 其他配置,不经常使用
    app.config['SESSION_COOKIE_DOMAIN'] = '127.0.0.1'  # 设置cookie的域名,不建议设置默认为server_name
    app.config['SESSION_COOKIE_PATH'] = '/'  # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
    app.config['SESSION_COOKIE_HTTPONLY'] = True  # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
    
    Session(app)
    
    
    @app.route('/login')
    def index():
        session["username"] = "jack"
        return 'login'
    
    
    if __name__ == '__main__':
        app.run()
    
    ###使用SQLAlchemy时候先确保数据库和表都存在在命令行中创建表
    # >>> from app import db
    # >>> db.create_all()

    原理

    这里以redis作为session存储方案做分析,以下是RedisSessionInterface源码:

    class RedisSessionInterface(SessionInterface):
        serializer = pickle
        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):
            sid = request.cookies.get(app.session_cookie_name)
            if not sid:
                sid = self._generate_sid()
                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')
            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
            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))
            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
            response.set_cookie(app.session_cookie_name, session_id,
                                expires=expires, httponly=httponly,
                                domain=domain, path=path, secure=secure)

    分析:RedisSessionInterface继承了SessionInterface

    class SessionInterface(FlaskSessionInterface):
    
        def _generate_sid(self):
            return str(uuid4())
        
        def _get_signer(self, app):
            if not app.secret_key:
                return None
            return Signer(app.secret_key, salt='flask-session',
                          key_derivation='hmac')

    而SessionInterface又继承了FlaskSessionInterface,而FlaskSessionInterface又继承了flask内置的SessionInterface,并且RedisSessionInterface重写了内置session的open_session和save_session.

    首先是RedisSessionInterface实例化用于初始化配置,例如redis的连接、签名配置、过期配置、前缀配置等。

    接下来看两个核心方法:open_session方法和save_session方法。

    open_session:

    def open_session(self, app, request):
        sid = request.cookies.get(app.session_cookie_name)  # 获取session id
        if not sid:  # 判断session id是否为空,为空表示第一次请求
            sid = self._generate_sid()  # 返回使用uuid4随机字符串
            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)  # 对session id 进行加密签名
                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)  # 获取seession数据
        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)

    改方法先从cookie中获取session id,然后对session id判断是否为空,为空表示第一次请求,则通过self._generate_sid()返回随机字符串,作为返回给浏览器的sessionid

    def _generate_sid(self):
        return str(uuid4())

    接着判断签名判断是否为true,然后对session 进行签名,这里和内置session不同的是获取session的时候通过self.redis.get(self.key_prefix + sid)在redis中进行获取。

    save_session:

    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)  # 获取cookie中的域名
        path = self.get_cookie_path(app)  # 获取cookie 中path
        if not session:  # 判断有误session对象
            if session.modified:  # 没有但是被修改了,表示已经被删除了
                self.redis.delete(self.key_prefix + session.sid)  # 清空session
                response.delete_cookie(app.session_cookie_name,
                                       domain=domain, path=path)
            return
    
        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))
        self.redis.setex(name=self.key_prefix + session.sid, value=val,
                         time=total_seconds(app.permanent_session_lifetime))  # 保存session
        if self.use_signer:
            session_id = self._get_signer(app).sign(want_bytes(session.sid))
        else:
            session_id = session.sid
        response.set_cookie(app.session_cookie_name, session_id,  # 设置cookie
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)

     

  • 相关阅读:
    POJ 3669 Meteor Shower【BFS】
    用于JS日期格式化,以及简单运算的Date包装工具类
    asp+jQuery解决中文乱码
    jQuery制作信息提示弹出层插件【推荐】
    让 SyntaxHighlighter 3.x 支持 Lua 语法着色
    JQuery操作TABLE,及console.info问题。
    可加装广告的swf播放器JS代码
    Java 绘制环形的文字 (Circle Text Demo)
    Java数据库操作类演示
    Java 通过 HTTP 下载文件
  • 原文地址:https://www.cnblogs.com/ryxiong-blog/p/11183147.html
Copyright © 2011-2022 走看看