zoukankan      html  css  js  c++  java
  • flask上下文管理机制

    flask中的上下文分为请求上写文和应用上下文,接下来,从以下三个大的方面分别探讨flask的两大上下文管理机制。

    1.  方面一:请求进来时
    2.  方面二:视图函数
    3.  方面三:请求结束前

    先来一个最简单的flask版的Hello World

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        return "Hello World"
    
    if __name__ == '__main__':
        app.run()
    
    Flask版Hello World
    flask版的Hello World

    启动一个flask项目时,会先执行app.run()方法,这是整个项目的入口,执行run方法时,接着执行werkzeug模块中的run_simple

    此时的self指的是我们实例化的Flask对象,即app,当请求到来时,会触发调用Flask的__call__方法。

    一、请求进来时

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

     此时的self指的是Flask的实例化对象app,接下来执行app的wsgi_app方法。

    此时的self仍然指的是Flask的实例化对象app,首先执行app这个对象的request_context的方法,将environ传进去,最后返回一个RequestContext类的对象,对象中封装了request和session等实例变量。ctx=RequestContext()

    RequestContext类在实例化过程中执行__init__方法,赋了两个非常有用的属性,一个是request,一个是session

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

    紧接着执行ctx.push()方法,此时的self为ctx

    这个方法中,在执行请求上下文对象ctx之前先执行的app的app_context()方法,返回值为AppContext()的实例化对象,即app_ctx = AppContext(),先执了AppContext()的push方法,然后才执行_request_ctx_stack对象中的top和_request_ctx_stack.push(self),最后对ctx中的session进行处理。

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

    但是我们先说请求上下文,在处理完应用上下文的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属性对应的值一定是一个类似于列表的东西。通过对列表的操作,实现一个类似于栈的存取。

     接下来我们开始执行LocalStack()对象的push方法

    在此方法中执行了self._local.stack = rv =[ ],self._local为Local类的实例化对象,Local类在实例化的过程中,会对每个对象生成__storage__和__indent_func__两个实例变量,__storage__对应的是一个空字典{},__ident_func__对应的一个获取当前线程ID的一个可执行函数get_ident。

     

    我们翻遍整个Local类的源码,发现内部并没有实现一个叫stack的方法或者属性,但是上面我们提到了LocalStack对象会对Local对象中的一个叫stack的东西进行一系列操作。找不到不会报错吗?

     这就是flask的巧妙之处,通过类的一些魔法方法巧妙的实现了相应的处理。如果对象中没有某个属性,取值时会执行类中的__getattr__方法,赋值时会执行类中的__setattr__方法。

     

    处理完_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_dispath_request方法
    full_dispatch_request

    在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

    执行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
            )
    
    save_session

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

     四、应用上下文

    与请求上下文类似,当请求进来时,先实例化一个AppContext对象app_ctx,在实例化的过程中,提供了两个有用的属性,一个是app,一个是g。self.app就是传入的全局的app对象,self.g是一个全局的存储值的对象。接着将这个app_ctx存放到LocalStack()。

    class AppContext(object):
      def __init__(self, app):
        self.app = app
        self.url_adapter = app.create_url_adapter(None)
        self.g = app.app_ctx_globals_class()

    视图函数中,我们就可以调用app对象和g对象,如果我们使用蓝图构建我们的项目时,在每一个直接引用app就会造成循环引用的异常,这时,应用上下文就会显得非常有用,我们可以直接调用current_app就可以在整个生命周期中使用我们的app对象了。比如使用我们的配置项:current_app.config

    current_app = LocalProxy(_find_app)

    最后,当视图函数执行结束后,从storage中pop掉app_ctx对象。

    总结:

    流程:
        请求到来:
          - 请求到来之后wsgi会触发__call__方法,由__call__方法再次调用wsgi_app方法,
                将request和session相关封装到ctx = RequestContext对象中。
                将app和g封装到app_ctx = AppContext对象中。
                再通过LocalStack对象将ctx、app_ctx封装到Local对象中。
                                    
        获取数据:
                通过LocalProxy对象+偏函数,调用LocalStack去Local中获取响应ctx、app_ctx中封装的值。
                        
          
        请求结束:
                调用LocalStack的pop方法,将ctx和app_ctx移除。
  • 相关阅读:
    自定义组件要加@click方法
    绑定样式
    647. Palindromic Substrings
    215. Kth Largest Element in an Array
    448. Find All Numbers Disappeared in an Array
    287. Find the Duplicate Number
    283. Move Zeroes
    234. Palindrome Linked List
    202. Happy Number
    217. Contains Duplicate
  • 原文地址:https://www.cnblogs.com/fengchong/p/10256552.html
Copyright © 2011-2022 走看看