flask中的session

Perequisite:

  • 什么是Cookie?

以最基本的官方文档中的示例flaskr.py中的代码片段中的session的用法为起点:

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)

首先,已经知道from flask import session中的session也是一个LocalProxy.

# globals.py
# session实际上同样是一个LocalProxy.
session = LocalProxy(partial(_lookup_req_object, 'session'))
def _lookup_req_object(name):
    # session储存在RequestContext中。
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

那么,本质上session到底是什么呢,官方文档说,session是基于cookies的。

        # Open the session at the moment that the request context is
        # available. This allows a custom open_session method to use the
        # request context (e.g. code that access database information
        # stored on `g` instead of the appcontext).
        # ----------------------------------------
        # ctx.py RequestContext.push()
        # 在RequestContext被推送之前,真正的session被Flask.open_session函数创建。
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
    # app.py
    def open_session(self, request):
        # 打开一个新的session,默认的实现是把所有session中的数据储存在一个
        # cookie中,这就要求secret_key需要被设置. 我们可以通过代替session_interface
        # 来自定义session.
        return self.session_interface.open_session(self, request)
    # app.py
    # 默认情况,session_interface为SecureCookieSessionInterface()
    # 可以自定义。
    session_interface = SecureCookieSessionInterface()
    # sessions.py//class SecureCookieSessionInterface()
    def open_session(self, app, request):
        # 获得签名序列化工具.
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        # 查看request中是否有Cookies.
        val = request.cookies.get(app.session_cookie_name)
        # 如果没有Cookies, 直接返回session_class(), 默认的session是SecureCookieSession.
        if not val:
            return self.session_class()
        # max_age被设置为31天.
        max_age = total_seconds(app.permanent_session_lifetime)
        try:
            # 加载data.'val:aSerializedString'-->'data:aNormalString'
            data = s.loads(val, max_age=max_age)
            # 返回SecureCookieSession.
            return self.session_class(data)
        # 处理异常.
        except BadSignature:
            return self.session_class()
    # sessions.py//class SecureCookieSessionInterface()
    def get_signing_serializer(self, app):
        # 必须保证app.secret_key被设置.
        if not app.secret_key:
            return None
        # 设置选项
        signer_kwargs = dict(
            key_derivation=self.key_derivation,
            digest_method=self.digest_method
        )
        # 返回一个itsdangerous中的URLSafaTimedSerializer对象.
        return URLSafeTimedSerializer(app.secret_key, salt=self.salt,
                                      serializer=self.serializer,
                                      signer_kwargs=signer_kwargs)
# session.py
class SecureCookieSession(CallbackDict, SessionMixin):
    """Base class for sessions based on signed cookies."""

    def __init__(self, initial=None):
        def on_update(self):
            self.modified = True
        CallbackDict.__init__(self, initial, on_update)
        self.modified = False

当open_session执行完毕后,RequestContext.push()的self.session本质就真相大白也即是SecureCookieSession类.
在RequestContext.push()最后,

        if self.session is None:
            self.session = self.app.make_null_session()

于是self.session就被设置为NullSession.

# NullSession继承自SecureCookieSession.只不过所有的methods都被设置为抛出异常.
class NullSession(SecureCookieSession):
    def _fail(self, *args, **kwargs):
        raise RuntimeError('The session is unavailable because no secret '
                           'key was set.  Set the secret_key on the '
                           'application to something unique and secret.')
    __setitem__ = __delitem__ = clear = pop = popitem = \
        update = setdefault = _fail
    del _fail

至此,RequestContext push完毕,RequestContext的session储存了SecureCookieSession类实例.

Part2


接下来处理request,在处理/login对应的视图函数时,当我们写session['logged_in'] = True.就在session中设置了一对健值.
在process_response函数中:

    # app.py/Flask.process_response.
    def process_response(self, response):
        ...
        # 此处完成对session的处理.
        if not self.session_interface.is_null_session(ctx.session):
            self.save_session(ctx.session, response)
        return response
    def is_null_session(self, obj):
        """Checks if a given object is a null session.  Null sessions are
        not asked to be saved.

        This checks if the object is an instance of :attr:`null_session_class`
        by default.
        """
        # 检查这是不是一个Null session,Null session不会被理会.
        return isinstance(obj, self.null_session_class)

接下来就是重要的save_session函数:

    def save_session(self, app, session, response):
        # 获取设置cookie需要的参数.
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)

        # Delete case.  If there is no session we bail early.
        # If the session was modified to be empty we remove the
        # whole cookie.
        if not session:
            if session.modified:
                response.delete_cookie(app.session_cookie_name,
                                       domain=domain, path=path)
            return

        # 什么情况下不需要设置cookie.
        if not self.should_set_cookie(app, session):
            return
        
        # 设置cookie需要的参数.
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)
        # 最重要的val
        val = self.get_signing_serializer(app).dumps(dict(session))
        # 在response中设置cookie.
        response.set_cookie(app.session_cookie_name, val,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)

下面结合客户端Requests库和服务端的flaskr应用来实验一下,理顺一下程序流.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容