Skip to content

4. Requests and Responses


1. Request objects

  • REST framework는 일반 HttpRequest를 확장하고 보다 유연한 요청 파싱을 제공하는 Request 객체를 제공한다.
  • Request 객체의 핵심은 request.data 속성으로 request.POST와 유사하지만 웹 API 작업에 더 유용하다.


request.POST # Only handles form data. Only works for 'POST' method.
request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods


2. Response objects

  • REST framework는 또한 렌더링되지 않은 콘텐츠를 가져오고, 클라이언트에게 반환할 올바른 콘텐츠 타입을 결정하는 TemplateResponse 타입인 Response 객체를 제공한다.


return Response(data) # Renders to content type as requested by the client.


3. Status codes

  • REST framework는 status 모듈의 HTTP_400_BAD_REQUEST와 같은 각 상태 코드에 대해 보다 명확한 식별자를 제공한다.
  • 숫자로 식별하는 것보다 글자도 나타나는 이 모듈을 사용하는 것이 좋다.


4. Wrapping API views

  • REST framework는 API 뷰를 작성하는 데 사용할 수 있는 두 개의 래퍼를 제공한다.


1] 함수 기반의 뷰 함수를 위한 @api_view 데코레이터

2] 클래스 기반의 뷰 함수를 위한 APIView 클래스


  • 이러한 래퍼는 뷰에서 요청 인스턴스를 수신하고 올바른 콘텐츠 타입을 결정할 수 있도록 응답 객체에 컨텍스트를 추가하는 것과 같은 몇 가지 기능을 제공한다.
  • 또한 래퍼는 잘못된 입력으로 request.data에 액세스할 때 발생하는 ParseError 예외 처리와 같은 동작을 제공한다.


5. Pulling it all together

  • 위에서 소개한 구성 요소를 사용하여 뷰를 리팩토링해 보자.


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):
    """
    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)


  • 뷰 함수는 이전의 코드보다 좀 더 간결해졌다.


  • 나머지 뷰 함수를 리팩토링해 보자.


snippets/views.py
@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(stauts=status.HTTP_204_NO_CONTENT)


  • 지정된 콘텐츠 타입에 대한 요청이나 응답을 더이상 명시적으로 연결하지 않아도 된다.
  • request.data는 들어오는 json 요청뿐만 아니라 다른 형식(Formats)도 처리할 수 있다.
  • 마찬가지로 데이터가 포함된 응답 객체를 반환하지만 REST framework가 응답을 올바른 콘텐츠 타입으로 렌더링하도록 허용한다.


6. Adding optional format suffixes to our URLs

  • 응답이 더이상 단일 콘텐츠 타입에 고정되어 있지 않다는 것을 활용하기 위해 API 엔드포인트에 형식 접미사(Format Suffixes)에 대한 지원을 추가해 보자.
  • 형식 접미사를 사용하면 지정된 형식을 명시적으로 참조하는 URL이 제공되며, 이는 API가 http://example.com/api/items/4.json과 같은 URL을 처리할 수 있음을 의미한다.


  • 다음과 같이 두 뷰 함수에 형식 키워드 인수를 추가한다.


snippets/views.py
def snippet_list(request, format=None):
...

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


  • 이제 snippets/urls.py 파일에 format_suffix_patterns(urlpatterns)를 추가하여 urlpatterns를 업데이트한다.


snippets/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)


7. How's it looking?

  • 터미널에서 Httpie를 사용하여 API를 테스트해 보자.


http http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
Allow: OPTIONS, POST, GET
Content-Length: 319
Content-Type: application/json
Date: Thu, 16 Dec 2021 09:13:00 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.8
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

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


  • 이전 튜토리얼에서 테스트한 결과과 다르지 않아 보이지만, 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


  • 또는 다음과 같이 형식 접미사를 추가하여 제어할 수 있다.


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)"
HTTP/1.1 201 Created
Allow: OPTIONS, POST, GET
Content-Length: 94
Content-Type: application/json
Date: Thu, 16 Dec 2021 09:19:30 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.8
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

{
    "code": "print(123)",
    "id": 4,
    "language": "python",
    "linenos": false,
    "style": "friendly",
    "title": ""
}
# POST using JSON
$ http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"
HTTP/1.1 201 Created
Allow: OPTIONS, POST, GET
Content-Length: 94
Content-Type: application/json
Date: Thu, 16 Dec 2021 09:20:52 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.8
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

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


  • 만약 위의 http 요청에 --debug 스위치를 추가하면 요청 헤더에서 요청 타입을 볼 수 있다.
  • 이제 웹 브라우저에서 http://127.0.0.1:8000/snippets/에 접속해 보자.


8. Browsability

  • API는 클라이언트 요청을 기반으로 응답의 콘텐츠 타입을 선택하기 때문에 기본적으로 웹 브라우저에서 해당 리소스를 요청할 때 HTML 형식의 리소스 표현을 반환한다.
  • 이를 통해 API 웹 브라우징이 가능한 HTML 표현을 반환할 수 있다.
  • 웹 브라우징 API가 있다는 것은 사용성 측면에서 큰 이점이며 API를 훨씬 쉽게 개발하고 사용할 수 있다.

References