Flask restful源码分析

参考地址:https://note.youdao.com/ynoteshare1/index.html?id=4ef343068763a56a10a2ada59a019484&type=note

Flask restful 简单示例如下:

from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)


class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}


api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
    app.run(debug=True)

使用 flask_restful 的 Api 封装 app 对象,并且自定义请求类,使用 add_resource 将路由和类进行链接,并请求对应的方法。在 Flask 中,每个路由对应的是一个方法,flask_restful 对应的是一个类,里面做了怎样的处理呢?add_resource 源码如下:

    def add_resource(self, resource, *urls, **kwargs):
        if self.app is not None:
            self._register_view(self.app, resource, *urls, **kwargs)
        else:
            self.resources.append((resource, urls, kwargs))

_register_view 源码如下:

    def _register_view(self, app, resource, *urls, **kwargs):
        # app 代表当前 app 对象,resource 是路由对应的类
        # 获取 endpoint,如果没有指定则使用路由对应的类的名称
        endpoint = kwargs.pop('endpoint', None) or resource.__name__.lower()
        # self.endpoints 的类型是 set,所有 endpoint 不能重复 
        self.endpoints.add(endpoint)
        # 获取要传递给 resource 类的 args 参数
        resource_class_args = kwargs.pop('resource_class_args', ())
        # 获取要传递给 resource 类的 kwargs 参数
        resource_class_kwargs = kwargs.pop('resource_class_kwargs', {})

        if endpoint in getattr(app, 'view_functions', {}):
            previous_view_class = app.view_functions[endpoint].__dict__['view_class']
            if previous_view_class != resource:
                raise ValueError('This endpoint (%s) is already set to the class %s.' % (endpoint, previous_view_class.__name__))
        # 获取 mediatypes 
        resource.mediatypes = self.mediatypes_method()  # Hacky
        resource.endpoint = endpoint
        # 这里很重要
        # 路由对应的类 resource 继承的是 Resource,Resource 继承自 MethodView 并且重写了 dispatch_request,MethodView 的元类是 MethodViewType 和 View。所以,resource.as_view 调用的是 View 中的 as_view 方法,as_view 方法的返回是一个函数,只不过函数中存在路由对应的类的属性。
        # self.output 的参数是 resource.as_view 的返回值。
        resource_func = self.output(resource.as_view(endpoint, *resource_class_args,
            **resource_class_kwargs))

        for decorator in self.decorators:
            resource_func = decorator(resource_func)

        for url in urls:
            # If this Api has a blueprint
            if self.blueprint:
                # And this Api has been setup
                if self.blueprint_setup:
                    # Set the rule to a string directly, as the blueprint is already
                    # set up.
                    self.blueprint_setup.add_url_rule(url, view_func=resource_func, **kwargs)
                    continue
                else:
                    # Set the rule to a function that expects the blueprint prefix
                    # to construct the final url.  Allows deferment of url finalization
                    # in the case that the associated Blueprint has not yet been
                    # registered to an application, so we can wait for the registration
                    # prefix
                    rule = partial(self._complete_url, url)
            else:
                # If we've got no Blueprint, just build a url with no prefix
                rule = self._complete_url(url, '')
            # 最后还是调用 flask 的 add_url_rule 方法将路由和函数进行关联映射
            app.add_url_rule(rule, view_func=resource_func, **kwargs)

as_view 源码如下:

    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        # cls 是当前路由所对应的 resource 类
        # name 是 endpoint
        # class_args 是要传递给 resource 类的 args 参数
        # class_kwargs 是要传递给 resource 类的 kwargs 参数
        """
        将类转换为可与路由系统一起使用的实际视图函数。内部会动态生成一个函数,该函数将在每个请求上实例化View类,并在其上调用dispatch_request方法。
        """
        def view(*args, **kwargs):
            self = view.view_class(*class_args, **class_kwargs)
            return self.dispatch_request(*args, **kwargs)

        if cls.decorators:
            view.__name__ = name
            view.__module__ = cls.__module__
            for decorator in cls.decorators:
                view = decorator(view)
        # view.view_class 就是当前 resource 
        view.view_class = cls
        view.__name__ = name
        # 将当前 resource 的 __doc__,__module__,methods 赋给函数的对应的属性
        view.__doc__ = cls.__doc__
        view.__module__ = cls.__module__
        view.methods = cls.methods
        view.provide_automatic_options = cls.provide_automatic_options
        # 返回一个函数,且这个函数里面调用了当前 resource 的 dispatch_request 方法
        return view

因为 HelloWorld 类是继承 Resource 类的,所以在 as_view 中调用的 dispatch_request 方法就会调用到 Resource 中被重写的 dispatch_request 方法

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

Resourcedispatch_request 源码如下:

class Resource(MethodView):
    representations = None
    method_decorators = []

    def dispatch_request(self, *args, **kwargs):
        # 获取当前请求方法,并寻找类中对应的方法
        meth = getattr(self, request.method.lower(), None)
        if meth is None and request.method == 'HEAD':
            meth = getattr(self, 'get', None)
        assert meth is not None, 'Unimplemented method %r' % request.method
        # 获取方法的装饰器
        if isinstance(self.method_decorators, Mapping):
            decorators = self.method_decorators.get(request.method.lower(), [])
        else:
            decorators = self.method_decorators
        # 将所有装饰器装饰到方法上
        for decorator in decorators:
            meth = decorator(meth)
        # 执行请求所对应的方法
        resp = meth(*args, **kwargs)
        # 如果方法对应返回就是一个 Response 对象,那么就直接返回
        if isinstance(resp, ResponseBase):  # There may be a better way to test
            return resp
        # 否则构建返回
        representations = self.representations or OrderedDict()

        #noinspection PyUnresolvedReferences
        mediatype = request.accept_mimetypes.best_match(representations, default=None)
        if mediatype in representations:
            data, code, headers = unpack(resp)
            resp = representations[mediatype](data, code, headers)
            resp.headers['Content-Type'] = mediatype
            return resp

        return resp

具体的方法执行之后,就会回到外层的 self.output 函数,self.output 源码如下:

def output(self, resource):
    # 这里的 resource 是 resource.as_view 返回的函数
    @wraps(resource)
    def wrapper(*args, **kwargs):
        resp = resource(*args, **kwargs)
        if isinstance(resp, ResponseBase):  # There may be a better way to test
            return resp
        data, code, headers = unpack(resp)
        return self.make_response(data, code, headers=headers)
       # output 返回的也是一个函数,准确来说 output 是一个装饰器
    return wrapper

启动流程:

  1. add_resource 调用 _register_view
  2. _register_view 调用 as_view 方法,as_view 返回 view 一个函数。资源类和 view 函数进行绑定。并且 view 中要调用 对应的 dispatch_request 方法
  3. 使用 output 装饰器对上述返回的函数进行装饰,返回一个被装饰的函数
  4. 调用 Flask add_url_rule 方法将上述返回的被装饰函数和路径进行绑定
  5. 执行 Flask 启动流程

调用流程

  1. Flask 请求进行,找到路由对应的函数
  2. 执行 output 函数,调用 view 函数
  3. view 函数中调用对应的 dispatch_request 函数
  4. 执行 Resource 中重写的 dispatch_request 函数,将请求转换为调用类中对应的方法
  5. 执行请求对应的目标方法
  6. 回到 output 方法,调用 make_response 构建返回
  7. 执行 Flask 返回流程