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
객체를 제공한다.
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/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
헤더를 사용하여 보내는 요청의 형식을 제어할 수 있다.
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": ""
}
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를 훨씬 쉽게 개발하고 사용할 수 있다.