zoukankan      html  css  js  c++  java
  • flask内置session原理

    内置session原理

    请求到来

    当请求进来之后,先执行Flask对象的 __call__ 方法

    def wsgi_app(self, environ, start_response):
            # 获取请求相关数据,并进行封装和加工
            ctx = self.request_context(environ)
            # 将请求消息推送到堆栈中,并执行 open_session方法
            ctx.push()
            error = None
            try:
                try:
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.make_response(self.handle_exception(e))
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)
    
        def __call__(self, environ, start_response):
            """Shortcut for :attr:`wsgi_app`."""
            return self.wsgi_app(environ, start_response)
     def push(self):
            top = _request_ctx_stack.top
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
    
            # Before we push the request context we have to ensure that there
            # is an application context.
            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)
    
            # 调用Flask对象的open_session方法
            self.session = self.app.open_session(self.request)
            if self.session is None:
                self.session = self.app.make_null_session()
     def open_session(self, request):
            """Creates or opens a new session.  Default implementation stores all
            session data in a signed cookie.  This requires that the
            :attr:`secret_key` is set.  Instead of overriding this method
            we recommend replacing the :class:`session_interface`.
    
            :param request: an instance of :attr:`request_class`.
            """
            # self指的是Flask对象,session_interface默认值为SecureCookieSessionInterface()
            return self.session_interface.open_session(self, request)

    由以上源码发现,当接收到用户请求之后,会调用 Flask对象的 session_interface对象的open_session方法,以此来获取一个session对象。

    class SecureCookieSessionInterface(SessionInterface):
        """The default session interface that stores sessions in signed cookies
        through the :mod:`itsdangerous` module.
        """
        #: the salt that should be applied on top of the secret key for the
        #: signing of cookie based sessions.
        salt = 'cookie-session'
        #: the hash function to use for the signature.  The default is sha1
        digest_method = staticmethod(hashlib.sha1)
        #: the name of the itsdangerous supported key derivation.  The default
        #: is hmac.
        key_derivation = 'hmac'
        #: A python serializer for the payload.  The default is a compact
        #: JSON derived serializer with support for some extra Python types
        #: such as datetime objects or tuples.
        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):
            # 获取加密相关的类,必须设置app.secret_key,不然s就是None
            s = self.get_signing_serializer(app)
    
            if s is None:
                return None
            # 去Cookie中获取 session 对应的值(该值默认是加密之后的session的值,也可以改造成随机字符串)
            val = request.cookies.get(app.session_cookie_name)
            if not val:
                # 未获取到值,则创建一个空字典(就是flask中用到的session)
                return self.session_class()
            max_age = total_seconds(app.permanent_session_lifetime)
            try:
                data = s.loads(val, max_age=max_age)
                # 如果获取到值,则将值放入字典中(就是flask中用到的session)
                return self.session_class(data)
            except BadSignature:
                # 解密失败,则创建一个空字典(就是flask中用到的session)
                return self.session_class()

    上述中 self.session_class 就是创建的一个SecureCookieSession对象,这个类是继承了字典的类,其实就是一个特殊的字典。

    class SessionMixin(object):
        """Expands a basic dictionary with an accessors that are expected
        by Flask extensions and users for the session.
        """
    
        def _get_permanent(self):
            return self.get('_permanent', False)
    
        def _set_permanent(self, value):
            self['_permanent'] = bool(value)
    
        #: this reflects the ``'_permanent'`` key in the dict.
        permanent = property(_get_permanent, _set_permanent)
        del _get_permanent, _set_permanent
    
        #: some session backends can tell you if a session is new, but that is
        #: not necessarily guaranteed.  Use with caution.  The default mixin
        #: implementation just hardcodes ``False`` in.
        new = False
    
        #: for some backends this will always be ``True``, but some backends will
        #: default this to false and detect changes in the dictionary for as
        #: long as changes do not happen on mutable structures in the session.
        #: The default mixin implementation just hardcodes ``True`` in.
        modified = True
    
    class UpdateDictMixin(object):
    
        """Makes dicts call `self.on_update` on modifications.
    
        .. versionadded:: 0.5
    
        :private:
        """
    
        on_update = None
    
        def calls_update(name):
            def oncall(self, *args, **kw):
                rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw)
                if self.on_update is not None:
                    self.on_update(self)
                return rv
            oncall.__name__ = name
            return oncall
    
        def setdefault(self, key, default=None):
            modified = key not in self
            rv = super(UpdateDictMixin, self).setdefault(key, default)
            if modified and self.on_update is not None:
                self.on_update(self)
            return rv
    
        def pop(self, key, default=_missing):
            modified = key in self
            if default is _missing:
                rv = super(UpdateDictMixin, self).pop(key)
            else:
                rv = super(UpdateDictMixin, self).pop(key, default)
            if modified and self.on_update is not None:
                self.on_update(self)
            return rv
    
        __setitem__ = calls_update('__setitem__')
        __delitem__ = calls_update('__delitem__')
        clear = calls_update('clear')
        popitem = calls_update('popitem')
        update = calls_update('update')
        del calls_update
    
    
    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)
            )
    
    
    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

    该字典其实就是继承了字典,并在其基础上定制了一些功能,如

    class MyDict(dict):
        def __init__(self, initial):
            dict.__init__(self, initial)
    
    
    session = MyDict({'k1': 123})
    
    print(session, type(session)) # {'k1': 123} <class '__main__.MyDict'>
    
    
    session['k2'] = 'v2'
    print(session)

    所以,Flask的视图函数中在对session进行操作时,其实就是在内存中修改一个字典的数据。

    业务处理

    设置session

    响应内容

    响应内容其实就讲数据返回给用户,并且把内容中的session重新保存

    def wsgi_app(self, environ, start_response):
            """The actual WSGI application.  This is not implemented in
            `__call__` so that middlewares can be applied without losing a
            reference to the class.  So instead of doing this::
    
                app = MyMiddleware(app)
    
            It's a better idea to do this instead::
    
                app.wsgi_app = MyMiddleware(app.wsgi_app)
    
            Then you still have the original application object around and
            can continue to call methods on it.
    
            .. versionchanged:: 0.7
               The behavior of the before and after request callbacks was changed
               under error conditions and a new callback was added that will
               always execute at the end of the request, independent on if an
               error occurred or not.  See :ref:`callbacks-and-errors`.
    
            :param environ: a WSGI environment
            :param start_response: a callable accepting a status code,
                                   a list of headers and an optional
                                   exception context to start the response
            """
            ctx = self.request_context(environ)
            ctx.push()
            error = None
            try:
                try:
                    # 处理业务请求,并获取返回值
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.make_response(self.handle_exception(e))
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)
     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()
            try:
                request_started.send(self)
                # 执行视图函数,处理业务请求
                rv = self.preprocess_request()
                if rv is None:
                    rv = self.dispatch_request()
            except Exception as e:
                rv = self.handle_user_exception(e)
            response = self.make_response(rv)
            # 处理响应内容
            response = self.process_response(response)
            request_finished.send(self, response=response)
            return response
    def process_response(self, response):
            """Can be overridden in order to modify the response object
            before it's sent to the WSGI server.  By default this will
            call all the :meth:`after_request` decorated functions.
    
            .. versionchanged:: 0.5
               As of Flask 0.5 the functions registered for after request
               execution are called in reverse order of registration.
    
            :param response: a :attr:`response_class` object.
            :return: a new response object or the same, has to be an
                     instance of :attr:`response_class`.
            """
            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):
                # 执行flask对象的save_session方法
                self.save_session(ctx.session, response)
            return response
    
        def save_session(self, session, response):
            """Saves the session if it needs updates.  For the default
            implementation, check :meth:`open_session`.  Instead of overriding this
            method we recommend replacing the :class:`session_interface`.
    
            :param session: the session to be saved (a
                            :class:`~werkzeug.contrib.securecookie.SecureCookie`
                            object)
            :param response: an instance of :attr:`response_class`
            """
            # 执行session_interface的save_session方法,将内存中的session保存。
            return self.session_interface.save_session(self, session, response)

    执行xxx的save_session方法,将内存中的数据保存。

    class SecureCookieSessionInterface(SessionInterface):
        """The default session interface that stores sessions in signed cookies
        through the :mod:`itsdangerous` module.
        """
        #: the salt that should be applied on top of the secret key for the
        #: signing of cookie based sessions.
        salt = 'cookie-session'
        #: the hash function to use for the signature.  The default is sha1
        digest_method = staticmethod(hashlib.sha1)
        #: the name of the itsdangerous supported key derivation.  The default
        #: is hmac.
        key_derivation = 'hmac'
        #: A python serializer for the payload.  The default is a compact
        #: JSON derived serializer with support for some extra Python types
        #: such as datetime objects or tuples.
        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()
    
        def save_session(self, app, session, response):
            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
    
            # Modification case.  There are upsides and downsides to
            # emitting a set-cookie header each request.  The behavior
            # is controlled by the :meth:`should_set_cookie` method
            # which performs a quick check to figure out if the cookie
            # should be set or not.  This is controlled by the
            # SESSION_REFRESH_EACH_REQUEST config flag as well as
            # the permanent flag on the session itself.
            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))
            response.set_cookie(app.session_cookie_name, val,
                                expires=expires, httponly=httponly,
                                domain=domain, path=path, secure=secure)
  • 相关阅读:
    Linux性能评测工具之一:gprof篇
    几个源码下载的网站
    linux svn代码回滚命令
    这就是阶层——你根本不知道世界有多残酷
    shell脚本中的数据传递方式
    XGBoost参数调优完全指南(附Python代码)
    机器学习(一) ---- 最优化理论基础
    Docker构建Java web应用服务
    使用Dockerfile创建支持SSH服务的镜像
    使用commit方式构建具有sshd服务的centos镜像
  • 原文地址:https://www.cnblogs.com/lianxuebin/p/8664340.html
Copyright © 2011-2022 走看看