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移除。
原文地址:https://www.cnblogs.com/fengchong/p/10256552.html