Flask源码分析

杂项

  1. 首先看了一下app.route方法,这是一个装饰器,但是感觉和我认知当中的装饰器略有不同,中间没有wrapper函数,这是为什么呢?思考了一下想到这个装饰器的作用是完成函数的注册,所以其行为只要在定义的时候将函数名和对应路由写入路由表就完事了,不需要干扰函数的正常调用。这个应该是装饰器的一大妙用——注册函数。
现在还有一个疑问,这个注册过程是在什么时候进行的呢?还是要了解总体的流程
  1. 搞了半天看了werkzeug的源码终于明白了Local对象的pop其实是pop(__ident_func__()),所以顶层pop()掉的不是最后push进来的那个东西,那为什么还要叫做stack呢???难道我对于stack的理解有误,之前就在想flask使用先进后出有什么好处,明明字典这种高级数据结构更加方便
  2. 更新一下对于flask运行方式的理解:
(1)运行run.py,import app,并且完成app对象的初始化,包括路由表的建立等
(2)调用app.run,开启werkzeug提供的一个http测试服务器,展开监听
(3)请求进来以后,服务器接受到请求,call app,并且传入environ和start_response回调,app的call部分其实就负责一个WSGI application该负责的部分(内部其实是调用app的wsgi_app方法,这能够说明问题了)
(4)他第一件事是干什么呢?先获取一个request_context对象,这个对象里面封装了environ当中的数据,而且是一个上下文对象
(5)后续...
  1. 有个很奇怪的地方,wsgi_app方法中为什么没有使用with语句,而是手动push和pop,可能原因是为了灵活性,看这个

注解

在0.9版本中,Flask引入了“应用上下文”的概念,这对“请求上下文”的实现有一定的改变。这个版本的“请求上下文”也是一个上下文对象。在使用with语句进入上下文环境后,_request_ctx_stack会存储这个上下文对象。不过与0.1版本相比,有以下几点改变:

(1)请求上下文实现了push、pop方法,这使得对于请求上下文的操作更加的灵活;

(2)伴随着请求上下文对象的生成并存储在栈结构中,Flask还会生成一个“应用上下文”对象,而且“应用上下文”对象也会存储在另一个栈结构中 去。这是两个版本最大的不同。

根据实例看源码

  1. 今天先分析这段flask app创建代码做了什么
def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    login_manager.init_app(app)
    db.init_app(app)
    configure_uploads(app, photos)
    patch_request_class(app)

    from .views import main as main_blueprint
    app.register_blueprint(main_blueprint)

    return app
  1. 首先 app = Flask(__name__) ,这里是赋值import name,从而app对象能找到资源路径,app对象在创建的时候必须有一个Import name
在这个过程当中,由于Flask类是继承_PackageBoundObject类的,因此初始化的过程当中还对这个类的属性进行了初始化,但这个类是用来干什么的呢?
  1. 初始化的过程当中有几个值得注意的实例变量:
(1)config:配置,注意也是一个对象,以dict的形式进行存储,默认情况下已经有一大堆配置了,这个都是初始化的时候加的默认配置
(2)view_functions:一个字典,用来放路由函数
(3)_error_handlers:一个字典,用来放错误处理函数,同样是通过装饰器注册的
(4)before_request_funcs :每个请求开始的时候被call的函数
(5)before_first_request_funcs:第一个对于该实例的请求开始的时候被call的函数
(6)after_request_funcs:每个请求结束的时候被call的函数,比如关闭数据库连接
(7)teardown_request_funcs:请求结束即使异常也要call的函数
(8)url_map:用来放url和endpoint之间的映射
../_images/58E69230-32B4-459C-8447-E846E2123623.png

url_map是通过werkzeug的Map class创建的,这个url映射是怎么加上的呢?初始化过程当中有一段

if self.has_static_folder:
    self.add_url_rule(self.static_url_path + '/<path:filename>',
                      endpoint='static',
                      view_func=self.send_static_file)

可以看到add_url_rule是怎么处理的

self.url_map.add(rule)
if view_func is not None:
    old_func = self.view_functions.get(endpoint)
    if old_func is not None and old_func != view_func:
        raise AssertionError('View function mapping is overwriting an existing endpoint function: %s' % endpoint)
    self.view_functions[endpoint] = view_func

从这里可以看出url-endpoint-view_func之间的映射关系是如何存在与app当中的,首先url_map存放了url-endpoint的映射,其次view_functions存放了endpoint-view_func的映射,至于问什么需要endpoint呢?可能是因为并非所有url都有对应的view_functions,只是猜想

  1. app.config.from_object(config[config_name]) 第二步是进行配置,其实就是把配置写入到app.config当中去
  2. 接下来都是调用各个extension对象的init_app方法
app.login_manager = self
    app.after_request(self._update_remember_cookie)
    self._login_disabled = app.config.get('LOGIN_DISABLED', False)
    if add_context_processor:
        app.context_processor(_user_context_processor)

看了下init_app做的事情,首先给实例创建了一个独特的属性,其次将自己的_update_remember_cookie方法进行注册进after_request函数表,请求结束后就会进行调用,after_request函数应该都会调用一下response函数,说明它的作用范围在response前,作用是在cookie当中写入用户信息,后面的这个context_processor就不是很清楚了

  1. 在一个请求的响应过程当中
(1)首先call flask app,调用其wsgi_app方法
(2)创建Request_context对象,Request_context对象有一个request属性,是一个Request对象(来自werkzeug),里面就放了请求的信息,Request_context对象本身并不存储所有请求信息,因为它是单次请求中被创建的,它借由一个叫做_request_ctx_stack的全局LocalStack对象来存储所有请求的信息,其本身只是作为一个上下文管理器使用,enter的时候将获取的environ信息(自身)压入_request_ctx_stack,exit的时候则将这个environ信息pop出来,要注意这里的pop、push还有top都是本线程的,这是因为Local这个字典重载了__getattr__方法, return self.__storage__[self.__ident_func__()][name] ,相当于你用local.stack取到的只是当前进程当中的本地stack中的东西
(3)push,新版本中Request_context的push方法会检查本地的_app_ctx_stack里面有没有东西,没有的话就会创建一个app_context对象,并且将自身push进去
(4)关于请求上下文的必要性,我之前一直想不通这个必要性是因为我一直觉得不同模块间一方调用另一方,另一方能够使用调用者的变量,实际上是不行的(python是词法作用域啊,时刻要牢记),要么有一个全局变量,要么把这个变量作为参数来传递,作为参数来传递显然是行不通,因此最好的方法是作为全局变量来使用,还有一个问题是为什么会需要栈呢?难道一个request会同时有两个需要存储的请求上下文对象?这个主要是用来做测试什么的比较方便
(5)通过full_dispatch_request方法处理请求,返回一个response对象
(6)通过调用response对象完成response
(7)总体上分析得还是有点乱,flask源码要完全看懂不是一朝一夕的事情,接下来按照模块每天看一些,并且给出详解