테키테크 TEKITECH

Flask 기반의 파이썬 웹 프로그래밍 part.1, Harvard's CS50(2018), Intro to Computer Science, 7. Web Programming with Flask 본문

Tech/Web

Flask 기반의 파이썬 웹 프로그래밍 part.1, Harvard's CS50(2018), Intro to Computer Science, 7. Web Programming with Flask

TEKI 2021. 9. 24. 01:10

하버드 2018년 CS50의 7번째 수업 <Web Programming with Flask> 내용 정리 part.1
Flask로 기숙사 신청 페이지 구축 및 동적 웹 구조 이해 (for Beginners)
하단에 강의 영상(유튜브) 첨부

강의 순서

  • HTTP (Hypertext transfer protocol)
  • Python Web Framework, Flask 파이썬 웹 프레임워크, 플라스크
  • Controller and View 컨트롤러와 뷰
  • Flask 플라스크
  • Split Controller Code and View Code 컨트롤러와 뷰 코드 분리하기
  • request 요청
  • Register for Frosh IMs 기숙사 신청 페이지 만들어보기
  • Flask Web Server Log 플라스크 웹 서버 로그
  • Add Submit Button 제출 버튼 추가하기
  • Form Tag: Action and Method
  • Route 라우트
  • Maintain Multiple Web Pages via layout.html 웹 페이지에서 레이아웃 활용하기
  • Dynamic Contrustion via Entends Extends 문법으로 다이나믹한 구조 구축하기
  • Dynamic Web Structure 동적 웹 구조
  • Any Questions?

 

수업에서 사용했던 코드

 

GitHub - lagunerio/example-code-for-cs50: Example Code for Harvard's CS50(2018)

Example Code for Harvard's CS50(2018). Contribute to lagunerio/example-code-for-cs50 development by creating an account on GitHub.

github.com

 

 

 

파이썬으로 다른 언어를 만들어보자

 

 

 

 

 

HTTP (Hypertext transfer protocol)

편지봉투를 생각해보자.

이 편지봉투는 정보가 담긴 패킷이다.
그냥 0과 1로 이루어져 있는 봉투인데, 얘네는 인터넷을 타고 넘어온다.
봉투에 담아 보내는 편지에는 이런 웹 request가 적혀 있다.

GET / HTTP/1.1
Host: www.example.com
...

HTTP는 답장에 적힌 숫자가 200이길 바라겠지만, 404나 500도 받는 것을 우리는 여러 번 봤다.

 

 

 

 

 

Python Web Framework, Flask 파이썬 웹 프레임워크, 플라스크

개발을 하면서 한 파일에 코드를 몽땅 넣지는 않는다. 이렇게 하는 대신 SoC(Seperation of Concerns), 즉 기능/목적 등에 따라 파일을 분리해 개발한다. 이러면 개발부터 유지 및 보수가 훨씬 쉽다.

하지만 과거 파이썬 개발자들에겐 문제가 남아있었다. 파이썬으로 웹 개발을 하려면 실행 한 번 할 때마다 같은 코드를 줄줄이 써주어야 했고, 여러 개발자가 협업하기에도 쉽지 않은 환경이었다. "Hello, World!"를 출력하는 간단한 웹을 가장 지루하고 짜증 나게 만들어보자.

from http.server import BaseHTTPRequestHandler, HTTPServer

class HTTPServer_RequestHandler(BaseHTTPRequestHandler):
	
    def do_GET(self):
    	self.send_response(200)
        
        self.send_header("Content-type", "text/html")
        self.end_headers()
        
        self.wfile().write(b"<!DOCTYPE html>")
        self.wfile().write(b"<html lang='en'>")
        self.wfile().write(b"<head>")
        self.wfile().write(b"<title>hello, title</title>")
        self.wfile().write(b"</head>")
        self.wfile().write(b"<body>")
        self.wfile().write(b"Hello, World!")
        self.wfile().write(b"</body>")
        self.wfile().write(b"</title>")
        self.wfile().write(b"</html>")
        
port = 8080
server_address = ("0.0.0.0", port)
httpd = HTTPServer(server_address, HTTPServer_RequestHandler)
httpd.serve_forever()

 

이렇게 열심히 쓴 코드에 기능은 GET밖에 없다. 웹 프레임워크는 이런 문제를 해결해준다. CSS에는 Bootstrap이라는 웹 프레임워크가 있다. JavaScript에도 쓸만한 것들이 있다. 그래서 파이썬에도 웹 프레임워크가 생겼다. 아름다운 Flask를 소개한다.

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route("/")
def index():
     return "Hello, World!"
728x90

 

 

 

 

Controller and View 컨트롤러와 뷰

C나 파이썬으로 만든 코드는 대부분 컨트롤러 코드 또는 컨트롤러 로직이라고 볼 수 있다. 비즈니스 로직이라고 해도 된다. 그러니까 논리적으로 원하는 바를 수행하도록 만든 코드라는 말이다.

웹 개발에는 이렇게 논리적인 구현을 하는 역할 외에 직접 움직이는 역할도 있어야 한다.  사용자가 직접 눈으로 보고, 만지고, 우리가 보여주려는 데이터를 띄워주고 하는 HTML이나 CSS의 역할을 말한다.

MVP모델에서 이렇게 머리(Logical)와 몸(Aesthetic)의 역할을 컨트롤러와 뷰로 나누어 설명한다. (M은 다음 주에 데이터베이스와 같이 설명할 예정이다.) 보통 이 둘은 서로 다른 파일로 나누어 개발한다.

 

 

 

 

 

 

Flask 플라스크

위에서 소개한 플라스크 코드를 해석해보자. 먼저 플라스크를 불러야 한다. flask 프레임워크에서 Flask와 render_template, request를 import 해준다.

from flask import Flask, render_template, request

 

이제 플라스크에게 Web Application을 달라고 정중하게 부탁한다. 이 명령어로 이 코드가 들어간 파일을 Web Application으로 만들어줄 수 있다. 앞으로 플라스크로 Web Application을 만들 때마다 이 코드를 복붙해주면 된다.

app = Flask(__name__)

 

플라스크에게 또 부탁을 해본다. "아까 말한 app을 만들건데, 얘가 편지 봉투에 든 슬래쉬(/)를 항상 listening 했으면 좋겠어."

@app.route("/")

 

이어서 얘기한다. "그래서 누가 슬래쉬(/)에게 request를 하면 "Hello, World!"를 return 해줘야 해."

def index():
    return "Hello, World!"

 

코드를 저장하고 flask run을 해주면 바로 실행된다! 출력된 글자 중에서 * Running on ... 뒤에 나오는 URL이 방금 만들어준 웹 페이지의 주소이다. 이 URL을 눌러보면 "Hello, World!"가 보인다. 브라우저에서 소스코드를 열어보자. 코드에서는 쓴 적 없는 HTML 코드가 보인다. 

 

 

 

 

 

 

Split Controller Code and View Code 컨트롤러 코드와 뷰 코드 분리하기

웹 서버를 내리고 코드를 조금 수정해보자.

def index():
    return render_template("index.html")
더보기

index.html

<html lang="en">
	<head>
		<meta name="viewport" content="initial-scale=1, width=devide-width">
		<title>hello</title>
	</head>
	<body>
		Hello, {{ name }}
	</body>
</html>

 

이제 다시 웹 서버를 올리고 브라우저에서 소스코드를 확인해보자. 이번에는 index.html의 내용이 보인다. 이렇게 Controller 코드와 View 코드를 완벽하게 분리할 수 있다.

 

 

 

 

 

 

request 요청

이번에는 데이터를 받아서 띄워보자.

def index():
    name = request.args.get("name")
    return render_template("index.html", name=name)

 

index.html의 변수 name에 request 값을 저장한 name을 넣어준다는 뜻이다. GET으로 name 값을 받으면 URL 뒤에 ?name= 를 붙이고, 입력값을 붙여주면 된다.

https://my_domain:8080/?name=David

 

새로운 URL로 들어가보면 아까 index.html에서 {{ name }} 가 있었던 자리에 David로 바뀌는 걸 볼 수 있다. name, world, 아니면 빈칸을 넣어봐도 잘 된다. 빈칸을 입력하면 World!로 바뀌도록 바꿔보자.

def index():
    name = request.args.get("name")
    if not name:
        name = "World!"
    return render_template("index.html", name=name)

 

아니면 GET에 들어온 값이 없을 때, World!가 들어가도록 하는 방법도 있다.

def index():
    name = request.args.get("name", "World!")
    return render_template("index.html", name=name)

 

 

 

 

 

 

Register for Frosh IMs 기숙사 신청 페이지 만들어보기

flask 웹 페이지로 기숙사를 신청하는 페이지를 만들어보자. HTML에서 사용할 수 있는 input type에는 뭐가 있을까?

  • Text Boxes
  • Check Boxes
  • Radio Buttons
  • ...

기숙사를 선택하는 기능을 구현하는 데에는 드롭다운 형식이 어울린다. 수많은 HTML input 타입 중에서 드롭다운을 구현하려면 select 태그가 제일 알맞다. select 메뉴로 기숙사를 선택할 수 있는 기능을 index.html에 추가해보자.

<html lang="en">
	<head>
		<meta name="viewport" content="initial-scale=1, width=devide-width">
		<title>froshim0</title>
	</head>
	<body>
		<h1>Register for Frosh IMs</h1>
		<form>
			<input name="name" placeholder="Name" type="text">
			<select name="dorm">
				<option value="Apley Court">Apley Court</option>
				<option value="Canaday">Canaday</option>
			</select>
		</form>
	</body>
</html>

 

input 태그의 placeholder에 Name을 넣어서 여기가 기숙사의 이름이 들어가는 곳이라고 알려주자.
option 태그에서 ""로 감싼 값(value)은 컴퓨터가 보는 값이고, option 태그로 감싼 값(value)은 기숙사를 신청할 학생이 화면에서 보게 될 값이다. 

 

 

 

 

 

 

Flask Web Server Log 플라스크 웹 서버 로그

열심히 코드를 짜고 플라스크를 실행했는데 코드 500, Internal Server Error가 발생했다. 이렇게 웹 서버에 문제가 생기면 어떻게 해야 할까?

위 캡쳐 화면과 같이 flask run을 실행한 콘솔에서 flask의 실행 로그와 웹 서버의 로그를 모두 보여준다. Flask를 실행하면 웹 서버를 실행하는 것이다. 그래서 이 로그에는 TCP/IP request 로그부터 오류 로그까지 다 남는다. 그러니까 Flask를 실행하면, 실행한 콘솔을 잘 지켜보면 된다.

오류 로그를 확인해보니 Template을 찾지 못한 것 같다. index.html을 template 폴더에 넣지 않는 바보 같은 실수를 했다. (render_template 함수를 사용하면, 기본적으로 templates 폴더 안에 있는 파일을 확인한다. 따라서 ./templates/ 하위에 파일을 넣어주어야 한다). 먼저 웹 서버를 종료하고, template 폴더를 만들어서 index.html을 옮겨주자.

teki@teki-MacBookPro 06_Register_for_Frosh_IMs % ls
application.py	index.html
teki@teki-MacBookPro 06_Register_for_Frosh_IMs % mkdir templates
teki@teki-MacBookPro 06_Register_for_Frosh_IMs % mv index.html templates
teki@teki-MacBookPro 06_Register_for_Frosh_IMs % ls
application.py	templates
teki@teki-MacBookPro 06_Register_for_Frosh_IMs % ls templates
index.html

 

행운을 빌며 다시 실행을 시켜보면..

 

성공!! 로그도 예쁘게 나온다.

 

 

 

 

 

 

Add Submit Button 제출 버튼 추가하기

기숙사 신청 웹 페이지를 띄웠는데 무언가 이상하다... 제출 버튼이 없다! 얼른 submit 타입 input 버튼을 추가해준다. 기숙사를 선택하는 드롭다운 메뉴라는 걸 알려주기 위해서 disabled 된 Dorm 항목도 추가해준다. 그리고 웹 페이지를 새로고침 해준다.

<input type="submit" value="Register">

 

그런데 추가한 버튼이 안 생긴다. 웹 서버를 종료하고 Flask를 다시 실행시켜서 수정한 파일을 포함한 모든 데이터를 새로 불러와 서버를 다시 올려줘야 수정 사항이 반영된다. Flask를 다시 실행하고 새로고침 해주면 이제 Dorm 항목과 Register 버튼이 보인다.

 

 

 

 

 

 

Form Tag: Action and Method

👨‍🏫 우리가 재미로 구글을 만들어 봤을 때, form 태그에 또 뭘 넣었었는지 기억나는 학생?
🧑‍🎓 Action입니다.
👨‍🏫 Action이 뭘 하는 태그인데?
🧑‍🎓 무언가를 하는 것입니다.

맞다. 우리는 action으로 이 form 태그의 내용을 어딘가로 보낼 것이다. (register 정보이니까) 왠지 보내야 하는 라우터 이름이 /register 일 것 같다. 구글에서는 /search 라우터였는데 이건 검색하는 행동은 아니니까 라우터 이름은 우리 마음대로 정하면 된다. 

<form action="/register">

 

그다음, method는 GET나 POST 중 하나를 method로 정해주어야 한다. 둘 중 하나를 명시하지 않으면 GET으로 고정된다. 보통 데이터베이스에 접속해서 다른 작업을 수행하려면 GET이 아닌 POST를 쓴다. 형제자매나 룸메이트, 아니면 다른 사람들이 사이트를 돌아다니면서 우리가 신청한 기숙사가 뭔지 보여주려는 게 아니라면 POST를 써야 한다. 내 이름하고 기숙사에다가 전화번호, 이메일, 카드번호까지 다 알려주려는 게 아니니까 우리는 당연히 POST를 쓸 것이다.

<form action="/register" mothod="post">

 

 

 

 

 

Route 라우트

전에는 아무나 보낼 수 있는 구글 /search 라우트를 써봤는데, 이제 우리는 라우트를 직접 만들 수 있다. 아래는 아까 만든 appication.py 코드인데 라우트는 "/" 하나뿐이다.

from flask import Flask, render_template, request

app = Flask(__name__)

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

 

/register 라우트를 만들어서 이제 서버가 Listen 하고 있는 라우트는 / 와 /register 2개가 됐다.

@app.route("/register")

 

그런데 우리는 /register에서 GET과 POST 중 POST만 받고 싶다. 그러니 method 설정을 추가해준다.

@app.route("/register", methods=["POST"])

 

사용자가 Submit 버튼을 클릭하면, POST로 전송한 정보를 어딘가에 저장해야 할 것 같다. 근데 기숙사를 신청하려는 학생이 신청을 하기 전에 기숙사 신청 정보를 확인하면 좋을 것 같다. 학생들이 실수로, 혹은 고의로 정보를 기입하기도 전에 신청을 해버리면 그런 데이터들은 스팸이나 다름없다. 그러니까 이름을 안 넣었거나 기숙사를 선택 안 했으면 사용자에게 어떻게든 알려줘야 한다. 앞에서 썼던 방법을 써보자.

@app.route("/register", methods=["POST"])
def register():
    name = request.args.get("name")
    dorm = request.args.get("dorm")
    if not name or not dorm:
    	return "failure"
    render_template("success")

 

이번에는 name 하고 dorm 두 개를 확인해야 하는데, 파이썬이 참 괜찮은 게 영어처럼 "if not name or not dorm"이라고 쓸 수 있다. 확인한 다음에 해야 할 것은 사용자가 정보를 빠뜨렸을 때, 경고 알림을 띄우는 것이다. 만약 사용자가 친절하게 name과 dorm을 잘 넣어줬다면, "success" render 템플릿으로 보내준다.

 

 

 

 

 

 

Maintain Multiple Web Pages via layout.html 웹 페이지에서 레이아웃 활용하기

이제 기숙사를 신청하는 페이지와 신청에 실패할 때, 신청에 성공할 때에 보이는 웹 페이지를 각각 index.html, failure.html, success.html로 만들 수 있다. 근데 수정을 할 일이 생길 때마다 모든 페이지들을 다 일일이 고칠 수는 없다. 모든 웹 페이지에서 항상 똑같이 들어가 있는 항목은 뭐가 있을까?  title, head, 제일 위에 있는 Doctype, body, 등등... 디자인한 페이지 템플릿에 따라서 끝없이 많겠다. 이럴 때, 리팩터링 하면서 layout.html을 사용해보았다. Flask로 웹 애플리케이션을 만들면 구조적으로 규칙이 있고, 모든 페이지에 비슷하게 만들려면 이렇게 해볼 수 있다.

<html lang="en">
	<head>
    	<meta name="viewpoint" content="initial-scale=1, width=devide-width">
        <title>froshims0</title>
    </head>
    <body>
    	{% block body %}{% endblock %}
    </body>
</html>

 

위의 하드 코딩된 HTML 코드는 layout.html이다. 자세히 보면 페이지의 일반적인 구조에 힙한 문법이 섞여 있다. body 태그 안에 있는 건 'block'이라고 하는, Flask 리미티드 기능이다. Flask는 두 콧수염을 보면 안에 특정 변수에 저장된 값을 넣어주고, 콧수염에 %를 붙여서 쓰면 HTML 코드를 직접 넣어주는 placeholder로 인식해서 원래 HTML 코드의 일부처럼 인식해 준다.

그래서 이 코드는 양식 그 자체로 기능해서 다른 코드들이 이 구조를 따라가되, 콧수염 안의 공간만 잘 바꾸어주어 각기 다른 페이지들을 만들어낼 수 있게 해준다. 그럼 이제 우리는 이 layout.html로 index.html과 failure.html, success.html을 다시 만들 수 있다. 새로운 index.html을 보자.

{% extends "layout.html" %}

{% block body %}
    <h1>Register for Frosh IMs</h1>
    <form action="/register" mothod="post">
      <input name="name" placeholder="Name" type="text">
      <select name="dorm">
        <option disabled selected value="Dorm">Dorm</option>
        <option value="Apley Court">Apley Court</option>
        <option value="Canaday">Canaday</option>
        <option value="Grays">Grays</option>

        ...(중략)...

{% end block body %}

 

더 이상 html doctype도, head도, title도, body태그도 없다.

 

 

 

 

 

 

Dynamic Contrustion via Entends 다이내믹한 구조

여기에서 제일 윗줄에 새로운 Flask 리미티드 문법이 나온다.

{% extends "layout.html" %}

 

위 코드의 첫 줄은 두 파일을 연결해주는 링크이다. 이걸 통해서 layout.html을 extend, 즉 확장시켜준다는 뜻이다. Flask는 이 "layout.html"이라는 템플릿을 가져와서 요소들을 제 위치에 꽂아준다. 예를 들어, 새로운 index.html에서 block 콧수염과 endblock 콧수염 사이에 있는 코드는 layout.html에서 불러온 양식에서 block태그와 endblock 태그 사이에 들어간다. 좀 더 간단한 success.html로 다시 이해해보자.

{% extends "layout.html" %}

{% block body %}
	You are registered! (Well, not really.)
{% endblock %}

 

layout.html에 있는 코드를 불러와서, block 콧수염과 endblock 콧수염 사이에 success.html의 block 콧수염과 endblock 콧수염 사이에 있는 코드를 넣어주는 방식이다. 이렇게 콧수염을 사용한 success.html은 훨씬 간단하다. 사실 이 정도면 HTML처럼 보이지도 않는다. 그러니까 Flask로 좀 더 dinamic한 요소들을 쓰고 있는 것이다. 

 

 

 

 

 

 

Dynamic Web Structure 동적 웹 구조

꼭 사용자 입장에서만 다이내믹한 구조를 만들 수 있는 게 아니다. Extends로 만들어본 것처럼 코드의 구조를 다이내믹하게 만들 수도 있다. 이렇게 만든 웹 페이지들은 파이썬을 전혀 모르는 브라우저에서도 HTML 페이지로 인식한다. 

 

 

 

 

 

 

Any Questions?

🧑‍🎓콧수염({})과 퍼센트(%)도 파이썬 언어인가요?

사실 이건 Jinja라고 하는 템플릿 언어다. 이 언어가 진짜 힙한 이유는 Jinja를 만든 사람이 다른 어떤 언어에서도 콧수염과 퍼센트를 합친 문자는 쓰지 않기 때문에 HTML이나 CSS, Python 같은 언어로 된 코드 사이에서도 쉽게 구별이 될 거라는 생각을 했다는 점이다. 

🧑‍🎓Jinja를 쓸려면 뭔가 설치를 해야 되거나 업로드를 해야 합니까? 아니면 자동인가요?

Flask는 기본적으로 Jinja를 지원한다. 다른 템플릿 언어를 만들어 사용했을 수도 있는데 Flask를 만든 사람은 이걸 굳이 wheel로 다시 만들 필요 없이 기존에 있는 걸 쓴 것이다. 아까 전까지는 사실 이걸 '언어'라고 하지 않았는데, 사실 HTML이나 CSS, Python, JavaScript,... 셀 수 없이 많은 언어들 사이에서 얘를 새로운 언어라고 하기엔 조금 다른 느낌으로 제어 흐름(Control Flow)의 성격이 있다. Python에 비교하면 상당히 제한적이기도 하다. 

🧑‍🎓success.html과 failure.html을 한 파일로 합치면 더 깔끔하지 않나요?

단순하게 얘기하면 맞다. 더 복잡한 상황에서 살펴봐야 하지만, 간단하게 말하면 위에서 만든 페이지를 하나로 합칠 수 있다. 아마 result.html이라고 새롭게 만들어서 Success와 Failure을 Boolean 값으로 받으면 True/False 문제로 바뀐다. 그럼 if 조건으로 다른 출력문을 만들면 한 템플릿으로 해결할 수 있겠다. 하지만 일반적으로 이렇게 서로 다른 기능을 하는 경우에는 메시지를 처리하는 기능을 분리하는 것이 명료하다. 우리가 만들었던 예시는 아주 짧은 예시니까 말이다.

🧑‍🎓Chrome에서 사용자들이 디버깅 콘솔을 열면 HTML 코드가 어떻게 보이나요?

아까 만든 페이지 코드를 열어보자. 이게 바로 브라우저가 보는 코드다.

앞서 말했던 것처럼 브라우저는 이게 파이썬인지 플라스크인지 전혀 모른다. 우여곡절 끝에 브라우저가 받는 건 그냥 동적으로 만들어진 HTML 코드 (Dynamically Contructed HTML)일 뿐이다. 

🧑‍🎓저 안에는 HTML 코드만 넣을 수 있나요? 아니면 파이썬 코드도 넣을 수 있나요?

그러니까 다시 말해보면 저 콧수염과 퍼센트 기호 안에 파이썬 코드를 넣을 수 있느냐는 질문인데, 파이썬 코드는 전부 쓸 수 없고 대신 넣을 수 있는 파이썬같이 생긴 코드가 몇 개 있다. 

🧑‍🎓그런 함수가 있는 건가요 아니면 비슷한 무언가가 있다는 건가요?

정해진 함수 몇 개만 된다. 간단히 말해서 템플릿 언어는 샌드박스라서 다른 프로그래밍 언어들처럼 자유롭게 표현할 수 있지는 않다. 잠재적인 공격에 아주 취약해질 수 있기 때문에 데이터를 출력하는 것과 같이 아주 제한적으로만 쓸 수 있어야 한다.

 

 

 

→ 5 min Break! < Flask 기반의 파이썬 웹 프로그래밍 part.2 >에서 이어집니다

 

 

 





 

 

 

 

참고 자료

 

Flask 실행하기 전에 환경변수 설정하기

flask run을 하려고 하면 "You did not provide the "FLASK_APP" environment variable"와 같은 오류가 생긴다. Flask 환경변수 오류이다. 필수 환경변수를 설정해보자. 1. 실행 파일 지정: FLASK_APP 2...

teki.tistory.com

 

반응형
Comments