안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

장고 (Django)

[Django] REST framework - ② Requests and Responses

피그브라더 2020. 6. 26. 22:41

본 포스팅은 아래 링크의 내용을 나름대로 정리한 글이다.

https://www.django-rest-framework.org/tutorial/2-requests-and-responses/

 

2 - Requests and responses - Django REST framework

From this point we're going to really start covering the core of REST framework. Let's introduce a couple of essential building blocks. REST framework introduces a Request object that extends the regular HttpRequest, and provides more flexible request pars

www.django-rest-framework.org

 

1. Request 객체

DRF에서는 HTTP 요청을 나타내는 객체로서 HttpRequest 객체를 확장한 Request 객체를 사용한다. 이는 기존의 HttpRequest 객체보다 요청 내용을 유연하게 파싱 할 수 있도록 돕는다. Request 객체의 가장 핵심은 바로 request.data 속성이다. request.data는 request.POST와 유사하지만, 웹 API로 동작하는 데 있어서는 훨씬 더 유용하다는 특징을 가지고 있다.

 

  • request.POST  # 폼 데이터만 처리할 수 있고, POST 메소드에서만 동작한다.
  • request.data  # 임의의 데이터를 처리할 수 있고, POST, PUT, PATCH 메소드에서 동작한다.

 

2. Response 객체

또한 DRF는 응답을 나타내는 객체로서 Response 객체를 사용한다. 이는 TemplateResponse 객체의 상위 타입으로, 렌더링 되지 않은 내용을 읽어서 클라이언트가 요청한 콘텐트 타입에 맞는 형식으로 자동 렌더링 해준다.

 

  • return Response(data)  # 클라이언트가 요청한 콘텐트 타입으로 자동 렌더링 해준다.

 

3. 응답 상태 코드

뷰에서 응답 상태 코드를 숫자로 적는 것은 가독성이 떨어지며, 코딩 실수하더라도 알아차리기 쉽지 않다. 그래서 DRF는 각 응답 상태 코드에 대한 명시적인 기호들을 제공한다. 예를 들어 HTTP_400_BAD_REQUEST는 응답 상태 코드 400을 나타낸다. 이것은 숫자로 적는 것보다 훨씬 더 가독성이 높고 코딩에서의 실수를 줄일 수 있게 해준다.

 

4. REST API 뷰 정의를 위한 DRF의 두 가지 Wrapper

DRF는 간편하게 REST API 뷰를 정의할 수 있도록 다음과 같은 두 가지의 Wrapper를 제공한다.

 

  • @api_view 데코레이터 : 함수 기반의 REST API 뷰를 정의할 때 사용
  • APIView 클래스 : 클래스 기반의 REST API 뷰를 정의할 때 사용

 

이러한 Wrapper들은 해당 뷰가 HTTP 요청을 나타내는 객체로서 앞서 소개한 Request 객체를 사용하도록 만들며, 클라이언트에서 요청한 콘텐트 타입으로 응답을 렌더링 해줄 수 있도록 Response 객체에 특정 context를 가미해주는 역할을 수행한다. 또한 이렇게 정의된 뷰의 경우 적절치 못한 메소드의 요청에 대해 "405 Method Not Allowed" 응답을 반환하거나 잘못된 형식의 입력을 가진 request.data에 접근하려 할 때 발생하는 ParseError 예외를 자동으로 처리해주는 등의 예외 처리도 어느 정도 이미 구현되어 있다는 특징이 있다.

 

5. REST API 뷰 정의하기 (@api_view 데코레이터 이용)

그러면 @api_view 데코레이터를 이용하여 앞서 정의했던 뷰들을 리팩토링 해보자. 코드가 더욱 간결해지고, 명시적인 응답 상태 코드를 사용함으로써 응답의 의미가 더욱 명확해지는 것을 볼 수 있다.

 

▼ snippets/views.py

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

 

위 코드에서 주목할 점은, 요청이나 응답을 다룰 때 콘텐트 타입을 전혀 신경 쓰지 않아도 된다는 것이다. 먼저, request.data는 요청으로 전송되는 데이터를 콘텐트 타입에 맞게 자동으로 처리하고 변환해준다. 예를 들어 위 코드에서는 요청으로 전송되는 JSON 데이터를 자동으로 파싱 해주었다는 것을 알 수 있다. 또한, Response 객체로 전달되는 데이터를 클라이언트가 요청한 콘텐트 타입에 맞게 자동으로 렌더링 해준다. 이는 @api_view 데코레이터가 Response 객체에 적절한 context를 가미하여 처리해준 결과이다.

 

6. 요청 및 응답의 콘텐트 타입 지정해보기

그렇다면 DRF가 정말로 요청과 응답의 콘텐트 타입을 알아서 잘 처리해주는지 직접 확인해보자. 여기서도 앞선 포스팅에서 했던 것과 동일한 방식으로 httpie 클라이언트를 이용하여 우리 서버에 HTTP 요청을 보내볼 것이다. 어느 정도의 예외 처리가 이미 구현되어 있다는 것 빼고는 앞선 포스팅 때와 상황이 거의 비슷하다.

 

먼저, HTTP 요청의 Accept 헤더를 지정하여 받고자 하는 응답의 콘텐트 타입을 지정해보자. JSON 형식을 지정하는 경우, 요청한 DB 인스턴스(들)의 정보가 JSON 형태의 응답으로 날아올 것이다. 반면에 HTML 형식을 지정하는 경우, 웹 브라우저로 접속했을 때 보이는 페이지에 해당하는 HTML 코드가 응답으로 날아올 것이다.

 

  • http http://127.0.0.1:8000/snippets/ Accept: application/json  # 응답 콘텐트 타입으로 JSON 형식을 지정
  • http http://127.0.0.1:8000/snippets/ Accept: text/html  # 응답 콘텐트 타입으로 HTML 형식을 지정

 

다음으로, HTTP 요청의 Content-Type 헤더를 지정하여 전송하는 데이터의 콘텐트 타입을 지정해보자. 어떻게 지정하든 request.data는 전송된 데이터를 적절히 처리하여 변환해주기 때문에 동일한 응답이 날아올 것이다.

 

  • http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"  # 폼 데이터로 POST 요청
  • http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"  # JSON 데이터로 POST 요청

 

7. Browsability (탐색 가능성)

DRF는 클라이언트가 요청한 콘텐트 타입에 맞게 데이터를 적절한 응답으로 렌더링 한다. 따라서 웹 브라우저를 통해서 해당 REST API에 요청을 보내는 경우(응답의 콘텐트 타입으로 HTML 형식을 지정) HTML 형식의 응답이 날아올 것이다. 따라서 이러한 원리를 응용하면 REST API가 Web-browsable HTML 페이지를 반환하도록 할 수 있게 된다. 여기서 말하는 Web-browsable HTML 페이지란, 해당 REST API 요청의 결과를 인간이 보기에 직관적인 모습으로 보여주고, 동시에 똑같은 URL에 대해 또 다른 메소드를 이용한 REST API 요청을 간편하게 보낼 수 있도록 인터페이스를 제공하는 페이지를 말한다. 이러한 Web-browsable HTML 페이지를 갖춘 REST API는 상당한 이점이 있다. 개발 과정에서 해당 API의 기능을 확인할 때 상당히 편리해지고, 더불어서 다른 개발자가 해당 API를 처음 사용하고자 할 때 진입 장벽을 상당히 낮춰줄 수 있기 때문이다. DRF의 Wrapper를 활용하여 정의한 REST API 뷰들은 기본적으로 Web-browsable HTML 페이지가 이미 구현되어 있기 때문에 새로 개발한 REST API의 기능을 웹 브라우저를 통해 확인하는 것이 상당히 수월할 것이다.

 

▼ Web-browsable HTML 페이지 예시

 

8. 마무리의 말

이번 포스팅에서는 DRF가 제공하는 @api_view 데코레이터를 활용하여 함수 기반의 REST API 뷰를 정의해 보았다. 다음 포스팅에서는 DRF가 제공하는 여러 클래스들을 활용하여 클래스 기반의 REST API 뷰들을 정의해 보도록 하자. 우리가 정의한 뷰들이 훨씬 더 간결해지는 모습을 볼 수 있을 것이다.