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)
  • 相关阅读:
    布局重用 include merge ViewStub
    AS 常用插件 MD
    AS 2.0新功能 Instant Run
    AS .ignore插件 忽略文件
    AS Gradle构建工具与Android plugin插件【大全】
    如何开通www国际域名个人网站
    倒计时实现方案总结 Timer Handler
    AS 进行单元测试
    RxJava 设计理念 观察者模式 Observable lambdas MD
    retrofit okhttp RxJava bk Gson Lambda 综合示例【配置】
  • 原文地址:https://www.cnblogs.com/ctztake/p/8260887.html
Copyright © 2011-2022 走看看