Django Rest framework 接收xml,yaml等格式请求

Django Rest framework 接收text/xml,yaml等格式请求

request 请求Accept

请求头accept

accept: image/webp,image/apng,image/*,*/*;q=0.8

accept 表示我当前的请求希望接受什么类型的文件,这是请求首部,当服务器没有客户端想要的资源的媒体类型时,会返回406 Not Acceptable 响应。当然使用了 * / * 表示愿意接受任意类型的资源, q 表示权重,权重在 0-1 之间,默认为 1 。这里前面几个类型都没有标明,则默认都是 1 ( image/webp,image/apng,image/* 权重都为1),表示优先这些类型,最后的 0.8 表示如果都没有,那么任意的类型都行。

返回response

content-type: image/x-icon

所以这里返回image/x-icon类型也是符合请求的。

Django 不显示.svg标图问题

django1.8 默认不支持显示.svg,所以需要做额外设置,在setting.py文件中加入以下代码,即可解决

mimetypes.add_type("image/svg+xml", ".svg", True)
mimetypes.add_type("image/svg+xml", ".svgz", True)

Rest framework 不支持text/xml application/xml类型的请求

rest_framework 框架默认的是以下的设置


'DEFAULT_PARSER_CLASSES':  (    
    'rest_framework.parsers.JSONParser',    
    'rest_framework.parsers.FormParser',    
    'rest_framework.parsers.MultiPartParser'
    )

默认是json格式,即以下这种格式的请求

    Content-Type: application/json

如果请求是 text/xml 获取 application/xml,text/yaml就会出现下面的错误

Unsupported media type "text/xml" in request
或者
不支持请求中的媒体类型 “text/xml

解决方法

  1. 安装djangorestframework-xml 或djangorestframework-yaml
XML: 
pip install djangorestframework-xml
YAML: 
pip install djangorestframework-yaml
  1. 修改parser

    application/xml 类型请求:

from rest_framework_xml.parsers import XMLParser
from rest_framework.views import APIView

class XMLRequestView(APIView):
    parser_classes = (XMLParser, )

​ text/xml类型请求:

from rest_framework_xml.parsers import XMLParser
from rest_framework.views import APIView

# XMLParser 默认media_type='application/xml'
class TextXMLParser(XMLParser):
    media_type = 'text/xml'

class XMLRequestView(APIView):
    parser_classes = (TextXMLParser, )

​ application/yaml类型请求:

from rest_framework_yaml.parsers import YAMLParser
from rest_framework.views import APIView

class YAMLRequestView(APIView):
    parser_classes = (YAMLParser, )

原理分析

流程图

先贴两张整个函数调用的流程图

Django Rest framework 接收xml,yaml等格式请求_第1张图片

Django Rest framework 接收xml,yaml等格式请求_第2张图片

代码分析

从接收请求入口开始分析

url(r'^yamltest', YAMLRequestView.as_view()),

从上面的测试url可以看出,重点需要关注as_view,而YAMLRequestView是继承APIView的。而APIView中点代码并没有包含太多信息,所以需要再追踪上一层View类

View 类中的as_views方法

class View(object):
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in six.iteritems(kwargs):
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        # 本次重点关注方法
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            # dispatch 重点
            return self.dispatch(request, *args, **kwargs)

        # take name and docstring from class
        update_wrapper(view, cls, updated=())

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())
        return view

根据上面as_view的代码,可以看出重点在dispatch的方法实现上。而View类中的dispatch仅仅只实现了method对于方法的调用(如get,post),这就是我们写了get方法,get请求最终对路由到该get方法的代码过程,但并非本次的分析对象,所以需要看APIView中dispatch方法实现

APIView代码 中的 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.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)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                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

request = self.initialize_request(request, *args, **kwargs) 是本次关注的重点,所以继续跟踪

APIView 中的initialize_request

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

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

APIView 中的get_parsers

    def get_parsers(self):
        """
        Instantiates and returns the list of parsers that this view can use.
        """
        return [parser() for parser in self.parser_classes]

再看parser_classes

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    # 这里使用的是默认的parser_classes
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings

api_settings.DEFAULT_PARSER_CLASSES的内容

'DEFAULT_PARSER_CLASSES':  (    
    'rest_framework.parsers.JSONParser',    
    'rest_framework.parsers.FormParser',    
    'rest_framework.parsers.MultiPartParser'
    )

所以支持的parser只有默认的JSONParser,FormParser,MultiPartParser。

initialize_request方法是初始化一个Request类,接着就要看看Request类中parsers是如何被使用的

Request类

class Request(object):
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers_classes(list/tuple). The parsers to use for parsing the
          request content.
        - authentication_classes(list/tuple). The authentications used to try
          authenticating the request's user.
    """

    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

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if (force_user is not None or force_token is not None):
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

到了这一步就不好再跟踪函数一步一步正向的追踪了,需要反向追踪,查看哪些方法使用了parsers

找到的是_parse的方法

Request类中_parse

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

    May raise an `UnsupportedMediaType`, or `ParseError` exception.
    """
    stream = self.stream
    media_type = self.content_type

    if stream is None or media_type is None:
        empty_data = QueryDict('', encoding=self._request._encoding)
        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:
        # 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)

select_parser方法

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

异常

class UnsupportedMediaType(APIException):
    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
    # 这一句就是熟悉的错误了
    default_detail = _('Unsupported media type "{media_type}" in request.')

    def __init__(self, media_type, detail=None):
        if detail is not None:
            self.detail = force_text(detail)
        else:
            self.detail = force_text(self.default_detail).format(
                media_type=media_type
            )

到了这里就就可以不继续往下追踪了,需要往上分析,谁调用了_parse

_load_data_and_files方法中调用了 _parse()

def _load_data_and_files(self):
    """
    Parses the request content into `self.data`.
    """
    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

再继续向上,可以发现有多个地方调用了 _load_data_and_files,选择其中一个data的方法。。

@property
def data(self):
    if not _hasattr(self, '_full_data'):
        self._load_data_and_files()
    return self._full_data

看到这个方法就一切明朗了。到我们在自己实现的方法(例如post)中,会用以下获取请求中的body数据

request.data.get('xxx')

当调用这一句时,就会触发对request,accept的parser校验,对比当前方法是否支持

而当设置了parser_classes时,就能替代默认的parser_classes

class YAMLRequestView(APIView):
    parser_classes = (YAMLParser, )

至此所有代码已经分析完成,整个流程如前面贴的流程图一般

data的方法。。

@property
def data(self):
    if not _hasattr(self, '_full_data'):
        self._load_data_and_files()
    return self._full_data

看到这个方法就一切明朗了。到我们在自己实现的方法(例如post)中,会用以下获取请求中的body数据

request.data.get('xxx')

当调用这一句时,就会触发对request,accept的parser校验,对比当前方法是否支持

而当设置了parser_classes时,就能替代默认的parser_classes

class YAMLRequestView(APIView):
    parser_classes = (YAMLParser, )

至此所有代码已经分析完成,整个流程如前面贴的流程图一般

你可能感兴趣的:(python)