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等等,并解决了多线程线程安全的问题
小而精,其他的功能都可以通过各种三方库来扩展、