Requests and responses | REST Framework Tutorial

jacoblee19·2021년 1월 25일
0

Django REST Framework

목록 보기
6/9
post-thumbnail

공식 문서를 직접 번역하고 공부한 글입니다!

> Request Objects

REST Framework는 기존의 HttpRequest를 확장하는 Request 객체를 소개하고 더욱 유연한 요청 파싱을 제공한다. Request 객체의 핵심 기능은 request.data 속성이다. request.POST와 비슷하면서도 WEB API를 작업할 때 더 유용한 속성이다.

request.POST # form data만 다루고 POST 메소드에서만 작동한다.
request.data # 모든 데이터를 다룬다. POST, PUT, 그리고 PATCH 메소드에서 작동한다.

> Response Objects

REST Framework Response 객체 또한 소개하는데, Response 객체는 렌더링 되지 않은 컨텐츠를 가지고, 클라이언트에게 정확한 컨텐츠 타입을 결정해서 반환하기 위한 컨텐츠 협상을 사용하는 TemplateResponse의 종류 중 하나이다.

return Response(data) # 클라이언트가 요청한대로 컨텐츠 타입을 렌더링한다.

> Status Codes

숫자로된 HTTP 상태 코드를 view에서 사용하는 것은 가독성이 항상 좋은 것만은 아닐 뿐더러, 사용자가 에러 코드를 잘 못 받았을 때 쉽게 깨닫지 못할 수도 있다. REST Framework는 각각의 상태 코드를 위해 더욱 명백한 식별자를 제공하는데, status모듈 안의 HTTP_400_BAD_REQUEST 같은 것이다. View를 작성하는 내내 숫자 식별자보다는 이런 방식으로 **status 모듈을 사용해 주는 것이 좋다**.

> Wrapping API Views

REST Framework는 API view를 작성할 때 사용할 수 있는 두 가지 wrapper를 제공한다.

  1. 함수 기반의 view에서 사용할 수 있는 @api_view
  2. 클래스 기반의 view에서 사용할 수 있는 APIView

이 두 wrapper는 몇 개의 기능들을 제공하는데, 사용자의 view에서 Request 인스턴스를 확실하게 받을 수 있게 해주는 것과, 컨텐츠 협상을 수행할 수 있게 Response 객체에 컨텍스트를 추가해주는 것이다.

또한, 이 두 wrapper는 행동들을 제공하는데, 적절할 때 405 Method Not Allowed를 반환해주는 것과 잘못된 입력으로 request.data에 접근할 때 발생하는 ParseError를 처리하는 것들이다.

> Pulling It All Together

지금까지 배운 RequestResponse의 구성 요소들로 이 전의 작성했던 view를 리팩토링 해보자.

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):
    """
    List all code snippets, or create a new snippet.
    """
    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)

이전에 작성했던 view보다 많은 발전이 있었음을 확인할 수 있다. 이렇게 리팩토링 함으로써 view가 더 간결해지고 코드가 Forms API를 사용했을 때처럼 비슷하게 바뀌었다. 그리고 숫자가 아닌 이름으로 된 상태 코드를 사용함으로써 응답의 정의가 더 명확해졌다.

snippet_detail view도 아래와 같이 수정할 수 있다.

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    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)

지금까지 작성했던 view는 장고의 기존 view와 많이 다르지 않다.

이제는 더 이상 명백히 request와 response가 주어진 컨텐츠 타입에 얽매이지 않는다.request.data는 들어오는 json요청을 처리할 수 있으며, 다른 포맷들도 처리가 가능하다. 마찬가지로, response 객체들을 데이터와 같이 반환하는데, REST Framework가 응답을 사용자에게 맞는 컨텐츠 타입으로 렌더링하는 것을 허락한다.

> Adding optional format suffixes to our URLs

더 이상 responses들은 하나의 컨텐츠 타입에 고정되어 있지 않다는 점을 활용하기 위해서 API 엔드포인트에 format suffixes를 추가해보자. Format suffixes를 활용한다는 것은 사용자가 주어진 포맷을 가리키는 URLs 얻게되고, http://example.com/api/items/4.json. 같은 URLs를 다룰 수 있게 되는 것을 의미한다.

format 키워드 인자를 두 view에 추가하자.

def snippet_list(request, format=None):

def snippet_detail(request, pk, format=None):

이제 snippets/urls.py를 약간 업데이트 해주어야 한다.
format_suffix_patterns를 urls.py에 추가하자.

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

사실 꼭 별도의 url pattern을 추가 해야하는 것은 아니다. 하지만 그럼으로써, 간결하고 깔끔하게 특정한 포맷을 가리킬 수 있다.

> API 테스트하기

터미널에서 작성한 API 테스트 해보자.

http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print(\"hello, world\")\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

Accept 헤더를 사용함으로써 다시 되돌려 받는 응답의 포맷을 제어할 수 있다.

http http://127.0.0.1:8000/snippets/ Accept:application/json  # Request JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html         # Request HTML

format suffix를 첨가할 수도 있다.

http http://127.0.0.1:8000/snippets.json  # JSON suffix
http http://127.0.0.1:8000/snippets.api   # Browsable API suffix

마찬가지로, Content-Type 헤더를 사용함으로써 사용자가 보낸 요청의 포맷도 제어할 수 있다.

# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"

{
  "id": 3,
  "title": "",
  "code": "print(123)",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"

{
    "id": 4,
    "title": "",
    "code": "print(456)",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}

만약 위 같은 http 요청에서--debug 스위치를 추가한다면, 사용자는 요청 헤더 안에 있는 요청 타입을 볼 수 있게 된다.

> Browsability

API는 사용자의 요청에 근거해서 컨텐츠 타입을 고르기 때문에, 자원이 웹 브라우저로 인해 요청 되었을 때, HTML으로 포맷이 된 자원의 대표를 기본적으로 반환한다. 이 것이 API가 완전한 web-browsable HTML 대표를 반환하도록 허용한다.

Web-browsable API를 갖는다는 것은 사용성면에서 엄청난 이득이고, 개발하는 것, 그리고 사용자의 API를 이용하는 것을 더욱 쉽게 만들어준다. 또한, 다른 개발자들이 어떤 API를 살피고, 사용할 때에 있는 진입 장벽까지도 극적으로 낮추어준다.

profile
Back-end Developer 🙇‍♂️ 💻 🙆‍♂️

0개의 댓글