본문 바로가기

Development/Web

Flask 기초 내용, 그리고 API 와 HTTP 요청

들어가며

지금부터는 로컬 서버에 앞에서 다룬 내용으로 구현한 웹 페이지를 올려보도록 하겠다. 웹 서버를 구현하기 위한 프레임워크로는 대표적으로 Node.js(Express), Flask, Django 등이 있는데, 스파르타코딩클럽에서는 Flask로 웹 서버를 구현하는 방법에 대해서 다루었다.

Flask 개요

Flask는 서버를 구동시켜주는 프레임워크이다. 서버를 직접 구현할 수도 있지만, 이는 매우 복잡하기 때문에 대부분 웹 서비스를 개발할 때에는 '누군가 이미 개발해놓은' 프레임워크를 사용한다. 이번에도 마찬가지로 venv에 Flask를 설치할 것이므로, 프로젝트를 생성할 때 venv를 추가시켜주도록 하자.

설치

터미널에 아래 명령어를 입력하여 flask를 설치한다.

$ pip install flask

기본 코드

Flask로 서버를 구동시키는 기본 코드는 다음과 같다.

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return "Index Page"


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

코드를 보면, @app.route데코레이터를 통해 접속할 웹 페이지의 URL를 받은 후 index 함수를 실행하여 'Index Page' 문자열을 반환한다는 것을 알 수 있다. URL을 여러 개로 나누고 싶다면, 아래와 같이 코드를 작성하면 된다.

from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return "Index Page"


@app.route('/about')
def index():
    return "About Page"


@app.route('/help')
def index():
    return "Help Page"


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

서버 실행

프로젝트 폴더에 app.py를 생성하고, 위의 코드를 붙여넣은 후 실행시켜보면 아래와 같이 서버가 실행되고 있는 상태 메시지가 출력된다.

이제 크롬에서 http://localhost:5000/으로 접속하면, 'Index Page' 가 보이는 것을 확인할 수 있다. 서버를 종료시키고 싶다면, 터미널 창을 클릭 후 ctrl + c를 누르거나, 프로그램을 종료(stop)시키면 된다.

Flask 구조

Flask와 같은 프레임워크는 서로 다른 기본적인 구조를 갖추고 있는데, Flask의 폴더의 기본 구조는 아래와 같다.

./Project_Directory
    (/venv)
    /static
    /templates
    app.py

static 폴더는 이미지, CSS 파일 등 정적인 리소스를 저장하는 폴더이고, templates 폴더는 html 파일을 보관하는 폴더이다.

HTML 파일 관리

templates 폴더에 index.html을 생성하고, 아래와 같은 코드를 입력해보자.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <title>Index Page</title>
</head>
<body>
    <h1>Index Page</h1>
</body>

위에서는 URL에 접속하면 문자열을 보내주었는데, Flask의 내장함수인 render_template을 통헤 HTML 파일을 보내줄 수도 있다.

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return render_template("index.html")


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

위의 코드를 보면 index.html 파일의 경로는 render_template 함수에 의해 /templates로 지정되었다는 것을 유추해볼 수 있다.

API와 HTTP 요청 메소드

API(Application Programming Interface)

API는 서버와 클라이언트 간 소통할 수 있는 일종의 수단이며, HTTP 요청 메소드를 통해 데이터를 주고 받을 수 있다. API URL 작명 요령, RESTful API 원칙 등 API를 설계하는데에는 여러 지식과 정보가 필요하나, 여기에서는 간단하기 API를 구현해보고, 클라이언트와 소통하는 것까지만 다루었다.

HTTP 요청 메소드(GET, POST)

HTTP 요청 메소드는 이전 개발일지에도 남겨두었으나, API를 개발할 때 가장 중요한 개념이므로 다시 한 번 확인하고 넘어가자. HTTP 요청 메소드는 GET, POST, PUT, DELETE가 있으나, 이 중에서 다룰 내용은 GET, POST이므로 이 둘에 대한 내용만 정리하였다.


👉 GET

일반적으로 데이터 조회(Read) 요청 시 사용하는 메소드이며, URL뒤에 쿼리 스트링(Query String)을 입력하여 데이터를 전달할 수 있다. 예를 들어, 한국 영화에 해당하는 데이터를 요청할 때, /movie?country=한국으로 요청할 수 있으며, 물음표 뒤의 문자열을 쿼리 스트링이라고 한다. 쿼리 스트링에 담긴 데이터는 key=value로 서버에게 전달된다.

# app.py

@app.route('/api/movie', methods=["GET"])
def get_movie():
    response = {"success": True}

    data = request.args
    country = data.get('country')

    try:
        rows = []    # DB 조회 결과
        response["rows"] = rows
    except Exception as error:
        response["success"] = False
        response["error"] = str(error)

    return jsonify(response)
<!-- index.html -->

<script>
    $.ajax({
        type: "GET",
        url: "/api/movie?country=한국",
        data: {},
        success: (res) => {
            const {success, rows, error} = res;

            if (!success) {
                return console.log(error);
            }

              const movies = $('#movies');

            rows.forEach(row => {
                const {title, country} = row;
                const movie = `<div><h1>${title}><span>${country}</span></div>`;
                movies.append(movie)
            });
        }
    });
</script>

👉 POST

일반적으로 데이터 생성(CREATE), 수정(UPDATE), 삭제(DELETE) 요청 시 사용하는 메소드이며, GET 요청과는 다르게 HTML body에 {key: value} 형태로 데이터를 전달한다. GET 요청을 통해서도 데이터 생성, 수정, 삭제가 가능하나, POST 요청을 사용하는 이유로는 URL의 길이 제한, URL에 중요한 데이터가 포함되는 경우 보안 문제 등 여러가지가 있으며, 이에 대한 내용은 꽤 길기 때문에 추후 별도의 글로 정리하여 작성하겠다.

# app.py

@app.route('/api/movie', methods=["POST"])
def add_movie():
    response = {"success": True}

    data = request.form
    doc = {"title": data["title"],
           "country": data["country"]}

    try:
        print("Done")    # DB에 저장
    except Exception as error:
        response["success"] = False
        response["error"] = error

    return jsonify(response)
<!-- index.html -->

<script>
    $.ajax({
        type: "POST",
        url: "/api/movie",
        data: {title: '바람과 함께 사라지다', country: '한국'},
        success: (res) => {
            const {success, error} = res;

            if (!success) {
                return console.log(error);
            }

            alert('저장되었습니다.');
            document.location.reload();
        }
    });
</script>

마치며

4주차에 '데이터 삭제가 안 돼요', '데이터 수정이 안 돼요'라는 질문이 Slack에 자주 보이곤 했는데, 이는 자료형이 서로 다르기 때문에 발생하는 것임을 인지해야 한다. MongoDB에서 데이터를 수정하거나 삭제할 때에는 주로 _id를 사용하는데, 이는 ObjectId라는 또 다른 자료형의 데이터이다. 즉, Ajax를 통해 POST 요청 시 전달받은 문자열 형태의 _id로 MongoDB에서 데이터를 검색하는 경우 해당 _id를 찾을 수 없기 때문에 삭제나 수정을 할 수 없다. 이를 해결하려면 MongoDB를 처리하는 코드에서 전달받은 _id를 ObjectId 자료형으로 변환해주어야 한다. 이를 간단하게 나타낸 코드는 아래와 같으며, 실제로 제출한 과제의 코드는 여기를 클릭하여 확인할 수 있다.

from bson import ObjectId

def delete_movie(_id):
    target = {_id: ObjectId(_id)}
    db.delete_one(target)

다음 5주차는 마지막주차로, 지금까지 개발한 Flask 웹 서버를 AWS에 업로드하여 실제 서비스를 운영하는 방법에 대해서 다룬다. 실습에 참여하고 싶으나, AWS 계정을 생성한지 1년이 지나서 프리티어(1년간 무료)를 벗어났기 때문에 내용만 확인하였고, AWS, Oracle Cloud 등 클라우드 서비스를 사용해본 경험이 있기 때문에 나중에 Oracle 클라우드로 실습한 내용을 따로 정리하도록 하겠다.