REST란?

REpresentational State Transfer의 약자로, 자원(리소스)의 표현에 의한 상태 전달을 하는, HTTP를 잘 활용하기 위해 만든 ‘아키텍쳐’이다. 따라서 REST를 잘 지키지 않는다고 해서 ‘trash code’나 ‘동작하지 않음’ 이런것은 절대 아니다.

다만 내가 RESTful한 API를 설계한다면 다른 crue, 아니면 혹시나 내 업무를 넘겨받을 사람에게는 충분한 도움이 될 것이다.

 

문제 발생

플라스크를 이용한 프로젝트를 설계하고 구현하는 과정에서, 상대방이 시간이 없어 구동 방식이나 완성본을 보지는 못하고 메인 컨트롤러만 확인할 수 있는 상황이었다. 그런데 메인 컨트롤러의 코드가 너무 중구난방이고, URL에서도 이게 어떤 코드인지, 어떻게 동작하는지를 알 수가 없었다.

다음은 app.py에 들어있는 api의 url과 method다.

  URL method 기능
home /    
search_get /search GET 검색 결과를 받아오는 메서드
list_post /playlist POST 플레이리스트에 추가하는 메서드
list_get /playlist GET 플레이리스트를 받아오는 메서드
list_update /playlist PUT 플레이리스트에서 삭제하는 메서드
getlogin /auth/login GET 로그인 화면을 렌더링하는 메서드
login /auth/login POST 로그인을 하는 메서드
logout /logout GET 로그아웃을 하는 메서드
getSignIn /auth/signin GET 회원가입 화면을 렌더링하는 메서드
signIn /auth/signin POST 회원가입을 하는 메서드

제일 문제인 것은, 지금 내가 이 포스트를 쓰면서도 이게 무슨 기능인지 몰라 다시 app.py를 열어봤다는 점이다.

 

해결

CRUD에 맞게 method를 설계했다고 생각은 하지만, directory나 url 설계과정에서는 전혀 RESTful하다고 느껴지지 않는다. Auth나 Playlist같은 directory를 만들어서 따로 관리했어야 했다.

또한 code-convention이 잘 되지도 않았고(snake와 camel의 혼용) 내가 가지고 있는 resource가 무엇인지도 잘 파악되지 않는다.

URL같은 경우에는 users/{id}/playlist같이 동적 라우팅을 해 주는것이 좋다.

지금은 리소스를 활용하는 프로젝트가 아니기도 하고, 코드가 많이 들어가는 복잡한 작업은 아니지만 git을 활용할 때 충분히 conflict 문제가 날 수 있는 문제이다.

그래서 url등의 문제를 다음과 같이 바꿔 준다.

  URL method 기능
home /    
search /users/{id}/search GET 검색 결과를 받아오는 메서드
addList /users/{id}/playlist POST 플레이리스트에 추가하는 메서드
getList /users/{id}/playlist GET 플레이리스트를 받아오는 메서드
updateList /users/{id}/playlist PUT 플레이리스트에서 삭제하는 메서드
getLogin /login GET 로그인 화면을 렌더링하는 메서드
login /login POST 로그인을 하는 메서드
logout /logout GET 로그아웃을 하는 메서드
getSignIn /signin GET 회원가입 화면을 렌더링하는 메서드
signIn /signin POST 회원가입을 하는 메서드

다음 목표는 method를 나누지 않고 methods=['GET', 'POST']로 하는걸로..

개괄

항해에 들어간 후 첫 프로젝트를 진행하였다.  

 

목표

프로젝트는 flask와 html, css를 활용한 youtube player를 구현하기로 했고,

session을 이용한 로그인 화면 구현과 bs4를 이용한 유튜브 search result를 크롤링해서 플레이리스트에 추가, 삭제하기

등이 주요 과제가 되었다.

 

배운 것 - TIL SERIES (Link)

REST-API : 누구나 읽기 쉽게 설계하기

https://choincnp.tistory.com/21

 

TIL - REST API : 누구나 읽기 쉽게 설계하기

REST란? REpresentational State Transfer의 약자로, 자원(리소스)의 표현에 의한 상태 전달을 하는, HTTP를 잘 활용하기 위해 만든 ‘아키텍쳐’이다. 따라서 REST를 잘 지키지 않는다고 해서 ‘trash code’나

choincnp.tistory.com

협업 - 합리적인 소통하기

https://choincnp.tistory.com/18

 

웹 개발 프로젝트 목표 설정하기

첫 팀이 분배되고, 프론트 지향 2명과 백엔드 지향 2명이 같은 팀이 되었다. 문제점 프로젝트 주제에 대해서는 많은 아이디어가 나왔지만, 그 주제에 어떤 기능을 넣어야 할지, 모여서 무엇을 해

choincnp.tistory.com

git flow - trunk-based 기법을 활용한 팀원들의 소통

db 저장하기 - Mongo DB를 활용한 dictionary를 element로 가진 Array를 저장하기

0주차 Troubleshooting

 

 

목표 달성 여부

결과적으로 말하자면, 절반의 성공과 절반의 실패라고 볼 수 있다.

GIT REPOSITORY : https://github.com/choincnp/duoback_mini

<main page>

좌측 상단 : iframe을 이용한 현재 재생중인 유튜브 영상

좌측 하단 : mongoDB를 이용한 내가 선택한 playlist

우측 상단 : search tab을 활용한 유튜브 영상 검색

우측 하단 : search result

<login>

pymongo를 이용해 로그인 페이지와 회원가입 페이지를 만들고, ID와 PW는 몽고DB에서 관리, 로그인 정보는 session에서 관리하게 만들었다.

클라이언트단은 완벽하지만, 서버단의 구조는 매우 빈약한것을 볼 수 있다. 조금만 더 알았더라면, 조금만 더 공부했더라면 다른 팀원들이 구현하고자 했던 기능들을 더 구현할 수 있었는데, 내 능력의 부족으로 이루지 못해 너무 속상했고, 각오를 다졌다.

 

 

느낀 점

컴퓨터는 앞으로만 가지만, 사람은 양방향성을 띈다.

앞으로만 가는 컴퓨터의 활동을 제어하는것은 쉽지만, 때로는 전진하고, 때로는 후퇴하는 사람을 제어하는것은 훨씬 힘든 일이다. 첫 협업에서부터 삐끗했던 활동을 생각하며, 옛날에 떠올렸던 '이런 사람과도 협업을 못하는데, 내가 과연 smooth한 협업을 진행할 수 있을까?'라는 생각을 다시금 했다.

 

내 코드는 컴퓨터만 읽는 게 아니라, 사람도 읽는다.

제발한번만

컴퓨터가 읽기 좋은 코드로 개발하는것도 좋지만, 사람이 읽기 좋은 코드로 개발하는것도 꼭 필요한 프로세스다.

읽기 쉽게 코딩하지 않으면 남들이 보는 데 거부감이 든다.

 

제대로 알지 못하면, 안 하느니만 못하다.

인강을 다 들었어도 실제 구현을 하려고 하면 막막하기도 하고, 도대체 내가 무엇을 하고 있는지, 어디에 있는지조차 헷갈린다. 

 

 

다음 주 목표

- 위의 것들을 기반으로 한 원활한 소통과 협업이 이루어지는, '케미스트리'가 가득한 오케스트라 같은 팀 구성하기.

- RESTful한 API 설계로 모르는 사람이 보아도 한 눈에 무슨 api인지 알게 하기

- code-convention과 commit-convention으로 명확한 의미를 지닌 코드/소통 설계하기

 

 

첫 팀이 분배되고, 프론트 지향 2명과 백엔드 지향 2명이 같은 팀이 되었다. 

 

문제점

프로젝트 주제에 대해서는 많은 아이디어가 나왔지만, 그 주제에 어떤 기능을 넣어야 할지, 모여서 무엇을 해야 할 지, 또 어떻게 일감을 나누어야할지, 내가 아는것은 무엇인지에 대해 정의할수가 없었다.

 

시도해본것 

아는것이 제일 많은 고수님께 PM을 맡기고,

팀장님이 정한 주제에다 기능구현을 몇개 입히기로 했다. 각 기능 구현은

https://hanghae99.spartacodingclub.kr/v2/exhibitions

 

온라인 코딩 부트캠프 항해99, 현업에서 인정받는 개발자로

가장 단기간에 프론트엔드, 백엔드 개발자로 취업할 수 있는 부트캠프 항해99. 4개의 팀 프로젝트, 수료 후에도 끝까지 지원되는 이력서, 포트폴리오, 면접 취업지원까지.

hanghae99.spartacodingclub.kr

여기서 재밌어보이는 기능을 하나씩 가져와서 넷이서 구현해보기로 했다.

 

결국

우리는 유튜브의 데이터를 긁어와서 모두가 같이 유튜브를 재생하는 프로젝트를 구성하기로 했다.

Discord에 있는 뮤직 봇같은 경우에는 대화방을 파서 유튜브 파일을 가져와서 재생하는데,

우리는 재생중인 화면에서도 대화방을 팔 수 있도록 할 것이다.

 

 

느낀 점

프로젝트는 아이디어만 있다고 되는 것이 아니라,

  • 내가 무엇을 알고, 무엇을 할 수 있는가 / 남이 무엇을 알고, 무엇을 할 수 있는가
  • 기능은 어떤 것을 구현할 수 있는가
  • BE/FE의 관점에서 어떻게 일을 분배할 수 있는가

를 알아야 한다. 기능도 '이런 기능이 있으면 좋겠어'가 아니라, '이 기능은 어떻게 동작하고 구현하고 나타내야 되겠어'를 생각하고 있어야 비로소 좋은 프로젝트로 가는 길이 될 것 같다.

웹 서비스를 런칭하기 위해선...

  • 언제나 요청에 응답할 수 있어야 한다. (컴퓨터가 항상 실행 중이어야 한다)
  • '모두가 접근할 수 있는' 공개 IP주소로 내 서비스에 접근할 수 있어야 한다.

서버는 그냥 '컴퓨터'이기 때문에 우리는 가상 컴퓨터인 Amazon EC2(인스턴스)를 이용해 구동하기로 한다.

 

주의사항

아마존은 자선사업가가 아니기 때문에  컴퓨터를 무제한 제공해주지는 않는다.

free tier로 가입을 하더라도 컴퓨터를 계속 켜놓으면 돈이 나간다.

이런 메세지를 무시하다간 피같은 나의 돈이 나간다.

예전에 서버 구동을 따라해보고 깜빡 잊었다가 돈이 나갔었던 기억에 이같은 경고 메세지를 남긴다.

가끔 선량한 대학생이 1학년때 팀프로젝트로 EC2를 돌렸다가 까먹고 대학교 4학년때 billing으로 몇백만원이 청구되었다는 썰도 심심찮게 들리니, 

꼭 인스턴스를 '중지' 해주도록 하자!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

참고로 인스턴스 종료를 누르면 컴퓨터를 반납하는거라서 여러분들의 소중한 작업물이 다 날라간다.

 

EC2 연결하기

Git bash에서 SSH(Secure Shell Protocol)이라는 방식으로 접속을 할 건데, SSH방식은 그냥 조금 더 보안에 안전한 프로토콜이라고 생각하면 된다. pem키를 받아서 써야 한다.

모자이크된 부분에 본인의 키 위치를 넣고, ubuntu@뒤에는 인스턴스의 공용 IP Address를 누르면 된다.

만약 인스턴스를 중지했다가 다시 켤 경우, 공용 IP주소가 변하기 때문에 바꿔줘야 한다.

다음은 flask 환경에서 설정하는 3단계이다.

#1. python3 -> python
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10

#2. pip3 -> pip
sudo apt-get update
sudo apt-get install -y python3-pip
sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1

#3. port forwarding
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000

 

  1. 첫 번째 명령어는 python3 ~~~~ 라고 해야하는 명령어를 python ~~~~로 바꿔주는 명령어다.
    프로그래머는 귀찮아해야한다. 그래야 더 나은 방식을 생각할 수 있으니까.
  2. 두 번째 명령어는 flask 환경에서 구동할 때 쓰이는 패키지들을 관리해주는 패키지 툴인 pip3를 설치하고,
    이름을 pip로 바꿔주는 것이다.
  3. 이게 가장 중요한데 우리가 계속 진행해왔던 localhost:5000에서 이 포트번호 5000을 포트번호 80으로
    리다이렉팅 해준다는 것이다. 리다이렉팅과 포워딩의 차이는 추후에 다시 설명하겠다.

그리고 Filezila에서 다시 연동하고 아마존의 인스턴스에 실행할 app.py나 index 등을 넣어준 후,
ls(list : 데이터 목록 보여주기)

cd(change directory : 디렉토리 변경)

mkdir(make directory : 디렉토리 생성)

pip install packagename(파이썬의 packang

등의 명령어를 입력해 '알아서 잘' 하고 python app.py 명령어로 가동시키면 된다.

 

인바운드 규칙 추가

인바운드 규칙 추가는 우리가 사용할 인스턴스의 포트번호를 추가해준다.

이렇게 TCP/5000 포트와 HTTP/80포트를 열어주어야 비로소 우리가 만들었던 홈페이지에 접속 가능하다.

 

80포트의 비밀

우리가 접속하는 대부분의 인터넷 사이트(네이버, 다음 등)는 사실 #http://www.naver.com:80 인데, 80포트가 생략된 것이다. 80은 생략이 가능하기 때문에. 그래서 아까 우리가 포트포워딩으로 5000번을 80에 연결해놓은 것이다. 그러면 접속할때마다 사이트 뒤에 5000포트를 생략해도 된다.

이 포트포워딩은 리눅스에서 자동 지원된다.

 

nohup 설정하기

nohup은 우리가 인스턴스에서 구동한 app.py를, git bash를 종료해도 구동될 수 있도록 하는것이다. 방법은 매우 쉽다.

그냥 git bash에 nohup python app.py를 써주면 계속 돌아간다.

나는 왜 그런진 모르겠지만 습관적으로 nohub를 써주는 경향이 있어 조금 더 주의를 요해야 한다. 왜 그럴까?

키는 건 알겠는데...

끌때는 다음과 같은 코드를 넣어주면 된다.

ps -ef | grep 'python app.py' | awk '{print $2}' | xargs kill

 

OG(Open Graph) tag

미리보기가 없다면 여기서 뭘 하라는건지, 무슨 사이트인지 모를 수도 있다.

OG태그는 위의 사진과 마찬가지로 미리보기 image, title, description등을 삽입하는 것이다.

그냥 <head></head>안에 다음과 같은 3줄을 심어주면 된다.

<meta property="og:title" content="내 사이트의 제목" />
<meta property="og:description" content="보고 있는 페이지의 내용 요약" />
<meta property="og:image" content="이미지URL" />

이로써 길다면 길고 짧다면 짧았던 5주차(라고 쓰고 5시간짜리) 수업이 끝났다.

 

웹 개괄을 이보다 더 잘 나타내준 수업은 없었다. 정말 감사한 수업이었다.

세 번째, 버킷 리스트

이번의 특별한 기능은, 완료 버튼을 누르면 완료버튼이 없어지고 내용에 줄이 그어져야 한다

STEP 1 : 무엇을 고려해야 하는가?

  • URL은 어떻게 만들 것인지, 요청 방식은 어떻게 될 것인지?
  • 클라이언트에서 서버로는 어떤 것을 넘겨 줄 것인지?
  • 서버에서 클라이언트로는 어떤 것을 넘겨 줄 것인지?
  • 주의사항 : hidden field로 번호를 넣어 주어야 완료버튼을 눌렀을 때 어떤 것이 완료인지를 알 수 있다.

STEP 2 : 서버단 설정 (POST방식)

💣'done'의 상태를 숫자 0, 1로 표현했을 때는 후처리가 중요하다. 이는 뒤에서 다시 설명하겠다.

@app.route("/bucket", methods=["POST"])
def bucket_post():
    bucket_receive = request.form["bucket_give"]
#클라이언트로 전달받은 bucket_give를 bucket_receive에 저장한다.
    count = list(db.bucket.find({},{'_id':False}))
    num = len(count) + 1
#bucket의 개수를 count, 그리고 새로 만드는 bucket은 +1이 붙어야 하니까
#num이라는 변수에 len함수를 이용해 길이를 알아내고 count+1을 해준다.
    doc = {
        'num':num,
        'bucket': bucket_receive,
        'done':0
    }
#done는 0(미완)상태와 1(완료)상태로 나누어서 구분해준다.
    db.bucket.insert_one(doc)
    return jsonify({'msg':'등록 완료!'})
#insert를 하고 json형식으로 메세지를 보내주면 끝!

STEP 3 : 클라이언트단 설정

function save_bucket(){
    let bucket = $('#bucket').val()
#bucket class의 value를 가져와서 bucket 변수에 저장한다.
    $.ajax({
        type: "POST",
        url: "/bucket",
        data: {bucket_give:bucket},
#bucket 변수의 데이터를 bucket_give라는 param에 넣어서 서버에 전달한다.
        success: function (response) {
            alert(response["msg"])
            window.location.reload()
#별 일이 없으면 response에 담긴 json에서 msg를 꺼내서 alert해주고
#새로고침 해주면 끝!
        }
    });
}

 

GET방식도 그대로 하면 된다.

STEP 2 : 서버단 설정(GET방식)

*POST가 아니므로 클라이언트에서 서버로 가져갈 것이 없기 때문에 간단하게 쓸 수 있다.

@app.route("/bucket", methods=["GET"])
def bucket_get():
    buckets_list = list(db.bucket.find({},{'_id':False}))
    return jsonify({'buckets':buckets_list})

STEP 3 : 클라이언트단 설정

#document가 완료되면 show_bucket를 실행하는 메서드를 추가한다.
function show_bucket(){
    $('#bucket-list').empty()
    $.ajax({
        type: "GET",
        url: "/bucket",
        data: {},
#서버로 줄 게 없다.
        success: function (response) {
#다만 서버에서 받은 response로 들어온 버킷리스트를 띄워줘야 한다.
            let rows = response['buckets']
            for (let i = 0; i < rows.length; i++) {
                let bucket = rows[i]['bucket']
                let num = rows[i]['num']
                let done = rows[i]['done']

                let temp_html = ``
                if (done == 0) {
                    temp_html = `<li>
                                    <h2>✅ ${bucket}</h2>
                                    <button onclick="done_bucket(${num})" type="button" class="btn btn-outline-primary">완료!</button>
#여기가 매우 중요한데, 여기서 done_bucket(${num})으로 onclick함수를 만들어주어야 한다.
#온클릭 함수에 아규먼트로 버킷리스트 번호를 들어가게 하고, 그 번호에 맞는 버킷리스트를
#종료처리(done=1)로 해 줄 것이다.
				</li>`
                } else {
                    temp_html = `<li>
                                    <h2 class="done">✅ ${bucket}</h2>
                                </li>`
                }
                $('#bucket-list').append(temp_html)
            }
        }
    });
}

 

DONE_BUCKET 구현하기

STEP 2 : 서버단 설정

아까 위에서 설명했던대로 done_bucket function을 만들어 줌으로써 완료처리를 하는 함수를 구현해야 한다.

처음 GET/POST의 차이를 설명했었는데, '수정'에 대한 사항은 POST 방식으로 해 주어야 하기 때문에 POST방식으로 구현을 해 주어야 한다.

메서드는 insert가 아닌 update로 해 주어야 하고, url도 /bucket이 아닌 뒤에 /done을 적어주어야 위의 bucket_post와 구별될 수 있다.

또한, 위의 💣 imoge에 써 놓았듯, num이 DB에 저장될 때에는 integer형태로 저장되어 있는데 꼭 형변환을 해 주어야 한다.

@app.route("/bucket/done", methods=["POST"])
def bucket_done():
    num_receive = request.form["num_give"]
    db.bucket.update_one({'num': int(num_receive)}, {'$set': {'done': 1}})
#num_receive는 꼭 integer로 형변환을 해 주어야 한다. default값이 문자열이기 때문
    return jsonify({'msg': '버킷 완료!'})

STEP 3 : 클라이언트단 설정

function done_bucket(num){
#다른 function과는 별개로, 파라미터로 num인자를 꼭 받아야 돌아가게 해 놓아야 한다
    $.ajax({
        type: "POST",
        url: "/bucket/done",
#url 또한 /bucket/done에서 진행된다고 해 주어야 한다.
#처음 설명했듯, /bucket라는 창구에서 두 가지 post를 진행할 수 없기 때문이다.
        data: {'num_give':num},
#데이터는 num_give라는 파라미터에 num을 담아서 주어야 한다.
        success: function (response) {
            alert(response["msg"])
            window.location.reload()
        }
    });
}

 

 

이렇게 해서 버킷리스트도 끝났다.

 

두 번째, 스파르타피디아

저번에 만들었던 스파르타피디아에 추가로 크롤링 기능까지 더해본다

조각 기능?

프로젝트에 들어갈 기능들을 미리 구현해보는것.

이번 프로젝트에서 meta tag를 크롤링하는 기능을 새로 구현해야하니 먼저 이 기능이 잘 되는지부터 검증해야 한다.

나중에 찐프로젝트를 들어가면 수십~수백가지 기능을 구현해야 하는 데,

모든 기능을 한꺼번에 돌리는것은 시간도 많이 소요되고 어떤 기능에 문제가 있어서 오류가 나는지 쉽사리 찾기 어렵다.

따라서 이런 작은 기능 하나하나에 대한 검증이 필요하다.

이런 조각 기능들을 검증하면서 개발을 진행하는것이 TDD(Test-Driven Development)의 기초가 되니, 열심히 해 두자.

 

META TAG 스크래핑 하기

과제를 끝낸 후의 내사랑 다펑의 OG태그

카톡에 URL을 전송하면, 썸네일과 title, 그리고 description이 자동으로 붙는다.

이것들은 어떻게 남기고, 어떻게 가져올까?

 

Meta tag의 위치

친절하게도, head를 까보면 다 들어있다. 여기의 프로퍼티를 가져오자.

우리가 찾고있던 것들은, og:property라는 이름 안에 있는 content에 들어있다.

이번에는 저번과 다른 방식으로 크롤링을 사용해 볼 것이다.

import requests
from bs4 import BeautifulSoup

url = 'url주소'

headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
data = requests.get(url,headers=headers)

soup = BeautifulSoup(data.text, 'html.parser')
#여기까진 똑같다

og_image = soup.select_one('meta[property="og:image"]')
og_title = soup.select_one('meta[property="og:title"]')
og_description = soup.select_one('meta[property="og:description"]')
#의미하는 바 : soup에서 하나를 고를 건데, meta tag의 property가 "og:~~~~"인 애를 가져와라.

image = og_image['content']
title = og_title['content']
description = og_description['content']
#그리고 각각의 property 안에 있는 content를 가져올 것이다.

이렇게 하면 우리가 원하던 영화의 메타값의 image, title, description을 가져올 수 있다 :)

 

STEP 1 : 무엇을 고려해야 하는가?

  • URL은 어떻게 만들 것인지, 요청 방식은 어떻게 될 것인지?
  • 클라이언트에서 서버로는 어떤 것을 넘겨 줄 것인지?
  • 서버에서 클라이언트로는 어떤 것을 넘겨 줄 것인지?

STEP 2 : 서버단 설정 (POST방식)

@app.route("/movie", methods=["POST"])
def movie_post():
    url_receive = request.form['url_give']
    star_receive = request.form['star_give']
    comment_receive = request.form['comment_give']
#클라이언트로 ~~~~_give들을 각각 받아와서 서버단의 변수에 담아준다.
#이제는 받은 url을 가지고 크롤링을 해 줄 것이다.
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
    data = requests.get(url_receive, headers=headers)
#받아온 데이터의 url을 data에 저장하고
    soup = BeautifulSoup(data.text, 'html.parser')
#그 데이터를 soup을 이용해 파싱해준다.
    image = soup.select_one('meta[property="og:image"]')['content']
    title = soup.select_one('meta[property="og:title"]')['content']
    description = soup.select_one('meta[property="og:description"]')['content']
#그 파싱한 데이터에서 메타데이터를 추출한다.

    doc = {
        'image':image,
        'title':title,
        'desc':description,
        'star':star_receive,
        'comment':comment_receive
    }
#그리고 함께 넘어온 request중 star와 comment를 모두 DB에 담고 JSON형태로 넘겨준다.

    db.movies.insert_one(doc)

    return jsonify({'msg':'POST 연결 완료!'})

STEP 3 : 클라이언트단 설정

function posting() {
    let url = $('#url').val()
    let star = $('#star').val()
    let comment = $('#comment').val()
# class name이 url, star, comment인것의 아규먼트를 각각 담는다.
    $.ajax({
        type: 'POST',
        url: '/movie',
#우리는 POST방식으로 movie라는 창구에서 데이터를 다룰거라고 말 하는 중
        data: {url_give: url, star_give: star, comment_give: comment},
#JSON방식으로 데이터를 다룰 것인데, data는 {param : value}형태로 들어가야 하므로
#~~~~_give에 ~~~~를 담아서 클라이언트에 넘겨준다.
        success: function (response) {
            alert(response['msg'])
            window.location.reload()
#문제가 없을 경우 서버에서 받은 메세지를 띄우고 새로고침한다.
        }
    });
}

 

GET방식도 그대로 하면 된다.

STEP 2 : 서버단 설정(GET방식)

*POST가 아니므로 클라이언트에서 서버로 가져갈 것이 없기 때문에 간단하게 쓸 수 있다.

@app.route("/movie", methods=["GET"])
def movie_get():
    movie_list = list(db.movies.find({},{'_id':False}))
    return jsonify({'movies':movie_list})

STEP 3 : 클라이언트단 설정

$(document).ready(function(){
	  listing();
#화면이 다 로딩되면 listing 함수를 실행한다.
});

function listing() {
    $('#cards-box').empty()
    $.ajax({
        type: 'GET',
        url: '/movie',
        data: {},
        success: function (response) {
#아까 movie_list를 movies라는 변수에 담아 response에 넘겨주었었다.
            let rows = response['movies']
            for(let i = 0; i < rows.length; i++) {
                let image = rows[i]['image']
                let title = rows[i]['title']
                let desc = rows[i]['desc']
                let star = rows[i]['star']
                let comment = rows[i]['comment']

                let star_image = '⭐'.repeat(star)
#repeat 함수는 파이썬이 얼마나 좋은 언어인지를 알려준다.
                let temp_html = `<div class="col">
                                    <div class="card h-100">
                                        <img src="${image}"
                                             class="card-img-top">
                                        <div class="card-body">
                                            <h5 class="card-title">${title}</h5>
                                            <p class="card-text">${desc}</p>
                                            <p>${star_image}</p>
                                            <p class="mycomment">${comment}</p>
                                        </div>
                                    </div>
                                </div>`

                $('#cards-box').append(temp_html)
            }
        }
    })
}

첫 번째, 화성 땅 공동구매

이름, 주소, 평수를 고르고 주문버튼을 누르면, 주문한 내역들이 밑에 뜨게 된다.

STEP 1 : 무엇을 고려해야 하는가?

  • URL은 어떻게 만들 것인지, 요청 방식은 어떻게 될 것인지?
  • 클라이언트에서 서버로는 어떤 것을 넘겨 줄 것인지?
  • 서버에서 클라이언트로는 어떤 것을 넘겨 줄 것인지?

STEP 2 : 서버단 설정 (POST방식)

@app.route("/mars", methods=["POST"])
def mars_post():
    name_receive = request.form['name_give']
    address_receive = request.form['address_give']
    size_receive = request.form['size_give']
 #클라이언트로부터 name, address, size를 받아와서 각각 @_receive 변수에 담아놓고,
    doc = {
        'name': name_receive,
        'address': address_receive,
        'size': size_receive
    }
#우리는 NoSQL인 MongoDB의 방식을 따르니 json형태로 저장을 해 두고
    db.mars.insert_one(doc)
#DB에 인서트하고
    return jsonify({'msg': '주문 완료!'})
#response로 json형식의 '주문 완료'메세지를 던져준다.

STEP 3 : 클라이언트단 설정

function save_order() {
    let name = $('#name').val()
    let address = $('#address').val()
    let size = $('#size').val()
#name, address, size에 무엇이 들어갈 지 체크해둔다.
    $.ajax({
        type: 'POST',
        url: '/mars',
#Post 방식으로 /mars라는 창구에서 일을 볼 것이고
        data: { name_give:name, address_give:address, size_give:size },       
#data는 {param : value}의 형태로 저장할 건데
#{requestparam인 ~~~~_give : responsevalue인 ~~~~}의 형태로 받아올 것이다
        success: function (response) {
#문제가 없었으면 response값을 가지고 function을 실행하는데
            alert(response['msg'])
#response의 msg값을 alert로 알려줄 것이다.
            window.location.reload()
#그리고 reload()를 통해 새로고침을 한번 해 줄것이다.
        }
    });
}

 

GET방식도 그대로 하면 된다.

STEP 2 : 서버단 설정(GET방식)

*POST가 아니므로 클라이언트에서 서버로 가져갈 것이 없기 때문에 간단하게 쓸 수 있다.

@app.route("/mars", methods=["GET"])
def mars_get():
    orders_list = list(db.orders.find({},{'_id':False}))
#db에 있는 주문 정보를 전부 끌고와서 orders_list라는 변수에 담는다.
    return jsonify({'orders':orders_list})
#그리고 json형식으로 'orders'라는 파라미터에 전체 주문 정보를 담아준다.

STEP 3 : 클라이언트단 설정

 		$(document).ready(function () {
            show_order();
#모든 준비가 완료되면 show_order function을 실행시킨다.
        });
        function show_order() {
            $('#order-box').empty()
#원래 있던 order-box class를 전부 지운다.
            $.ajax({
                type: 'GET',
                url: '/mars',
                data: {},
#넘겨줄게 없으니 data는 공란으로 넘긴다.
                success: function (response) {
#서버단에서 넘겨준 response (전체 주문 정보)를 가지고 함수를 돌린다.
                    let rows = response['orders']
#rows는 response의 각각의 order 하나씩을 맡을 것이다.
                    for (let i = 0; i < rows.length; i++) {
                        let name = rows[i]['name']
                        let address = rows[i]['address']
                        let size = rows[i]['size']
#그리고 response의 name을 클라이언트에서 name으로,
#response의 address를 클라이언트의 address로
#response의 size를 클라이언트의 size라는 변수에 담아준다.
                        let temp_html = `<tr>
                                            <td>${name}</td>
                                            <td>${address}</td>
                                            <td>${size}</td>
                                          </tr>`
#각 name, address, size는 변동될 수 있으므로 EL문의 ${}에 담아주고
                        $('#order-box').append(temp_html)
#주문 정보를 나타내는 body class에 넣어주도록 한다.
                    }

                }
            });
        }

 

개괄

app.py파일을 통해 flask서버를 구동해서 api환경들을 구동했었다. 

여태 로컬에서만 개발을 해 왔지만, 이제는 서버를 이용해서 Atlas에 DB를 전해주고, 받아오는 일을 할 것이다.

 

데이터 통신 방식

데이터 통신을 할 때도 방식에 따라서 업무가 달라진다.

HTTP통신의 request method에 따라 많은 요청 종류가 있지만, 여기서는 단 2개의 핵심 방식을 가지고 진행한다.

HTTP 통신에 대해서는 나중에 따로 포스팅 할래

GET

  • 데이터를 조회할 때 사용
  • 데이터 전달은 ?key=value의 형태로 전달한다. 

아델의 hello를 구글에 검색했을 때의 주소창을 보면, ?q=hello 형태로 데이터를 받아오는것을 볼 수 있다.

POST

  • 데이터를 생성, 변경, 삭제할때 사용
  • 보통 body에 key:value형태로 숨겨서 전달

GET 요청의 코드 뜯어보기

프론트단의 function 안에 들어가는 ajax 코드이다.

button의 onclick에 function이 연동되어 있고, 그 function 안에 이 ajax 코드가 들어있다고 생각해보자.

$.ajax({
    type: "GET",
#GET 방식으로 가져올거야
    url: "/test?title_give=봄날은간다",
#/test라는 창구에서 title_give로 '봄날은간다'를 가져 갈게 (/id?주민등록번호=990123-1234567)과 비슷한 내용
    data: {},
    success: function(response){
#잘 된다면 내가 response에 받은 것들로 이러이러한 것들을 해볼게
       console.log(response)
    }
  })
#이 '봄날은간다'는 추후에 python의 콘솔창에 찍히게 된다.

그러면 이제 서버단으로 넘어와서, app.py에 추가작업을 해 주어야 한다.

@app.route('/test', methods=['GET'])
#/test라는 창구에서 get방식으로 무엇을 해볼게! 
def test_get():
   title_receive = request.args.get('title_give')
#title_give라는 이름으로 무엇인가를 받아와서 title_receive라는 변수에 넣고
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})
#그러면 json 방식으로 {'result':'success', 'msg': '이 요청은 GET!'}를 response로 넘겨줄게!

다시 설명해보자면,

  1. button을 누르는 순간 함수가 동작하여 그 안에 있는 ajax코드가 실행된다.
  2. ajax 코드는 get 방식으로, 아규먼트인 title_give로 '봄날은 간다'를 가져간다.
  3. 그 '봄날은 간다'는 app.py 안의 함수인 test_get의 'title_give'에 들어가고, title_receive 변수 안에 저장된다.
  4. 그리고 print함수로 (title_receive)를 찍기 때문에 파이썬 콘솔에 '봄날은 간다'가 찍힌다.
  5. 문제가 없었으니 다시 클라이언트 단으로 넘어가서 response에 {'result' : 'success', 'msg' :'이 요청은 get'}가 저장되고
  6. console.log로 인해 브라우저의 콘솔 창에 json형식의 response가 찍힌다.

POST 요청의 코드 뜯어보기

프론트단의 function 안에 들어가는 ajax 코드이다.

button의 onclick에 function이 연동되어 있고, 그 function 안에 이 ajax 코드가 들어있다고 생각해보자.

$.ajax({
    type: "POST",
    url: "/test",
    data: { title_give:'봄날은간다' },
 #/test의 url 안에있는 request로 봄날은간다를 가져간다.
    success: function(response){
 #다시 response로 돌아온 json형식의 텍스트를 받아서
       console.log(response)
 #브라우저의 콘솔에 찍는다.
    }
  })

app.py

@app.route('/test', methods=['POST'])
def test_post():
   title_receive = request.form['title_give']
#그래서 title_give에 있는 '봄날은 간다'를 가져와서 title_receive에 넣는다.
   print(title_receive)
#그러면 콘솔에 '봄날은 간다'가 찍히고,
   return jsonify({'result':'success', 'msg': '이 요청은 POST!'})
#json 형식으로 {'result':'success', 'msg': '이 요청은 POST!'}를 response에 전달한다.

순서

  1. button을 누르는 순간 onclick이 동작해서 function를 실행한다.
  2. /test 안에 있는 '봄날은간다'가 request body를 통해 'title_give'에 저장된다.
  3. 저장된 title_give가 app.py로 건너와서 title_receive 안에 담기고, print 메서드를 통해 파이썬 콘솔에 찍힌다.
  4. 그리고나서 json형태에 담긴 파일이 response를 통해 다시 ajax에 전달되고,
  5. 오류가 없었으므로 response를 아규먼트로 갖는 function이 동작하고, 그 안에 있는 console.log 메서드를 통해
  6. 브라우저의 콘솔에 json이 찍힌다.

 

 

주의할 점

/test를 은행 창구에서 동시에 다른 방식으로 찾아갈 수는 없다.

POST 방식의 메소드가 2개가 붙을 수 없다.

DB는 왜 쓸까?

내 방 구조를 바꿀 때에는 크게 두가지 일로 나뉜다. 방을 꾸밀 것인가? 방을 정리할 것인가?

방 정리는 왜 할까? 내가 필요한 물건을 나중에 잘 찾기 위해 정리해야 한다.

그렇지 않다면 내 옷이 수백 벌이라고 생각했을 때 나가기 위해 옷 하나 찾는다고 해도 엄청난 시간이 소요될 것

데이터도 하나의 옷/책과 같아서 이를 정리해두어야 나중에 데이터를 쉽고 빠르게 꺼내 쓸 수 있다.

 

DB의 종류

DB는 크게 두가지로 나눌 수 있다.

RDBMS(SQL : Structured Query Language) -> 관계형 데이터베이스, 빡빡하지만 나중에 꺼내쓰기 편하다

No-SQL(Not only SQL) -> 딕셔너리 형태로 저장되어 있으며 저장하기에 유하지만 나중에 꼬이면 매우 힘들다

RDB에는 MySql, Oracle 등이 있고, No-SQL에는 MongoDB등이 있다.

어떤 데이터베이스는 RDB가 좋고 어떤 데이터는 NoSQL이 좋으니, 잘 골라서 써야 한다.

 

MongoDB

몽고DB는 NoSql의 대표주자로, 전체 DB 사용 랭킹에서는 5위 (2023.1 기준), NoSQL중에서는 압도적 1위를 달리고 있다.

출처 https://db-engines.com/en/ranking

몽고DB도 아틀라스를 통해 클라우드 형태로 DB를 제공해주고 있는데, 설치 및 사용도 간편한 축에 속한다.

 

주의할 점 및 실행

왼쪽 메뉴에서 Network Access를 열어 꼭 IP를 추가해야한다.

이렇게 해주지 않으면 도메인에서 들어오는 값이 저장되지 않는다.

이렇게 연결하고 파이썬에서 dnspython과 pymongo 패키지를 설치해준 후

from pymongo import MongoClient
client = MongoClient('여기에 URL 입력')
db = client.db'name 입력'

위와같은 코드를 써주고 insert, update, find 등의 코드로 확인해주면 된다.

패키지란?

모듈을 모아 놓은 단위로, 패키지의 모음은 라이브러리로 볼 수 있다.

이런 패키지를 돌리는 하나의 격리된 실행 환경을 가상 환경이라고 한다.

 

request 패키지

일종의 Ajax 역할을 하는 패키지로, API 데이터를 추출할 때 사용되며

대개 크롤링/스크래핑을 할 때 많이 사용한다.

사용 예

import requests # requests 라이브러리 설치 필요

r = requests.get('http://spartacodingclub.shop/sparta_api/seoulair')
rjson = r.json()

gus = rjson['RealtimeCityAir']['row']

for gu in gus:
	if gu['IDEX_MVL'] < 60:
		print (gu['MSRSTE_NM'], gu['IDEX_MVL'])

1. request 패키지의 get 함수를 사용해서 api 정보를 받아온다.

2. 받아온 api 정보를 json형식으로 파싱한다.

3. 파싱된 정보에서 내가 원하는 값을 추출하고 돌린다.

 

beautifulsoup 패키지

html 파싱을 통해 필요한 부분이 어디에 있는지를 파이썬 콘솔에서 찾고,

브라우저의 콘솔창에서 카피셀렉터를 통해 정확한 위치를 찾은 뒤, split으로 잘라서

soup.select 등으로 필요한 부분만 가져오면 된다.

사용 예

# 선택자를 사용하는 방법 (copy selector)
soup.select('태그명')
soup.select('.클래스명')
soup.select('#아이디명')

soup.select('상위태그명 > 하위태그명 > 하위태그명')
soup.select('상위태그명.클래스명 > 하위태그명.클래스명')

# 태그와 속성값으로 찾는 방법
soup.select('태그명[속성="값"]')

# 한 개만 가져오고 싶은 경우
soup.select_one('위와 동일')

그래서 결국 어떻게 원하는걸 가져오느냐?

카피셀렉터를 통해 만약 찾아낸 title의 주소가 old_content>table>tbody>tr:~~~ > td.title>div>a일때

import requests
from bs4 import BeautifulSoup

headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
data = requests.get('https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=pnt&date=20210829',headers=headers)

soup = BeautifulSoup(data.text, 'html.parser')

movies = soup.select('#old_content > table > tbody > tr')

for movie in movies:
    a_tag = movie.select_one('td.title > div > a')
    if a_tag is not None:
        print (a_tag.text)

이렇게 영화 제목만을 딸 수 있다.

+ Recent posts