Flask request,g,session的实现原理

最近一直在研究Flask,由于gfirefly中提供的Http接口使用了Flask,以前都是写一些游戏中简单的操作,最近涉及到Flask的方面比较多,所以就认真研究了下。对Flask的request context和app context略有心得,所以和小伙伴们分享一下Flask的request原理。

在我们视图中要使用request时只需要from flask import request就可以了很好奇在多线程的环境下,是如何保证request没有混乱的在flask.globals.py中

def _lookup_req_object(name):top = _request_ctx_stack.topif top is None:raise RuntimeError(‘working outside of request context’)return getattr(top, name)_request_ctx_stack = LocalStack()request = LocalProxy(partial(_lookup_req_object, ‘request’))session = LocalProxy(partial(_lookup_req_object, ‘session’))

LocalProxy是werkzeug.local.py中定义的一个代理对象,它的作用就是将所有的请求都发给内部的_local对象

class LocalProxy(object):def __init__(self, local, name=None):#LocalProxy的代码被我给简化了,这里的local不一定就是local.py中定义的线程局部对象,也可以是任何可调用对象#在我们的request中传递的就是_lookup_req_object函数object.__setattr__(self, ‘_LocalProxy__local’, local)object.__setattr__(self, ‘__name__’, name)def _get_current_object(self):#很明显,_lookup_req_object函数没有__release_local__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__)def __getattr__(self, name):return getattr(self._get_current_object(), name)当我们调用request.method时会调用_lookup_req_object,对request的任何调用都是对_lookup_req_object返回对象的调用。既然每次request都不同,要么调用top = _request_ctx_stack.top返回的top不同,要么top.request属性不同,在flask中每次返回的top是不一样的,所以request的各个属性都是变化的。现在需要看看_request_ctx_stack = LocalStack(),LocalStack其实就是简单的模拟了堆栈的基本操作,push,top,pop,内部保存的线程本地变量是在多线程中request不混乱的关键。class Local(object):__slots__ = (‘__storage__’, ‘__ident_func__’)def __init__(self):object.__setattr__(self, ‘__storage__’, {})object.__setattr__(self, ‘__ident_func__’, get_ident)def __getattr__(self, name):return self.__storage__[self.__ident_func__()][name] 简单看一下Local的代码,__storage__为内部保存的自己,键就是thread.get_ident,也就是根据线程的标示符返回对应的值。下面我们来看看整个交互过程,_request_ctx_stack堆栈是在哪里设置push的,push的应该是我们上面说的同时具有request和session属性的对象,那这家伙又到底是什么?

flask从app.run()开始

class Flask(_PackageBoundObject):def run(self, host=None, port=None, debug=None, **options):from werkzeug.serving import run_simplerun_simple(host, port, self, **options)使用的是werkzeug的run_simple,根据wsgi规范,app是一个接口,并接受两个参数,即,application(environ, start_response)在run_wsgi的run_wsgi我们可以清晰的看到调用过程 def run_wsgi(self):environ = self.make_environ()def start_response(status, response_headers, exc_info=None):if exc_info:try:if headers_sent:reraise(*exc_info)finally:exc_info = Noneelif headers_set:raise AssertionError(‘Headers already set’)headers_set[:] = [status, response_headers]return writedef execute(app):application_iter = app(environ, start_response)#environ是为了给request传递请求的#start_response主要是增加响应头和状态码,最后需要werkzeug发送请求try:for data in application_iter: #根据wsgi规范,app返回的是一个序列write(data) #发送结果if not headers_sent:write(b”)finally:if hasattr(application_iter, ‘close’):application_iter.close()application_iter = Nonetry:execute(self.server.app)except (socket.error, socket.timeout) as e:passflask中通过定义__call__方法适配wsgi规范class Flask(_PackageBoundObject):def __call__(self, environ, start_response):"""Shortcut for :attr:`wsgi_app`."""return self.wsgi_app(environ, start_response)def wsgi_app(self, environ, start_response):ctx = self.request_context(environ)#这个ctx就是我们所说的同时有request,session属性的上下文ctx.push()error = Nonetry:try:response = self.full_dispatch_request()except Exception as e:error = eresponse = self.make_response(self.handle_exception(e))return response(environ, start_response)finally:if self.should_ignore_error(error):error = Nonectx.auto_pop(error)def request_context(self, environ):return RequestContext(self, environ)哈哈,终于找到神秘人了,RequestContext是保持一个请求的上下文变量,,之前我们_request_ctx_stack一直是空的,当一个请求来的时候调用ctx.push()将向_request_ctx_stack中push ctx。我们看看ctx.pushclass RequestContext(object):def __init__(self, app, environ, request=None):self.app = appif request is None:request = app.request_class(environ) #根据环境变量创建requestself.request = requestself.session = Nonedef push(self):_request_ctx_stack.push(self) #将ctx push进 _request_ctx_stack# Open the session at the moment that the request context is# available. This allows a custom open_session method to use the# request context (e.g. code that access database information# stored on `g` instead of the appcontext).self.session = self.app.open_session(self.request)if self.session is None:self.session = self.app.make_null_session()def pop(self, exc=None):rv = _request_ctx_stack.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 = Trueself._preserved_exc = excelse:self.pop(exc)

发光并非太阳的专利,我也可以发光 。

Flask request,g,session的实现原理

相关文章:

你感兴趣的文章:

标签云: