session 的 源码

session 的 源码

1.session 和 cookie 的 区别:

答: cookie 它是保存在浏览器的键值对。

  session 它是保存在服务端的键值对。  它 依赖cookie 存在。

  流程:

  一个服务端,     一个客户端第一次来

  服务端  去 客户端 发来的请求里的cookie 里面找一个 随机字符串。

  新用户没有,服务端就会为客户端创建一个随机字符串,发到它的cookie里,并且保存到服务端。

  下次这个客户端又来了,那么这次服务端就可以获取到了随机字符串。

  根据这个随机字符串看看你以前有没有在session里面放数据。(对比) 

  如果数据更新,是会先更新到内存里面,请求返回到时候,在会把内存的数据保存的session。

 

  session

    {

      pc随机字符串: {},

    }

  当请求第一次来,生成随机字符串,发给用户的cookie,保存到session字段中。(其实就是调用stark(中介),将随机字符串和对应的值,放到local(房主)里)

  以后去取的时候,top就行了。

  请求处理完毕,就将session持久化,存到数据库,存到redis,存到加密cookie中。

 

2.分析session源码:

1.请求刚进来先走Flask类的__call__方法。

 

2.用到类和方法。

 

3. 细分:

 0.app.run()   ==>  run_simple(host, port, self, **options) #第一: hold 住 对象过来就执行 __call__方法.

 

 

 1.__call__ 跳入:

  

 

 2.self.wsgi_app 跳入:

   def wsgi_app(self, environ, start_response): 

  2.1 如何封装? push 跳入

   

 

 3. push跳入之后

   

  _request_ctx_stack跳入: request 和 session 都会放入local 中。

  

  3.1 刚开始处理session

    

 

 4.self.wsgi_app. 处理函数

  

  full_dispatch_request()跳入:

 

 5.full_dispatch_request

  

 

 

 6.full_dispatch_request。对返回值进行封装,请求完了之后 after。

 

  finalize_request跳入;

 

 7.finalize_request

 

  process_response跳入:

 

 8.process_response

 

 

 9. self.wsgi_app 里。

   

 

4.超级细分:

三部曲:

 1. 上下文的处理:  他做的是: 把request,session 的请求相关信息放到local里。(赋值给RequestContext对象)

  首先.

if __name__ == '__main__':
    app.run()
    app.__call__()   #第一

ps:   __call__ 跳入

 

def __call__(self, environ, start_response):
     """Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)   #第二:请求开始的地方,也是结束的地方

ps:  wsgi_app  跳入

 

    def wsgi_app(self, environ, start_response):    #第三
        '''
         {
            "唯一标识":{'stack':[ResquestContext对象(request,session)]}
         }
        '''
        #第四: 开始之前做的事情。(两件事:处理了request,session),将这两个数据封装到local中。
        ctx = self.request_context(environ) #将request信息封装到Request(environ)对象并赋值给了 RequestContext对象
        ctx.push()
        error = None
        try:
            try:
                response = self.full_dispatch_request()   #第七:执行:before装饰器函数,视图函数:after装饰器函数
            except Exception as e: #如果请求出现异常
                error = e
                response = self.handle_exception(e)  # 信号6: 第2/3/4/5/got_request_exception
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)  # 结束了  #ctx是request-Context对象 信号7: 里面做了 第6信号:

ps:  request_context  跳入

 

    def request_context(self, environ):

        return RequestContext(self, environ)

ps:  RequestContext 跳入

 

class RequestContext(object):
    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:     #如果request 为空
            request = app.request_class(environ)   # app.request_class就是request对象,app就是flask对象
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

        # Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []

        # indicator if the context was preserved.  Next time another context
        # is pushed the preserved context is popped.
        self.preserved = False

        # remembers the exception for pop if there is one in case the context
        # preservation kicks in.
        self._preserved_exc = None

        # Functions that should be executed after the request on the response
        # object.  These will be called before the regular "after_request"
        # functions.
        self._after_request_functions = []

        self.match_request()

    def _get_g(self):
        return _app_ctx_stack.top.g
    def _set_g(self, value):
        _app_ctx_stack.top.g = value
    g = property(_get_g, _set_g)
    del _get_g, _set_g

    def copy(self):
        """Creates a copy of this request context with the same request object.
        This can be used to move a request context to a different greenlet.
        Because the actual request object is the same this cannot be used to
        move a request context to a different thread unless access to the
        request object is locked.

        .. versionadded:: 0.10
        """
        return self.__class__(self.app,
            environ=self.request.environ,
            request=self.request
        )

    def match_request(self):
        """Can be overridden by a subclass to hook into the matching
        of the request.
        """
        try:
            url_rule, self.request.view_args = \
                self.url_adapter.match(return_rule=True)
            self.request.url_rule = url_rule
        except HTTPException as e:
            self.request.routing_exception = e

    def push(self):
        """Binds the request context to the current context."""
        # If an exception occurs in debug mode or if context preservation is
        # activated under exception situations exactly one context stays
        # on the stack.  The rationale is that you want to access that
        # information under debug situations.  However if someone forgets to
        # pop that context again we want to make sure that on the next push
        # it's invalidated, otherwise we run at risk that something leaks
        # memory.  This is usually only a problem in test suite since this
        # functionality is not active in production environments.
        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()  # 信号1: 把app放到 local里。
            app_ctx.push()  # 信号2:AppContext(self)的push  (里面做了:触发执行 第1信号:appcontext_pushed)
            self._implicit_app_ctx_stack.append(app_ctx) # 信号3: 把上面app_ctx对象加进来,以后可以取
        else:
            self._implicit_app_ctx_stack.append(None)

        if hasattr(sys, 'exc_clear'):
            sys.exc_clear()

        _request_ctx_stack.push(self)  #第五:将requestContext对象(request,session)的引用,只要一修改,都修改了,添加到local中。

        # 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).
        #self.session_interface.open_session(self, request) = self.app.open_session(self.request)
        #return self.session_interface.open_session(self, request)
        #return SecureCookieSessionInterface().open_session(sekf,request)  经过一段处理,session就不是None了
        self.session = self.app.open_session(self.request)  #第六:刚开始处理session。self.app 表示:flask 对象
        if self.session is None:  #  self 表示:requestContext对象.请求一开始进来,为空
            self.session = self.app.make_null_session()  #  走这里,最后还是指null_session_class = NullSession。

    def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.

        .. versionchanged:: 0.9
           Added the `exc` argument.
        """
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)  # 信号7。2: 第6信号  request_tearing_down

                # If this interpreter supports clearing the exception information
                # we do that now.  This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()

            # get rid of circular dependencies at the end of the request
            # so that we don't require the GC to be active.
            if clear_request:
                rv.request.environ['werkzeug.request'] = None

            # Get rid of the app as well if necessary.
            if app_ctx is not None:
                app_ctx.pop(exc)  # 信号8:pop属Appcontxt 跳入: 它做了触发了 第7信号 appcontext_tearing_down

            assert rv is self, 'Popped wrong request context.  ' \
                '(%r instead of %r)' % (rv, self)

    def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)  #信号7.1  跳入

    def __enter__(self):
        self.push()
        return self

    def __exit__(self, exc_type, exc_value, tb):
        # do not pop the request stack if we are in debug mode and an
        # exception happened.  This will allow the debugger to still
        # access the request object in the interactive shell.  Furthermore
        # the context can be force kept alive for the test client.
        # See flask.testing for how this works.
        self.auto_pop(exc_value)

        if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
            reraise(exc_type, exc_value, tb)

    def __repr__(self):
        return '<%s \'%s\' [%s] of %s>' % (
            self.__class__.__name__,
            self.request.url,
            self.request.method,
            self.app.name,
        )

ps:   这里,我们暂时先可以看下: session 和 request 一开进来都None。  接着,我们返回,从push 方法进入:

 

 def wsgi_app(self, environ, start_response):    #第三
        '''
         {
            "唯一标识":{'stack':[ResquestContext对象(request,session)]}
         }
        '''
        #第四: 开始之前做的事情。(两件事:处理了request,session),将这两个数据封装到local中。
        ctx = self.request_context(environ) #将request信息封装到Request(environ)对象并赋值给了 RequestContext对象
        ctx.push()

ps:  push 跳入,看看里面都执行了什么?

 

_request_ctx_stack = LocalStack()

ps:  LocalStack()  跳入

 

  def __init__(self):
        self._local = Local()

ps:  Local () 跳入

 

执行setattr 方法,

 def __setattr__(self, name, value):
        ident = self.__ident_func__()
        storage = self.__storage__
        try:
            storage[ident][name] = value
        except KeyError:
            storage[ident] = {name: value}

 

返回:

        #self.session_interface.open_session(self, request) = self.app.open_session(self.request)
        #return self.session_interface.open_session(self, request)
        #return SecureCookieSessionInterface().open_session(sekf,request)  经过一段处理,session就不是None了
        self.session = self.app.open_session(self.request)  #第六:刚开始处理session。self.app 表示:flask 对象
        if self.session is None:  #  self 表示:requestContext对象.请求一开始进来,为空
            self.session = self.app.make_null_session()  #  走这里,最后还是指null_session_class = NullSession。

ps:  open_session  跳入

 

    def open_session(self, request):

        return self.session_interface.open_session(self, request)

ps: session_interface 跳入

 

 session_interface = SecureCookieSessionInterface()

ps: SecureCookieSessionInterface()  跳入

 

class SecureCookieSessionInterface(SessionInterface):
#: 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 #其实,最后返回的还是个字典的形式

ps: SecureCookieSession跳入

 

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

ps: CallbackDict 跳入

 

class CallbackDict(UpdateDictMixin, dict):   #dict 字典

    """A dict that calls a function passed every time something is changed.
    The function is passed the dict instance.
    """

ps:  从这可以看出,它最后继承是  dict  字典。

 

返回:

return self.session_interface.open_session(self, request)

ps: 从open_session 进入

 

    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)  #request:请求的对象,去cookie中获取session作为key对应的值。
        if not val:  #没有
            return self.session_class()  #返回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()

ps: 进行一系列处理,最后的返回的 session_class 是以字典形式存的。

 

小结: 以上的流程就是上下文处理的过程。

    简单的来说,上下文处理做的就是把请求的数据给封装到了 local 里。

 

 2. 视图函数处理

  首先:

    def wsgi_app(self, environ, start_response):    #第三
        '''
         {
            "唯一标识":{'stack':[ResquestContext对象(request,session)]}
         }
        '''
        #第四: 开始之前做的事情。(两件事:处理了request,session),将这两个数据封装到local中。
        ctx = self.request_context(environ) #将request信息封装到Request(environ)对象并赋值给了 RequestContext对象
        ctx.push()
        error = None
        try:
            try:
                response = self.full_dispatch_request()   #第七:执行:before装饰器函数,视图函数:after装饰器函数
            except Exception as e: #如果请求出现异常
                error = e
                response = self.handle_exception(e)  # 信号6: 第2/3/4/5/got_request_exception
            except:
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)  # 结束了  #ctx是request-Context对象 信号7: 里面做了 第6信号:

ps: full_dispatch_request  跳入

 

 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() #第八: 执行@before_first_request 装饰的所有函数
        try:
            request_started.send(self) #信号4: 请求刚进来执行,触发了  第2信号:request_started.
            rv = self.preprocess_request()  #第九: 执行 @before_request 装饰的所有函数
            if rv is None:
                rv = self.dispatch_request()
        except Exception as e:
            rv = self.handle_user_exception(e)
        return self.finalize_request(rv)  #第十: 执行@after_request 装饰的所有函数:session保存

ps:   finalize_request  跳入

 

    def finalize_request(self, rv, from_error_handler=False):
        """Given the return value from a view function this finalizes
        the request by converting it into a response and invoking the
        postprocessing functions.  This is invoked for both normal
        request dispatching as well as error handlers.

        Because this means that it might be called as a result of a
        failure a special safe mode is available which can be enabled
        with the `from_error_handler` flag.  If enabled, failures in
        response processing will be logged and otherwise ignored.

        :internal:
        """
        response = self.make_response(rv)
        try:
            response = self.process_response(response)  #第11:  进入process_response
            request_finished.send(self, response=response) #信号5:第5信号 request_finished 执行之前,视图函数如果有模板语法,会先执行:第3,4,信号  :   before_render_template   template-rendered
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response

ps: process_session  跳入

 

    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:   # 执行 @after_request 装饰的所有函数
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):  #最后处理session
            self.save_session(ctx.session, response)
        return response

ps:  save_session 跳入

 

    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`
        """
        return self.session_interface.save_session(self, session, response)

ps:  save_session  跳入

 


 3. 保存,返回

    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)) #内存中的session,字典进行加密,也有序列化
        response.set_cookie(app.session_cookie_name, val,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)  #存到加密的cookie里。

ps: 最后  加密,存到cookie里  返回: 执行  finally

 

ctx.auto_pop(error)  # 结束了  #ctx是request-Context对象 信号7: 里面做了 第6信号:

ps: auto_pop  跳入

 

 def auto_pop(self, exc):
        if self.request.environ.get('flask._preserve_context') or \
           (exc is not None and self.app.preserve_context_on_exception):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)  #信号7.1  跳入

ps:  self.pop(exc)  跳入

 

    def pop(self, exc=_sentinel):
        """Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.

        .. versionchanged:: 0.9
           Added the `exc` argument.
        """
        app_ctx = self._implicit_app_ctx_stack.pop()

        try:
            clear_request = False
            if not self._implicit_app_ctx_stack:
                self.preserved = False
                self._preserved_exc = None
                if exc is _sentinel:
                    exc = sys.exc_info()[1]
                self.app.do_teardown_request(exc)  # 信号7。2: 第6信号  request_tearing_down

                # If this interpreter supports clearing the exception information
                # we do that now.  This will only go into effect on Python 2.x,
                # on 3.x it disappears automatically at the end of the exception
                # stack.
                if hasattr(sys, 'exc_clear'):
                    sys.exc_clear()

                request_close = getattr(self.request, 'close', None)
                if request_close is not None:
                    request_close()
                clear_request = True
        finally:
            rv = _request_ctx_stack.pop()  #把ContentText对象 POP 出来

ps:  把ContentText对象 POP 出来

结束。

 

 

 

 

posted @ 2018-01-08 23:39  Aray007  阅读(304)  评论(0编辑  收藏  举报