上文说到Flask的本地WSGI Server建立,WSGI Server 监听本地端口,当有请求进入时会触发WSGIRequestHandler,WSGIRequestHandler会触发Flask app实例,这样Server,Request,APP实例就建立起了关系,本文从app实例被触发说起,谈谈Flask的上下文管理。
一 请求到来前,入栈
从上文的分析中可以知道,WSGIRequestHandler会触发app实例,其实就是Flask 类中的__call__
方法,我们从这个call开始。
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
call 调用了wsgi_app
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
try:
try:
ctx.push()
ctx = self.request_context(environ)
def request_context(self, environ):
return RequestContext(self, environ)
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
ctx其实就是一个RequestContext对象,里面包含request,session,app
再看这一句request = app.request_class(environ),app.request_class来自flask.app.Flask中的request_class = Request
class Request(RequestBase, JSONMixin):
到这里基本可以知道,其实ctx就是一步一步封装起来的Request对象。
再来看wsgi_app这个方法,封装了ctx对象后,紧接着就是ctx.push()。我们重点来分析下这个push方法。
flask.ctx
def push(self):
top = _request_ctx_stack.top
_request_ctx_stack.push(self)
top = _request_ctx_stack.top这个操作是从_request_ctx_stack栈里取一个对象。从
Flask源码剖析二之本地栈的建立可知,此时_request_ctx_stack是一个空字典,top = None。_request_ctx_stack.push(self) 是将ctx也就是将requestContext对象入栈。
入栈过程:
_request_ctx_stack.push(self)就是执行的LocalStack类中的push方法
werkzeug.local.LocalStack
class LocalStack(object):
def __init__(self):
self._local = Local()
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, "stack", None)
if rv is None:
self._local.stack = rv = []
rv.append(obj)
return rv
rv = getattr(self._local, "stack", None)当请求第一次进来时栈为空,返回一个None
self._local.stack = rv = [] 。self._local 是Local()的一个对象。
werkzeug.local. Local
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
self._local.stack = [] 触发执行了Local()对象的setattr方法。 ident = self.ident_func()拿到的是当前线程或协程的ID,这个一个以为表示,每个请求的request对象都保存自己的ID里,这样就做到了线程隔离。
storage[ident][name] = value 其实就是向本地栈的空字典_request_ctx_stack里写值
self._local.stack = []执行后,_request_ctx_stack = {唯一ID:{'stack': []}}。
if rv is None:
self._local.stack = rv = []
rv.append(obj)
这就执行后_request_ctx_stack = {唯一ID:{'stack': [RequestContext(ctx)]}}
_request_ctx_stack 中现在已经有值了,值是请求上下文对象ctx 。所以总结起来ctx.push就是把请求上下文对象写入_request_ctx_stack中。
清楚了请求上下文,应用上下文和请求上下文是一个套路。
_app_ctx_stack = {唯一ID:{'stack': [AppContext(app_ctx)]}}
简单看一下应用上下文的入栈流程
flask.ctx
class RequestContext(object):
def push(self):
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()
app_ctx = _app_ctx_stack.top同样的请求第一次进来,_app_ctx_stack为空。app_ctx = None
app_ctx = self.app.app_context() 这句获取app上下文对象。
def app_context(self):
return AppContext(self)
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_ctx就是一个AppContext对象。里面包含app和g
app_ctx.push() 这句其实和请求上下文中的ctx.push()是一样的逻辑。只不过app_ctx用的是_app_ctx_stack栈,_app_ctx_stack = LocalStack() 这个栈的结构和_request_ctx_stack栈是一样的。 后面的push操作可以参考_request_ctx_stack.push()。
二 请求到来,执行视图函数
在视图函数中对上下文操作
@app.route('/')
def index():
print(request.args)
对这个函数只做 print(request.args),在flask.globals中
request = LocalProxy(partial(_lookup_req_object, 'request')),request是个LocalProxy对象,同时将偏函数partial(_lookup_req_object, 'request') 传给LocalProxy。
class LocalProxy(object):
def __init__(self, local, name=None):
object.__setattr__(self, "_LocalProxy__local", local)
在LocalProxy中self.__LocalProxy__local = partial(_lookup_req_object, 'request'),
self.__LocalProxy__local 其实就是self.__local。所以self.__local = partial(_lookup_req_object, 'request')其实也就是 self.__local = _lookup_req_object('request')。
当执行request.args是执行了LocalProxy的getattr,向LocalProxy传了2个参数,local= _lookup_req_object('request'), name = args
def __getattr__(self, name):
if name == "__members__":
return dir(self._get_current_object())
return getattr(self._get_current_object(), name)
self._get_current_object()执行了
def _get_current_object(self):
"""Return the current object. This is useful if you want the real
object behind the proxy at a time for performance reasons or because
you want to pass the object into a different context.
"""
if not hasattr(self.__local, "__release_local__"):
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)
从上面的分析可以知道self.__local = _lookup_req_object('request'),所以getattr(self.__local, self.name) 就是getattr(_lookup_req_object('request'),args) ,再看_lookup_req_object这函数
def _lookup_req_object(name):
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
这是一个全局函数,是从_request_ctx_stack栈顶的请求上下文对象ctx中取一个叫request的对象。拿到request对象后再执行
def __getattr__(self, name):
return getattr(self._get_current_object(), name)
返回的结果是getattr(request,args) 从request对象里面取args。
三 请求结束
def wsgi_app(self, environ, start_response):
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
在wsgi_app方法里可以看到请求最后都会调用ctx.auto_pop来结束
def auto_pop(self, exc):
if self.request.environ.get('flask._preserve_context') or \
(exc is not None and self.app.preserve_context_on_exception):
self.preserved = True
self._preserved_exc = exc
else:
self.pop(exc)
auto_pop最终调用pop方法,将对象移除。
def pop(self, exc=_sentinel):
finally:
rv = _request_ctx_stack.pop()
if app_ctx is not None:
app_ctx.pop(exc)