[REAL Python – Flask] – “Flask – 플라스크의 Routing, Http GET, POST Method 처리하기, (Create, Read 구현)”
[REAL Python – Flask] – “Flask – 플라스크의 Routing, Http GET, POST Method 처리하기, (Create, Read 구현)”
app = Flask(__name__)
이란?
완벽하게 동작하는, 다음의 파이썬 코드를 살펴보겠습니다. 파일 이름은 app.py
입니다. 이 점을 기억해주세요!
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
첫째로 보이는 app = Flask(__name__)
코드를 살펴보겠습니다. Flask
라는 키워드가 먼저 눈에 보입니다. 결론부터 말하자면, Flask
는 클래스입니다. 실제로 코드가 위치한 공간에는 다음과 같은 주석이 달려있는 걸 확인할 수 있습니다.
첫 줄만 살펴보겠습니다. 플라스크 객체는 WSGI
를 구현했다고 설명되어 있네요. WSGI
에 대한 설명은 https://wsgi.readthedocs.io/en/latest/what.html#what-is-wsgi 로 대체하겠습니다. (사실, 실력과 정보의 부족입니다.)
이후 괄호 안에 있는 __name__
이란 코드는 파이썬에서 특별하게 정해져 있는 변수입니다. https://www.gdsanadevlog.com/13762 에 대해 참고하면 좋을 것 같습니다.
간단하게, __name__
이라는 변수 안에는 모듈 이름, 즉 app.py
가 담기게 됩니다. 결국 현재의 코드 안에서 Flask(__name__)
는 Flask('app')
과 같은 의미가 되고, 실제로 저대로 코드를 바꾸어도 서버는 동작합니다.
결론적으로, app = Flask(__name__)
라는 코드는 Flask
클래스의 인스턴스를 만듬으로서, 플라스크 앱을 만드는 역할을 합니다.
@app.route
– 라우팅이란?
주어진 URL과 옵션들을 통해서 view 함수를 decorate 한다고 설명되어 있습니다. 설명을 보고 아래의 코드를 살펴보면 이해가 쉬울 것입니다. @app.route('/')
코드는 URL(‘/
‘)과, hello_world
함수를 매핑합니다. 플라스크는 ‘/’ 라는 URL 요청이 들어오면 hello_world
함수를 실행하게 됩니다.
@app.route('/')
def hello_world():
return 'Hello World!'
간단한 플라스크 프로젝트로 HTTP POST, GET
요청, 변수 규칙 이해하기
앞으로 만들 간단한 예제의 URL 규칙을 다음과 같이 정의하겠습니다.
- 홈페이지 : /
- 자기소개 페이지 : /about_me
- 포스트 Read 페이지 : /post/read/(포스트 번호)
- 포스트 Create 페이지 : /post/create/(포스트 번호)
그렇다면, 아래의 코드를 가진 간단한 플라스크 프로젝트를 상상할 수 있을 겁니다. 위의 내용을 이해했다면, 페이지에 어떤 url
을 요청했을 때에 어떠한 함수가 실행되겠구나를 알 수 있을 것입니다.
from flask import Flask
app = Flask(__name__)
# '/' 를 요청하면 홈페이지로 가게 된다.
@app.route('/')
def home():
return 'This is Home page.'
# '/about_me' 를 요청하면 자기소개 페이지로 가게 된다.
@app.route('/about_me')
def about_me():
return 'Let me introduce myself...'
# '/post/read/(포스트의 id)' 를 요청하면 해당 id에 맞는 포스트를 볼 수 있는 페이지로 가게 된다.
@app.route('/post/read/<int:id>')
def read():
return ''
# '/post/create' 를 요청하면 포스트를 작성할 수 있는 페이지로 가게 된다.
@app.route('/post/create')
def create():
return ''
if __name__ == '__main__':
app.run(debug=True)
아직은 기능은 구현하지 않은 상태입니다.
‘게시글’ 혹은 ‘포스트’ 라는 것은 아래와 같은 요소를 가진다고 가정하겠습니다.
보통은 데이터베이스에서 이것을 저장하지만 데이터베이스를 다루는 방법은 나중에 소개할 것이므로 간단한 파이썬 코드로 이것을 대체하겠습니다.
posts = [
{'id':1, 'title':'파이썬이란?', 'subtitle':'파이썬에 대해서 알아봅시다.', 'content':'파이썬은 여러 분야에서 쓰이고 있는 인터프리터 언어입니다..'},
{'id':2, 'title':'장고란?', 'subtitle':'장고에 대해서 알아봅시다.', 'content':'장고는 파이썬 언어로 제작된 풀 스택 웹 프레임워크입니다…'},
{'id':3, 'title':'플라스크란?', 'subtitle':'플라스크에 대해서 알아봅시다.', 'content':'플라스크는 장고와 함께 양대 산맥을 이루는 파이썬 웹 프레임워크입니다…'},
]
post_data.py
라는 새로운 파이썬 파일을 하나 생성하겠습니다. 딕셔너리와 리스트로 표현되었지만 관계형 데이터베이스의 구조와 닮아 있죠?
앞서, /post/read/(포스트의 id)
을 요청했을 때에 포스트의 상세보기 페이지가 나타나도록 규칙을 정했습니다. 그리고, /post/read/(포스트의 id)
라는 url은 read()
함수에서 처리하도록 되어 있죠. 그 read() 함수를 구워삶아서, 원하는 id가 url에 들어갔을 때에 제목, 부제목, 내용을 웹 브라우저에 띄워 주는 읽기 기능을 구현하겠습니다.
Read
구현
먼저, 데이터는 post_data.py 라는 파일에 저장되어 있으므로 그것을 불러오겠습니다.
# '/post/read/(포스트의 id)' 를 요청하면 해당 id에 맞는 포스트를 볼 수 있는 페이지로 가게 된다.
@app.route('/post/read/<int:id>')
def read(id):
title = ''
subtitle = ''
content = ''
for p in posts:
if id == p['id']:
title = p['title']
subtitle = p['subtitle']
content = p['content']
return f'제목 : {title}<br/>부제목 : {subtitle}<br/>내용 : {content}<br>'
@app.route
는 url과 read()
를 매핑해주는 데코레이터라고 했었죠? ('/post/read/<int:id>')
는 /post/read
뒤에 int
형을 id
라는 이름으로 받겠다는 것입니다.
이는 플라스크 공식 문서에서 variable-rules
라는 이름으로 소개해 주고 있습니다.
https://flask.palletsprojects.com/en/2.1.x/quickstart/#variable-rules
@app.route('/post/read/<int:id>')
코드 덕분에, read() 함수에는 이제 id 값이 들어옵니다. 위의 read() 함수는 모든 포스트들의 id들을, url에서 들어온 id와 비교하고 그 둘이 같은 값이라면 제목, 부제목, 내용 변수에 그 리스트에 담긴 값을 저장하고 리턴합니다.
실제로 플라스크 프로젝트를 실행해 보면 위처럼 파이썬 파일에서 값을 읽어와서 잘 뿌려주는 걸 볼 수 있습니다.
위에서 수행한 작업의 과정을 정리해 보면 다음과 같습니다.
- 관계형 데이터베이스 대신, 리스트 형태의 파이썬 파일에 게시물의 “id”, “제목” “부제목” “내용” 을 저장했습니다.
- 플라스크 함수에서, URL로 들어온 값으로 게시물의 id를 조회해 글을 읽을 수 있는 기능을 구현했습니다.
그렇다면, 이제 쓰기 기능을 구현할 차례입니다.
create
구현
새 게시물을 생성하려면 어떻게 해야 할까요?
가장 기본적인 아이디어는, 위의 post_data.py 파일에 값을 추가해 주는 것입니다. 하지만 대부분의 웹 서비스에서는 데이터베이스에 유저가 직접 접근해서 값을 바꾸지 않습니다. 예컨대 회원가입을 한다면 유저가 서버의 회원 테이블에 직접 접근해서 값을 추가하지 않는 것처럼 말이죠. 보통은 form과 같은 기능을 활용해서 값을 입력하고, “회원가입 완료하기” 와 같은 버튼을 누름으로서 가입을 완료합니다.
# '/post/create' 를 요청하면 포스트를 작성할 수 있는 페이지로 가게 된다.
@app.route('/post/create')
def create():
return '''
<form action="/post/create" method="POST">
<p><input type="text" name="title" placeholder="제목을 입력하세요."></p>
<p><input type="text" name="subtitle" placeholder="부제목을 입력하세요."></p>
<p><textarea name="content" placeholder="내용을 입력하세요"></textarea></p>
<p><input type="submit" value="게시물 작성하기"></p>
</form>
'''
단순히 위의 문자열을 리턴해줌으로서 해당 url을 입력했을 때에 폼이 나타나도록 할 수 있을 겁니다. action 속성으로 /post/create 라는 url에서 그것을 처리하도록 했고, http method는 POST 로 지정했습니다. https://ofcourse.kr/html-course/form-%ED%83%9C%EA%B7%B8 에 정리가 잘 되어 있어 참고하면 좋을 것 같습니다.
여기서 잠깐 “게시물 작성하기” 버튼을 눌렀을 때에 create() 함수가 어떤 일을 처리해야 하는가에 대해서 생각해 보겠습니다.
- 유저가 “게시물 작성하기” 버튼을 누릅니다.
- form action 에 따라서 유저가 작성한 폼의 데이터들이 /post/create 라는 곳으로 넘어갑니다. (여기까지 구현했습니다.)
- 먼저 작성해 둔 라우팅 코드 (
@app.route('/post/create')
) 에 의해서create()
함수가 수행됩니다. (이제부터 구현할 내용입니다.) create()
함수는 데이터가 저장되어 있는 파이썬 파일인post_data.py
를 열고, 리스트에 유저가 입력한 데이터를 추가한 다음, 저장하는 역할을 수행해야 합니다.
어떤 역할을 수행해야 하는지 알게 되었네요. 앞에서 만들었던 “게시물 작성하기” 버튼을 눌러 봅시다. 그러면, 아래와 같은 오류가 발생할 겁니다.
요청한 URL에 이러한 방식의 Http method는 허용되지 않았다는 것입니다. http 405 에러입니다.
https://developer.mozilla.org/ko/docs/Web/HTTP/Status/405 에서 조금 더 자세히 설명해 주고 있네요.
그런데 이 HTTP 메서드라는 것은 무엇일까요? 플라스크 문서를 찾아보면, “웹 애플리케이션들은 접근하는 URL들에 대해서 각기 다른 HTTP 메서드들을 사용합니다.” 라는 문구가 보이네요. 아래의 글을 읽어 봅시다.
기본적으로, route() 함수는 GET 메소드에만 응답을 한다고 적혀 있네요. 그러면 GET 메서드라는 것은 대체 무엇인고 하니,
https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/GET 를 참고해 봅시다. GET 메서드는 특정한 리소스를 가져오도록 하는 메서드이고, 데이터를 가져올 때만 사용한다고 되어 있죠?
실제로, 개발자 도구를 열어 에러가 난 우리의 /post/create 에 대해서 어떤 http 메서드가 요청되었는지 확인해 볼까요?
예상대로 POST 요청이 들어온 것을 확인할 수 있네요. 다른 예시로, 위에서 만들었던 게시물 조회 페이지를 잠깐 접속해서 메서드를 확인해 보겠습니다.
요청 메서드는 GET이라는 것을 확인할 수 있네요. 위처럼 주소를 URL에 주소를 쳐서 접근하면 기본적으로 GET 요청을 하게 됩니다. 하지만, <form method=”POST”> 와 같이 메서드가 특정된 폼에서는 POST 요청을 하게 됩니다. Http 메서드에는 다양한 종류가 있지만, 지금은 POST와 GET에 대해서만 알아보겠습니다.
https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/POST
POST메서드는 서버로 데이터를 전송하는 역할을 한다고 되어 있네요. 우리는 게시물의 제목 등을 작성해서 서버로 보내 처리하도록 할 것이므로 잘 보낸 것이 맞습니다. 이제 이 POST 요청을 처리하기 위해서, route
를 바꿀 차례입니다.
아래의 작업에서는 id 값을 자동으로 올리는데, 맨 위에 post_data.py 를 불러오며 변수 선언을 하나 해 줍시다.
# '/post/create' 를 요청하면 포스트를 작성할 수 있는 페이지로 가게 된다.
@app.route('/post/create', methods=['GET', 'POST'])
def create():
# POST 요청이 들어온다면 서버는 게시물을 작성하는 역할을 수행해야 합니다.
if request.method == 'POST':
'''
1. 서버로부터 넘어온 값을 저장하고,
2. post_data.py 파일을 열고,
3. post_data.py 의 posts 리스트에 서버에서 넘어온 값을 추가한 다음,
4. 변경된 post_data.py 파일을 저장
'''
# form 태그의 name 속성으로 넘어온 값 저장하기
title = request.form['title']
subtitle = request.form['subtitle']
content = request.form['content']
# post_data.py 파일 열기
data = open('post_data.py', 'a')
global last_id
# post_data.py 의 posts 리스트에 서버에서 넘어온 값을 추가하기
data.write(f"\nposts.append({{ 'id':{last_id + 1}, 'title':'{title}', 'subtitle':'{subtitle}', 'content':'{content}' }})")
data.close()
last_id = last_id + 1 # id 자동으로 올라가게끔
이렇게 소스를 작성하고 나서 제출 버튼을 눌러 보겠습니다.
아직은 create() 함수에 적절한 리턴값을 지정해주지 않았으므로 에러가 뜰 것입니다.
post_data.py
파일을 열어보겠습니다. 우리가 폼에 넣었던 값이 6번째 줄에 잘 추가된 것을 볼 수 있죠? 성공적으로 post_data.py
에 값을 추가했습니다.
이후 게시물을 조회하는 페이지에 새로운 id값인 4를 넣어 보면, 실제로 잘 동작하는 것을 확인할 수 있습니다. 폼에 넣은 그대로네요!
만약 id를 넣었는데 게시물이 정상적으로 표시되지 않는다면 새로고침을 해 주세요!
flask의 url_for()
과 후행 슬래시 사용 규칙
위의 예제에서는 /projects/ 와 /about 의 예시를 들어서 후행 슬래시가 어떻게 동작하는지에 대해서 설명하고 있습니다.
# '/about_me' 를 요청하면 자기소개 페이지로 가게 된다.
@app.route('/about_me')
def about_me():
return 'Let me introduce myself...'
위에서 작성한 이 예제를 기억하시나요? 먼저 /about_me를 요청하면 about_me 함수가 수행되고, 브라우저에는 ‘Let me introduce myself..’ 라는 문구가 뜰 겁니다.
후행 슬래시가 없는 경우이므로, /about_me/
를 요청하면 페이지는 404 error를 발생시킵니다.
이러한 경우를 공식 문서에서는 다음과 같이 설명해 주고 있네요.
그렇다면 이번에는 후행 슬래시가 존재하는 경우를 살펴보겠습니다.
이러한 경우에는 후행슬래시를 포함해서 요청을 보내도 정상적으로 응답합니다.
이번에는 후행 슬래시가 없는 경우를 살펴보겠습니다. 아래의 상황에서 엔터키를 누른다면 에러가 발생할까요?
놀랍게도 엔터를 누르면 후행 슬래시가 자동으로 붙은 곳으로 리디렉션되는 것을 볼 수 있습니다. 흐름을 그림으로 나타내본다면, 아래와 같을 것입니다.
결론적으로 후행 슬래시가 있다면, /about_me/ 요청과 /about_me 요청을 모두 허용하고 그렇지 않다면 404 not found 에러를 발생시키는 것입니다.
플라스크에는 url을 조금 더 편하게 관리할 수 있게끔 해 주는 url_for() 함수가 있습니다.
# '/' 를 요청하면 홈페이지로 가게 된다.
@app.route('/')
def home():
# url_for() 에 문자열로 함수 이름을 넣어 주면, 알아서 url을 생성해 준다.
return 'This is Home page.' \
f'<p><a href="{url_for("home")}">home</a></p>' \
f'<p><a href="{url_for("about_me")}">about_me</a></p>' \
f'<p><a href="{url_for("create")}">create new post</a></p>'
home
함수를 바꾸어서 실행해 봅시다. 각각에 맞는 url이 만들어지는 것을 확인할 수 있을 겁니다.
그러면, 작성을 완료했을 때에 리디렉션 되는 코드도 다음과 같이 작성할 수 있을 겁니다.
이렇게 하면 리다이렉트도 처리할 수 있습니다.
위에서 작성한 코드들은 데이터베이스가 아니라 파이썬 리스트 값을 바꾸는 것이었기 때문에 서버를 재시작해야지만 새로 작성한 글이 보이는 등의 문제가 있습니다. (사실은, 새로운 값을 불러오려 했는데 실패했어요..ㅠㅠ)
그러한 문제들을 해결하기 위해서 MySQL, Mariadb, sqlited와 같은 관계형 데이터베이스를 연동해서 사용해 볼 겁니다.
다음 포스팅에서~~