Python Web 模块之 Flask v0.1

第 3 部分 源码阅读之 App 代码阅读

3.6 Flask __call__

uml: Flask-__call__.puml

def __call__(self, environ, start_response):
    """Shortcut for :attr:`wsgi_app`"""
    return self.wsgi_app(environ, start_response)

执行 __call__ 函数时 , 直接返回了 wsgi_app 函数的执行结果 。

3.7 Flask wsgi_app

uml: Flask-wsgi_app.puml

def wsgi_app(self, environ, start_response):
    """
    :param environ: a WSGI environment
    :param start_response: a callable accepting a status code,
                           a list of headers and an optional
                           exception context to start the response
    """
    with self.request_context(environ):
        rv = self.preprocess_request()
        if rv is None:
            rv = self.dispatch_request()
        response = self.make_response(rv)
        response = self.process_response(response)
        return response(environ, start_response)

首先打开一个请求上下文 , 在这个上下文过程中 , 先执行 preprocess_request 函数进行预处理请求的执行 , 如果没有预处理请求 , 则执行 dispatch_request 函数 , 再然后执行 make_response 对预处理请求或分发的请求生成响应对象 , 然后处理这个响应对象 , 其结果作为返回值返回出去 。

3.8 Flask request_context

uml: Flask-request_context.puml

def request_context(self, environ):
    return _RequestContext(self, environ)

接上文 , 进入 request_context 后直接返回 _RequestContext 类实例 , 换句话说 request_context 就是 _RequestContext 类实例。

3.9 _RequestContext

uml: Flask-_RequestContext.puml

class _RequestContext(object):
    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        self.g = _RequestGlobals()
        self.flashes = None

    def __enter__(self):
        _request_ctx_stack.push(self)

    def __exit__(self, exc_type, exc_value, tb):
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()

在上文中 , 执行 with request_context 的时候 , 会执行 _RequestContext 类的 __enter__ 函数 , 当然是在执行 __init__ 函数之后 , 举个例子可以看一下 with 的执行顺序 :

class testwith:
    def __init__(self):
        print('__init__()')

    def __enter__(self):
        print('__enter__()')
        return '__enter__'

    def __exit__(self, type, value, trace):
        print('__exit__()')

with testwith() as tt:
    print(tt)

Result:
>>>__init__()
>>>__enter__()
>>>__enter__
>>>__exit__()

这个示例代码充分说明了执行过程是先执行初始化函数 , 然后执行 __enter__ 函数 , 上下文结束时执行 __exit__ 函数 。

因此 _RequestContext 类中也是这样的顺序 , 先初始化 6 个变量 :

  • self.app = app

  • self.url_adapter = app.url_map.bind_to_environ(environ)

  • self.request = app.request_class(environ)

  • self.session = app.open_session(self.request)

  • self.g = _RequestGlobals()

  • self.flashes = None

初始化中的 app 参数就是 Flask 类实例 , 因为 return _RequestContext(self, environ) self 代表的就是 Flask 类实例 ; url_adapter 为当前 Flask app 的 url_map 绑定到 wsgi 环境中 ; request 为当前 Flask app 的 request_class ; session 为当前 Flask app 的 open_session ; g 为_RequestGlobals 类实例 ; flashes 为空 (None) 。

然后执行 _request_ctx_stack.push 函数 , 将当前请求上下文推入到请求上下文堆栈中 , 上下文结束后执行 _request_ctx_stack.pop , 弹出当前请求上下文 。

3.10 Flask request_class

uml: Flask-request_class.puml

class Flask:

    request_class = Request

在 _RequestContext 中 , bind_to_environ 函数属于 werkzeug 模块 , 先放过 。 而 self.request 的值 Flask.request_class 中的 request_class 就是 Request 类实例 。

3.11 Request

uml: Flask-Request.puml

class Request(RequestBase):
    """The request object used by default in flask.  Remembers the
    matched endpoint and view arguments.
    """

    def __init__(self, environ):
        RequestBase.__init__(self, environ)
        self.endpoint = None
        self.view_args = None

接上文 , Request 类继承了 werkzeug.wrappers.Request 类 , 然后记录了匹配的 endpoint 和 view_args 。

3.12 open_session

uml: Flask-open_session.puml

def open_session(self, request):
    key = self.secret_key
    if key is not None:
        return SecureCookie.load_cookie(request, self.session_cookie_name,
                                        secret_key=key)

在 _RequestContext 类中继续 , self.session 的值 open_session 函数的 request 参数就是当前请求对象 , 因为 app.open_session(self.request) 。 self.request 是一个 Request 类实例 , 当 self.secret_key 不为空时 , 返回 SecureCookie 类 。

3.13 _RequestGlobals

接着上文 , _RequestContext 中 g 变量是 _RequestGlobals 类实例 , 代码如下 :

class _RequestGlobals(object):
    pass

因此 g 变量为空 。

OK , 到这里 _RequestContext 类解析完毕 , 也就是说 request_context 解析完毕 , 接下来返回到 wsgi_app 函数中 , 进入请求上下文当中 , 解析 preprocess_request 方法

3.14 Flask preprocess_request

preprocess_request 的源代码如下所示 , self.before_request_funcs 是一个列表 , 默认情况下是空值 , 其值为可调用对象 , 通过 before_request 函数进行操作 。

def preprocess_request(self):
    for func in self.before_request_funcs:
        rv = func()
        if rv is not None:
            return rv

由于一般情况下是空值 , 所以该函数没有返回值 , 但是当 before_request_funcs 有值的时候 , 会返回其值的返回值 , 换句话说 , before_request_funcs 中是一个个函数 , 返回的是函数的执行结果 。

3.15 Flask before_request

def before_request(self, f):
    """Registers a function to run before each request."""
    self.before_request_funcs.append(f)
    return f

直接看一下这个函数 , 它用来注册在每个请求执行之前的函数 , 也就是说在执行一个视图函数之前 , 先执行 before_request_funcs 列表中的函数 , 调用这个函数之后 , 会将参数对象追加到 before_request_funcs 列表中 , 最后返回这个参数对象 。

3.16 Flask dispatch_request

继续 wsgi_app 中的解析 , 由于 preprocess_request 为空 , 判断条件为 False , 因此执行 dispatch_request 函数 , 该函数代码如下 :

def dispatch_request(self):

    try:
        endpoint, values = self.match_request()
        return self.view_functions[endpoint](**values)
    except HTTPException, e:
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)
    except Exception, e:
        handler = self.error_handlers.get(500)
        if self.debug or handler is None:
            raise
        return handler(e)

其实这个函数在前文中有过解析 , 这里在详细解析一下 。 首先执行 try 内部的步骤 , 执行 match_request 函数获得 endpoint 和 values , 这里的 endpoint 其实就是视图函数名称 , values 就是视图函数的参数 , 然后从 view_functions (视图函数关联字典) 中获取到视图函数对象 , 再将参数传递过去 , 最终返回视图函数的执行结果 。

如果出现 HTTPException , 则执行错误事件处理函数 , error_handlers 是一个字典 , 通过 errorhandler 函数注册错误事件处理函数 , 从 error_handlers 字典中获取到错误事件处理对象之后 , 执行这个对象并返回出去结果 。

如果是其他的 Exception , 直接按照错误代码 500 进行处理 。

3.17 Flask match_request

def match_request(self):
    """Matches the current request against the URL map and also
    stores the endpoint and view arguments on the request object
    is successful, otherwise the exception is stored.
    """
    rv = _request_ctx_stack.top.url_adapter.match()
    request.endpoint, request.view_args = rv
    return rv

接着 dispatch_request 函数中的步骤 , match_request 函数的功能就如函数注释 , 将当前请求与 URL 映射进行匹配 , 匹配成功就存储 endpoint 和视图函数的参数 , 否则就存储异常 。 最终返回匹配结果 。

3.18 Flask errorhandler

def errorhandler(self, code):

    def decorator(f):
        self.error_handlers[code] = f
        return f
    return decorator

接着 dispatch_request 函数中的步骤 , 如果出现异常 , 就会从异常处理列表中查找异常处理方法 , error_handlers 是一个字典 , 通过 errorhandler 函数注册错误事件处理函数 , 类似于 route 注册路由 , errorhandler 会注册某些错误代码的处理方法 , 假如错误代码是 404 :

@app.errorhandler(404)
def page_not_found():
    return 'This page does not exist', 404

其注册后的结果 errorhandler = {'404': page_not_found} , 之后会通过异常代码查找异常处理方法 , 如果出现了 404 异常代码 , 然后就查到 page_not_found 方法 , 然后就执行它 。

到此 dispatch_request 函数解析完毕 。

3.19 Flask make_response

回到 wsgi_app 中的步骤 , make_response 创建响应对象 。 代码如下 :

def make_response(self, rv):
    if isinstance(rv, self.response_class):
        return rv
    if isinstance(rv, basestring):
        return self.response_class(rv)
    if isinstance(rv, tuple):
        return self.response_class(*rv)
    return self.response_class.force_type(rv, request.environ)

首先判断参数 rv 到底是什么对象 , 如果是 self.response_class 实例 , 直接返回 rv ; 如果是 basestring 实例 , 则返回 self.response_class(rv) ; 如果是 tuple 元组则返回 self.response_class(*rv) ; 如果都不是 , 则返回 self.response_class.force_type(rv, request.environ)

self.response_class 实际上就是 Response 类实例 , 因为 response_class = Response , basestring 是 str 和 unicode 的超类 (父类) , 也是抽象类 , 不能被调用和实例化 , 但可以被用来判断一个对象是否为 str 或者 unicode 的实例 , isinstance(obj, basestring) 等价于 isinstance(obj, (str, unicode)) 。

3.20 Flask Response

Flask 中的 Response 类继承自 werkzeug 中的 ResponseBase , 只设置了默认处理格式 。 其代码如下 :

class Response(ResponseBase):
    default_mimetype = 'text/html'

在这里只指定了 default_mimetype 为 html , 用于控制响应类型 。 其他值均继承 ResponseBase 类 。

make_response 的分析就到此结束了 , 回到 wsgi_app 中 。

3.21 Flask process_response

wsgi_app 执行到 process_response , 用于处理响应对象 , 其代码如下 :

def process_response(self, response):
    """Can be overridden in order to modify the response object
    before it's sent to the WSGI server.  By default this will
    call all the :meth:`after_request` decorated functions.

    :param response: a :attr:`response_class` object.
    :return: a new response object or the same, has to be an
             instance of :attr:`response_class`.
    """
    session = _request_ctx_stack.top.session
    if session is not None:
        self.save_session(session, response)
    for handler in self.after_request_funcs:
        response = handler(response)
    return response

这里的 response 参数联系上下文就知道是 make_response 生成的响应对象 , 以生成的响应对象为参数传入 process_response 函数中 。

首先局部变量 session 表示的是当前的请求的 session , 在 _RequestContext 中有定义 , 如果 session 不为 None , 执行 save_session 函数 , 当 after_request_funcs 中有值的时候 , 循环执行其中的方法 , self.after_request_funcs 是一个列表 , 存储着每个请求执行完毕后应该执行的方法 , 通过 after_request 函数操作 。 最终返回一个 response_class 实例对象 。

3.22 Flask save_session

def save_session(self, session, response):
    """Saves the session if it needs updates.  For the default
    implementation, check :meth:`open_session`.

    :param session: the session to be saved (a
                    :class:`~werkzeug.contrib.securecookie.SecureCookie`
                    object)
    :param response: an instance of :attr:`response_class`
    """
    if session is not None:
        session.save_cookie(response, self.session_cookie_name)

save_session 其实就是更新一下之前的 session , 当当前请求的 session 不为空时 , 更新一下 cookie 。 由于 save_cookie 是 werkzeug 中的方法 , 这里就不展开了 。

3.23 Flask after_request

def after_request(self, f):
    """Register a function to be run after each request."""
    self.after_request_funcs.append(f)
    return f

after_request 会将参数对象注册到 after_request_funcs 列表中 , 会在每个请求之后运行 。

3.24 response

回到 wsgi_app 中的最后一个步骤 , 即一个请求上下文结束时的步骤 , 执行的是 response(environ, start_response) , 这里的 response 实际上还是一个 Response 类 , 只不过是 process_response 返回的一个 Response 类 。 它的意思是以当前的 wsgi 环境和响应参数执行响应步骤 。

到此 wsgi_app 就完成解析了 , 其他的一些方法在示例 App 中有用到 , 就先不解析 , 放在测试代码中解析 。

第 3 部分 源码阅读之测试用例

3.1 BasicFunctionality

首先阅读基础功能方面的测试用例 , 按照源码中的 TestCase 依次阅读 。

3.1.1 Request Dispatching

第一个是请求转发功能 , 详情看测试用例代码 。

class BasicFunctionality(unittest.TestCase):

    def test_request_dispatching(self):
        app = flask.Flask(__name__)

        @app.route('/')
        def index():
            return flask.request.method

        @app.route('/more', methods=['GET', 'POST'])
        def more():
            return flask.request.method

        c = app.test_client()
        b = c.get('/')
        assert c.get('/').data == 'GET'
        rv = c.post('/')
        assert rv.status_code == 405
        assert sorted(rv.allow) == ['GET', 'HEAD']
        rv = c.head('/')
        assert rv.status_code == 200
        assert not rv.data # head truncates
        assert c.post('/more').data == 'POST'
        assert c.get('/more').data == 'GET'
        rv = c.delete('/more')
        assert rv.status_code == 405
        assert sorted(rv.allow) == ['GET', 'HEAD', 'POST']

为了方便调试中查看变量数据 , 插入一行 b = c.get('/') , 并在此处设置断点 , 开始调试 , 调试结果如下 。 当然首先要先了解两个视图函数的作用 。

index 函数返回了当前请求的请求方法 , 注册路由的时候没有添加请求方法 , 默认为 GET , 也就是说最终返回的是 'GET' 。

more 设置了请求方法 , 'GET' 和 'POST' 都可以 , 那最终结果就看请求方法了 , 如果用 GET 请求 , 返回值为 'GET' , 如果用 POST 请求 , 返回值为 'POST' 。

../../../_images/3-1.png

这里的 data 就是视图函数的结果 , 然后 case 中的步骤就很好理解了 , 判断请求视图函数的方法是否正确 , 当然我有些疑惑的是 , 用一个视图函数不支持的请求方法请求响应的路由后 , 允许的请求方法会多一个 'HEAD' 方法 , 这个问题就先留在这了 , 以后有这方面的知识后再解答 。

未完待续 ...

上一篇文章 : 上一篇

下一篇文章 : 下一篇