全局对象session解析

flask全局对象之session

接下来我们来看看flask的session对象,session对象也来自与globals.py文件,现贴出session对象的产生逻辑的源码:

from functools import partial

from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


_request_ctx_stack = LocalStack()

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

LocalProxy(partial(_lookup_req_object, "session"))可知,session来自request上下文的session属性,接下来看一下session的具体处理逻辑和生命周期:

class Flask(_PackageBoundObject):
    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:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

我们回忆下reqeust上下文的实例化逻辑,来找寻session的踪迹:

class RequestContext(object):

    def __init__(self, app, environ, request=None, session=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = None
        try:
            self.url_adapter = app.create_url_adapter(self.request)
        except HTTPException as e:
            self.request.routing_exception = e
        self.flashes = None
        self.session = session
        self._implicit_app_ctx_stack = []

        self.preserved = False
        self._preserved_exc = None
        self._after_request_functions = []

    def push(self):

        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            app_ctx = self.app.app_context()
            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)

        # session具体处理过程
        if self.session is 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)

        if self.url_adapter is not None:
            self.match_request()

由代码可知RequestContext实例化时,对session进行初始化,默认为None,而后在push方法中,针对不同的session值,进行不同的处理。接下来具体的分析一下:
如果session为None时,将其值赋为session_interface.open_session的返回值,session_interfaceself.app.session_interface, 即Flask类的session_interface,默认为flask.sessions.SecureCookieSessionInterface的实例化对象,那flask.sessions.SecureCookieSessionInterface的实现原理如何,我们从源码中发现秘密:

class SecureCookieSessionInterface(SessionInterface):

    salt = "cookie-session"
    digest_method = staticmethod(hashlib.sha1)
    key_derivation = "hmac"
    serializer = session_json_serializer
    session_class = SecureCookieSession

    def get_signing_serializer(self, app):
        if not app.secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation, digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(
            app.secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs,
        )

    def open_session(self, app, request):
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        val = request.cookies.get(app.session_cookie_name)
        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()

SecureCookieSessionInterface.open_session方法中,首先获取序列化实例s,由get_signing_serializer可知,当Flask的SECRET_KEY为None, s值也为None,open_session直接返回None,否则继续获取request的cookies,cookies的key为app.session_cookie_name, 默认值为session,翻看Flask的源码,发现这里使用的是python的数据描述符类,关于此概念可以自行百度,才能具体理解此处的妙用。最后返回self.session_class实例化对象,self.session_class默认为session.py中的SecureCookieSession类。
继续看当self.session为None时,将会重新赋值为session_interface.make_null_session(self.app),我们看一下make_null_session的具体实现: 需要注意的是SecureCookieSessionInterface中并未发现make_null_session方法,但是在父类SessionInterface中实现了此方法,下面看一下具体的代码:

class SessionInterface(object):

    null_session_class = NullSession
    pickle_based = False

    def make_null_session(self, app):
        """Creates a null session which acts as a replacement object if the
        real session support could not be loaded due to a configuration
        error.  This mainly aids the user experience because the job of the
        null session is to still support lookup without complaining but
        modifications are answered with a helpful error message of what
        failed.

        This creates an instance of :attr:`null_session_class` by default.
        """
        return self.null_session_class()

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

看代码的实现方式,当Flask的配置SECRET_KEY为None,session将会是不可用。默认为NullSession的实例,此对象将不能进行任何操作。当我们启用session时,即SECRET_KEY不为None,此时我们的session将在response返回给客户端是进行save_session操作,即下面的逻辑:

class Flask(_PackageBoundObject):
    def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        for handler in funcs:
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response

可以看到我们对cookie的修改将会在cookie未过期之前一直保留,并通过itsdangerous库进行序列化和反序列化。至此session的基本逻辑已介绍完毕,具体的代码还需要我仔细去读才能理解。

「真诚赞赏,手留余香」

roc

请我喝杯咖啡?

使用微信扫描二维码完成支付