HTTP 提供了几种 “内容协商” 机制,用于在有多个表示形式可用时,选择要返回给客户端的最佳表示形式。
内容协商是基于客户端或服务器的偏好,从多个可能的表示形式中选择一种返回给客户端的过程。
确定接受的渲染器
Django REST framework 使用一种简单的风格来确定应该返回给客户端的媒体类型。这种风格部分基于客户端驱动,部分基于服务器驱动。判断逻辑如下:
- 更具体的媒体类型优先于不那么具体的媒体类型。
- 如果多个媒体类型具有相同的特异性,则根据为给定视图配置的渲染器的顺序来确定优先级。
例如,给定以下 Accept
头:
application/json; indent=4, application/json, application/yaml, text/html, */*
各给定媒体类型的优先级如下:
application/json; indent=4
application/json
、application/yaml
和text/html
*/*
如果请求的视图仅配置了 YAML 和 HTML 的渲染器,那么 Django REST framework 将选择在 renderer_classes
列表或 DEFAULT_RENDERER_CLASSES
设置中排在第一位的渲染器。
关于 HTTP Accept
头的更多信息,请参见 RFC 2616 。
注意 :Django REST framework 在确定优先级时不会考虑 “q” 值。“q” 值的使用会对缓存产生负面影响,并且在作者看来,它们是一种不必要且过于复杂的内容协商方法。
这是一个有效的方法,因为 HTTP 规范故意没有具体规定服务器应如何权衡基于服务器的偏好与基于客户端的偏好。
自定义内容协商
你可能不太需要为 Django REST framework 提供自定义内容协商方案,但如果需要的话,可以通过覆盖 BaseContentNegotiation
来实现。
Django REST framework 的内容协商类处理请求的合适解析器的选择以及响应的合适渲染器的选择,因此你应该实现 .select_parser(request, parsers)
和 .select_renderer(request, renderers, format_suffix)
方法。
select_parser()
方法应从可用解析器列表中返回一个解析器实例,如果列表中的解析器都无法处理传入的请求,则返回 None
。
select_renderer()
方法应返回一个包含(渲染器实例,媒体类型)的二元组,或者引发一个 NotAcceptable
异常。
示例
以下是一个自定义内容协商类的示例,它在选择合适的解析器或渲染器时忽略客户端请求。
from rest_framework.negotiation import BaseContentNegotiation
class IgnoreClientContentNegotiation(BaseContentNegotiation):
def select_parser(self, request, parsers):
"""
选择 `.parser_classes` 列表中的第一个解析器。
"""
return parsers[0]
def select_renderer(self, request, renderers, format_suffix):
"""
选择 `.renderer_classes` 列表中的第一个渲染器。
"""
return (renderers[0], renderers[0].media_type)
设置内容协商
可以使用 DEFAULT_CONTENT_NEGOTIATION_CLASS
设置全局设置默认的内容协商类。例如,以下设置将使用我们的示例 IgnoreClientContentNegotiation
类。
REST_FRAMEWORK = {
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation',
}
也可以使用基于类的视图 APIView
为单个视图或视图集设置所使用的内容协商。
from myapp.negotiation import IgnoreClientContentNegotiation
from rest_framework.response import Response
from rest_framework.views import APIView
class NoNegotiationView(APIView):
"""
一个不执行内容协商的示例视图。
"""
content_negotiation_class = IgnoreClientContentNegotiation
def get(self, request, format=None):
return Response({
'accepted media type': request.accepted_renderer.media_type
})