• Flask运行原理


    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等等,并解决了多线程线程安全的问题

    小而精,其他的功能都可以通过各种三方库来扩展、

  • 相关阅读:
    最近ACM刷题见到的没见过的名词
    最近刷题常常遇见的需要百度的知识点
    简单RPG场景实现(改
    简单RPG场景实现
    位图载入与位图移动
    动态菜单
    静态菜单
    双人五子棋
    HDU 2112 HDU Today
    音痴又音痴的LT
  • 原文地址:https://www.cnblogs.com/cx59244405/p/11451010.html
Copyright © 2020-2023  润新知