请求头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类型也是符合请求的。
django1.8 默认不支持显示.svg,所以需要做额外设置,在setting.py文件中加入以下代码,即可解决
mimetypes.add_type("image/svg+xml", ".svg", True)
mimetypes.add_type("image/svg+xml", ".svgz", True)
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
解决方法:
XML:
pip install djangorestframework-xml
YAML:
pip install djangorestframework-yaml
修改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, )
先贴两张整个函数调用的流程图
从接收请求入口开始分析
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, )
至此所有代码已经分析完成,整个流程如前面贴的流程图一般