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移除。
  • 相关阅读:
    my ReadBook_dianzishangwu / 2020216 / dianzishangwuwuliu
    my ReadBook_shichangyingxiao / 2020208
    C#编写的clock
    Java建立JProgressBar
    java基本类型byte的取值范围
    正则表达式整理大全
    (C#)如何利用Graphics画出一幅图表
    设置鼠标可以移动窗体
    Java编程提高性能的26个方法
    数据加密工具设计经验
  • 原文地址:https://www.cnblogs.com/fengchong/p/10256552.html
Copyright © 2011-2022 走看看