Flask框架第八篇.请求上下文

1.偏函数partial

python中有一个小工具包functools,这个包中包含了几个在很有用的小功能,比如:wraps:在使用装饰器时,使用这个方法可以保护函数的元信息。reduce:一个合并序列项为一个单一值的小方法。还有一个就是偏函数: partial ,一句话来总结partial的作用,固定函数中的一些参数,返回一个新的函数,方便调用

def ab(a,b)
    return a+b
print(ab(1,2))   #3
from functools import partial
​
def ab(a,b):
    print(a,b)
    return a+b
​
# print(ab(1,2))
# new_func = partial(ab,1)
# print(new_func(2))   #3
​
new_func = partial(ab,4,6)
print(new_func())   #10
View Code

2. threading.local

在多线程中,同一个进程中的多个线程是共享一个内存地址的,多个线程操作数据时,就会造成数据的不安全,所以我们就要加锁。但是,对于一些变量,如果仅仅只在本线程中使用,怎么办?

  • 方法一,可以通过全局的字典,key为当前线程的线程ID,value为具体的值。

  • 方法二,使用threading.local方法

import time
from threading import Thread
​
class Foo(object):
    num = 0
​
foo = Foo()
​
def  addi(i):
    foo.num = i
    time.sleep(0.5)   #相当于 I/O 操作
    print(foo.num)   
​
for i in range(20):
    task = Thread(target=addi,args=(i,))
    task.start()
View Code

import time
from threading import Thread,local
import threading
​
#实现例子:时空转换
# rom = {     #rom内存
#     9527:"foo.num=0",
#     2233: "foo.num=1",
#     3478: "foo.num=3",
#     ...
# }
​
​
class Foo(local):
    num = 0
​
foo = Foo()
​
def  addi(i):
    foo.num = i
    time.sleep(0.5)   #相当于 I/O 操作
    print(foo.num,threading.currentThread().ident)
​
for i in range(10):
    task = Thread(target=addi,args=(i,))
    task.start()
View Code

3. 面向对象双下方法

  • __call__

    这个方法相信大家并不陌生,在单例模式中,我们可能用到过,除此之外,还想就没有在什么特殊场景中用到了。我们往往忽视了它一个很特殊的用法:对象object+()或者类Foo()+()这种很特殊的用法。在Flask上下文管理中,入口就是使用了这种方式。

  • __getitem__系列

    使用这个系列的方法时,我们最大的印象就是调用对象的属性可以像字典取值一样使用中括号([])。使用中括号对对象中的属性进行取值、赋值或者删除时,会自动触发对应的getitemsetitemdelitem方法。

    class Foo(object):
    ​
        def __init__(self):
            self.name = "boo"def __getitem__(self, item):
            print("调用__getitem__了")
            if item in self.__dict__:
                return self.__dict__[item]
    ​
        def __setitem__(self, key, value):
            print("调用__setitem__方法了")
            self.__dict__[key] = value
    ​
        def __delitem__(self, key):
            print("调用__delitem__")
            del self.__dict__[key]
    ​
    ​
    foo = Foo()
    ret = foo["name"]
    # print(ret)     # 输出     调用__getitem__了      boo
    foo["age"] = 18
    # print(foo["age"])   # 输出   调用__setitem__方法了   调用__getitem__了    18
    del foo["age"]   #  输出  调用__delitem__
    View Code
  • __getattr__系列

    使用对象取值、赋值或者删除时,会默认的调用对应的getattrsetattrdelattr方法。

    对象取值时,取值的顺序为:先从getattribute中找,第二步从对象的属性中找,第三步从当前类中找,第四步从父类中找,第五步从getattr中找,如果没有,直接抛出异常。

    class Foo(object):
    ​
        def __init__(self):
            self.name = "boo"def __getattr__(self, item):
            print("调用__getattr__了")
    ​
        def __setattr__(self, key, value):
            print("调用__setattr__方法了")
    ​
        def __delattr__(self, item):
            print("调用__delattr__")
    ​
    ​
    foo = Foo()
    ret = foo.xxx    # 输出     调用__getattr__了
    foo.age = 18    # 调用__setattr__方法了
    del foo.age   #  输出  调用__delattr__
    View Code

4.请求上下文的实现机制

https://www.cnblogs.com/zhaopanpan/articles/9457343.html

接下来,从以下三个大的方面分别探讨flask的两大上下文管理机制。

  • 方面一:请求进来时

  • 方面二:视图函数

  • 方面三:请求结束前

对于 run_simple

from werkzeug.wrappers import Response,Request
from werkzeug.serving import run_simple
​
@Request.application
def app(req):     # 函数
    print(req.method)    # GET
    return Response("200 ok")
​
run_simple("127.0.0.1",9527,app)
View Code

一运行会自动在执行 app()

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

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

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

def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
    # 省略......
    from werkzeug.serving import run_simple
​
    try:
        run_simple(host, port, self, **options)
        # self = app = Flask(__name__)
        finally:
            self._got_first_request = False
View Code

进行run_simple方法时,会自动self(),即Flask(_ name ) () 就是触发调用了Flask的 _ call_ _方法

4.1 请求进来时

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

img

执行wsgi_app方法

def wsgi_app(self, environ, start_response):
    
        ctx = self.request_context(environ)
        # ctx = RequestContext(self, environ)  --> ctx--request/session
        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)
View Code
  • 第一步 :先执行了一个request_context的方法,将environ传进去,最后返回一个RequestContext类的对象,被ctx的变量接收(ctx=request_context(environ))

    request_context方法

     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)
    View Code

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

        def __init__(self, app, environ, request=None, session=None):
            # self = RequestContext  , app = Flask ,environ = 请求原始信息
            self.app = app
            if request is None:
                request = app.request_class(environ)
                # request = Flask 中的 request真身
            self.request = request
            self.url_adapter = None
            try:
                self.url_adapter = app.create_url_adapter(self.request)
            except HTTPException as e:
                self.request.routing_exception = e
            self.flashes = None
            self.session = session
    View Code

    这两个属性中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 ,obj):           
        top = _request_ctx_stack.top
    
        # 在执行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)
                    """
            _request_ctx_stack = 
                LocalStack : {
                    _local : {
                        "__storage__":{
                            "9527":{
                                stack:rv[ctx-reuqest/session]
                            }
                        },
                        "__ident_func__":get_ident  #获取ID
                    }
                }
            """
    
                    # 最后处理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)
    View Code

    但是我们先说请求上下文,在处理完应用上下文的push方法后,紧接着执行了request_ctx_stack对象的两个方法。而这个request_ctx_stack是LocalStack这个类的对象。request_ctx_stack = LocalStack() ,LocalStack有没有很眼熟,没错,flask内部使用的机制就是类似于我们上文中自定义的LocalStack的机制,实例化过程中使用了面向对象中的组合概念,self.local = Local(),

    class LocalStack:
        def __init__(self):
            self._local = Local()
            
        @property
        def top(self):
            """The topmost item on the stack.  If the stack is empty,
            `None` is returned.
            """
            try:
                return self._local.stack[-1]
            except (AttributeError, IndexError):
                return None
    try:
        from greenlet import getcurrent as get_ident
    except ImportError:
        try:
            from threading import get_ident
        except ImportError:
            from _thread import get_ident
    ​
    class Local:
        __slots__ = ("__storage__","__ident_func__")  # 插槽,最多有这两个属性
    def __init__(self) :
            object.__setattr__(self, "__storage__", {})
            object.__setattr__(self, "__ident_func__", get_ident)
    View Code

    然后在自身又实现了push、pop、top方法,这三个方法中都是通过反射从Local类的实例化对象中对一个stack属性进行append、pop、[-1]的操作,所以,Local对象中的stack属性对应的值一定是一个类似于列表的东西。通过对列表的操作,实现一个类似于栈的存取。

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

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

    class LocalStack:
        def __init__(self):
            self._local = Local()
            
        @property
        def top(self):
            """The topmost item on the stack.  If the stack is empty,
            `None` is returned.
            """
            try:
                return self._local.stack[-1]
            except (AttributeError, IndexError):
                return None
    View Code

    处理完_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) 
    View Code

    在full_dispatch_request方法中先执行preprocess_request方法,这个方法,会先执行所有被before_request装饰器装饰的函数,然后就通过路由的分发执行视图函数了(dispatch_request)

4.2 执行视图函数时

在执行视图函数之前,先执行了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'))

4.3 请求结束前

  视图函数执行完后,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
View Code

执行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
        )
View Code

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

请求上下文的执行流程:

_request_ctx_stack = 
    LocalStack : {
        _local : {
            "__storage__":{
                "9527":{
                    stack:rv[ctx-reuqest/session]
                }
            },
            "__ident_func__":get_ident  #获取ID
        }
    }

img

 

posted @ 2021-11-17 19:43  Primrose  阅读(134)  评论(0编辑  收藏  举报