• Flask Session 使用和源码分析 —— (6)


     基本使用

    from flask import Flask, session, redirect, url_for, escape, request
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        if 'username' in session:
            return 'Logged in as %s' % escape(session['username'])
        return 'You are not logged in'
    
    
    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            session['username'] = request.form['username']
            return redirect(url_for('index'))
        return '''
            <form action="" method="post">
                <p><input type=text name=username>
                <p><input type=submit value=Login>
            </form>
        '''
    
    
    @app.route('/logout')
    def logout():
        # remove the username from the session if it's there
        session.pop('username', None)
        return redirect(url_for('index'))
    
    
    # set the secret key.  keep this really secret:
    app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
    View Code

    使用第三方插件

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    """
    pip3 install redis
    pip3 install flask-session
    
    """
    
    
    from flask import Flask, session, redirect
    from flask.ext.session import Session
    
    
    app = Flask(__name__)
    app.debug = True
    app.secret_key = 'asdfasdfasd'
    
    
    app.config['SESSION_TYPE'] = 'redis'
    from redis import Redis
    app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379')
    Session(app)
    
    
    @app.route('/login')
    def login():
        session['username'] = 'alex'
        return redirect('/index')
    
    
    @app.route('/index')
    def index():
        name = session['username']
        return name
    
    
    if __name__ == '__main__':
        app.run()
    View Code 

    Flask内置session的处理机制大致的可以分为以下3个步奏

    1 请求到来的时候

      在这里会获取请求数据中的session中的随机字符串,如果存在则去Local中获取用户的数据,如果不存在则会创建一个对象容器(类似字典的对象)

    2 执行视图函数的时候

      如果对session中的数据进行修改,获取Local中获取这个对象,进行修改

    3 返回响应的时候 

       把对象容器 (随机字符串,{放置的数据}),中的数据保存到数据库,随机的字符串写入cookie中

    源码分析

    请求到来时候

        def wsgi_app(self, environ, start_response):
            """The actual WSGI application. This is not implemented in
            :meth:`__call__` so that middlewares can be applied without
            losing a reference to the app object. 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
                Teardown events for the request and app contexts are called
                even if an unhandled error occurs. Other events may not be
                called depending on when an error occurs during dispatch.
                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.
            """
            # 将请求相关的数据environ 封装到request_context 对象中
            # ctx.app = app
            # ctx.request = app.request_class(environ)
            ctx = self.request_context(environ) # 生成一个类
            error = None
            try:
                try:
                    # 把请求的对象封装到local中,每个线程 / 协程都是独立的空间存储
                    ctx.push()
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.handle_exception(e)
                except:
                    error = sys.exc_info()[1]
                    raise
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                # 最后把请求在local中的数据删掉
                ctx.auto_pop(error)
    wsgi_app
        def request_context(self, environ):
            """Create a :class:`~flask.ctx.RequestContext` representing a
            WSGI environment. Use a ``with`` block to push the context,
            which will make :data:`request` point at this request.
    
            See :doc:`/reqcontext`.
    
            Typically you should not call this from your own code. A request
            context is automatically pushed by the :meth:`wsgi_app` when
            handling a request. Use :meth:`test_request_context` to create
            an environment and context instead of this method.
    
            :param environ: a WSGI environment
            """
            return RequestContext(self, environ)
    self.request_context(environ)
    class RequestContext(object):
        """The request context contains all request relevant information.  It is
        created at the beginning of the request and pushed to the
        `_request_ctx_stack` and removed at the end of it.  It will create the
        URL adapter and request object for the WSGI environment provided.
    
        Do not attempt to use this class directly, instead use
        :meth:`~flask.Flask.test_request_context` and
        :meth:`~flask.Flask.request_context` to create this object.
    
        When the request context is popped, it will evaluate all the
        functions registered on the application for teardown execution
        (:meth:`~flask.Flask.teardown_request`).
    
        The request context is automatically popped at the end of the request
        for you.  In debug mode the request context is kept around if
        exceptions happen so that interactive debuggers have a chance to
        introspect the data.  With 0.4 this can also be forced for requests
        that did not fail and outside of ``DEBUG`` mode.  By setting
        ``'flask._preserve_context'`` to ``True`` on the WSGI environment the
        context will not pop itself at the end of the request.  This is used by
        the :meth:`~flask.Flask.test_client` for example to implement the
        deferred cleanup functionality.
    
        You might find this helpful for unittests where you need the
        information from the context local around for a little longer.  Make
        sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
        that situation, otherwise your unittests will leak memory.
        """
    
        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
    RequestContext

    在上面的源码中我们可以看到默认的会把self.session设置为None

    继续追踪wsgi_app源码中的ctx.push

        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 =  AppContext(object)  app_ctx.g   app_ctx.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()
            # self 是request_contenx的对象,其中包含了请求相关的所有数据
            #  _request_ctx_stack==>LocalStack
            _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.
            # 帮助我们获取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)
    ctx_push

     追踪中的_request_ctx_stack.push(self)

    _request_ctx_stack = LocalStack()

    查看其中的push方法

        def push(self, obj):
            """Pushes a new item to the stack"""
            rv = getattr(self._local, 'stack', None)
            if rv is None:
                # 执行local 对象的__setatr__方法
                self._local.stack = rv = []
            # 把requestContext 对象添加到列表中 self._local.stack = rv = [把requestContext]
            rv.append(obj)
            return rv
    _request_ctx_stack_push

    到这里为每个线程开辟了一个独立的存储空间,存储请求相关的数据(Request_content)

    storage = {
    
        "stack" : {"线程的唯一表示":[ Request_content封装后的请求 ]}  
    
    }  

    继续追踪上面ctx.push中的 self.app.session_interface

            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)
    
    session_interface = SecureCookieSessionInterface()
    app.session_interface

    继续追踪ctx.push中的session_interface.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)
            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()
    open_session
    session_class = SecureCookieSession
    session_class
    class SecureCookieSession(CallbackDict, SessionMixin):
    SecureCookieSession
    class CallbackDict(UpdateDictMixin, dict):
    CallbackDict

    到这里我们可以看到其返回的是一个类似于字典的一个对象

     当我们在视图中操作session的时候

     我们获取session相关的数据可以直接从全局变量session中获取

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

    partial(_lookup_req_object, 'session')

    def _lookup_req_object(name):
        top = _request_ctx_stack.top
        if top is None:
            raise RuntimeError(_request_ctx_err_msg)
        # 去requestContext中获取request的值
        return getattr(top, name)  

    最终返回的结果是会去一开始我们当前线程开辟的存储空间中获取session(类字典对象的一个容器)

    当我们对session进行操作是会把最终的数据存储到这个容器中

    在返回响应的时候

    追踪wsgi_app中的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()
            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)
            return self.finalize_request(rv)
    full_dispatch_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)
                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
    finalize_request
        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_interface.save_session(self, ctx.session, response)
            return response
    process_response
        def save_session(self, app, session, response):
            domain = self.get_cookie_domain(app)
            path = self.get_cookie_path(app)
    
            # If the session is modified to be empty, remove the cookie.
            # If the session is empty, return without setting the cookie.
            if not session:
                if session.modified:
                    response.delete_cookie(
                        app.session_cookie_name,
                        domain=domain,
                        path=path
                    )
    
                return
    
            # Add a "Vary: Cookie" header if the session was accessed at all.
            if session.accessed:
                response.vary.add('Cookie')
    
            if not self.should_set_cookie(app, session):
                return
    
            httponly = self.get_cookie_httponly(app)
            secure = self.get_cookie_secure(app)
            samesite = self.get_cookie_samesite(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,
                samesite=samesite
            )
    save_session

    我们可以在最后的时候把随机的字符串放在了cookie中  { “session ” :’随机的字符串 }

            response.set_cookie(
                app.session_cookie_name,
                val,
                expires=expires,
                httponly=httponly,
                domain=domain,
                path=path,
                secure=secure,
                samesite=samesite
            )

      自定义session

     通过上面的源码分析我们知道,我们要自定义一个session需要写一个类,我们通过配置使他走我们自定义的类,里面要实现opension和savesession的方法

    from flask import Flask,session
    
    
    app = Flask(__name__)
    app.secret_key = 'suijksdfsd'
    
    
    import json
    class MySessionInterFace(object):
        def open_session(self,app,request):
            return {}
    
        def save_session(self, app, session, response):
            response.set_cookie('session_idfsdfsdfsdf',json.dumps(session))
    
        def is_null_session(self, obj):
            """Checks if a given object is a null session.  Null sessions are
            not asked to be saved.
    
            This checks if the object is an instance of :attr:`null_session_class`
            by default.
            """
            return False
    
    app.session_interface = MySessionInterFace()
    
    @app.route('/')
    def index():
        # 特殊空字典
        # 在local的ctx中找到session
        # 在空字典中写值
        # 在空字典中获取值
        session['xxx'] = 123
    
    
        return 'Index'
    
    # # 一旦请求到来
    # app.__call__
    app.wsgi_app
    app.session_interface
    app.open_session
    
    
    if __name__ == '__main__':
    
        app.run()
  • 相关阅读:
    EF6(CodeFirst)+MySql开发遇到的坑
    Entity Framework mvc Code First data migration
    SQL 修改排序规则的问题 sql_latin1_general_cp1_ci_as
    sql 与linq的转换
    Entity Framework的事务提交
    .net Quartz 服务 作业调度
    如何插上U盘 自动复制内容
    SQL学习之--触发器
    原生javascript与jquery 的比较
    原生javascript学习
  • 原文地址:https://www.cnblogs.com/crazymagic/p/9607696.html
Copyright © 2020-2023  润新知