일하는/Cloud, Web

Django REST Framework : (2) Requests and Responses

김논리 2020. 9. 2. 16:38

원문은 Django REST Framework 공식 페이지 🏠 https://www.django-rest-framework.org/tutorial/2-requests-and-responses/ 에서 확인할 수 있습니다.

 

https://ungodly-hour.tistory.com/23

 

Django REST Framework : (1) Serialization

원문은 Django REST Framework 공식 페이지 🏠 https://www.django-rest-framework.org/tutorial/1-serialization/ 에서 확인할 수 있습니다. Introduction 이 튜토리얼에서는 pastebin과 같은 간단한 코드 하이라..

ungodly-hour.tistory.com


이제부터 우리는 Django REST Framework의 핵심을 다루고자 한다. 먼저 몇 가지 핵심 요소에 대해 알아보자.

 

Request objects

DRF는 일반 HttpRequest를 확장하여 보다 유연하게 요청 구문을 파싱 한다. Request 객체의 핵심은 request.data 속성으로, request.POST와 비슷하지만 Web API 작업에 더 적합한 형태이다.

 

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

 

Response objects

DRF는 렌더링되지 않은 객체를 불러와 클라이언트에게 리턴할 콘텐츠 형태로 변환하는 TempalteResponse 타입인 Response 객체도 존재한다.

 

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

 

Status codes

뷰에서 숫자 형태의 HTTP Status code를 사용하는 경우, 읽기에도 어려울 뿐만 아니라 오류가 발생하여도 발견하기 어렵다. DRF에서는 status 모듈의 HTTP_400_BAD_REQUEST와 같은 각 상태 코드에 대한 더 명확한 식별자를 제공한다. 숫자로 된 식별자 대신 이와 같은 식별자를 사용하는 것이 구현할 때도, 디버깅할 때도 더 편리하다.

 

return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

Wrapping API views

DRF는 API view에서 사용할 수 있는 두 가지 Wrapper를 제공한다.

 

  1. @api_view 데코레이터: 함수 기반의 뷰에서 사용할 수 있다.
  2. APIView 클래스: 클래스 기반의 뷰에서 사용할 수 있다.

이 Wrapper들은 뷰에서 수신한 Request에 몇가지 기능을 더하거나, 콘텐츠의 변환이 잘 이루어지도록 Response에 특정 context를 추가한다. 또한 적절한 경우 405 Method Not Allowed 코드를 반환하거나, 잘못된 입력으로 request.data에 접근할 때 발생하는 ParseError 예외 처리와 같은 동작을 제공한다.

 

Pulling it all together

그럼 이제, 이 새로운 요소들을 이용하여 앞서 만든 뷰를 리팩터링 해 보자. 우선 Request, Reponse object를 사용하고 @api_view 데코레이터를 이용하도록 수정한다.

 

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import status                       # status code return을 위한 import
from rest_framework.decorators import api_view          # api_view 데코레이터 impot
from rest_framework.response import Response            # Response object import
 
 
@api_view(['GET', 'POST'])      # api_view 데코레이터를 이용하여 지원하는 메소드를 지정
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)    # Response object를 사용
 
    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)   # Request object를 사용
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)    # 적절한 status code를 리턴
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)  # 적절한 status code를 리턴

 

snippet_list() 메서드가 이전 예제보다 개선된 것을 확인할 수 있다. 조금 간결해진 코드는 Form API와 유사하다. 또한 문자로된 status code를 이용하여 의미를 보다 명확하게 하였다.

 

snippet_datail() 메서드도 추가 수정해 보자.

 

@api_view(['GET', 'PUT', 'DELETE'])     # api_view 데코레이터를 이용하여 지원하는 메소드를 지정
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)   # Response object를 사용하며, 적절한 status code를 리턴
 
    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)    # Response object를 사용
 
    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)    # Response object를 사용
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)  # Response object를 사용하며, 적절한 status code를 리턴
 
    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)  # Response object를 사용하며, 적절한 status code를 리턴

 

이 코드 또한 매우 친숙하게 느껴질 건데, 일반 Django View를 사용하는 것과 크게 다르지 않기 때문이다. 여기서 특정 콘텐츠 유형(JSON, XML 등)에 대한 요청이나 응답을 명시적으로 연결하지 않았음에 주목해야 한다. (JSON Parser가 존재하지 않는다!) request.data는 JSON뿐만 아니라 다른 형식에 대한 처리도 할 수 있기 때문이다. Response object 역시 데이터를 담아 리턴할 뿐, 특정 콘텐츠 유형으로 변환하지 않는다.

 

Adding optional formats suffices to our URLs

이제 Reponse object는 더이상 하나의 콘텐츠 유형으로 고정되지 않는다. 이 장점을 활용하기 위해 API endpoint에 여러 형태의 포맷에 대한 지원을 추가해 보자.

 

포맷의 접미사를 사용하려면, http://example.com/api/items/4.json와 같이 지정된 포맷을 명시적으로 참조하는 URL이 제공되고 API에서 이를 처리할 수 있어야 한다. 우선, format 파라미터를 두가지 뷰에 모두 추가해 보자.

 

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

 

그리고 이제 snippets/urls.py 파일을 조금 수정하여 기존 URL에 format_suffix_patterns라는 패턴을 추가한다.

 

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
 
urlpatterns = [
    path(r'snippets/', views.snippet_list),
    path(r'snippets/<int:pk>', views.snippet_detail),
]
 
urlpatterns = format_suffix_patterns(urlpatterns)

 

이렇게 패턴을 추가한 것만으로도 프로젝트의 URL 파일을 수정하지 않고도 여러 형태의 포맷을 전달받을 수 있다.

 

How's it looking?

이제 Django REST Framework : (1) Serialization 에서와 같이 API를 테스트해 보자. 앞에서 했던 것과 비슷하게 동작하는 것처럼 보이지만, 이번에는 잘못된 요청에 대한 오류 처리도 가능하다.

 

모든 Snippets 리스트를 GET해 보자.

 

$ http GET http://127.0.0.1:8000/snippets/
HTTP/1.1 200 OK
Allow: OPTIONS, POST, GET
Content-Length: 215
Content-Type: application/json
Date: Wed, 02 Sep 2020 07:27:44 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.6.9
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

[
    {
        "code": "foo = \"bar\"",
        "id": 1,
        "language": "python",
        "linenos": false,
        "style": "friendly",
        "title": "test1"
    },
    {
        "code": "printf(\"hello, world!\");",
        "id": 2,
        "language": "c",
        "linenos": false,
        "style": "friendly",
        "title": "test2"
    }
]

 

Accpet 헤더를 사용하여 응답받을 데이터의 콘텐츠 유형도 지정할 수 있다.

 

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

 

또는 형식의 접미사를 추가하여 지정할 수도 있다.

 

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

 

마찬가지로 Content-Type 헤더를 사용하여 보내는 데이터의 포맷도 지정할 수 있다.

 

# 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: Wed, 02 Sep 2020 07:33:47 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.6.9
Vary: Accept, Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

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

 

Browsability

API는 클라이언트의 요청에 따라 Response의 컨텐츠 유형을 선택하므로, 기본적으로 웹 브라우저가 자원을 요청할 때에는 HTML 형식으로 응답하게 된다. 이를 통해 API는 완전하게 웹 브라우징이 가능한 HTML을 반환할 수 있게 되었다. (이전 예제에서는 웹 브라우저 상에서도 JSON 형태의 Response 만 확인할 수 있었음)

 

Browsable API

 

Browsable API는 사용성 면에서 굉장히 유용하며, API를 훨씬 쉽게 개발하고 사용할 수 있도록 도와준다. 또한 API를 사용하려는 다른 개발자들의 진입 장벽 또한 크게 낮춰 줄 수 있다.

 


 

ungodly-hour.tistory.com/26

 

Django REST Framework : (3) Class-based Views

원문은 Django REST Framework 공식 페이지 🏠 www.django-rest-framework.org/tutorial/3-class-based-views/ 에서 확인할 수 있습니다. ungodly-hour.tistory.com/24 Django REST Framework : (2) Requests a..

ungodly-hour.tistory.com