drf源码_解析器

本文详细解释了在DjangoRestFramework(DRF)中,如何通过解析器处理不同格式的请求数据,包括JSON、表单数据、multipart数据以及文件上传,以及DefaultContentNegotiation的选择过程。
摘要由CSDN通过智能技术生成

1、解析器

使用 request.data 获取请求体中的数据。

这个 reqeust.data 的数据怎么来的呢?其实在drf内部是由解析器,根据请求者传入的数据格式 + 请求头来进行处理。

解析器源码

1、drf 將解析器封装到 Request中

2、將解析器传给 parsers 参数

3、將选择解析器参数DefaultContentNegotiation 实例化对象传给 negotiator,DefaultContentNegotiation.select_parser 作用是根据request的请求头 和 列表中解析器的请

求头匹配选择合适的解析器

4、通过drf中的request.data 获取数据时,触发解析器

from rest_framework.negotiation import DefaultContentNegotiation

class Request:
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None)
        self._request = request
        self.parsers = parsers or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty
        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
	
    @property
    def data(self):
        # 如果已经解析过数据后,再次调用request.data时
        # 就直接拿_full_data,所以数据解析一次就够了,不会重复解析
        if not _hasattr(self, '_full_data'):
            self._load_data_and_files()
        return self._full_data
	
      def _load_data_and_files(self):
        if not _hasattr(self, '_data'):
            # 获取解析后的数据和文件
            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 is_form_media_type(self.content_type):
                self._request._post = self.POST
                self._request._files = self.FILES
	
        def _parse(self):
            media_type = self.content_type
            try:
                stream = self.stream
            except RawPostDataException:
                if not hasattr(self._request, '_post'):
                    raise
                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:
                parsed = parser.parse(stream, media_type, self.parser_context)
            except Exception:
                self._data = QueryDict('', encoding=self._request._encoding)
                self._files = MultiValueDict()
                self._full_data = self._data
                raise
            try:
                return (parsed.data, parsed.files)
            except AttributeError:
                empty_files = MultiValueDict()
                return (parsed, empty_files)


class DefaultContentNegotiation(BaseContentNegotiation):
    settings = api_settings

    def select_parser(self, request, parsers):
        for parser in parsers:
            if media_type_matches(parser.media_type, request.content_type):
                return parser
        return None

class APIView(View):
     parser_classes = api_settings.DEFAULT_PARSER_CLASSES
     
     #content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    content_negotiation_class = DefaultContentNegotiation
    
    def get_parsers(self):
        #实例化解析器对象列表,parser_classes可以在全局配置,
        # 也可以在每个类中自定义
        #例如 parser_classes = [JSONParser, FormParser, MultiPartParser, FileUploadParser]
        return [parser() for parser in self.parser_classes]
	
     def get_parser_context(self, http_request):t.
        return {
            # 实例化视图
            'view': self,
            # 位置参数
            'args': getattr(self, 'args', ()),
            # 关键字参数
            'kwargs': getattr(self, 'kwargs', {})
        }
    
     def get_content_negotiator(self):
        if not getattr(self, '_negotiator', None):
            self._negotiator = self.content_negotiation_class()
        return self._negotiator
    
    def initialize_request(self, request, *args, **kwargs):
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            # DefaultContentNegotiation实例化对象
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

2、解析器类别

2.1 JSONParser (*)

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):
        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
            # 解析来自内存的二进制文件对象,json.loads序列化字典对象
            return json.load(decoded_stream, parse_constant=parse_constant)
        except ValueError as exc:
            raise ParseError('JSON parse error - %s' % str(exc))

2.2 FormParser

# 该解析器用的还是Django的解析器,变成QueryDict类型
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)
        return QueryDict(stream.read(), encoding=encoding)

2.3 MultiPartParser(*)

class MultiPartParser(BaseParser):
    """
    Parser for multipart form data, which may include file data.
    """
    media_type = 'multipart/form-data'

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Parses the incoming bytestream as a multipart encoded form,
        and returns a DataAndFiles object.

        `.data` will be a `QueryDict` containing all the form parameters.
        `.files` will be a `QueryDict` containing all the form files.
        """
        parser_context = parser_context or {}
        request = parser_context['request']
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        meta = request.META.copy()
        meta['CONTENT_TYPE'] = media_type
        upload_handlers = request.upload_handlers

        try:
            parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding)
            data, files = parser.parse()
            return DataAndFiles(data, files)
        except MultiPartParserError as exc:
            raise ParseError('Multipart form parse error - %s' % str(exc))

2.4 FileUploadParser(*)

class FileUploadParser(BaseParser):
    """
    Parser for file upload data.
    """
    media_type = '*/*'
    errors = {
        'unhandled': 'FileUpload parse error - none of upload handlers can handle the stream',
        'no_filename': 'Missing filename. Request should include a Content-Disposition header with a filename parameter.',
    }

    def parse(self, stream, media_type=None, parser_context=None):
        """
        Treats the incoming bytestream as a raw file upload and returns
        a `DataAndFiles` object.

        `.data` will be None (we expect request body to be a file content).
        `.files` will be a `QueryDict` containing one 'file' element.
        """
        parser_context = parser_context or {}
        request = parser_context['request']
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        meta = request.META
        upload_handlers = request.upload_handlers
        filename = self.get_filename(stream, media_type, parser_context)

        if not filename:
            raise ParseError(self.errors['no_filename'])

        # Note that this code is extracted from Django's handling of
        # file uploads in MultiPartParser.
        content_type = meta.get('HTTP_CONTENT_TYPE',
                                meta.get('CONTENT_TYPE', ''))
        try:
            content_length = int(meta.get('HTTP_CONTENT_LENGTH',
                                          meta.get('CONTENT_LENGTH', 0)))
        except (ValueError, TypeError):
            content_length = None

        # See if the handler will want to take care of the parsing.
        for handler in upload_handlers:
            result = handler.handle_raw_input(stream,
                                              meta,
                                              content_length,
                                              None,
                                              encoding)
            if result is not None:
                return DataAndFiles({}, {'file': result[1]})

        # This is the standard case.
        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
        chunk_size = min([2 ** 31 - 4] + possible_sizes)
        chunks = ChunkIter(stream, chunk_size)
        counters = [0] * len(upload_handlers)

        for index, handler in enumerate(upload_handlers):
            try:
                handler.new_file(None, filename, content_type,
                                 content_length, encoding)
            except StopFutureHandlers:
                upload_handlers = upload_handlers[:index + 1]
                break

        for chunk in chunks:
            for index, handler in enumerate(upload_handlers):
                chunk_length = len(chunk)
                chunk = handler.receive_data_chunk(chunk, counters[index])
                counters[index] += chunk_length
                if chunk is None:
                    break

        for index, handler in enumerate(upload_handlers):
            file_obj = handler.file_complete(counters[index])
            if file_obj is not None:
                return DataAndFiles({}, {'file': file_obj})

        raise ParseError(self.errors['unhandled'])

    def get_filename(self, stream, media_type, parser_context):
        """
        Detects the uploaded file name. First searches a 'filename' url kwarg.
        Then tries to parse Content-Disposition header.
        """
        try:
            return parser_context['kwargs']['filename']
        except KeyError:
            pass

        try:
            meta = parser_context['request'].META
            disposition, params = parse_header_parameters(meta['HTTP_CONTENT_DISPOSITION'])
            if 'filename*' in params:
                return params['filename*']
            else:
                return params['filename']
        except (AttributeError, KeyError, ValueError):
            pass

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值