• flask源码解析之session


    内容回顾

    cookie与session的区别:
    1. session 是保存在服务端的键值对
    2. cookie 只能保存4096个字节的数据,但是session不受限制
    3. cookie保存在浏览器,安全性差,但是session的安全性高
    4. 通过cookie 识别浏览器,获取cookie中的随机序列从session中获取该用户的相关数据,解决了http无状态的缺点

    源码流程

    1. session的"生成"

    在 RequestContext 类中

        def __init__(self, app, environ, request=None):
            self.app = app
            if request is None:
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None

    2. 在 app.wsgi_app()函数中

      2.1  ctx.push() 

      2.2 将生成的 SecureCookieSession类实例赋值个app.session 

        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  == ctx
            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.
            """
                _request_ctx_stack 和 _app_ctx_stack 都是 Local 类的实例
            """
            # 获取 应用上下文的栈顶元素,得到 app_ctx
            app_ctx = _app_ctx_stack.top
            if app_ctx is None or app_ctx.app != self.app:
                # self.app == Fask()
                # 得到 一个 AppContext类的实例对象,得到一个 应用上下文对象 app_ctx,此时 app_ctx拥有以下属性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
                app_ctx = self.app.app_context()
                # 将 app_ctx 入栈,应用上下文入栈
                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()
    
            # self 指的是 ctx,即将ctx入栈,即 _request_ctx_stack._local.stack = [ctx]。请求上下文入栈
            _request_ctx_stack.push(self)
    
            # Open the session at the moment that the request context is available.
            # This allows a custom open_session method to use the request context.
            # Only open a new session if this is the first time the request was
            # pushed, otherwise stream_with_context loses the session.
            # 由于每次请求都会初始化创建你ctx,因此session都为None
            if self.session is None:
                # SecureCookieSessionInterface()
                # session_interface = SecureCookieSessionInterface(),即session_interface就是一个SecureCookieSessionInterface类的实例对象
                session_interface = self.app.session_interface
                # 第一次访问:生成一个 字典(容器) 返回至 self.session
                self.session = session_interface.open_session(
                    self.app, self.request
                )
                if self.session is None:
                    self.session = session_interface.make_null_session(self.app)
    # 创建 SecureCookieSessionInterface类的实例对象
    session_interface = SecureCookieSessionInterface()
        def open_session(self, app, request):
            # 获取 app的 secret_key
            s = self.get_signing_serializer(app)
            if s is None:
                return None
            # 从 cookie 中获取 session 的value
            val = request.cookies.get(app.session_cookie_name)
            if not val:
                # 生成一个 SecureCookieSession类的实例对象,用于保存用户的session数据。 SecureCookieSession类其实就是一个特殊的字典
                return self.session_class()
         # 第二次访问 max_age
    = total_seconds(app.permanent_session_lifetime) try: # 将 获取到的cookie进行反序列化 data = s.loads(val, max_age=max_age) # 返回一个含有 cookie信息的 SecureCookieSession 类实例 return self.session_class(data) except BadSignature: return self.session_class()
    session_class = SecureCookieSession

      2.4 在  response = self.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
            """
            #  将 _got_first_request =  True,依次执行定义的 before_first_request 函数
            self.try_trigger_before_first_request_functions()
            try:
                # 触发 request_started 信号
                request_started.send(self)
                #  执行钩子函数:before_request,before_first_request
                rv = self.preprocess_request()
                # 如果执行的before_request,before_first_request函数没有返回值,则继续执行视图函数。若有返回值,则不执行视图函数
                if rv is None:
                    # 执行此url对应的别名的视图函数并执行该函数,返回视图函数的返回值,得到相应信息
                    rv = self.dispatch_request()
            except Exception as e:
                # 如果发生错误,则将异常信息作为返回值进行返回
                rv = self.handle_user_exception(e)
            # 封装返回信息并返回,包括 session
            return self.finalize_request(rv)
        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)
                # 触发 request_finished 信号
                request_finished.send(self, response=response)
            except Exception:
                if not from_error_handler:
                    raise
                self.logger.exception('Request finalizing failed with an '
                                      'error while handling an error')
            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):
                #  将 self。session 这个字典序列化,并返回,写入到客户端浏览器的cookie中
                self.session_interface.save_session(self, ctx.session, response)
            return response

    3. 在视图函数中对app.session赋值

      3.1  session["user"] = 1213 

      3.2 向  app.session(实际上是向SecureCookieSession类实例中)写值

    # 获取 ctx.session
    session = LocalProxy(partial(_lookup_req_object, 'session'))
    # 偏函数
    def _lookup_req_object(name):
        # top == ctx
        top = _request_ctx_stack.top
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        # 返回 ctx.request 或者 ctx.session
        return getattr(top, name)
        def __init__(self, local, name=None):
            # local 指的是 偏函数的内存地址
            object.__setattr__(self, '_LocalProxy__local', local)
            object.__setattr__(self, '__name__', name)
            if callable(local) and not hasattr(local, '__release_local__'):
                # "local" is a callable that is not an instance of Local or
                # LocalManager: mark it as a wrapped function.
                object.__setattr__(self, '__wrapped__', local)
        def __setitem__(self, key, value):
            # 1.obj = self._get_current_object()
            # 2. obj[key] = value
            self._get_current_object()[key] = value
        def _get_current_object(self):
            """Return the current object.  This is useful if you want the real
            object behind the proxy at a time for performance reasons or because
            you want to pass the object into a different context.
            """
            if not hasattr(self.__local, '__release_local__'):
                # 执行偏函数,返回session 或者 request
                return self.__local()
            try:
                return getattr(self.__local, self.__name__)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.__name__)

    对于session的写值操作实际上还是调用的dict类的__setitem__方法

    注意:

      在flask默认的session中,没有生成随机字符串和进行session数据持久化的操作,而只是单穿把用户赋值给session的数据加密再反序列化之后返回给浏览器。在进行判断的时候也只是单纯的获取cookie中有没有"session"对应的value,可以说在很大程度上造成了数据的不安全。

    原生session组件的扩展---flaks_session组件

    from flask import Flask,session
    
    
    app = Flask(__name__)
    app.secret_key = 'suijksdfsd'
    
    # 方式一:
    from redis import Redis
    from flask_session import RedisSessionInterface
    conn = Redis()
    app.session_interface = RedisSessionInterface(conn,key_prefix='__',use_signer=False,permanent=True)
    
    
    # 方式二:
    # from redis import Redis
    # from flask.ext.session import Session
    # app.config['SESSION_TYPE'] = 'redis'
    # app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379')
    # Session(app)
    
    
    
    @app.route('/')
    def index():
        session['xxx'] = 123
        return 'Index'
    
    
    if __name__ == '__main__':
    
        app.run()
  • 相关阅读:
    杭电1312--Red and Black(Dfs)
    杭电1102--Constructing Roads(简单并查集)
    杭电1969--Pie(二分法)
    最小生成树:HDU1863-畅通工程
    并查集:HDU1213-How Many Tables(并查集最简单的应用)
    并查集:HDU5326-Work(并查集比较简单灵活的运用)
    最小生成树:POJ1251-Jungle Roads(最小生成树的模板)
    图论:HDU2544-最短路(最全、最经典的最短路入门及小结)
    动态规划、记忆化搜索:HDU1978-How many ways
    记忆化搜索:POJ1088-滑雪(经典的记忆化搜索)
  • 原文地址:https://www.cnblogs.com/liuyinzhou/p/9703427.html
Copyright © 2020-2023  润新知