Programming/Web

[개발일지/회고] 스파르타코딩클럽 웹개발 종합반 - 5주차

망고밥 2022. 2. 12. 16:07

드뎌 5주차 입성!!!!

벌써 5주차라니,,, 요새 학교를 안가서 그런가

시간이 왜이리 빨리도 가는지 ㅋㅋㅋ

마지막 회고를 써보도록 하겠습니다..! 8ㅅ8


수업 목표

  1. Flask 프레임워크를 활용해서 API를 만들 수 있다.
  2. '마이 페이보릿 무비스타'를 완성한다.
  3. EC2에 내 프로젝트를 올리고 자랑한다!

 

배운 것

FileZilla 설치 및 도메인 구입

  • FileZilla : 오픈소스 크로스플랫폼 무료 FTP 소프트웨어이다.

 

  • 필자는 설치 단계에서 문제가 생겨 아래 글을 참고하여 해결하였다.
 

파일질라 설치가 안 될 때 (지정한 파일에 액세스할 수 없습니다, 무료 FTP 프로그램 FileZilla)

문제 상황 파일질라 공식 홈페이지에서 다운로드 받은 설치 파일을 실행했을 때 (1) 항목에 액세스할 수 있...

blog.naver.com

 

 

  • 가비아는 IaaS형 클라우드 서비스와 PaaS, 그리고 SaaS형 그룹웨어 솔루션 등 비즈니스 IT에 필요한 서비스 전반을 제공하는 클라우드 기업이다. 사이트에 가입한 후, 원하는 도메인을 검색해 도메인을 구매한다!

 

  • .shop 도메인이 할인 중(500원/1년)이니 그것으로 선택 후 무통장 입금으로 결제를 진행하였다.

 

 

DB 만들기 (영화인 정보 웹 스크래핑)

  • init_db.py : 사용할 데이터를 웹 스크래핑 해서 DB에 저장하는 코드
import requests
from bs4 import BeautifulSoup

from pymongo import MongoClient

client = MongoClient('localhost', 27017)
db = client.dbsparta


# DB에 저장할 영화인들의 출처 url을 가져옵니다.
def get_urls():
    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/rpeople.nhn', headers=headers)

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

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

    urls = []
    for tr in trs:
        a = tr.select_one('td.title > a')
        if a is not None:
            base_url = 'https://movie.naver.com/'
            url = base_url + a['href']
            urls.append(url)

    return urls


# 출처 url로부터 영화인들의 사진, 이름, 최근작 정보를 가져오고 mystar 콜렉션에 저장합니다.
def insert_star(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')

    name = soup.select_one('#content > div.article > div.mv_info_area > div.mv_info.character > h3 > a').text
    img_url = soup.select_one('#content > div.article > div.mv_info_area > div.poster > img')['src']
    recent_work = soup.select_one(
        '#content > div.article > div.mv_info_area > div.mv_info.character > dl > dd > a:nth-child(1)').text

    doc = {
        'name': name,
        'img_url': img_url,
        'recent': recent_work,
        'url': url,
        'like': 0
    }

    db.mystar.insert_one(doc)
    print('완료!', name)


# 기존 mystar 콜렉션을 삭제하고, 출처 url들을 가져온 후, 크롤링하여 DB에 저장합니다.
def insert_all():
    db.mystar.drop()  # mystar 콜렉션을 모두 지워줍니다.
    urls = get_urls()
    for url in urls:
        insert_star(url)


### 실행하기
insert_all()

 


무비스타 API 설계

  • 영화인 정보(이름, 이미지, 좋아요 수, 최근 작품 내용)를 카드로 보여주는 API를 작성해야 함!
    1. 조회(Read) 기능: 영화인 정보 전체를 조회
    2. 좋아요(Update) 기능: 클라이언트에서 받은 이름(name_give)으로 찾아서 좋아요 수 증가
    3. 삭제(Delete) 기능: 클라이언트에서 받은 이름(name_give)으로 영화인을 찾고, 해당 데이터를 삭제

 

 

GET 연습 (무비스타 - 보여주기)

  • 서버코드 - app.py
@app.route('/api/list', methods=['GET'])
def show_stars():
    movie_star = list(db.mystar.find({}, {'_id': False}).sort('like', -1))
    return jsonify({'movie_stars': movie_star})

 

  • 클라이언트 코드 - index.html
function showStar() {
                $.ajax({
                    type: 'GET',
                    url: '/api/list?sample_give=샘플데이터',
                    data: {},
                    success: function (response) {
                        let mystars = response['movie_stars']
                        for (let i = 0; i < mystars.length; i++) {
                            let name = mystars[i]['name']
                            let img_url = mystars[i]['img_url']
                            let recent = mystars[i]['recent']
                            let url = mystars[i]['url']
                            let like = mystars[i]['like']

                            let temp_html = `<div class="card">
                                                <div class="card-content">
                                                    <div class="media">
                                                        <div class="media-left">
                                                            <figure class="image is-48x48">
                                                                <img
                                                                        src="${img_url}"
                                                                        alt="Placeholder image"
                                                                />
                                                            </figure>
                                                        </div>
                                                        <div class="media-content">
                                                            <a href="${url}" target="_blank" class="star-name title is-4">${name} (좋아요: ${like})</a>
                                                            <p class="subtitle is-6">${recent}</p>
                                                        </div>
                                                    </div>
                                                </div>
                                                <footer class="card-footer">
                                                    <a href="#" onclick="likeStar('${name}')" class="card-footer-item has-text-info">
                                                        위로!
                                                        <span class="icon">
                                              <i class="fas fa-thumbs-up"></i>
                                            </span>
                                                    </a>
                                                    <a href="#" onclick="deleteStar('${name}')" class="card-footer-item has-text-danger">
                                                        삭제
                                                        <span class="icon">
                                              <i class="fas fa-ban"></i>
                                            </span>
                                                    </a>
                                                </footer>
                                            </div>`
                            $('#star-box').append(temp_html)
                        }
                    }
                });
            }

 

 

POST 연습 (무비스타 - 좋아요 기능)

  • 서버 코드 - app.py
@app.route('/api/like', methods=['POST'])
def like_star():
    name_receive = request.form['name_give']

    target_star = db.mystar.find_one({'name': name_receive})
    current_like = target_star['like']

    new_like = current_like + 1

    db.mystar.update_one({'name': name_receive}, {'$set': {'like': new_like}})

    return jsonify({'msg': '좋아요 완료!'})

 

  • 클라이언트 코드 - index.html
function likeStar(name) {
    $.ajax({
        type: 'POST',
        url: '/api/like',
        data: {name_give:name},
        success: function (response) {
            alert(response['msg']);
            window.location.reload()
        }
    });
}

 

 

POST 연습 (무비스타 - 삭제 기능)

  • 서버 코드 - app.py
@app.route('/api/delete', methods=['POST'])
def delete_star():
    name_receive = request.form['name_give']
    db.mystar.delete_one({'name': name_receive})
    return jsonify({'msg': '삭제 완료!'})

 

  • 클라이언트 코드 - index.html
function deleteStar(name) {
      $.ajax({
          type: 'POST',
          url: '/api/delete',
          data: {name_give:name},
          success: function (response) {
              alert(response['msg']);
              window.location.reload()
          }
      });
  }

 

  • 전체 코드
<!--index.html-->

<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>마이 페이보릿 무비스타 | 프론트-백엔드 연결 마지막 예제!</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css"/>
        <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
        <style>
            .center {
                text-align: center;
            }

            .star-list {
                width: 500px;
                margin: 20px auto 0 auto;
            }

            .star-name {
                display: inline-block;
            }

            .star-name:hover {
                text-decoration: underline;
            }

            .card {
                margin-bottom: 15px;
            }
        </style>
        <script>
            $(document).ready(function () {
                showStar();
            });

            function showStar() {
                $.ajax({
                    type: 'GET',
                    url: '/api/list?sample_give=샘플데이터',
                    data: {},
                    success: function (response) {
                        let mystars = response['movie_stars']
                        for (let i = 0; i < mystars.length; i++) {
                            let name = mystars[i]['name']
                            let img_url = mystars[i]['img_url']
                            let recent = mystars[i]['recent']
                            let url = mystars[i]['url']
                            let like = mystars[i]['like']

                            let temp_html = `<div class="card">
                                                <div class="card-content">
                                                    <div class="media">
                                                        <div class="media-left">
                                                            <figure class="image is-48x48">
                                                                <img
                                                                        src="${img_url}"
                                                                        alt="Placeholder image"
                                                                />
                                                            </figure>
                                                        </div>
                                                        <div class="media-content">
                                                            <a href="${url}" target="_blank" class="star-name title is-4">${name} (좋아요: ${like})</a>
                                                            <p class="subtitle is-6">${recent}</p>
                                                        </div>
                                                    </div>
                                                </div>
                                                <footer class="card-footer">
                                                    <a href="#" onclick="likeStar('${name}')" class="card-footer-item has-text-info">
                                                        위로!
                                                        <span class="icon">
                                              <i class="fas fa-thumbs-up"></i>
                                            </span>
                                                    </a>
                                                    <a href="#" onclick="deleteStar('${name}')" class="card-footer-item has-text-danger">
                                                        삭제
                                                        <span class="icon">
                                              <i class="fas fa-ban"></i>
                                            </span>
                                                    </a>
                                                </footer>
                                            </div>`
                            $('#star-box').append(temp_html)
                        }
                    }
                });
            }

            function likeStar(name) {
                $.ajax({
                    type: 'POST',
                    url: '/api/like',
                    data: {name_give:name},
                    success: function (response) {
                        alert(response['msg']);
                        window.location.reload()
                    }
                });
            }

            function deleteStar(name) {
                $.ajax({
                    type: 'POST',
                    url: '/api/delete',
                    data: {name_give:name},
                    success: function (response) {
                        alert(response['msg']);
                        window.location.reload()
                    }
                });
            }

        </script>
    </head>
    <body>
        <section class="hero is-warning">
            <div class="hero-body">
                <div class="container center">
                    <h1 class="title">
                        마이 페이보릿 무비스타😆
                    </h1>
                    <h2 class="subtitle">
                        순위를 매겨봅시다
                    </h2>
                </div>
            </div>
        </section>
        <div class="star-list" id="star-box">
        </div>
    </body>
</html>

 

## app.py

from pymongo import MongoClient

from flask import Flask, render_template, jsonify, request

app = Flask(__name__)

client = MongoClient('localhost', 27017)
db = client.dbsparta


# HTML 화면 보여주기
@app.route('/')
def home():
    return render_template('index.html')


# API 역할을 하는 부분
@app.route('/api/list', methods=['GET'])
def show_stars():
    movie_star = list(db.mystar.find({}, {'_id': False}).sort('like', -1))
    return jsonify({'movie_stars': movie_star})


@app.route('/api/like', methods=['POST'])
def like_star():
    name_receive = request.form['name_give']

    target_star = db.mystar.find_one({'name': name_receive})
    current_like = target_star['like']

    new_like = current_like + 1

    db.mystar.update_one({'name': name_receive}, {'$set': {'like': new_like}})

    return jsonify({'msg': '좋아요 완료!'})


@app.route('/api/delete', methods=['POST'])
def delete_star():
    name_receive = request.form['name_give']
    db.mystar.delete_one({'name': name_receive})
    return jsonify({'msg': '삭제 완료!'})


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

 

 

프로젝트 서버에 올리기 (웹서비스 런칭)

  • 언제든지 클라이언트 요청에 응답하기 위해 필요한 조건
    1. 컴퓨터가 항상 켜저있고 프로그램이 실행되어있어야 함.
    2. 모두가 접근할 수 있는 공개 주소인 IP 주소(Public IP Address)로 접근할 수 있도록 해야 함.

  • 용어 설명
    • DNS(Domain Name System): 사람이 알아볼 수 있는 도메인 이름(URL)을 컴퓨터가 읽을 수 있게 IP 주소로 변환하는 시스템이다.
    • IP 주소(Internet Protocol address): 컴퓨터가 통신할 수 있도록 컴퓨터마다 가지는 고유한 주소. 서버는 하나의 주소를 가지고 있다.
    • 포트(Port): OS 통신의 종단점. 하나의 IP에 여러 포트가 있으며, 하나의 포트에 하나의 프로그램을 실행시킬 수 있다.

 

 

AWS 서버 구매하기

  • AWS EC2 콘솔페이지
 

https://ap-northeast-2.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-2

 

ap-northeast-2.console.aws.amazon.com

 

 

범용성이 좋은 Ubuntu Server 18.04 LTS를 선택한다.

 

 

 

 

  • EC2 서버 종료하는 방법 (1년 후 자동결제 방지) : 대상 인스턴스에 마우스 우클릭 → '인스턴스 상태' 를 클릭 → 중지 또는 종료 중 하나를 클릭하면 명령을 실행함.

 

 

EC2 접속하기

  • SSH(Secure Shell Protocol): 다른 컴퓨터에 접속할 때 쓰는 프로그램. 다른 프로그램에 비해 상대적으로 보안이 뛰어나다.

22번 포트가 열려있어야 접속 가능하다. (위와 같이 AWS EC2는 이미 22번 포트가 열려있다.)

 

 

  • Mac OS는 ssh가 있어서 터미널에서 바로 접근이 가능하지만, Window는 ssh가 없으므로 git bash를 사용해 접근한다.
# ssh -i 받은키페어를끌어다놓기 ubuntu@AWS에적힌내아이피
예시) ssh -i /path/my-key-pair.pem ubuntu@13.125.250.20

 

  • 간단한 리눅스 명령어
ls: 내 위치의 모든 파일을 보여준다.

pwd: 내 위치(폴더의 경로)를 알려준다.

mkdir: 내 위치 아래에 폴더를 하나 만든다.

cd [갈 곳]: 나를 [갈 곳] 폴더로 이동시킨다.

cd .. : 나를 상위 폴더로 이동시킨다.

cp -r [복사할 것] [붙여넣기 할 것]: 복사 붙여넣기

rm -rf [지울 것]: 지우기

sudo [실행 할 명령어]: 명령어를 관리자 권한으로 실행한다.
sudo su: 관리가 권한으로 들어간다. (나올때는 exit으로 나옴)

 

 

서버 세팅하기

  • 파일질라 실행 후 File → Site Manager → New Site를 클릭한다.

 

  • Host에 EC2 서버 IP를, User에 ubuntu를 입력한 후 OK 하면 서버의 파일들을 열람하거나 업로드 할 수 있게 된다.

로컬 PC와 원격 PC 간 파일 이동이 간편해진다!

 

  • initial_ec2.sh : 서버 환경(업그레이드, DB 설치, 명령어 등)을 통일하는 코드로, 실제 업무에서는 인프라 엔지니어 또는 개발 팀장님이 작성하는 경우가 많으므로 맥락만 이해해두자.
# UTC to KST : EC2 컴퓨터의 시간대를 한국으로 설정
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

# python3 -> python : python3 명렁어를 python으로 사용할 수 있게 설정
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10

# pip3 -> pip : pip3 설치 후 pip3 명령어를 pip으로 사용할 수 있게 설정
sudo apt-get update
sudo apt-get install -y python3-pip
pip3 --version
sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1

# port forwarding : 포트포워딩, 80포트로 들어오는 요청을 5000포트로 넘겨주는 명령어
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000

# MongoDB - install : mongoDB 설치
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
sudo apt-get update
sudo apt-get install -y mongodb-org

sudo mkdir -p /data/db

# MongoDB - run : mongoDB 실행
sudo service mongod start
sleep 7
netstat -tnlp

# MongoDB set user, set conf file
mongo admin --eval 'db.createUser({user: "test", pwd: "test", roles:["root"]});'
sudo sh -c 'echo "security:\n  authorization: enabled" >> /etc/mongod.conf'
sudo sed -i "s,\\(^[[:blank:]]*bindIp:\\) .*,\\1 0.0.0.0," /etc/mongod.conf

sudo service mongod stop
sudo service mongod start
sleep 5
netstat -tnlp

 

  • 위 파일을 파일질라에 업로드 후 git bash에서 아래 코드를 순서대로 입력하면 세팅이 완료된다.
sudo chmod 755 initial_ec2.sh
./initial_ec2.sh

 

  • Robo3T에서 접속 정보 세팅하기: 좌측 상단 아이콘 클릭 → Create 클릭 → 접속 정보 세팅 (Name, Address) → Authentication (Perform authentication 체크박스 클릭, 계정 아이디와 비번 입력 후 save)

 

 

flask 서버 실행

  • flask 설치
pip install flask

 

  • app.py : 기초 flask 서버 파일
from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
   return 'This is Home!'

if __name__ == '__main__':  
   app.run('0.0.0.0', port=5000, debug=True)

 

  • 파일질라를 통해 EC2에 업로드 한 후 실행
python app.py

 

  • 서버 실행이 되면 크롬에서 접속 시도 (아직 실행되지 않음. AWS에서 설정이 더 필요하다!)
http://[내 EC2 IP]:5000/

 

 

AWS에서 포트 열어주기

  • AWS에서 5000포트 열어주기: EC2 관리 콘솔 → 보안그룹(launch-wizard-1) → 해당 보안그룹 클릭 → Edit inbound rules → 80포트(HTTP 접속 기본 포트), 5000포트(flask 기본 포트), 27017포트(외부에서 mongoDB 접속 하기위한 포트) 추가

해당 인스턴스의 보안 그룹 클릭

 

해당 보안그룹 클릭

 

Edit inbound rules 클릭

 

80, 5000, 27017 세가지 포트 추가

 

  • app.py 코드 수정 : pymongo 계정 접속 코드
# client = MongoClient('mongodb://아이디:비밀번호@localhost', 27017)
client = MongoClient('mongodb://test:test@localhost', 27017)

 

  • 파일질라에서 homework 폴더를 EC2 인스턴스의 home/ubuntu 폴더에 업로드
# pymongo 패키지 설치
pip install pymongo
# home 디렉토리로 이동
cd ~

# 해당 폴더로 이동해서 아래 코드를 실행합니다.
python app.py
# 서버 파일 실행
python app.py

 

  • 브라우저에서 접속해보기
http://내AWS아이피:5000/

 

포트포워딩(port forwarding)

  • 포트 번호를 떼고 접속하기 : 현재 5000포트에서 웹 서비스가 실행되고 있어 :5000를 붙인 주소로 접근하고 있는데, 포트 번호를 입력하지 않아도 자동으로 접속하기 위해 포트포워딩(port forwarding)을 사용한다. (HTTP 요청에서는 80포트가 기본이라 :80을 붙이지 않아도 자동으로 연결된다.)

 

  • 리눅스에서 기본으로 제공하는 포트포워딩을 사용한다.

 

nohup 설정하기

  • SSH 원격 접속을 종료하더라도 서버가 계속 돌아가게 하기
nohup python app.py &

 

  • 서버 강제 종료하기
# 아래 명령어로 미리 pid 값(프로세스 번호)을 본다
ps -ef | grep 'app.py'

# 아래 명령어로 특정 프로세스를 죽인다
kill -9 [pid값]

 

도메인 구입하기

  • 도메인을 구입한다는 것은 네임서버를 운영해주는 업체에 IP와 도메인 매칭 유지비용을 내는 것.
  • 도메인 구입 후 DNS 관리 툴 클릭
 

웹을 넘어 클라우드로. 가비아

그룹웨어부터 멀티클라우드까지 하나의 클라우드 허브

www.gabia.com

 

 

도메인 연결 클릭

 

DNS 설정 클릭

 

호스트 이름에 @, IP주소에 IP주소를 입력

 

  • 네임서버에 내 도메인-IP가 매칭되는 시간이 필요해 10분 정도 기다린다.
  • http://내도메인/ 으로 접속하면 끝!!!!!

 

 

og태그 설정

  • 카카오톡, 페이스북, 슬랙 등 SNS에 공유했을 때 예쁘게 나오도록 꾸며보기!

  • static 폴더 아래에 이미지 파일(800x400)을 넣고, 프로젝트 html의 <head>태그 사이에 아래 내용 작성!
<meta property="og:title" content="내 사이트의 제목" />
<meta property="og:description" content="보고 있는 페이지의 내용 요약" />
<meta property="og:image" content="{{ url_for('static', filename='ogimage.png') }}" />

 

 

이미지를 바꾼 후 이전 이미지가 그대로 나온다면?

 

 

만든 것

 

mangobab shop

제로웨이스트 대나무 칫솔

mangobab.shop

 

 

좋았던 것

웹 프로젝트는 종종 해봤어도 배포는 처음 해보는 거였는데, 강의를 듣고 배포를 손쉽게 진행해 볼 수 있어서 뜻깊은 경험이었다!

메이킹 챌린지도 신청해서 열심히 참여해보겠다 !!!!