<스파르타 웹개발> 4주차 개발일지

결과물

기존에 완성했던 팬명록 페이지에

서버와 클라이언트의 상호작용을 포함하여 완성하였다. 

 

두 가지 기능이 있는데, 

 

1. 코멘트 저장 기능

client side : 닉네임과 응원댓글을 남기고 버튼을 클릭하면

server side : 해당 내용을 받아 다음과 같은 창을 띄워주고

이 내용을 DB에 저장해준다.

 

2. 저장된 코멘트 불러오기 기능

페이지를 로드하면

show_comment 함수가 실행되면서 DB에 있는 응원댓글 내용들을 불러와서

이와 같이 페이지에 보여준다.

 


4주차에 배운 내용들..

4주차에 배운 내용의 핵심은 아래와 같다. 

  • 서버와 클라이언트의 상호작용
  • 프로젝트를 만드는 기본적인 사고의 순서

 

1. 서버

예전엔 생각없이 어느 웹페이지가 안되거나 하면 서버 터졌네~ 어쩌네 하며 단순히 서버라는 단어를 사용했다. 

그러나, 이 수업을 들으면서 서버가 어떤 역할을 하는지 생각해보면 

말 그대로 식당에서 serving을 하는 server와 똑같이

웹페이지를 이용하는 사람에게 이런 저런 서비스(기능)를 제공하는 역할을 하는 것이 서버였다. 

 

이 때 '기능'이라고 하면 잘 기억은 나지 않지만 2주차에 복습하며 잠시 언급한..

https://nogrowth.tistory.com/56?category=1064610 

 

<스파르타코딩클럽> 2주차 개발일지

결과물 1주차 복습 1주차 간단히 복습하고 넘어가자. 1. 웹페이지를 이루는 3가지 요소 HTML(뼈대), CSS(꾸미기), JavaScript(반응) 2. HTML(tag)의 구조는 들로 이루어지고, tag의 추가 속성은 attribute로 tag..

nogrowth.tistory.com

 

클라이언트는 서버에 정보 조회를 요청하거나(Read)

정보를 만들거나(Create), 변경하거나(Update), 삭제하라고(Delete) 요청할 수 있다.

이를 앞글자를 따서 보통 CRUD라고 지칭한다.

 

이 때 은행 창구와 같은 역할을 하는 것이 API라고 한다. 

 

잠깐, 그렇다면 서버와 API는 어떻게 다를까? 또 헷갈리기 시작하지만 검색해봐도 개념 정리가 잘 안되어서 묻어두고 넘어가자.

 

이번에 듣는 웹개발 강의에서 만드는 '웹페이지'들은 보통 

 

사용자 : 정보를 입력할래! -> 서버 : 입력한 정보를 DB에 저장한다. 

사용자 : 정보를 조회할래! -> 서버 : DB에 저장된 정보를 불러와서 보여준다. 

 

이정도의 역할을 하게 된다. 

 

이러한 기능을 직접 만들 수도 있겠지만.. 

python의 프레임워크인 Flask를 사용하면 몇 줄 만으로 이런 기능을 구현할 수 있다. 

 

2. 서버 만들기 - Flask

Flask의 기본 사용은 다음과 같다. 

static 폴더에는 CSS, image 파일이 들어간다. 

templates 폴더에는 웹페이지의 html 파일이 들어간다. 

app.py가 서버 구동 파일이다. (여기서 Flask를 사용한다.)

 

app.py의 기본적인 구조는 다음과 같다. 

# import하는 부분
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

# index.html을 불러오는 부분
@app.route('/')
def home():
    return render_template('index.html')

# 서버의 기능을 구현하는 부분
@app.route("/homework", methods=["POST"])
def homework_post():
    name_receive = request.form['name_give']
    comment_receive = request.form['comment_give']

    doc = {
        'name': name_receive,
        'comment': comment_receive
    }
    db.homework.insert_one(doc)
    return jsonify({'msg':'저장 되었습니다!'})

1) Import 부분

여느 패키지처럼 import하게 되고

(통상적으로) app이라는 변수에 Flask(__name__)을 저장한다. 

이때 Flask는 이 패키지의 뼈대가 되는 class라고만 알고 넘어가자. 

 

2) index.html을 불러오는 부분

@app.route('/') : 기본 주소가 실행되면 home함수가 실행되며

이때 render_template 메서드를 이용하여 우리가 만든 index.html 을 로드하여 웹페이지를 보여준다.

 

3) 기능 구현 부분

@app.route('/homework', method=['POST']

=> homework라는 창구(url)에 POST 요청이 들어오면 실행된다는 의미이다. 

이 아래에 실행될 함수가 정의되고

함수가 모두 실행되고 나면 jsonify 메서드를 통해 필요한 정보가 JSON 형식으로 클라이언트에게 넘어간다. 

 

 

3. 서버-클라이언트 상호작용

client side에는 우리가 만든 웹페이지인 index.html이 보여진다. 

server side는 Flask를 이용해서 만든 서버인 app.py가 있다. 

 

이번에 만든 페이지가 제공하는 기능들을 통해 어떤 식으로 서버와 클라이언트의 핑퐁이 이루어지는지 살펴보자. 

 

페이지 켜기

핑퐁의 과정

client side : 1) 페이지가 로딩되면, 2) 서버에 "정보들을 보여줘"라고 요청을 보낸다. (조회할래! Read, 즉 GET 요청)

server side : 3) 요청을 받으면 DB에서 현재 저장된 정보들을 불러와, 4) client에 넘겨주면

client side : 5) 받은 정보를 웹페이지에 뿌려서 보게된다. 

 

이때 5)의 경우 얼핏 생각하면 client가 해야하나? server가 해줄 수는 없나? 싶기도 한데

이 또한 2주차에 내가 개요를 공부하면서 괴리를 느꼈던 부분과 비슷한 부분이기도 하다. 

즉 웹페이지에 보여줄 정보를 보내주는 것은 server가 할 일이고, 이 정보를 웹페이지에 그려내는 것은 html, css, JS 등 frontend에서 할 일이라는 것을 되새기고 가자. 

 

1) 페이지가 로딩되면, 2) GET 요청을 보낸다. 

client side인 index.html의 일부를 보자. 

<script> 부분에 다음과 같은 코드가 있다. 

이전에도 다뤘듯, document(현재의 index.html document) 준비되면(ready)

set_temp()와 show_comment() 함수를 실행한다. 

(set_temp 함수는 이전에 만들었던 현재 날씨를 보여주는 함수이다)

 

show_comment함수는 다음과 같이 생겼다. 

        function show_comment() {
            $.ajax({
                type: "GET",
                url: "/homework",
                data: {},
                success: function (response) {
                    let rows = response['list']
                    for (let i = 0; i < rows.length; i++) {
                        let name = rows[i]['name']
                        let comment = rows[i]['comment']
                        let temp_html = `<div class="card">
                                            <div class="card-body">
                                                <blockquote class="blockquote mb-0">
                                                    <p>${comment}</p>
                                                    <footer class="blockquote-footer">${name}</footer>
                                                </blockquote>
                                            </div>
                                        </div>`
                        $('#comment-list').append(temp_html)
                    }
                }
            });
        }

순서대로 보면 ajax 콜을 해서 GET 요청을 하게 되는데

잠시 리마인드 해보면 Ajax는 JavaScript의 라이브러리로, API에 요청을 쉽게 할 수 있게 해주는 도구이다. 

 

GET type 요청을

/homework라는 url에 하는데

GET 요청이므로 따로 들고가는 data는 없으며({})

요청이 성공할 경우 서버에서 주는 내용을 response로 받는다. 

 

3) 요청을 받으면 DB에서 현재 저장된 정보들을 불러와, 4) client에 넘겨주면

이제 server side인 app.py로 넘어가보자. 

@app.route("/homework", methods=["GET"])
def homework_get():
    comment_list = list(db.homework.find({},{'_id':False}))
    return jsonify({'list':comment_list})

app.py에서는 /homework라는 주소에 GET 요청이 들어오면 

homework_get()이라는 함수를 실행하게 된다. 

 

이 때 pymongo 라이브러리의 기능을 이용해서 (DB와의 상호작용)

comment_list 에 DB에 저장된 모든 내용을 id만 빼고 ('_id':False) 가져와서 list형태로 저장한다. 

이후 이를 jsonify 메서드를 이용하여 JSON 형태로 (딕셔너리 형태!) client에 넘겨준다. 

 

5) 받은 정보를 웹페이지에 뿌려서 보게된다.

위의 ajax 부분을 다시 보면, 

요청이 성공할 경우 서버에서 받은 대답을 response에 저장하는데, 

우리는 대답을 jsonify를 이용해서 만든 JSON 형태로 받았으므로 아마 딕셔너리 모양일 것이다. 

이를 console에 찍어보면,

이와 같은 내용임을 알 수 있다. 

success: function (response) {
    let rows = response['list']
    for (let i = 0; i < rows.length; i++) {
        let name = rows[i]['name']
        let comment = rows[i]['comment']
        let temp_html = `<div class="card">
                            <div class="card-body">
                                <blockquote class="blockquote mb-0">
                                    <p>${comment}</p>
                                    <footer class="blockquote-footer">${name}</footer>
                                </blockquote>
                            </div>
                        </div>`
        $('#comment-list').append(temp_html)

다시 위의 부분을 살펴보면

response의 구조가 list > 0, 1, 2 와 같은 형식이므로

rows에 response['list']를 저장하고

그 안에서 for문을 돌려 정보들을 각각 name, comment라는 변수에 저장한 후

 

우리가 만들고 싶은 구조를 html로 작성하여 temp_html에 저장하는데 (이때 내용은 '가 아닌 `백틱` 사이에 들어가야 한다)

변수가 들어가는 부분은 ${변수명}으로 대체한다. 

 

이를 우리가 넣고 싶은 태그의 id인 comment-list를 지정하여 append 메소드로 추가해주면 된다. 

$('#id명').append()

 

그러면 페이지의 해당 부분에 해당 내용이 뿌려지게 된다. 

 

 

응원 문구 저장하기

핑퐁의 과정

client side : 1) 내용을 입력하고 <응원남기기>버튼을 누르면, 2) 서버에 "정보들을 저장해줘"라고 요청을 보낸다. (저장할래, Create!, 즉 POST 요청)

server side : 3) 요청과 함께 받은 데이터를 DB에 저장한다.

client side : 4) 저장되었습니다! 라는 알림이 뜨고, 페이지가 새로고침 된다. 

 

1) 내용을 입력하고 버튼을 누르면,

client side인 index.html을 먼저 살펴보자.

닉네임과 댓글을 작성하고 우리가 누를 버튼인 <응원남기기>버튼이 눌려지면(onclick)

<button onclick="save_comment()" type="button" class="btn btn-dark">응원 남기기</button>

onclick="save_comment()" attribute로 save_comment()함수가 실행된다.

함수는 다음과 같다. 

function save_comment() {
    let name = $('#name').val()
    let comment = $('#comment').val()
    $.ajax({
        type: 'POST',
        url: '/homework',
        data: {name_give: name, comment_give: comment},
        success: function (response) {
            alert(response['msg'])
            window.location.reload()
        }
    })
}

이제 한결 읽기가 편하다. 

<div class="form-floating mb-3">
    <input type="text" class="form-control" id="name" placeholder="url">
    <label for="floatingInput">닉네임</label>
</div>
<div class="form-floating">
<textarea class="form-control" placeholder="Leave a comment here" id="comment"
      style="height: 100px"></textarea>
    <label for="floatingTextarea2">응원댓글</label>
</div>

사용자가 닉네임을 입력하는 input box의 id는 name

응원문구를 입력하는 textarea의 id는 comment이므로

 

name = $('#name').val()

comment = $('#comment ').val() 를 이용하여 사용자가 입력한 값을 각각 name과 comment라는 변수에 저장한다. 

 

2)  서버에 "정보들을 저장해줘"라고 요청을 보낸다.

 

이후 요청을 위해 ajax 콜을 한 후,

/homework라는 url

POST 요청을 하는데

우리가 저장한 변수를 가져가야 하므로

{name_give: name, comment_give: comment}

라는 모양으로 데이터를 서버에 보내준다. 

 

3) 요청과 함께 받은 데이터를 DB에 저장한다.

요청을 받은 서버가 어떻게 행동하는지 app.py를 살펴보자. 

@app.route("/homework", methods=["POST"])
def homework_post():
    name_receive = request.form['name_give']
    comment_receive = request.form['comment_give']

    doc = {
        'name': name_receive,
        'comment': comment_receive
    }
    db.homework.insert_one(doc)
    return jsonify({'msg':'저장 되었습니다!'})

/homework라는 urlPOST 요청이 들어왔으므로

homework_post 함수가 실행된다.

 

이때 name_receive에 name_give가 저장되고 (request.form은 뭐 flask가 제공하는 메서드일 것이다..)

comment_receive에 comment_give가 저장된다. 

 

다시한번 pymongo의 insert_one 기능을 이용하여 DB에 저장해주며

dictionary 형태로 (왜? mongoDB는 딕셔너리 형태를 가지는, 정확히는 document 형태 No-SQL이기 때문에) doc를 정의하여 이를 db의 homework라는 collection에 저장해준다. 

 

이후 response에 JSON형태인 {'msg' : '저장되었습니다'}를 클라이언트로 보내준다. 

 

4) 저장되었습니다! 라는 알림이 뜨고, 페이지가 새로고침 된다. 

success: function (response) {
    alert(response['msg'])
    window.location.reload()
}

response는  {'msg' : '저장되었습니다'}이므로 response['msg']를 alert로 띄워주고

window.location.reload() 메서드로 페이지를 새로 고침 해준다.

새로고침 되면 당연히 위의 페이지 켜기 과정을 통해 DB의 모든 데이터가 다시 불러와서 뿌려진다. 

 

4. 프로젝트 구상의 순서와 팁

지금까지 사용자의 입력을 받아 DB에 저장하고, 사용자에게 DB에 저장된 내용을 보여주는 간단한 웹사이트들을 만들어 보았다. 

전체적으로 보면 복잡할 수도 있지만, 하나씩 뜯어보면 그리 복잡하지 않은데, 다음과 같은 과정을 거치면 더 편하게 만들 수 있을 것 같다. 

 

1. 기능을 구상하기 (핑퐁)

대부분의 기능은 다음과 같은 구조를 갖는다. 

 

client가 뭘 요청하면(ajax를 이용한 GET, POST 요청)

-> (Flask로 만든) server에서 뭔가를 해주고(여러 기능이 있겠지만 DB에 저장하는 경우 pymongo 이 있겠다.)

-> server에서 처리한 내용을 다시 client로 보내주면 (response)

-> client가 답변을 받는다. (답변을 다시 frontend에서 가공해서 보여준다.)

 

이렇게 특정 기능을 구현할 때 서버 사이드에서 무엇을 해야하는지, 클라이언트 사이드에서 무엇을 해야하는지 정리하고 기능을 구현하는 것이 중요하겠다. 

 

 

2. 조각 기능 만들어보기

물론 두뇌와 기억력이 아주 비상하여 모든 기능을 한번에 왔다갔다 하며 구현할 수 있다면 참 좋겠지만, 현실적으로 쉽지 않다. 따라서 특정 기능이 정말로 구현 가능한지를 미리 만들어보고 적용하면 편리하다. 

 

예를 들어, (강의에서 실습했던 내용 중 하나이다.)

클라이언트가 영화 URL을 입력하면

해당 URL에서 영화 제목과 포스터, 줄거리 설명 등을 가져와서 DB에 저장하고 싶다면

 

URL을 이용하여 영화 제목과 포스터, 줄거리 설명등만 크롤링 하는 기능을 따로 파일을 만들어서 구현해보면 된다. 

import requests
from bs4 import BeautifulSoup

url = 'https://movie.naver.com/movie/bi/mi/basic.naver?code=191597'

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')

title = soup.select_one('meta[property="og:title"]')['content']
image = soup.select_one('meta[property="og:image"]')['content']
desc = soup.select_one('meta[property="og:description"]')['content']

print(title, image, desc)

해당 임시 파일에서 기능이 정상 작동하면, 프로젝트에 가져다 쓰면 된다. 

본 프로젝트에 처음부터 기능을 구현하다가 꼬여버리면 너무 복잡해질 수 있기 때문이다. 

 

 

 

5. 정리

여기까지 총 5주차 과정 중 4주차까지가 끝났다. 

이제는 사용자와 아주 간단한 상호작용을 할 수 있는 웹페이지와 서버를 구성하는 것은 할 수 있게 되어 아주 큰 수확이라고 생각하고

5주차에는 이를 실제로 internet에 올리는 실습을 하게 된다니 기대가 된다. 

 

물론 실제로 유용하고 복잡한 여러 기능들을 제공하는 웹페이지를 만드는 것이 쉽지는 않겠으나

그 기반이 마련된 것에 만족하며, 

 

웹페이지를 만드는 편리한 도구들이 많이 나오고는 있지만

그래도 이런 일들이 어떻게 돌아가는지를 이해하고 있어야 그런 도구들을 사용하더라도 내 마음이 편할 것이다....

댓글()