zoukankan      html  css  js  c++  java
  • Flask上下文管理

    Flask上下文管理机制

    可以从三个大的方面来探讨flask的两大上下文管理机制

    方面一:请求进来时

    方面二:视图函数

    方面三:请求结束前

    请求进来时

    触发执行__call__方法,__call__方法的逻辑很简单,直接执行wsgi_app方法,将包含所有请求相关数据和一个响应函数传进去。

    def application(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html')])
        return '<h1>Hello, web!</h1>'
    
    上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
    
        environ:一个包含所有HTTP请求信息的dict对象;
    
        start_response:一个发送HTTP响应的函数。
    
    在application()函数中,调用:
    
    start_response('200 OK', [('Content-Type', 'text/html')])
    
    符合wsgi协议标准的函数

      备注:__call__是一个符合wsgi标准的函数

    执行wsgi_app方法

    def wsgi_app(self, environ, start_response):
            
            ctx = self.request_context(environ)
            error = None
            try:
                try:
                    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
                ctx.auto_pop(error)

      第一步先执行了一个request_context的方法,将environ传进去,最后返回一个RequestContext类的对象,被ctx的变量接收(ctx=request_context(environ))

     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)

    这个ctx对象在初始化时,赋了两个非常有用的属性,一个是request,一个是session

    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
    

      这两个属性中request是一个Request()对象,这个对象就是我们在flask中使用的request对象,为我们提供了很多便捷的属性和方法,比如:request.method、request.form、request.args等等,另一个属性是session,初始为None。

      紧接着执行ctx.push()方法,这个方法中,在执行请求上下文对象ctx之前先实例化了一个app_context对象,先执行了app_context的push方法,然后才执行_request_ctx_stack对象中的top和_request_ctx_stack.push(self),最后对ctx中的session进行处理。

      所以,flask中的应用上下文发生在请求上下文之前

    def push(self):           
           
            top = _request_ctx_stack.top
            if top is not None and top.preserved:
                top.pop(top._preserved_exc)
                
            # 在执行request_context请求上下文的push方法时,先执行了app_context应用上下文的push方法
            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()
                
            # 然后执行请求上下文对象中LocalStack对象的push方法
            _request_ctx_stack.push(self)
    
            # 最后处理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)

      但是我们先说请求上下文,在处理完应用上下文的push方法后,紧接着执行了_request_ctx_stack对象的两个方法。

      而这个_request_ctx_stack是LocalStack这个类的对象。_request_ctx_stack = LocalStack()

       LocalStack有没有很眼熟,没错,flask内部使用的机制就是类似于我们上文中自定义的LocalStack的机制,实例化过程中使用了面向对象中的组合概念,self._local = Local(),然后在自身又实现了push、pop、top方法,这三个方法中都是通过反射从Local类的实例化对象中对一个stack属性进行append、pop、[-1]的操作,所以,Local对象中的stack属性对应的值一定是一个类似于列表的东西。通过对列表的操作,实现一个类似于栈的存取。

      接着聊聊这个Local类,在实例化时,会对每个对象生成一个storage的空字典。我们翻遍整个Local类的源码,发现内部并没有实现一个叫stack的方法或者属性,但是上面我们提到了LocalStack对象会对Local对象中的一个叫stack的东西进行一系列操作。找不到不会报错吗?

      这就是flask的巧妙之处,通过类的一些魔法方法巧妙的实现了相应的处理。在前引中,提到如果对象中没有某个属性,取值时,最终会执行类中的__getattr__方法,然后再做后续的异常处理,flask将所有的对应逻辑都实现在了类的__getattr__方法中,将每一个线程存储到字典中,在请求进来时,将每一个对应的请求ctx存在一个列表中,使用时直接调用,而不是通过传参的形式,更体现出了flask框架的轻量级。

      处理完_request_ctx_stack后,就该处理session了。

      在flask中,处理session时,非常的巧妙,完美的遵循了开闭原则,会先执行session_interface对象的open_session方法,在这个方法中,会先从用户请求的cookie中获取sessionid,获取该用户之前设置的session值,然后将值赋值到ctx.session中。

      处理完session后,ctx.push方法就执行完了,返回到最开始的app.wsgi_app方法中,执行完push方法后,接着执行full_dispatch_request方法,从这个名字中我们也能猜到,这个方法只要是负责请求的分发。

    def full_dispatch_request(self):
          
                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方法中先执行preprocess_request方法,这个方法,会先执行所有被before_request装饰器装饰的函数,然后就通过路由的分发执行视图函数了(dispatch_request)

    执行视图函数时

      在执行视图函数之前,先执行了before_request,在执行我们的视图函数。

      视图函数主要处理业务逻辑。在视图函数中可以调用request对象,进行取值,也可以调用session对象对session的存取。

      在整个request的请求生命周期中,获取请求的数据直接调用request即可,对session进行操作直接调用session即可。request和session都是LocalProxy对象,借助偏函数的概念将对应的值传入_lookup_req_object函数。先从_request_ctx_stack(LocalStack)对象中获取ctx(请求上下文对象),再通过反射分别获取request和session属性。整个过程中LocalStack扮演了一个全局仓库的角色,请求进来将数据存取,需要时即去即用。所以,flask实现了在整个请求的生命周期中哪儿需要就直接调用的特色。

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

    请求结束前

      视图函数执行完后,dispatch_request执行结束,执行full_dispatch_request方法的返回值finalize_request方法。这个方法中,同样的,在返回响应之前,先执行所有被after_request装饰器装饰的函数。

    ---->finalize_request ------> process_response
     def process_response(self, response):
            
            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过程中,执行完after_request后,然后,执行session的save_session方法。将内存中保存在ctx.session的值取到后,json.dumps()序列化后,写入响应的cookie中(set_cookie),最后返回响应。

        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))
            # set_cookie将session写入响应的cookie中
            response.set_cookie(
                app.session_cookie_name,
                val,
                expires=expires,
                httponly=httponly,
                domain=domain,
                path=path,
                secure=secure,
                samesite=samesite
            )

      返回响应后,自动的调用ctx.auto_pop(error),将Local中存储的ctx对象pop掉,整个请求结束。

    请求上下文的执行流程:

     上下文的执行流程

  • 相关阅读:
    从零开始学安全(七)●Linux基础命令学习笔记
    从零开始学安全(六)●黑客常用的Dos命令
    ABAP
    ABAP modify screen:修改屏幕,实现隐藏、禁止输入字段
    C#以对象为成员的例子
    C#构造函数、属性的应用
    C#属性方法 构造函数(不知道自己理解的对不对)
    C#斐波那契数列求法(比较阶乘和循环所用时间)
    C#函数的递归
    C#函数的参数传递2(refout)
  • 原文地址:https://www.cnblogs.com/yet-320/p/11917934.html
Copyright © 2011-2022 走看看