Django REST Framework
REST API
API(Application Programming Interface)
- 프로그램과 프로그램을 연결시켜주는 매개체
- 프로그램과 통신을 위해서 제공
- 프레임워크 형태로 제공하기도 하고 데이터 형태로 제공하기도 함
- 비슷한 말로 Software Developer Kit 이라고 하기도 함
- Open API 라고 부르면 API를 누구나 사용할 수 있도록 해준 것
CSR(Client Side Rendering)
- API Server를 만들어서 데이터를 제공하도록 만들고 클라이언트에서 이 데이터를 제공받아서 출력하는 방식
- Server Side Rendering은 서버가 처리를 한 후 그 데이터를 가지고 템플릿 엔진을 이용해서 HTML을 만들어서 클라이언트에게 제공하는 방식
- CSR 방식은 클라이언트에서 데이터를 가공해서 화면에 출력을 하기 때문에 데이터를 어떤 형식으로 주고 받을지 결정을 해야 하는데 가장 많이 사용된 방식은 XML과 JSON
- XML(eXtensible Markup Language)
- HTML은 브라우저가 해석하지만 xMl은 개발자나 프레임워크가 해석
- XML은 구조적
- JSON보다 인간이 읽기가 쉽지만 용량이 큼
- 프레임워크의 설정 파일에 많이 이용되어 있는데 클라우드 환경에서는 이 방식보다 YAML을 선호
- JSON(Javascript Object Notation)
- 자바스크립트의 객체 표현 방식을 이용
- XML보다 경량이지만 인간이 읽기가 어려움
- 인간이 읽기가 어렵기 때문에 설정 파일에 사용하기는 어렵고 데이터 전송에 많이 사용함
- 스마트폰 운영체제의 데이터 전송 방식은 전부 JSON
REST(REpresentational State Transfer)
- 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 형식
- 자원을 정의하고 자원에 대한 주소를 지정하는 방법 전반을 일컫는 말로 웹 상의 자료를 HTTP 위에서 SOAP(Simple Object Access Protocol)이나 쿠키를 통한 세션 트래킹같은 별도의 전송 계층없이 전송하기 위한 아주 간단한 인터페이스
- REST원리를 따르는 시스템을 RESTful 하다라고 함
- 6가지 제약조건
- Client - Server 구조
- Stateless(무상태): 클라이언트가 서버에 요청을 보낼 때 이전 요청의 영향을 받지 않아야 함
- Cacheable Data: 서버에서 리소스를 리턴할 때 캐시가 가능한지 아닌지를 명시해야 하는 것인데 HTTP에서 cache-control 이라는 헤더에 리소스의 캐시 여부를 명시할 수 있음
- Layered System: 여러 개의 레이어로 된 서버를 사용할 수 있음, 클라이언트는 이 사실을 알 필요가 없음. 하나의 시스템을 여러 개의 애플리케이션으로 분리해서 구현하기도 하고 인증 서버, 캐싱 서버, 로드 밸런서를 거쳐서 응답을 하기도 함
- A Uniform Interface: 일관성있는 인터페이스를 가져야 함, HTML과 JSON두가지 형태의 데이터를 사용하는 것은 안됨, 동일한 컨텐츠를 사용하는 경우 동일한 URL을 사용하는 것을 권장
- http://www.ex.com/todo: GET방식이면 가져오기, POST 방식이면 삽입 PUT이면 수정 DELETE이면 삭제 이렇게 HTTP 메서드로 작업을 구분
- Code-On-Demand: 선택 사항으로 클라이언트는 서버에 코드를 요청할 수 있고 서버가 리턴한 코드를 실행할 수 있음
Django REST Framework
- REST API를 만들 수 있는 프레임워크
- 설치
pip install djangorestframework
프로젝트 생성
django-admin startproject 프로젝트이름 경로
애플리케이션 생성
- 프로젝트의 manage.py 파일이 있는 디렉터리에서 수행
python manage.py startapp 애플리케이션이름
settings.py 파일 수정
- INSTALLED_APPS 부분에 rest_framework와 자신의 애플리케이션 등록
- DATABASES 부분, TIME_ZONE 수정
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'rest',
'USER': 'root',
'PASSWORD': '0000',
'HOST': '127.0.0.1',
'PORT': ''
}
}
TIME_ZONE = 'Asia/Seoul'
urls.py 파일을 수정해서 example로 시작하는 url에 대한 처리는 apiapp의 urls.py에서 처리하도록 수정
- 프로젝트에 여러 개의 애플리케이션 존재하는 경우 프로젝트에서 모든 요청을 처리하도록 하면 프로젝트의 urls.py 가 너무 복잡해지므로 요청을 처리하는 로직이나 url 설정을 애플리케이션 단위로 분할하는 것이 좋음
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('example/', include('apiapp.urls')),
]
요청을 만들고 JSON을 리턴하는 코드를 작성
- apiapp 디렉터리에 urls.py 파일을 추가하고 작성
from django.urls import path
from .views import helloAPI
urlpatterns = [
path("hello/", helloAPI),
]
- apiapp 디렉터리의 views.py 파일에 helloAPI 함수를 추가
from django.shortcuts import render # 템플릿 엔진을 이용해서 HTML 출력에 사용
from rest_framework.decorators import api_view
from rest_framework.response import Response # json 데이터 생성에 사용
# GET 방식으로 온 요청만 처리
@api_view(['GET'])
def helloAPI(request):
return Response("Hello REST API")
- 관리자용 테이블을 생성
python manage.py makemigrations
python manage.py migrate
- 확인
- 웹서버를 실행:
python manage.py runserver 127.0.0.1:80
- 브라우저에서 확인: http://127.0.0.1/example/hello/
- 웹서버를 실행:
Response
- 응답 결과를 만들기 위한 클래스
- 인스턴스를 생성할 때 첫번째 매개변수가 클라이언트에게 전송될 데이터
- status는 상태 코드
- 주요 상태 코드
- 200: OK
- 201: created - 요청은 처리되어서 새로운 리소스를 생성
- 202: Accepted - 요청은 접수되었지만 처리가 완료되지 않음
- 3xx: 리다이렉션 중
- 400: 잘못된 요청
- 401: 권한이 없음
- 403: 권한 처리 이외의 사유로 리소스에 대한 액세스가 금지
- 404: 지정한 리소스를 찾을 수 없음, 서버에서 처리하는 로직을 찾을 수 없음
- 500: 내부 서버 오류
- 응답 결과를 전송할 때 상태 코드를 같이 전송해서 정상 처리 여부를 알려주는 것이 좋음
Django와 다른점
- 실행 부분에 HTTP의 헤더가 보임
- 템플릿의 형태가 아닌 JSON과 같은 형태의 응답을 제공
- Serializer(직렬화)
- 인스턴스 단위로 파일이나 네트워크를 통해 전송하는 것
- Djangorestframework에서는 Model을 JSON 데이터로 변환하는 작업
Model 생성
- 애플리케이션의 models.py 파일에 Model 클래스로부터 상속받는 클래스 생성
from django.db import models
# Create your models here.
class newclass(models.Model):
bid = models.IntegerField(primary_key=True)
title = models.CharField(max_length=50)
author = models.CharField(max_length=50)
category = models.CharField(max_length=50)
pages = models.IntegerField()
price = models.IntegerField()
published_date = models.DateField()
description = models.TextField()
- 변경 내용 저장
python manage.py makemigrations
python manage.py migrate
모델의 데이터를 json 문자열로 변환해서 출력해주는 Serializer를 생성
- apiapp 디렉터리에 시리얼라이저를 구현할 serializers.py 파일을 생성하고 작성
from rest_framework import serializers
from .models import newclass
class newclassSerializer(serializers.ModelSerializer):
class Meta:
model = newclass
fields = ['bid', 'title', 'author', 'category', 'pages', 'price', 'published_date', 'description']
apiapp 디렉터리에 요청을 처리하는 함수를 작성 - 전체 가져오기, 데이터 삽입, 데이터 1개 가져오기
from rest_framework import status
from rest_framework.generics import get_object_or_404
from .models import newclass
from .serializer import newclassSerializer
# 하나의 함수를 가지고 GET과 POST를 구분해서 처리
@api_view(['GET', 'POST'])
def newclassesAPI(request):
if request.method == 'GET':
# 테이블의 전체 데이터 가져오기
newclasses = newclass.objects.all()
# 가져온 데이터를 JSON 문자열로 변환
serializer = newclassSerializer(newclasses, many=True)
# JSON으로 출력
return Response(serializer.data, status = status.HTTP_200_OK)
elif request.method =='POST':
# 클라이언트에게서 전송된 문자열을 모델로 변환
serializer = newclassSerializer(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'])
def newclassAPI(request, bid):
newc = get_object_or_404(newclass, bid=bid)
serializer = newclassSerializer(newc)
return Response(serializer.data, status=status.HTTP_200_OK)
apiapp 디렉터리의 urls.py 파일에 url과 요청을 처리하는 함수를 연결
from django.urls import path
from .views import helloAPI, newclassAPI, newclassesAPI
urlpatterns = [
path("hello/", helloAPI),
path("fbv/newclasses", newclassesAPI),
path("fbv/newclass/", newclassAPI),
]
브라우저에서 확인
- 127.0.0.1/example/fbv/newclasses 와 fbv/newclass/
로 확인
웹 클라이언트 애플리케이션에서 웹 서버의 데이터 가져오기
- 템플릿 엔진을 이용하는 방식은 서버를 만들 때 사용한 언어를 가지고 클라이언트에게 출력물을 만들어서 제공하는 방식
- 서버의 데이터를 가져와서 출력하는 방식은 서버는 JSON이나 XML 데이터를 만드는 것까지만 하고 클라이언트가 데이터를 파싱해서 출력하는 방식
방법
- 가장 전통적인 방법: ajax(Asynchronous + XML)
- Javascript를 이용해서 XML데이터를 비동기적으로 받아서 처리하는 기술
- 최근에는 XML뿐 아니라 JSON데이터를 가져와도 ajax라고함
- 비동기적으로 동작을 하기 때문에 콜백을 이용해서 처리해야 하고 동작이 수행중이더라도 따른 작업을 수행하는 것이 가능
- 웹 페이지가 사용자가 하고 있는 것을 방해하지 않으면서 서버에서 데이터를 받아서 일부분을 수정하고자 할 때 사용
Fetch API
- ajax와 동일한 기능을 수행하는데 콜백이 아닌 다른 방식으로 요청을 처리
- 전역 fetch 함수를 이용
- axios와 같은 외부 라이브러리 이용
- 서버에서 일방적으롣 데이터를 전송하고자 하는 경우는 Eventsource를 이용한 SSE(Server Side Event)를 이용 메시지 구독과 게시 방식으로 서버가 클라이언트에게 데이터를 전송
- 완전 양방향 통신(채팅을 원하는 경우 Web socket을 사용)
ajax(XMLHttpRequest 인스턴스 이용) 구현
- 작업 순서
- XMLHttpRequest 인스턴스를 생성
- 이벤트별 리스너(콜백 함수)를 등록
- 서버로 보낼 데이터를 생성
- 연결 요청을 준비: open메서드 사용
- 요청을 전송: send 메서드 이용
- 응답을 처리: status를 확인
- 결과를 가지고 다음 작업을 수행: 화면에 출력할 것인지 아니면 다른 페이지로 이동할 지 결정
- 프로젝트의 urls.py 파일에 ajax 요청을 추가
from django.contrib import admin
from django.urls import path, include
from apiapp import views
urlpatterns = [
path('admin/', admin.site.urls),
path('example/', include('apiapp.urls')),
path('ajax/', views.ajax),
]
- apiapp 디렉터리의 views.py 파일에 ajax 함수 추가
def ajax(request):
return render(request, "ajax.html")
- apiapp 디렉터리에 templates 디렉터리 생성
- templates 디렉터리에 ajax.html 작성
- 서버를 다시 실행하고 브라우저에서 127.0.0.1/ajax를 요청해서 브라우저의 대화상자에 문자열이 출력되는지 확인
- JSON Parsing
- JSON은 문자열임
- 프로그래밍 언어에서 사용하기 위해서는 이 문자열을 원하는 인스턴스로 변환을 해야 하는데 이 작업을 Parsing이라고 함
JSON.parse(JSON 문자열)
: 문자열을 자바스크립트 객체로 변환JSON.stringfy(JavaScript 객체)
: 자바스크립트 객체를 JSON 문자열로 변환
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ajax</title>
</head>
<body>
</body>
<script>
// ajax 객체를 생성
let request = new XMLHttpRequest();
// 전체 데이터를 가져오는 요청을 생성
request.open('GET', '../example/fbv/newclasses/');
// 요청 전송
request.send('');
// 응답이 오면 호출될 함수를 등록
request.addEventListener('load', () => {
// alert(request.responseText)
// 데이터 파싱
let data = JSON.parse(request.responseText)
// 데이터 사용
for (let idx in data){
alert(data[idx].bid)
}
})
</script>
</html>
- POST 방식에 데이터를 포함시켜 전송: ajax.html 수정
<script>
// ajax 객체를 생성
let request = new XMLHttpRequest();
// 전체 데이터를 가져오는 요청을 생성
request.open('POST', '../example/fbv/newclasses/');
// 전송할 데이터를 생성
let formdata = new FormData();
formdata.append('bid', 3);
formdata.append('title', '파이썬');
formdata.append('author', 'adsf');
formdata.append('category', 'programm');
formdata.append('pages', 23);
formdata.append('price', 34245);
formdata.append('published_date', '2024-08-30');
formdata.append('description', '설명');
// 데이터를 헤더에 포함시켜서 전송하기
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
let param = ""
for (let pair of formdata.entries()) {
param += pair[0] + '=' + pair[1] + '&'
}
// 요청 전송
request.send(param);
// 응답이 오면 호출될 함수를 등록
request.addEventListener('load', () => {
// alert(request.responseText)
// 데이터 파싱
let data = JSON.parse(request.responseText)
// 데이터 사용
for (let idx in data){
alert(data[idx].bid)
}
})
</script>
</html>
- Fetch API
- 콜백을 이용하는 것이 아니고 then을 이용해서 비동기 요청을 수행
- 대다수의 응답이 JSON으로 오기 때문에 파싱을 별도의 메서드로 처리하지 않고 응답 결과 json 메서드 호출로 수행
<script>
fetch('../example/fbv/newclasses/')
.then(response)
.then((data => {
for(idx in data) {
alert(data[idx])
}
}))
</script>
<script>
const data = {
bid:6,
title: '자바',
author: '고슬링',
category: '프로그래밍',
pages: 245,
price: 38000,
published_date: '2024-08-08',
description: 'OOP'
}
fetch('../example/fbv/newclasses/', {
method:'POST',
headers:{
'Content-Type':'application/json'
},
body:JSON.stringify(data)
})
.then(response)
.then((data => {
for(idx in data) {
alert(data[idx])
}
}))
</script>
- axios 라이브러리 이용
- 코드를 단순화시켜서 비동기 통신을 수행