[DRF] Content negotiation

강민성·2024년 9월 28일

DRF API Guide

목록 보기
20/28

콘텐츠 협상(Content negotiation)

HTTP는 여러 가지 메커니즘을 통해 콘텐츠 협상을 지원합니다. 콘텐츠 협상이란, 여러 표현 방식이 있을 때 주어진 응답에 대해 가장 적합한 표현 방식을 선택하는 과정입니다.
— RFC 2616, Fielding 등.

콘텐츠 협상은 클라이언트 또는 서버의 선호도에 따라 여러 가지 가능한 표현 방식 중 하나를 선택하여 클라이언트에게 반환하는 과정입니다.

허용된 렌더러 결정하기

REST 프레임워크는 간단한 형태의 콘텐츠 협상을 사용하여 클라이언트에 반환할 미디어 타입을 결정합니다. 이는 사용 가능한 렌더러, 각 렌더러의 우선 순위, 클라이언트의 Accept: 헤더를 바탕으로 이루어집니다. 이 방식은 부분적으로는 클라이언트 주도형이며, 부분적으로는 서버 주도형입니다.

  • 보다 구체적인 미디어 타입덜 구체적인 미디어 타입보다 우선됩니다.
  • 동일한 구체성을 가진 미디어 타입이 여러 개일 경우, 해당 뷰에 대해 구성된 렌더러들의 순서에 따라 우선순위를 정합니다.

예를 들어, 다음과 같은 Accept 헤더가 주어진 경우:

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

각 미디어 타입에 대한 우선순위는 다음과 같습니다.

  1. application/json; indent=4
  2. application/json, application/yaml, text/html
  3. */*

만약 요청된 뷰가 YAML과 HTML에 대한 렌더러만으로 구성되어 있다면, 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):
        """
        `.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
        })

Reference

DRF API Guide - Content negotiation

profile
Back-end Junior Developer

0개의 댓글