수업 22일차

galoo·2023년 7월 26일
0

HelloVision Dx Data School

목록 보기
22/72
post-thumbnail

✔ Django REST API

API(Application Programming Interface)

  • 프로그램과 프로그램을 연결시켜주는 매개체
  • 데이터의 형태로 제공될 수도 있고, 함수나 클래스 또는 패키지, 프레임워크 형태로 제공될 수 도 있습니다.

OPEN API

  • API를 누구나 사용할 수 있도록 공개한 것

CSR(Client Side Rendering)

  • API Server는 화면 구성은 하지 않고 데이터만 제공하며, 클라이언트 프로그램에서 데이터를 받아 화면 출력을 하게 됩니다.
    - 이런 구조를 CSR이라고 합니다.
  • API Server구축을 할 때, Restful하게 구성하는 것을 권장

REST

  • REpresentational State Transfer의 약자
  • 분산 하이퍼미디어 시스템을 위한 소프트웨어 아키텍처의 종류

원칙

  • 인터페이스 일관성
    - 어떤 기기에서 접근을 하던, 동일한 작업은 동일한 URL로 처리
  • 무상태(Stateless)
    - 클라이언트의 context(정보)를 서버에 저장하지 않아야 한다.
    - 아니면 동의를 얻어라
  • 캐싱 기능 활용
  • 계층화
  • Code on Demand
    - 기능을 확장할 수 있도록 생성
  • 클라이언트와 서버 분리

Client <-> 연결 서버 <-> Middle Ware <-> API Server <-> Data Server


Middle Ware : Security, Load Balancing(트래픽 분산) 등
API Server : 일을 하고 결과를 생성

  • 역할 분리 시, 실제 일하는 애들이 피해를 덜 입음

Client가 일하고 있는 server에 직접 접근을 하게 하지 말아라

✔ Rest API Server 프로젝트 생성 및 설정

프로젝트 디렉토리 생성

필요한 패키지 설치

django
djnagorestframework
mysqlclient

django 프로젝트 생성

  • django-admin startproject apiserverproejct

django 애플리케이션 생성

  • python manage.py startapp apiserverapplication

settings.py에서 필요한 내용 수정

  • INSTALLED_APPS 에 생성한 application 과 rest_framework 등록
  • DATABASES 등록 (mysql을 사용했어요)
  • TIME_ZONE Aisa/Seoul 설정

프로젝트 실행 및 브라우저 접속

프로젝트에 사용할 테이블 생성 및 DB 연결 확인

models.py 파일에 필요한 모델을 생성

models.py 의 변경 내용을 db에 적용

  • python manage.py makemigrations
  • python manage.py migrate

변경 내용이 적용되었는지 확인

  • use db 이름
  • show tables
  • desc 테이블이름(애플리케이션이름_model이름)
    - mysql 대소문자 구분함
    - 모델 이름은 소문자로 만든다.

애플리케이션-models.py

 class 클래스이름(models.Model<-상속):
 	컬럼이름=models.자료형(옵션)
_____________________________________________________
class Book(models.Model):
    bid = models.IntegerField(primary_key=True)
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=100)
    category = models.CharField(max_length=100)
    pages = models.IntegerField()
    price = models.IntegerField()
    published_date = models.DateField()
    #시간까지 두고싶다면 Datatime이라 하자
    description = models.TextField()

makemigrations, migrate 하고 dbeaver에서 desc 앱네임_클래스명; 해보자.

Serializable - 직렬화

  • 프로그래밍 언어에서 만든 객체를 전송하기 위한 작업

client에서 받은 데이터를 python 객체로 변환해주고, python 객체를 client에게 전송하기 위한 JSON 문자열로 변환해주는 역할을 수행

  • 이 작업을 수행하면 Response로 리턴을 해야 하고, 안했다면 JsonResponse로 리턴하면 됩니다.

Application에 serializers.py 파일을 만들고 작성

from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model=Book
        fields=['bid', 'title', 'author', 'category',
                'pages', 'price', 'published_date', 'description']

Sample URL 처리

urls.py 파일에 가서 url을 처리할 함수나 클래스와 url을 연결

  • 프로젝트의 urls.py에 직접 연결을 시키게 된다면 애플리케이션이 여러 개 일 때, 코드가 길어지고 가독성이 떨어진다.
    - 각 애플리케이션이 URL을 처리하도록 설정하는 것이 좋다.
from django.contrib import admin
from django.urls import path, include #include 써주기
urlpatterns = [
    path("admin/", admin.site.urls),
    # example로 시작하는 url은 해당 애플리케이션.urls.py파일에서 처리
    path("example/", include("apiserverapplication.urls"))
]

애플리케이션에 urls.py파일을 추가하고, url과 처리할 함수나 클래스를 연결

from django.urls import path
from .views import helloAPI #views.py에서 만든다.
urlpatterns = [
    #/example/hello/ 요청이 오면 helloAPI 함수가 처리
    path("hello/", helloAPI)
]

애플리케이션에 views.py에 helloAPI 함수 작성

from rest_framework.response import Response
from rest_framework.decorators import api_view
@api_view(['GET']) #요청 처리 방식 선택
def helloAPI(request):
    return Response("HELLO REST API")

서버 재실행하고 확인하기

  • 현재 설정한 것으로 하면 메인은 안뜬다.
  • http://127.0.0.1:8000/example/hello/ 로 들어가야 한다.
  • 일반적인 html 파일이 아닙니다.

Response 클래스

  • 응답 결과를 위한 클래스
  • 첫 번째 매개변수는 데이터, status는 상태코드
    - 첫 번째 매개변수만을 이용하면 성공한 경우에는 문제가 없지만, 실패를 했을 때, 어떤 이유로 실패했는지 알 수 없습니다.

상태 코드

  • 200 번대 : 정상 응답
  • 300 번대 : Redirect 중
  • 400 번대 : Client 오류
  • 500 번대 : Server 오류
    - 가용성 문제로 500번대 오류가 생길 수 있음
    - 하지만 대부분 잘못 만든거임

403 : forbidden(권한 부족)
404 : url이 잘못됨

forwarding ? Redirect ?

  • 포워딩 : 이전 작업 기억
  • redirect : 이전 작업 기억 x
  • 이를 설정하는 것은 매우 중요합니다.
  • CQRS (조회, 조회x 작업으로 나눔)
    - 조회 : forwarding
    - 조회 x : redirect

데이터 삽입 (POST 방식으로 처리합니다.)

처리할 함수를 생성 - views.py

from .serializers import BookSerializer
from rest_framework import status
@api_view(['POST'])
def booksAPI(request):
    # 클라이언트가 전송한 데이터를 
    # Model이 사용할 수 있는 데이터로 변환
    serializer=BookSerializer(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)

url과 함수를 연결 - urls.py

from django.urls import path
from .views import helloAPI, booksAPI #views.py에서 만든다.
urlpatterns = [
    #/example/hello/ 요청이 오면 helloAPI 함수가 처리
    path("hello/", helloAPI),
    #/example/fbv/books 요청이 오면 booksAPI 함수가 처리
    path("fbv/books", booksAPI)
]

  • 이거 글자 틀리면 모델이 받을 수 없다.

select * from apiserverapplication_book;


  • 잘 들어간 것을 확인했다. (한글도 안깨지고 잘 들어갔다.)
  • 실행 한 뒤, 브라우저에 localhost:8000/example/fbv/books를 입력해보자.
    - 화면의 box에 데이터 입력하고 post를 눌러 데이터 전송이 되는지 확인
    - 에러가 발생하면, urls의 url과 views의 함수 이름을 먼저 확인하고는 models와 serializers에 작성한 클래스 필드 이름을 확인하고 브라우저에서 입력한 것과 같은지 확인
    - models에 작성한 모델과 serializers에 입력한 내용은 처음 한 번만 확인하면 된다.

전체 데이터 가져오기 구현 - GET

views.py 파일의 booksAPI함수만 수정한다.

from .serializers import BookSerializer
from rest_framework import status
from .models import Book
@api_view(['POST', 'GET']) #post, get 둘다 처리할 수 있는데, 
#구분은 해놔야 할것 같아.
def booksAPI(request):
    #전송 방식을 확인하는 방법은 request.method를 확인하면 됩니다.
    if request.method=='GET':
        # 전체 데이터 가져오기
        books=Book.objects.all()
        serializer=BookSerializer(books,many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    elif request.method=='POST': #post일때
        # 클라이언트가 전송한 데이터를
        # Model이 사용할 수 있는 데이터로 변환
        # print("1") # 이 코드가 실행 안된다면url과 method 연결 실수
        serializer = BookSerializer(data=request.data)
        # print("2") # 이 코드가 실패했다면, serializable 실패
        # 유효성 검사
        if serializer.is_valid():
            # print("3") # 이 코드가 안된다면, 이름이 잘못된 것이다.
            serializer.save()  # 데이터 저장
            # 성공했을 때 전송한 데이터를 다시 전송
            return Response(serializer.data,
                            status=status.HTTP_201_CREATED)
        # 실패했을 때 처리
        return Response(serializer.errors, 
        status=status.HTTP_400_BAD_REQUEST)
        # return은 안써도 되는거야?

예전에는 요청마다 url을 다르게 했지만, 요즘에는 get, post, put, delete로 구분

데이터 1개 가져오기

  • 기본키 값을 매개변수로 받아야 합니다.
  • 매개변수를 1개 받을 때는 직접 입력이 아닌, url 뒤에 붙여서 전송합니다.

urls.py에 url을 처리할 수 있는 함수를 등록

from django.urls import path
from .views import helloAPI, booksAPI,bookAPI #views.py에서 만든다.
urlpatterns = [
    #/example/hello/ 요청이 오면 helloAPI 함수가 처리
    path("hello/", helloAPI),
    #/example/fbv/books 요청이 오면 booksAPI 함수가 처리
    path("fbv/books", booksAPI),
    #이번엔 bid 1개마다 데이터 조회하기
    path("fbv/books/<int:bid>/",bookAPI)
]

views.py에 bookAPI 함수를 추가

@api_view(['GET','PUT'])
def bookAPI(request, bid): #bid가 url에 포함되어있다.
    #기본키를 가지고 데이터 1개를 가져오는 것입니다.
    try:
        books = Book.objects.get(bid=bid)
    except Book.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)
    if request.method=='GET':
        serializer=BookSerializer(books)
        return Response(serializer.data, status=status.HTTP_200_OK)
    elif request.method=='PUT':
        serializer = BookSerializer(books, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors,
        status=status.HTTP_400_BAD_REQUEST)
    return Response(status=status.HTTP_404_NOT_FOUND)

강사님이 해주신 GET 하나만 한 version

#기본키를 가지고 데이터를 찾아오고 없다면 404error 발생을 하겠다.
from rest_framework.generics import get_object_or_404
@api_view(['GET'])
def bookAPI(request, bid):
  book = get_object_or_404(Book, bid=bid)
  serializer = BookSerializer(book)
  return Response(serializer.data, status=status.HTTP_200_OK)

✔ Web Client 에서 Server에 데이터 요청

ajax(Asynchronous Javascript XML)

  • 비동기적으로 Server에게 데이터를 요청하는 것
  • 한 번 요청하고 응답이 오면 연결이 해제됩니다.
  • 헤더의 크기가 Web Socket 보다 크다.

Web Socket

  • TCP 연결을 이용해서 Web Server와 데이터를 주고 받을 수 있는 HTML5 API
  • 연결 한 후 명시적으로 close()를 호출하지 않으면 연결이 유지됨
  • 짧은 메시지를 자주 전송하는 경우네는 Web Socket이 ajax보다 효율적
    - EX) 채팅, 줌(화상 채팅 등..)

SSE(Server Sent Event - Web Push)

  • 서버에게 구독 신청을 하면, 서버가 데이터를 일방적으로 보내주는 방식
  • 한 번 구독 신청을 하면, 이후에는 데이터 요청을 하지 않아도 받을수 있음
  • 알림을 받는 방식입니다.

Mobile Push

  • 스마트 폰 애플리케이션에서 알림을 받고자 할 때, 아래 2개 중 하나를 학습

APNS(Apple Push NOtification Service)

  • 아이폰 알림

FCM(Firebase Cloud MEassaging Service)

  • 안드로이드 알림

비동기 통신

구현 방법

  • ajax 이용
    - XMLHttpRequest 이용
  • promise 이용
  • Fetch API 이용
    - 가장 권장합니다.
  • axios 같은 외부 라이브러리 이용
    - 가장 많이 사용합니다.

ajax

  • 객체 생성
  • new XMLHttpREquest()하면 생성됩니다.
  • 객체 속성
  • responseText
    - XML 이외의 데이터를 받았을 때 데이터
  • responseXML
    - XML 데이터를 받은 경우 받은 내용
  • 객체의 메서드
  • setRequestHeader(헤더이름, 데이터)
    - 헤더 설정
  • open(전송방식, 전송할 URL, 비동기 전송 여부)
    - 연결 설정
  • send(데이터)
    - 요청 전송
  • sendBinary(데이터)
    - 바이너리 데이터(파일) 전송 시 호출
  • 이벤트
  • load
    - 데이터를 전부 전송받으면 호출되는 이벤트
  • error
    - 데이터를 전송받는 도중, 에러가 발생한 경우에 호출되는 이벤트
  • 실습을 위해서 화면 생성
  • 프로젝트의 urls.py 파일에 요청을 생성
from django.contrib import admin
from django.urls import path, include
from apiserverapplication import views
urlpatterns = [
    path("admin/", admin.site.urls),
    # example로 시작하는 url은 해당 애플리케이션.urls.py파일에서 처리
    path("example/", include("apiserverapplication.urls")),
    path("ajax/",views.ajax)
]

  • 애플리케이션의 views.py 파일에서 ajax 함수 생성
from django.shortcuts import render
def ajax(request):
    return render(request, "ajax.html")

  • 애플리케이션의 templates에서 ajax.html 생성
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My ajax</title>
</head>
<body>
    AJAX
</body>
</html>

  • ajax.html에서 전체 데이터 가져와서 출력하기
  • 나중에 리액트에서 할 거에요
<body>
    <div id="display">
    </div>
    <button id="allbtn">전체 데이터 가져오기</button>
</body>
<script>
 document.getElementById("allbtn").addEventListener("click",(e)=>{
        let request=new XMLHttpRequest(); //재정의 안했음
        // 출력하는 함수에 객체를 대입하면 toString 메서드 호출
        // python은 __str__ 메서드 호출
        // 위 메서드를 재정의하면 그 내용이 출력되지만
        // 재정의를 안하면 기본 설정 내용이 출력됩니다.
        // alert(request)
        // 요청 생성하기
        // request.open('전송방식','url','비동기전송여부')
        // http://localhost:8000/example/fbv/books
        //현재는 http://localhost:8000/ajax
        request.open('GET', 'example/fbv/books/',true);
        // 요청 전송
        request.send('')
        // 응답이 오면 호출
        request.addEventListener('load',(e)=>{
            alert(request.responseText)
        })
    })
</script>

  • 오류임
  • 경로가 잘못 되어있다.
  • / 잘 들어있는지 다 확인해야한다.
request.open('GET', '../example/fbv/books',true); 이렇게 수정

  • 잘 나오는 것을 확인하였다.
  • 이를 화면에 출력해보자.
request.addEventListener('load',(e)=>{
            // 문자열
            //alert(request.responseText)
            // 문자열을 JS 데이터로 변환 (파싱)
            let data=JSON.parse(request.responseText)
            // alert(data) // [object]로 print 된다. 즉 객체들의 배열
            let txt='';
            for(let item of data){ // in으로 하면idx가 나옴
            // (0,1,2) of는 object
                txt+='<h3>'+'&lt'+item.title+'&gt'+'</h3>';
                txt+='<h3>'+item.author+'</h3>';
                txt+='<h3>'+item.description+'</h3>';
            }
            document.getElementById('display').innerHTML=txt;
        })


  • 이번엔 bid로 하나만 받아오자.
  • 하나만 받는 것은 문제가 많을 수 있다.
  • 데이터가 없다면?? : 비어있는 데이터가 옵니다.
    - 즉, 예외(if data.length>0) 이런거 생각해야합니다.
    - 데이터가 없다면 빈 화면만 보입니다. (제일 최악)

  • 없는 번호를 입력하니 detail에 NotFound값이 넘어와야 하는데?
  • 이거는 데이터 하나를 가져올 때, try exception을 해서 단순 예외처리를 당해서 파싱 자체를 못한 것이다.
def bookAPI(request, bid): #bid가 url에 포함되어있다.
    #기본키를 가지고 데이터 1개를 가져오는 것입니다.
    try:
        books = Book.objects.get(bid=bid)
    except Book.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

이 부분을 보면, bid가 있는 것들을 가져오는데, 존재하지 않는다면 단순히 404에러만 발생시키고 예외처리만 해두었다는 것이다. get_object_or_404라면 데이터가 없다면 detail에 not found를 실어준다.
단순 object.get()에서 데이터가 없다면 아무것도 없는 상태가 된다.
이를 데이터 가져올 때, trim 해서 length==0 이런거 해서 없는 데이터 처리 하자.

<body>
    <div id="display">
    </div>
    <button id="allbtn">전체 데이터 가져오기</button> <br><br><br>
    bid : <input type="text" id="bid" /> <button id="getbook">
    데이터 1개 가져오기</button>
    <div id="display2"></div>
</body>
<script>
document.getElementById("getbook").addEventListener("click",
        (e)=>{
            // id가 bid인 입력의 값을 가져오기
            let bid=document.getElementById('bid').value;
            //alert(bid);
            let request=new XMLHttpRequest();
            request.open('GET','../example/fbv/books/'+bid,true);
            request.send('');
            request.addEventListener('load',(e)=>{
                //없는 bid라면 detail에 NotFound값이 넘어와야 합니다.
                //alert(request.responseText)
                let txt="";
                // 내가 짠 코드는 없는 데이터가 들어오면 " " or "" 상태
                // trim으로 공백을 없애고 길이를 보자.
                // 이게 조심해야 하는 점이다. 
                if(request.responseText.trim().length <= 0){
                    //데이터가 없을 때 처리
                    txt="<h3>해당 되는 bid가 없습니다.</h3>";
                }
                else{
                    let data=JSON.parse(request.responseText);
                    //데이터가 있을 때 처리
                    // alert(data.bid)
                    txt+="<h3>"+data.bid+"</h3>";
                    txt+="<h3>"+data.title+"</h3>";
                    txt+="<h3>"+data.description+"</h3>";
                }
                // 안에 태그를 전부 해석해서 disp하기 위해 사용
                document.getElementById('display2').innerHTML=txt;
            })
        });
 </script>

https://sss20-02.tistory.com/6
예외 처리를 할거면 잘 알고 있어야 할 것 같아.
get_object_or_404 걍 이거 쓰는게 마음이 편할 것 같다.


  • form에 데이터를 입력받아서 삽입
    - form을 만들어서 데이터를 전송할 때는 form 안의 name과 파라미터 이름이 일치해야 합니다.
profile
밀가루 귀여워요

0개의 댓글