0、写在前面
通过阅读Flask的源码来学习下运行原理
1、启动,从请求到响应的过程
一个最简单的程序HelloWrold.py
1 from flask import Flask 2 3 app = Flask(__name__) 4 5 @app.route('/') 6 def hello_world(): 7 return 'Hello World!' 8 9 if __name__ == '__main__': 10 app.run()
可以看到,主要运行服务的代码只有2行
实例化 app = Flask(__name__)
运行 app.run()
实例化FLASK后,运行其中的run函数
run函数中的代码,传入一些配置参数后,实际运行的是werkzeug.serving.run_simple(host, port, self, **options)
Flask的底层运行的服务实际是调用werkzeug.serving.run_simple()后做了一些封装
run_simple()传入的self就是app,而且会以app()的形式运行
app()相当于执行app.__call__()
1 def run(self, host=None, port=None, debug=None, 2 load_dotenv=True, **options): 3 from werkzeug.serving import run_simple 4 5 try: 6 run_simple(host, port, self, **options) 7 finally: 8 self._got_first_request = False
app.__call__(),执行了一行self.wsgi_app(environ, start_response)
按照wsgi协议,
environ:一个包含所有HTTP请求信息的dict对象
start_response:一个发送HTTP响应的函数
1 def __call__(self, environ, start_response): 2 return self.wsgi_app(environ, start_response)
environ被经过一系列封装处理后,最终返回了封装了request和session的Request类的对象,赋值给ctx
这个ctx即为请求上下文,下文中再说
最后返回response(environ, start_response)
1 def wsgi_app(self, environ, start_response): 2 ctx = self.request_context(environ) 3 error = None 4 try: 5 try: 6 ctx.push() 7 response = self.full_dispatch_request() 8 except Exception as e: 9 error = e 10 response = self.handle_exception(e) 11 except: 12 error = sys.exc_info()[1] 13 raise 14 return response(environ, start_response) 15 finally: 16 if self.should_ignore_error(error): 17 error = None 18 ctx.auto_pop(error)
然后进入full_dispatch_request(self)
执行 self.try_trigger_before_first_request_functions()即装饰器@before_first_request装饰所有函数
执行 rv = self.preprocess_request()方法 即@before_request装饰所有函数
return self.finalize_request(rv)方法 即@after_request装饰所有函数
进入self.finalize_request(rv),response = self.process_response(response)
1 def full_dispatch_request(self): 2 self.try_trigger_before_first_request_functions() 3 try: 4 request_started.send(self) 5 rv = self.preprocess_request() 6 if rv is None: 7 rv = self.dispatch_request() 8 except Exception as e: 9 rv = self.handle_user_exception(e) 10 return self.finalize_request(rv)
从请求上下文栈中取出request,进行路由匹配,执行视图函数
1 def dispatch_request(self): 2 req = _request_ctx_stack.top.request 3 if req.routing_exception is not None: 4 self.raise_routing_exception(req) 5 rule = req.url_rule 6 if getattr(rule, 'provide_automatic_options', False) 7 and req.method == 'OPTIONS': 8 return self.make_default_options_response() 9 return self.view_functions[rule.endpoint](**req.view_args)
2、路由
通过HelloWrold.py可以看到路由是通过一个装饰器@app.route('/')添加进来的
找个装饰器实际运行的代码self.add_url_rule(rule, endpoint, f, **options)
1 def route(self, rule, **options): 2 def decorator(f): 3 endpoint = options.pop('endpoint', None) 4 self.add_url_rule(rule, endpoint, f, **options) 5 return f 6 return decorator
endpoint是路由的唯一标识,如果为空,则把函数名赋值给endpoint
实际添加路由的self.url_map.add(rule),该函数来自于self.url_map= werkzeug.routing.Map(),在这里进行路由的添加
路由分发上文中有
1 @setupmethod 2 def add_url_rule(self, rule, endpoint=None, view_func=None, 3 if endpoint is None: 4 endpoint = _endpoint_from_view_func(view_func) 5 options['endpoint'] = endpoint 6 methods = options.pop('methods', None) 7 8 rule = self.url_rule_class(rule, methods=methods, **options) 9 rule.provide_automatic_options = provide_automatic_options 10 11 self.url_map.add(rule) 12 if view_func is not None: 13 old_func = self.view_functions.get(endpoint) 14 if old_func is not None and old_func != view_func: 15 raise AssertionError('View function mapping is overwriting an ' 16 'existing endpoint function: %s' % endpoint) 17 self.view_functions[endpoint] = view_func
3、本地上下文
FLask上下文包含两种,请求上下文(ctx)、程序上下文(ctx_app),原理相同。
包括全局变量request、session、current_app、g
都是通过本地代理LocalProxy来实例化出来的
1 _request_ctx_stack = LocalStack() 2 _app_ctx_stack = LocalStack() 3 current_app = LocalProxy(_find_app) 4 request = LocalProxy(partial(_lookup_req_object, 'request')) 5 session = LocalProxy(partial(_lookup_req_object, 'session')) 6 g = LocalProxy(partial(_lookup_app_object, 'g'))
3.1 全局变量
reques 全局请求对象t
session 全局session对象
current_app 当前app实例
g 一个可存值的对象
全局变量通过本地代理LocalProxy(local)生成
传入具体对象,例如request,在通过代理从request中取值
上下文的push和pop是动态进行的
使用代理来取值,可以拥有动态的获取上下文对象的能力
1 class LocalProxy(object): 2 3 __slots__ = ('__local', '__dict__', '__name__', '__wrapped__') 4 5 def __init__(self, local, name=None): 6 object.__setattr__(self, '_LocalProxy__local', local) 7 object.__setattr__(self, '__name__', name) 8 if callable(local) and not hasattr(local, '__release_local__'): 9 # "local" is a callable that is not an instance of Local or 10 # LocalManager: mark it as a wrapped function. 11 object.__setattr__(self, '__wrapped__', local) 12 13 def _get_current_object(self): 14 if not hasattr(self.__local, '__release_local__'): 15 return self.__local() 16 try: 17 return getattr(self.__local, self.__name__) 18 except AttributeError: 19 raise RuntimeError('no object bound to %s' % self.__name__) 20 21 @property 22 def __dict__(self): 23 try: 24 return self._get_current_object().__dict__ 25 except RuntimeError: 26 raise AttributeError('__dict__')
request、session的偏函数partial(_lookup_req_object, name)
传入对应的name,从请求上下文中获取具体的对象
1 def _lookup_req_object(name): 2 top = _request_ctx_stack.top 3 if top is None: 4 raise RuntimeError(_request_ctx_err_msg) 5 return getattr(top, name)
g的偏函数partial_lookup_app_object(name)
从程序上下文中获取具体的对象
1 def _lookup_app_object(name): 2 top = _app_ctx_stack.top 3 if top is None: 4 raise RuntimeError(_app_ctx_err_msg) 5 return getattr(top, name)
current_app从程勋上下文获取当前的app
1 def _find_app(): 2 top = _app_ctx_stack.top 3 if top is None: 4 raise RuntimeError(_app_ctx_err_msg) 5 return top.app
3.2 请求上下文
根据上文中的请求响应过程,请求进来后先wsgi_app()
创建了ctx上下文,从上文中得知ctx是封装了request和session的Request类的对象,然后执行push()
1 def wsgi_app(self, environ, start_response): 2 ctx = self.request_context(environ) 3 ctx.push()
这是ctx.push()的代码
实际运行了_request_ctx_stack.push()
进行了各种栈操作,再看看_request_ctx_stack栈是如何工作的
1 def push(self): 2 top = _request_ctx_stack.top 3 if top is not None and top.preserved: 4 top.pop(top._preserved_exc) 5 app_ctx = _app_ctx_stack.top 6 if app_ctx is None or app_ctx.app != self.app: 7 app_ctx = self.app.app_context() 8 app_ctx.push() 9 self._implicit_app_ctx_stack.append(app_ctx) 10 else: 11 self._implicit_app_ctx_stack.append(None) 12 13 if hasattr(sys, 'exc_clear'): 14 sys.exc_clear() 15 16 _request_ctx_stack.push(self) 17 18 if self.session is None: 19 session_interface = self.app.session_interface 20 self.session = session_interface.open_session( 21 self.app, self.request 22 ) 23 24 if self.session is None: 25 self.session = session_interface.make_null_session(self.app)
运行_request_ctx_stack.push(self,obj)
self._local.stack = rv = [],再rv.append(obj),此时obj即为ctx
_request_ctx_stack栈中的各种操作实际都依赖于Local类,再向上看看Local类
1 class LocalStack(object): 2 3 def __init__(self): 4 self._local = Local() 5 6 def __release_local__(self): 7 self._local.__release_local__() 8 9 def _get__ident_func__(self): 10 return self._local.__ident_func__ 11 12 def _set__ident_func__(self, value): 13 object.__setattr__(self._local, '__ident_func__', value) 14 __ident_func__ = property(_get__ident_func__, _set__ident_func__) 15 del _get__ident_func__, _set__ident_func__ 16 17 def __call__(self): 18 def _lookup(): 19 rv = self.top 20 if rv is None: 21 raise RuntimeError('object unbound') 22 return rv 23 return LocalProxy(_lookup) 24 25 def push(self, obj): 26 """Pushes a new item to the stack""" 27 rv = getattr(self._local, 'stack', None) 28 if rv is None: 29 self._local.stack = rv = [] 30 rv.append(obj) 31 return rv 32 33 def pop(self): 34 """Removes the topmost item from the stack, will return the 35 old value or `None` if the stack was already empty. 36 """ 37 stack = getattr(self._local, 'stack', None) 38 if stack is None: 39 return None 40 elif len(stack) == 1: 41 release_local(self._local) 42 return stack[-1] 43 else: 44 return stack.pop() 45 46 @property 47 def top(self): 48 """The topmost item on the stack. If the stack is empty, 49 `None` is returned. 50 """ 51 try: 52 return self._local.stack[-1] 53 except (AttributeError, IndexError): 54 return None
Local类构造函数中定义了两个熟悉,__storage__和__ident_func__
__ident_func__是一个获取当前线程ID的函数
__storage__是一个嵌套的字典
对Local实例进行添加属性时,调用__setattr__(),__storage__的值变为{ ident:{ name:value } },即{ 线程ID: { 名称:实际数据 } }
对Local实例进行获取属性时,调用__getattr__(),根据线程ID和属性名进行取值self.__storage__[self.__ident_func__()][name]
1 class Local(object): 2 __slots__ = ('__storage__', '__ident_func__') 3 4 def __init__(self): 5 object.__setattr__(self, '__storage__', {}) 6 object.__setattr__(self, '__ident_func__', get_ident) 7 8 def __iter__(self): 9 return iter(self.__storage__.items()) 10 11 def __call__(self, proxy): 12 """Create a proxy for a name.""" 13 return LocalProxy(self, proxy) 14 15 def __release_local__(self): 16 self.__storage__.pop(self.__ident_func__(), None) 17 18 def __getattr__(self, name): 19 try: 20 return self.__storage__[self.__ident_func__()][name] 21 except KeyError: 22 raise AttributeError(name) 23 24 def __setattr__(self, name, value): 25 ident = self.__ident_func__() 26 storage = self.__storage__ 27 try: 28 storage[ident][name] = value 29 except KeyError: 30 storage[ident] = {name: value} 31 32 def __delattr__(self, name): 33 try: 34 del self.__storage__[self.__ident_func__()][name] 35 except KeyError: 36 raise AttributeError(name)
最后ctx.push()进行的操作实际上是_request_ctx_stack栈添加了属性{ __storage__ : { 线程ID1 : { stack : [ctx] } } }
如果是多线程运行的时候数据就是{ __storage__ : { 线程ID1 : { stack : [ctx] } , 线程ID2 : { stack : [ctx] } , 线程ID3 : { stack : [ctx] } } }
每个线程有一个独立的栈,栈中保存的全局变量request和session为每个单独线程使用,这样就保证了线程安全
在请求进来时将request和session入栈,在生成响应后出栈
3.3 程序上下文
程序上下文的生命周期伴随请求上下文的产生和销毁
每个请求都会创建新的请求上下文栈,同时会创建新的程序上下文栈
AppContext,于请求上下文类似
1 class AppContext(object): 2 def __init__(self, app): 3 self.app = app 4 self.url_adapter = app.create_url_adapter(None) 5 self.g = app.app_ctx_globals_class() 6 self._refcnt = 0 7 8 def push(self): 10 self._refcnt += 1 11 if hasattr(sys, 'exc_clear'): 12 sys.exc_clear() 13 _app_ctx_stack.push(self) 14 appcontext_pushed.send(self.app) 15 16 def pop(self, exc=_sentinel): 17 """Pops the app context.""" 18 try: 19 self._refcnt -= 1 20 if self._refcnt <= 0: 21 if exc is _sentinel: 22 exc = sys.exc_info()[1] 23 self.app.do_teardown_appcontext(exc) 24 finally: 25 rv = _app_ctx_stack.pop() 26 assert rv is self, 'Popped wrong app context. (%r instead of %r)' 27 % (rv, self) 28 appcontext_popped.send(self.app) 29 30 def __enter__(self): 31 self.push() 32 return self 33 34 def __exit__(self, exc_type, exc_value, tb): 35 self.pop(exc_value) 36 37 if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None: 38 reraise(exc_type, exc_value, tb)
app_ctx在请求上下文push()时创建app_ctx = _app_ctx_stack.top
1 class RequestContext(object): 2 def push(self): 3 app_ctx = _app_ctx_stack.top 4 if app_ctx is None or app_ctx.app != self.app: 5 app_ctx = self.app.app_context() 6 app_ctx.push() 7 self._implicit_app_ctx_stack.append(app_ctx) 8 else: 9 self._implicit_app_ctx_stack.append(None) 10 11 if hasattr(sys, 'exc_clear'): 12 sys.exc_clear() 13 14 _request_ctx_stack.push(self) 15 16 if self.session is None: 17 session_interface = self.app.session_interface 18 self.session = session_interface.open_session( 19 self.app, self.request 20 ) 21 22 if self.session is None: 23 self.session = session_interface.make_null_session(self.app)
3.4 上下文总结
Flask的上下文由请求上下文RequestContext类实例和程序上下文AppContext实例
请求上下文对象存储在请求上下文堆栈(_request_ctx_stack)
程序上下文对象存储在程序上下文堆栈(_app_ctx_stack)
每个请求都会创建新的请求上下文栈,同时会创建新的程序上下文栈
全局变量request,session保存在RequestContext实例中
全局变量current_app,g保存存在AppContext
4、总结
Flask实际上就是通过代码,将主要的处理请求的库werkzeug和模板库jinja2组合起来
通过上下文使用户可以在程序中方便的使用全局变量request、session等等,并解决了多线程线程安全的问题
小而精,其他的功能都可以通过各种三方库来扩展、