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应用来实验一下,理顺一下程序流.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容