原理
Request
Flask
把前端传过来的数据environ
封装成了 flask.wrappers.Request
类
这个类的实例又是RequestContext
的request
属性值
1 | class RequestContext(object): |
当然实际的代码是这样
1 | class RequestContext(object): |
这个app.request_class
= flask.wrappers.Request
之所以这样写,是为了扩展性,你可以修改Flask
的request_class
属性来自定义你的Request
类
1 | from flask import g, request |
例如
1 | import flask |
继续来看RequestContext
,这个类在源码中实例化了
1 | ctx=RequestContext(self,envirion) |
所以,也就是说以后我们只要拿到这个实例ctx
,然后访问ctx.request
就相当于访问flask.wrappers.Request
了,也就相当于可以访问envirion
,,而RequestContext
就是请求上下文。
那ctx
存储在哪里呢?怎么访问呢?
RequestContext
存储
实际上,ctx
存在栈结构中,也就是后进先出,这是为了处理一个客户端请求需要多个ctx
的情况
用伪代码表示就是
1 | stack=[RequestContext(),RequestContext()] |
而且,我们知道有的wsgi server
对于每个请求都开一个线程,因此为了处理多线程隔离的情况,这个栈结构又存在了local
中,这个local
数据结构类似ThreadLocal
,他们共同组成了LocalStack
用伪代码表示就是
1 | localstack={0:{"stack":stack}} # 0是线程id或者协程id |
访问
访问ctx.request
也不是直接访问的,是通过一个代理类,叫LocalProxy
,他是代理模式在flask
中的应用
具体来说你要访问 ctx.request
的某个属性,先访问LocalProxy
的对应属性,LocalProxy
帮你访问
LocalProxy
代理了对 ctx.request
的所有操作
伪代码就是
1 | # 例如flask.wrapper.Request()有一个get_json()方法 |
当然这个例子是不真实的,如果对于每个 flask.wrapper.Request
的方法我们都在 LocalProxy
实现一遍,那太麻烦了
request
我们经常会引用这个request
对象,它实际上就是LocalProxy
的实例,位置在flask.globals.py
1 | from flask import request |
1 | request = LocalProxy(partial(_lookup_req_object, "request")) |
他代理了对 RequestContext.request
也就是flask.wrappers.Request
实例的操作
而current_app
和g
则分别代理了对AppContext.app
(也就是flask
实例)和 AppContext.g
的操作
实现
Stack
一个栈结构,一般要实现 push,top,pop
这几个方法
1 | class Stack(object): |

Local
local
源码,作用就是线程或者协程隔离
1 | class Local(object): |
LocalStack
LocalStack
大概相当于
1 | {0:{"stack":[ctx,]}} |
1 | class LocalStack(object): |
值得注意的是,当我们执行如下代码
1 | ls=LocalStack() |
会调用 __call__
方法,返回的是目前栈顶对象的代理对象,栈顶对象例如RequestContext
LocalProxy
LocalProxy
就是代理对象了,它可以代理对RequestContext
的操作
1 | class LocalProxy(object): |
原理很简单,我们访问 LocalProxy
的某个属性,会调用 __getattr__
方法,__getattr__
方法又会调用 _get_current_object
去获取栈顶对象或者栈顶对象的属性,例如获取RequestContext
对象或者RequestContext.request
以request
对象为例
他是一个LocalProxy
的实例
1 | request = LocalProxy(partial(_lookup_req_object, "request")) |
LocalProxy
实例化传入了一个偏函数 _lookup_req_object
(偏函数作用就是固定函数的参数),这个函数的作用就是获取栈顶的RequestContext
对象的request
属性,也就是 flask.wrappers.Request()
1 | def _lookup_req_object(name): |
也就是说 LocalProxy
的__local
就是 partial(_lookup_req_object, "request")
那么 _get_current_object
实际上就是执行 partial(_lookup_req_object, "request")()
来获取栈顶对象
入栈
我们继续来看wsgi_app
这个方法,我去掉了一些无关代码,那些部分会在其他博文中介绍
1 | def wsgi_app(self, environ, start_response): |
1 | def request_context(self, environ): |
简单看一下RequestContext
的定义,位置在源码的ctx.py
中
1 | class RequestContext(object): |
回到wsgi_app
这个方法,我们看到接下来ctx.push
这一句调用了RequestContext
的push
方法,这个方法就是把自身也就是 RequestContext
实例压入到 LocalStack
这个数据结构中
1 | def wsgi_app(self, environ, start_response): |
push
方法
1 | def push(self): |
_request_ctx_stack
的定义在globals.py
中,就是创建了一个空的本地栈
1 | _request_ctx_stack = LocalStack() |
它现在的状态是这样,栈是空的
1 | {0:'stack':[]} |
如果栈是空的话,我们把self
也就是RequestContext
压入栈(最后一句),注意push
是RequestContext
的方法,所以self
就是RequestContext
的实例
1 | def push(self): |
我们还注意到这里还有一个_app_ctx_stack
,这也是LocalStack
,位置在globals.py
,他现在也是空的
1 | _app_ctx_stack = LocalStack() |
只不过这个栈里面存储的是应用上下文,类似下面的字典
1 | {0:'stack':[AppContext]} |
然后我们也执行了app_ctx.push()
方法,也就是把应用上下文压入栈
到这里我们就知道了,执行RequestContext
对象的push
方法会把RequestContext
的实例压入_request_ctx_stack
中,还会把AppContext
的实例压入_app_ctx_stack
中
为什么要用LocalProxy
我们常常需要在一个视图函数中获取客户端请求中的参数,例如url
,remote_address
我们当然可以每次手动获取_request_ctx_stack
栈顶的RequestContext
对象,然后调用RequestContext
的request
属性,但每次操作栈结构还是有点繁琐,像下面这样
1 | from flask import Flask, request, Request |
flask
的做法是使用LocalProxy
1 | from flask import Flask, request |
我们执行request.remote_addr
就相当于执行 _request_ctx_stack.top.request.remote_addr
那为什么用LocalProxy
而不是直接request=_request_ctx_stack.top.request
呢?
原因是这样写,项目run
的时候,这句 request=_request_ctx_stack.top.request
就已经执行了(因为被引用了),但是项目启动的时候_request_ctx_stack.top
还是None
,因为还没有请求进来,push
方法还没执行。这就导致了request
固定成了None
,这显然不行
而LocalProxy
重写了__getattr__
方法,让每次执行 request.remote_addr
会先去 LocalStack
中拿到 RequestContext
,然后执行 RequestContext.request.remote_addr
,获取其他属性也是一样
也就是说,代理模式延迟了 被代理对象的获取,代理对象Localproxy
创建的时候不会获取,获取被代理对象属性的时候才会获取被代理对象
总结
到这里,我们就可以看出flask
中的请求上下文是如何存储和获取的
请求上下文存储在LocalStack
结构中,Local
是为了线程安全,LocalStack
是为了多请求上下文的场景
而获取是通过LocalProxy
,使用代理模式是为了动态地获取请求上下文,在访问request属性的时候,才会从栈中获取真实的请求上下文,然后代理 属性的获取
flask
中还有应用上下文,current_app
,我们有时候会通过current_app.config
来获取配置信息,原理和request
类似,只不过代理的是AppContext
对象
说了这么多,wsgi_app
的第一步,也就是请求的第一步,就是先把请求上下文和应用上下文入栈