DRF之解析器

前言

在 Django REST Framework(DRF)中,解析器(Parser)是用于处理传入请求数据的组件。解析器负责将原始请求数据(如 JSON、表单数据、XML 等)解析成 Python 数据结构,以便序列化器(后续章节将会讲解)可以进一步处理这些数据

1. 流程概述

解析器一般用于解析POST请求请求体中的数据,根据content-type不同,客户端传递来的数据格式也不同,因此就需要对其进行处理格式,以便后续序列化器对其操作。

大体的流程为:

  1. 读取请求头
  2. 根据请求头解析数据
    • 根据请求头获取解析器 -> 如JSON解析器
    • request.data = JSON解析器.parse
  3. request.data获取处理后的数据
class Form 解析器

	content-type:"urlencode"

class JSON 解析器

	content-type:"application/json"
	
    def parse():
    	...
        
请求者:
	GET
    	http://127.0.0.1:8000/api/home/?xxx=123&abc=xxx
        请求头
    POST
    	http://127.0.0.1:8000/api/home/?xxx=123&abc=xxx   -->  request.query_params
        请求头
        	content-type:"urlencode..."
    		content-type:"application/json"
        请求体
        	name=zhangs&age=18
            {"name":"zhangs","age":19}

2. 常见应用

2.1 JSONParser

解析JSON格式的请求数据,返回python中的字典类型
在这里插入图片描述

2.2 FormParser

解析表单数据为QueryDict类型数据
在这里插入图片描述

2.3 MultiPartParser

解析多部分请求数据,既可以解析文件,又可以解析普通数据,通常用于文件上传

在这里插入图片描述

2.4 FileUploadParser

只能解析文件数据
在这里插入图片描述

3. 源码流程

dispatch()的流程走完仅仅实现parsers,negotiator,parser_context等值的封装,真正的解析发生在调用request.data时

  • 加载时触发dispatch()方法
    • 调用initialize_request
      • parser_context = self.get_parser_context(request) # {视图对象,URL路由参数}
      • parsers=self.get_parsers()# 解析器 [JSONParser(),FormParser(),]
      • negotiator=self.get_content_negotiator()# DefaultContentNegotiation()
      • parser_context=parser_context
      • 传入parsers,negotiator,parser_context初始化Request # parser_context={视图对象,URL路由参数 + drf的request,encoding}
    • 调用initial
      • perform_content_negotiation根据客户端的 Accept 请求头来确定响应的内容类型
  • 调用request.data
    • 有值直接返回,无值执行_load_data_and_files去解析
      • self.data, self.files = self._parse() # 找到合适的解析器并将数据传入解析完成后赋值
class DefaultContentNegotiation(BaseContentNegotiation):
    # 根据请求头类型与解析器内定义的media_type匹配出对应的解析器
    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 FormParser(BaseParser):
    media_type = 'application/x-www-form-urlencoded'

    def parse(self, stream, media_type=None, parser_context=None):
        parser_context = parser_context or {}
        encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
        return QueryDict(stream.read(), encoding=encoding)


class JSONParser(BaseParser):
    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
            return json.load(decoded_stream, parse_constant=parse_constant)
        except ValueError as exc:
            raise ParseError('JSON parse error - %s' % str(exc))


class Request:
    def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None):
        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators 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):
        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

    @property
    def content_type(self):
        meta = self._request.META
        return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE', ''))

    def _parse(self):
        # 获取请求头中的content_type
        media_type = self.content_type
        # 请求发送过来的原始数据
        stream = self.stream
        # DefaultContentNegotiation.select_parser()
        # 循环解析器列表,找到content-type符合的解析器并返回
        parser = self.negotiator.select_parser(self, self.parsers)  # request [JSONParserm,FormParser]
        # 执行解析器的parse方法
        parsed = parser.parse(stream, media_type, self.parser_context)
        try:
            return (parsed.data, parsed.files)
        except AttributeError:
            empty_files = MultiValueDict()
            return (parsed, empty_files)


class APIView(View):
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS

    def get_parser_context(self, http_request):
        return {
            'view': self,
            'args': getattr(self, 'args', ()),
            'kwargs': getattr(self, 'kwargs', {})
        }

    def get_parsers(self):
        return [parser() for parser in self.parser_classes]

    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):
        # {视图对象,URL路由参数}
        parser_context = self.get_parser_context(request)

        return Request(
            request,  # django的request
            parsers=self.get_parsers(),  # 解析器 [JSONParser(),FormParser(),]
            authenticators=self.get_authenticators(),  # 认证组件 [认证对象1,认证对象2]
            negotiator=self.get_content_negotiator(),  # DefaultContentNegotiation()
            parser_context=parser_context  # {视图对象,URL路由参数 + drf的request,encoding}
        )

    def dispatch(self, request, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)
            handler = getattr(self, request.method.lower(), 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

    def initial(self, request, *args, **kwargs):
        neg = self.perform_content_negotiation(request)
        # 第一个元素是被选择的渲染器实例,第二个元素是对应的媒体类型字符串
        request.accepted_renderer, request.accepted_media_type = neg

        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
	# 该方法负责根据客户端的 Accept 请求头来确定响应的内容类型。这个方法会检查客户端接受哪些媒体类型,并根据服务器能够提供的媒体类型来选择一个合适的渲染器(Renderer)
    def perform_content_negotiation(self, request, force=False):
        renderers = self.get_renderers()
        conneg = self.get_content_negotiator()

        try:
            return conneg.select_renderer(request, renderers, self.format_kwarg)
        except Exception:
            if force:
                return (renderers[0], renderers[0].media_type)
            raise

    def get_renderers(self):
        return [renderer() for renderer in self.renderer_classes]

    def get_content_negotiator(self):
        if not getattr(self, '_negotiator', None):
            self._negotiator = self.content_negotiation_class()
        return self._negotiator


class HomeView(APIView):
    # 所有的解析器
    parser_classes = [JSONParser, FormParser]
    # 根据请求,匹配对应的解析器
    content_negotiation = DefaultContentNegotiation

    def post(self, request, *args, **kwargs):
        print(request.data, type(request.data))
        return Response("请求来了")

4. 解析器全局配置

默认解析器有JSONParser,FormParser,MutiPartParser三种
在这里插入图片描述
但为了防止不能上传图片等功能点因为默认解析器可以上传图片,导致request.data的类型的不确定性,建议局部配置
修改全局配置

REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': None,
    'VERSION_PARAM': 'version',
    'DEFAULT_VERSION': "v1",
    'ALLOWED_VERSIONS': ["v1", "v2"],
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning',
    'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser', ]  # 设置默认解析器只有JSONParser
}

后续如果有上传图片的功能点,再局部配置其解析器即可

parser_classes = [MutiPartParser,]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值