ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django] 간단한 endpoint를 django 에서 구현해 보기
    Software, Computer Science/Django, Flask 2020. 2. 7. 22:24

    이번에는 django를 본격적으로 사용하는 포스트인데 실제 프로젝트를 하기 앞서 먼저 HTTP 통신으로 데이터를 주고받을 수 있는 가능한 간단한 프로젝트를 진행해 보고 알고 있는 내용을 체크해 본다.

     

    1. Django project 생성 및 view의 최소 뼈대 생성

     

    ([virtualenv_name]) $ django-admin startproject simple_end
    

    (가상환경을 설정할 수 있다면 설정한 상태에서) 위와 같은 명령어로 프로젝트를 만들게 되면

     

    simple_end
    ├── manage.py
    └── simple_end
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py
    

    위와 같은 형태의 트리 구조의 django project를 생성할 수 있다. 여기서 대표적으로 project를 만들어 내기 위해 다음 3개의 프로젝트 파일에 대해서 작업을 수행한다.

    • urls.py : URLConf 파일(URL부분을 정의해주는 부분), 필자는 여기서 / 위치 때문에 종종 실수(패턴주의)
    • settings.py : django project 관련 설정 정보를 담고 있는 파일
    • (app 생성 후 app 내에서) models.py : Django ORM을 이용하여 DB 테이블을 정의하는 파일
    • (app 생성 후 app 내에서) views.py : 화면에 보여줄 수 있도록 데이터의 처리 및 로직을 수행하는 파일(이번 포스트의 핵심이 됨)

     

    ** 이 프로젝트에서 화면을 나타내는 template을 만들지 않는 이유 : 주로 data의 모델링 HTTP 통신 규격에 맞춰 주고 받는 api를 만들 것이며 화면을 나타내는 부분은 전적으로 Front-end에 의존한다고 가정하기 때문

     

    해당 프로젝트 경로로 들어가서 본격적으로 endpoint app을 만든다.

    $ python manage.py startapp endpoint
    

     

    endpoint
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py
    

     

    views.py 를 열어 다음과 같이 작성한다(class 기반의 view 작성)

     

    # endpoint/views.py
    import json
    from django.views import View
    from django.http	import JsonResponse
    
    class MainView(View):
      	# get 요청 처리를 위한 View 클래스의 get메소드 overriding
      	def get(self, request):
        	return JsonResponse({"Hello":"World"}, status=200)
    

     

    # endpoint/urls.py
    from django.urls 	import path
    from main.views		import MainView
    
    # django에서 경로를 명시할 때 urlpatterns 리스트의 패턴을 항상 참고한다
    urlpatterns = [
      # as_view()메소드의 역할 : 해당 view의 주소를 호출하면 HTTP method가 GET인지 POST인지 DELETE인지 
      # UPDATE인지 판별해서 그에 맞는 함수를 실행
      path('', MainView.as_view())
    ]
    

     

    # simple_end/urls.py
    from django.urls import path, include
    
    urlpatterns = [
        path('endpoint', include('endpoint.urls'))
    ]
    

     

    class 기반 view안에 get(HTTP GET 역할을 하는 메소드라 생각하면 된다)을 정의하고 일단 url을 통해 해당 view에 GET요청을 할 시 Hello World가 화면에 보여지게 구성했다 실제 결과는 아래와 같다.

     

    결과를 확인하기 위한 서버 구동

    $ python manage.py runserver
    

     

    실제 localhost:8000(runserver 기본값)에 찍혀진 내용

    {"Hello": "World"}
    

     

    httpie 를 통한 HTTP 통신 요청 예제

    $ http -v http://127.0.0.1:8000/endpoint
    
    GET /endpoint HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Host: 127.0.0.1:8000
    User-Agent: HTTPie/2.0.0
    
    
    
    HTTP/1.1 200 OK
    Content-Length: 18
    Content-Type: application/json
    Date: Fri, 07 Feb 2020 12:14:39 GMT
    Server: WSGIServer/0.2 CPython/3.8.1
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    
    {
        "Hello": "World"
    }
    

     

    결과를 설명하자면 이렇다

    • 웹 브라우저를 통해 또는 httpie등과 같이 client단에서 올바른 url로 http 통신을 하게 되면(GET)
    • 서버단에 해당하는 MainViewget()메소드를 통해 JSON 형태인 "Hello": "World"를 리턴하게 된 것이다
    • 직접적으로 응답을 한 view.py 부분을 통신을 하는 끝단 즉 endpoint라고 정의한다

     

    해당 예제는 가장 간단하게 구현할 수 있는 django 기반의 endpoint가 된다.

     

    (추가) Httpie란 ?

    HTTP메소드를 전송할 수 있는 쉘 프로그램 아래와 같이 설치하고 사용하면 된다

    # Ubuntu
    $ sudo apt-get install httpie
    
    # Macos(homebrew)
    $ brew install httpie
    

     

    GET Method 전송 예제

    $ http -v <http-전송을 요청할 URL>
    

     

    POST Method 전송 예제

    $ http -v <http-전송을 요청할 URL> [data1]=[value1] [data2]=[value2] ... ...
    

     

    2. 모델 추가 및 설정 정리

    이제는 가상이지만 계정을 만들어 로그인 하는 상황을 가정해 보자. 회원 정보를 담을 테이블을 다음과 같이 models.py에 정의한다. 아래처럼 정의했다.

     

    from django.db import models
    
    class Users(models.Model):
    	name				= models.CharField(max_length=50)
        email				= models.CharField(max_length=50)
        password			= models.CharField(max_length=300)
        created_at			= models.DateTimeField(auto_now_add=True)
        updated_at			= models.DateTimeField(auto_now=True)
    

     

    # 그전에 settings.py에서 INSTALLED_APP 항목에 만들어 준 app을 추가해준다
    # 그렇지 않으면 새롭게 만들어진 app이 제대로 인식되지 않아 data modeling이 진행되지 않는다
    # settings.py 중 일부
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'endpoint', # 이 부분을 추가해 준다.
    ]
    

    위와 같이 만든 후 아래와 같은 명령어를 통해 migration을 진행해 준다.

    $ python manage.py makemigrations
    Migrations for 'endpoint':
      endpoint/migrations/0001_initial.py
        - Create model Users
    
    $ python manage.py migrate endpoint 0001
    Operations to perform:
      Target specific migration: 0001_initial, from endpoint
    Running migrations:
      Applying endpoint.0001_initial... OK
    
    • migrate부분에서 app명칭과 migration file number를 명시해준 이유 : 새롭게 모델링 한 데이터를 하나씩 migrate를 해야 복잡하고 거대한 데이터 모델 구조일 때 부하도 적어지고 문제가 생길 여지가 줄어든다 문제가 생겼어도 해당 데이터 모델만 확인하면 되니깐 유지보수에도 용이하다.(일종의 습관적 문제?)

     

    다음으로 만든 모델을 기반으로 post()가 가능한 View제작을 해본다.

    import json
    from django.views		import View
    from django.http		import JsonResponse
    from .models				import Users
    
    class MainView(View):
      	def post(self, request):
          	# HTTP 통신으로 전달 받은 데이터를 json의 형태로 load하는 함수
          	data = json.loads(request.body)
            Users(
            	name			= data['name'],
              	email			= data['email'],
              	password		= data['password']
            ).save()
            
            # 제대로 응답처리 했다면 200 status와 함께 성공 메세지 전달
            return JsonResponse({'message':'SUCCESS'}, status=200)
        
        def get(self, request):
          	return JsonResponse({'Hello':'World'}, status=200)
    

     

    새롭게 제작한 부분이 post()부분인데 받아온 데이터를 Users라는 모델 클래스의 형태로 모델 객체를 생성하여 저장하고 저장이 제대로 완료 되었다면 성공했다는 메세지를 띄우게 구성했다.

     

    위에 POST 메소드 작성 예제를 참고하여 POST 테스트를 해보았을 때 정상적인 결과가 출력된다면 아래와 같이 나온다

     

    February 07, 2020 - 12:52:28
    Django version 3.0.2, using settings 'simple_end.settings'
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CONTROL-C.
    Forbidden (CSRF cookie not set.): /endpoint
    [07/Feb/2020 12:52:53] "POST /endpoint HTTP/1.1" 403 2864
    

     

    위와 같이 에러가 뜰텐데 CSRF cookie 처리가 되지않은 데이터가 post가 일어나기 때문에 django 웹서버에서 사전 차단한 것이다. 그렇다면 왜 뭐 때문에 저런 설정이 어디 되어 있단 말인가 ? 답은 settings.py에 있었다.

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        # 'django.middleware.csrf.CsrfViewMiddleware', <- 이 부분 주석처리
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

     

    django는 csrf 보안 사고를 막기 위해 데이터를 받아올 때 'django.middleware.csrf.CsrfViewMiddleware'라는 middleware 모듈이 해당 문제를 자동으로 처리해 주게 되어 있다. 일단 endpoint 통신 자가 테스트를 하는 와중이므로 과감하게 주석 처리해 주면 된다.

     

    그러고 나서 httpie를 통해 post()메소드 작동을 체크해보면 다음과 같다.

     

    $ http -v http://127.0.0.1:8000/endpoint name=test email=test@test.com password=1234
    
    POST /endpoint HTTP/1.1
    Accept: application/json, */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Content-Length: 62
    Content-Type: application/json
    Host: 127.0.0.1:8000
    User-Agent: HTTPie/2.0.0
    
    {
        "email": "test@test.com",
        "name": "test",
        "password": "1234"
    }
    
    HTTP/1.1 200 OK
    Content-Length: 22
    Content-Type: application/json
    Date: Fri, 07 Feb 2020 13:10:36 GMT
    Server: WSGIServer/0.2 CPython/3.8.1
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    
    {
        "message": "SUCCESS"
    }
    

     

    정상적으로 POST 응답이 진행되는 것을 확인할 수 있다. 그렇다면 POST(DB에)한 데이터를 get()메소드를 통해 다시 한 번 가져와 보자.

     

    # endpoint/views.py
    # 데이터를 받아오기 위해 아래와 같이 수정
    def get(self, request):
    	user_data = Users.objects.values() # ORM 메소드를 통해 DB에서 데이터를 가져옴
      	return JsonResponse({'users':list(user_data)}, status=200)
    

     

    GET /endpoint HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Host: 127.0.0.1:8000
    User-Agent: HTTPie/2.0.0
    
    
    
    HTTP/1.1 200 OK
    Content-Length: 168
    Content-Type: application/json
    Date: Fri, 07 Feb 2020 13:20:20 GMT
    Server: WSGIServer/0.2 CPython/3.8.1
    X-Content-Type-Options: nosniff
    X-Frame-Options: DENY
    
    {
        "users": [
            {
                "created_at": "2020-02-07T13:10:36.976Z",
                "email": "test@test.com",
                "id": 1,
                "name": "test",
                "password": "1234",
                "updated_at": "2020-02-07T13:10:36.976Z"
            }
        ]
    }
    

     

    정상적으로 가져 왔다면 위와 같은 결과를 얻어올 수 있다.

     

    Reference

    • Wecode 과정에서 진행한 django endpoint 기초 내용을 요약 정리.

     

     

     

     

    댓글

Designed by Tistory.