Django restframework解析器组件增加及源码分析

注意:一定要跟着博主的解说再看代码的中文注释及其下面的一行代码!!!

在讲述解析器之前,博主需要Django框架内部中(即没有restframework)前端发出POST请求,后台获取的方式:request.body中一定有值,request.POST不一定有值。因为request.POST有值,需要满足两个条件,我们看下源码,既然没有restframework,那么request对象就是原生的request对象,我们可以使用type函数来获取原生request对象类的位置:from django.core.handlers.wsgi import WSGIRequest。我们看下这个类的POST方法源码及中文注释

class WSGIRequest(HttpRequest):

    def _get_post(self):
        if not hasattr(self, '_post'):
            self._load_post_and_files()
        return self._post

    def _set_post(self, post):
        self._post = post

    # 这里就是我们经常使用的request.POST来获取参数值,这里的request是原生的request
    POST = property(_get_post, _set_post)

之后我们可以点击self._load_post_and_files(),就进入了原生request类中的_load_post_and_files方法,这样就可以看到request,POST为什么没有值,另外还可以发现request.body和request.POST是存在一定的关系的,博主添上原生request的部分代码及中文注释

    def _load_post_and_files(self):
        """Populate self._post and self._files if the content-type is a form type"""
        if self.method != 'POST':
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
            return
        if self._read_started and not hasattr(self, '_body'):
            self._mark_post_parse_error()
            return

        # 这种请求头代表上传文件的
        if self.content_type == 'multipart/form-data':
            if hasattr(self, '_body'):
                # Use already read data
                data = BytesIO(self._body)
            else:
                data = self
            try:
                self._post, self._files = self.parse_file_upload(self.META, data)
            except MultiPartParserError:
                # An error occurred while parsing POST data. Since when
                # formatting the error the request handler might access
                # self.POST, set self._post and self._file to prevent
                # attempts to parse POST data again.
                # Mark that an error occurred. This allows self.__repr__ to
                # be explicit about it instead of simply representing an
                # empty POST
                self._mark_post_parse_error()
                raise
        # 这种请求头会将request.body的数据按照一定格式转化为request.POST
        elif self.content_type == 'application/x-www-form-urlencoded':
            self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
        # 例如application/json这种请求头,就会执行下面的语句
        else:
            self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()
Request._load_post_and_files()

所以request.POST有值的一个条件就是用户的请求头必须是application/x-www-form-urlencoded;还有一个条件是请求的参数格式必须是name=huxiansen&age=18类似这种的。

前端传参数的格式一般分为四种:前两种格式request.POST是有值的,后两种是没有的

①form表单传参

②ajax传参,例如以下这种,默认的请求头就是application/x-www-form-urlencoded,数据格式也会默认转化name=huxiansen&age=18

$.ajax({
  url: ".....",
  type: "POST",
  data: {"name": "huxiansen", "age": 18}
})

③定制请求头ajax传参,例如以下这种,请求头就是application/json,数据格式也会默认转化name=huxiansen&age=18

$.ajax({
    url: ".....",
    type: "POST",
    headers: {"Content-Type": "application/json"},
    data: {"name": "huxiansen", "age": 18}
})

④定制请求参数格式ajax传参,例如以下这种,请求头就是application/json,数据格式{"name":"huxiansen", "age":"18"},后台需要使用json.loads(request.body)

$.ajax({
    url: ".....",
    type: "POST",
    headers: {"Content-Type": "application/json"},
    data: JSON.stringfy({"name": "huxiansen", "age": 18})
})

为简化上面的获取方式,restframework就提供解析器组件,获取数据时调用data即可,如request.data,而且只有编写了这一句代码,解析器才会起到真正的作用。接下来我们还是按照流程来查看解析器组件源代码。

1、从用户请求开始到APIView.dispatch()的这部分流程可以参考其他Django restframework组件源码的介绍。这里直接从dispatch()方法开始,添上代码及中文注释

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        # 注意:这里的self还是请求视图类对象
        self.args = args
        self.kwargs = kwargs
        # 这里是对原生的request加工处理,返回一个新的request对象
        # 封装的内容:原生的request对象,解析器列表,用户认证列表
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            # 初始化(版本控制,用户登录认证,权限验证,访问频率限制)
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                # 通过python的反射机制反射到请求视图类的方法名称
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            # 最后就是执行请求视图类的方法
            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
APIView.dispatch()

2、可以看到解析器的类是被封装到新的Reqeust中,附上Request中有关于解析器组件的代码

class APIView(View):

    # 如果请求视图类中没有这个变量和值,就会使用全局配置文件的值
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        # 封装成新的request对象
        return Request(
            # 原生的request
            request,
            # 解析器类列表
            parsers=self.get_parsers(),
            # 点击查看用户认证的列表是什么
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

    def get_parsers(self):
        """
        Instantiates and returns the list of parsers that this view can use.
        """
        # 返回解析器类对象列表
        return [parser() for parser in self.parser_classes]
Request

3、看到上面这一部分的代码就很容易看出在请求视图类中或者在全局配置文件中需要如何配置了:parser_classes = [解析器类]或者REST_FRAMEWORK = {"DEFAULT_PARSER_CLASSES":["解析器类的全路径"]}

4、解析类的封装已经完成,其他的操作就是请求视图类中使用request.data了,开头也已提及原因了。附上请求视图类的post方法及request.data代码了

class GroupsView(APIView):

    def post(self, request):
        # 从这开始一追究竟式如何解析数据
        print(request.data)
        return Response(data={"code": 200})

5、这个request是新封装的request,博主附上有关于request.data的源代码及中文注释

class Request(object):

    @property
    def data(self):
        if not _hasattr(self, '_full_data'):
            # 先执行下面的这个方法
            self._load_data_and_files()
        return self._full_data

    def _load_data_and_files(self):
        """
        Parses the request content into `self.data`.
        """
        if not _hasattr(self, '_data'):
            # 根据用户的请求头信息判断使用哪一个解析器, 一般files的应该是文件上传
            self._data, self._files = self._parse()
            if self._files:
                self._full_data = self._data.copy()
                self._full_data.update(self._files)
            else:
                self._full_data = self._data

            # if a form media type, copy data & files refs to the underlying
            # http request so that closable objects are handled appropriately.
            if is_form_media_type(self.content_type):
                self._request._post = self.POST
                self._request._files = self.FILES

    def _parse(self):
        """
        Parse the request content, returning a two-tuple of (data, files)

        May raise an `UnsupportedMediaType`, or `ParseError` exception.
        """
        # 用户请求头信息
        media_type = self.content_type
        try:
            stream = self.stream
        except RawPostDataException:
            if not hasattr(self._request, '_post'):
                raise
            # If request.POST has been accessed in middleware, and a method='POST'
            # request was made with 'multipart/form-data', then the request stream
            # will already have been exhausted.
            if self._supports_form_parsing():
                return (self._request.POST, self._request.FILES)
            stream = None

        if stream is None or media_type is None:
            if media_type and is_form_media_type(media_type):
                empty_data = QueryDict('', encoding=self._request._encoding)
            else:
                empty_data = {}
            empty_files = MultiValueDict()
            return (empty_data, empty_files)

        # 根据里面继承父类的子类的方法就可以看出在循环判断用户的请求头信息
        # 等于哪一个解析器的请求头信息
        # 返回值是一个解析器
        parser = self.negotiator.select_parser(self, self.parsers)

        if not parser:
            raise exceptions.UnsupportedMediaType(media_type)

        try:
            # 根据匹配的解析器对象去执行parse方法
            parsed = parser.parse(stream, media_type, self.parser_context)
        except Exception:
            # If we get an exception during parsing, fill in empty data and
            # re-raise.  Ensures we don't simply repeat the error when
            # attempting to render the browsable renderer response, or when
            # logging the request or similar.
            self._data = QueryDict('', encoding=self._request._encoding)
            self._files = MultiValueDict()
            self._full_data = self._data
            raise

        # Parser classes may return the raw data, or a
        # DataAndFiles object.  Unpack the result as required.
        try:
            return (parsed.data, parsed.files)
        except AttributeError:
            empty_files = MultiValueDict()
            return (parsed, empty_files)
Request

6、里面有一行代码比较关键:parser = self.negotiator.select_parser(self, self.parsers),这一行代码方法名称可以知道是去匹配使用哪一个解析器类对象,参数也是解析器类对象列表,可以从继承父类的子类中select_parser()方法可以看出,附上源代码及中文注释

class BaseContentNegotiation(object):
    def select_parser(self, request, parsers):
        raise NotImplementedError('.select_parser() must be implemented')

    def select_renderer(self, request, renderers, format_suffix=None):
        raise NotImplementedError('.select_renderer() must be implemented')


class DefaultContentNegotiation(BaseContentNegotiation):
    settings = api_settings

    def select_parser(self, request, parsers):
        """
        Given a list of parsers and a media type, return the appropriate
        parser to handle the incoming request.
        """
        # 循环解析器类对象列表的元素
        for parser in parsers:
            # 这部分肯定就是判断解析器的请求头信息是否相等于用户的请求头信息
            if media_type_matches(parser.media_type, request.content_type):
                return parser
        return None
negotiation.py

7、第6步中已经是匹配到了一种解析器类对象,然后在第5步中根据得到的解析器类对象去执行下面的parse方法,一般前端传来的参数和请求头的方式的POST请求有两种:一种是JSON传参,一种是Form表单传参。restframework也已经有了这两种解析器,其中还有其他解析器(from rest_framework.parser import BaseParser)

class JSONParser(BaseParser):
    """
    Parses JSON-serialized data.
    """
    media_type = 'application/json'
    renderer_class = renderers.JSONRenderer
    strict = api_settings.STRICT_JSON

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as JSON and returns the resulting data.
        """
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)

        try:
            decoded_stream = codecs.getreader(encoding)(stream)
            parse_constant = json.strict_constant if self.strict else None
            return json.load(decoded_stream, parse_constant=parse_constant)
        except ValueError as exc:
            raise ParseError('JSON parse error - %s' % six.text_type(exc))


class FormParser(BaseParser):
    """
    Parser for form data.
    """
    media_type = 'application/x-www-form-urlencoded'

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as a URL encoded form,
        and returns the resulting QueryDict.
        """
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        data = QueryDict(stream.read(), encoding=encoding)
        return data
parser.py

 

posted @ 2020-06-09 10:30  xsha_h  阅读(219)  评论(0编辑  收藏  举报