目录
前言
在 Django REST Framework(DRF)中,解析器(Parser)是用于处理传入请求数据的组件。解析器负责将原始请求数据(如 JSON、表单数据、XML 等)解析成 Python 数据结构,以便序列化器(后续章节将会讲解)可以进一步处理这些数据
1. 流程概述
解析器一般用于解析POST请求请求体中的数据,根据content-type不同,客户端传递来的数据格式也不同,因此就需要对其进行处理格式,以便后续序列化器对其操作。
大体的流程为:
- 读取请求头
- 根据请求头解析数据
- 根据请求头获取解析器 -> 如JSON解析器
- request.data = JSON解析器.parse
- 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 请求头来确定响应的内容类型
- 调用initialize_request
- 调用request.data
- 有值直接返回,无值执行_load_data_and_files去解析
- self.data, self.files = self._parse() # 找到合适的解析器并将数据传入解析完成后赋值
- 有值直接返回,无值执行_load_data_and_files去解析
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,]