negotiation.py

内容协商

HTTP 为“内容协商”提供了多种机制的规定 - 在有多个可用表示形式时,选择最佳表示形式的过程。

RFC 2616,Fielding 等人。

内容协商是根据客户端或服务器首选项,从多个可能的表示形式中选择一个返回给客户端的过程。

确定接受的渲染器

REST 框架使用简单的内容协商样式,以确定应根据可用渲染器、每个渲染器的优先级和客户端的 Accept: 标头返回哪种媒体类型。所使用的样式部分由客户端驱动,部分由服务器驱动。

  1. 更具体的媒体类型优先于不太具体的媒体类型。
  2. 如果多个媒体类型具有相同的特异性,则根据为给定视图配置的渲染器的顺序进行优先级排序。

例如,给定以下 Accept 标头

application/json; indent=4, application/json, application/yaml, text/html, */*

每个给定媒体类型的优先级将为

  • application/json; indent=4
  • application/jsonapplication/yamltext/html
  • */*

如果请求的视图仅配置了 YAMLHTML 的渲染器,则 REST 框架将选择在 renderer_classes 列表或 DEFAULT_RENDERER_CLASSES 设置中首先列出的渲染器。

有关 HTTP Accept 标头的更多信息,请参阅 RFC 2616


注意:REST 框架在确定首选项时不考虑“q”值。使用“q”值会对缓存产生负面影响,并且在作者看来,它们是一种不必要且过于复杂的内容协商方法。

这是一种有效的方法,因为 HTTP 规范故意没有具体说明服务器应如何权衡基于服务器的首选项和基于客户端的首选项。


自定义内容协商

您不太可能希望为 REST 框架提供自定义内容协商方案,但如果需要,您可以这样做。要实现自定义内容协商方案,请覆盖 BaseContentNegotiation

REST 框架的内容协商类处理为请求选择适当的解析器和为响应选择适当的渲染器,因此您应该同时实现 .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):
        """
        Select the first parser in the `.parser_classes` list.
        """
        return parsers[0]

    def select_renderer(self, request, renderers, format_suffix):
        """
        Select the first renderer in the `.renderer_classes` list.
        """
        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):
    """
    An example view that does not perform content negotiation.
    """
    content_negotiation_class = IgnoreClientContentNegotiation

    def get(self, request, format=None):
        return Response({
            'accepted media type': request.accepted_renderer.media_type
        })