flask-session

  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,session


app = Flask(__name__)
app.secret_key = 'suijksdfsd'

# 指定session_interface 类
from redis import Redis
from flask_session import RedisSessionInterface
conn = 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 Redis
from flask_session import Session
app.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,过程和第一方式一样的,只不过就多做了一层封装

 

posted @ 2018-09-10 09:53  财经知识狂魔  阅读(417)  评论(0编辑  收藏  举报