어제 우리는 Flask의 기초를 배웠습니다. 단순한 @app.route('/') 하나로요. 🤔 ──하지만 실제 웹사이트는 수백, 수천 개의 URL을 가지고 있습니다!
/user/123, /products/electronics, /search?q=python 이런 복잡한 URL들을 어떻게 처리할까요?
오늘은 Flask의 라우팅 시스템을 마스터합니다. URL 패턴 매칭, 동적 라우트, HTTP 메서드, 리다이렉트까지! 웹 개발의 핵심 개념들을 모두 배워봅시다! 💡
(30분 완독 ⭐⭐⭐⭐)
🎯 오늘의 학습 목표
- 동적 라우트와 URL 변수 처리하기
- HTTP 메서드 (GET, POST) 이해하기
- 리다이렉트와 URL 빌더 사용하기
- 요청 데이터와 쿼리 파라미터 다루기
📚 사전 지식
🎯 학습 목표 1: 동적 라우트와 URL 변수 처리하기
한 줄 설명
동적 라우트 = URL에 변수를 넣어서 하나의 함수로 여러 페이지를 처리하는 방법
실생활 비유
1
2
3
4
5
6
7
8
9
10
11
12
13
| 🏠 아파트 호실 시스템:
정적 라우트 = 각 호실마다 별도의 문
- 101호 전용 문, 102호 전용 문, 103호 전용 문... (비효율적!)
동적 라우트 = 호실 번호판이 있는 하나의 문 양식
- "/아파트/<동>/<호수>" 하나로 모든 집을 찾아갈 수 있음
- /아파트/1동/101호, /아파트/1동/102호, /아파트/2동/201호
- URL 패턴 하나로 무한대의 페이지 처리!
코드 비교:
❌ @app.route('/user/alice') # 사용자마다 라우트 추가 (불가능!)
✅ @app.route('/user/<username>') # 모든 사용자 처리 (효율적!)
|
정적 라우트 vs 동적 라우트
1
2
3
4
5
6
7
| # 정적 라우트 (Static Route)
@app.route('/about') # 항상 같은 URL
@app.route('/contact') # 항상 같은 URL
# 동적 라우트 (Dynamic Route)
@app.route('/user/<username>') # username이 변함
@app.route('/post/<int:post_id>') # post_id가 변함
|
왜 동적 라우트가 필요한가?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # ❌ 나쁜 방법: 모든 사용자마다 라우트를 만든다면?
@app.route('/user/alice')
def user_alice():
return "Alice의 프로필"
@app.route('/user/bob')
def user_bob():
return "Bob의 프로필"
# ... 사용자가 1만명이면? 😱
# ✅ 좋은 방법: 동적 라우트 하나로 해결!
@app.route('/user/<username>')
def user_profile(username):
return f"{username}의 프로필"
|
기본 동적 라우트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # app.py
from flask import Flask
app = Flask(__name__)
# 문자열 변수 (기본값)
@app.route('/hello/<name>')
def hello(name):
return f"""
<h1>안녕하세요, {name}님!</h1>
<p>환영합니다.</p>
"""
# 테스트:
# http://localhost:5000/hello/철수 → "안녕하세요, 철수님!"
# http://localhost:5000/hello/영희 → "안녕하세요, 영희님!"
if __name__ == '__main__':
app.run(debug=True)
|
URL 변수 타입 지정
Flask는 다양한 변수 타입을 지원합니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # app.py
from flask import Flask
app = Flask(__name__)
# 1. string (기본값) - 슬래시(/)를 제외한 모든 텍스트
@app.route('/user/<string:username>')
def show_user(username):
return f"사용자: {username}"
# 2. int - 정수만 허용
@app.route('/post/<int:post_id>')
def show_post(post_id):
return f"게시글 번호: {post_id} (타입: {type(post_id)})"
# 3. float - 실수 허용
@app.route('/price/<float:amount>')
def show_price(amount):
return f"가격: {amount:.2f}원 (타입: {type(amount)})"
# 4. path - 슬래시(/)를 포함한 모든 텍스트
@app.route('/files/<path:filepath>')
def show_file(filepath):
return f"파일 경로: {filepath}"
# 5. uuid - UUID 형식만 허용
@app.route('/object/<uuid:id>')
def show_object(id):
return f"객체 ID: {id}"
if __name__ == '__main__':
app.run(debug=True)
|
테스트:
1
2
3
4
5
6
7
8
| ✅ /post/123 → OK (int)
❌ /post/abc → 404 (문자열은 불가)
✅ /price/19.99 → OK (float)
❌ /price/free → 404
✅ /files/docs/report.pdf → OK (path - 슬래시 포함)
❌ /user/admin/root → 404 (string - 슬래시 불가)
|
여러 변수 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| # app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return """
<h1>URL 패턴 테스트</h1>
<ul>
<li><a href="/user/alice/25">사용자: alice, 나이: 25</a></li>
<li><a href="/blog/2024/03/15">블로그: 2024년 3월 15일</a></li>
</ul>
"""
# 여러 변수를 URL에 포함
@app.route('/user/<username>/<int:age>')
def user_info(username, age):
return f"""
<h1>사용자 정보</h1>
<ul>
<li>이름: {username}</li>
<li>나이: {age}살</li>
<li>성인 여부: {'예' if age >= 19 else '아니오'}</li>
</ul>
"""
# 날짜 형식의 URL
@app.route('/blog/<int:year>/<int:month>/<int:day>')
def show_blog_post(year, month, day):
return f"""
<h1>블로그 포스트</h1>
<p>날짜: {year}년 {month}월 {day}일</p>
"""
if __name__ == '__main__':
app.run(debug=True)
|
🎯 학습 목표 2: HTTP 메서드 (GET, POST) 이해하기
한 줄 설명
HTTP 메서드 = 서버에게 “무엇을 할지” 알려주는 동작 명령어 (GET은 조회, POST는 전송)
실생활 비유
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| 📬 우체국에서 소포 보내기:
GET (조회) = 우체국 창구에서 "우편물 조회해주세요"
- URL에 모든 정보가 보임: "123번 우편물 어디있나요?"
- 누구나 URL만 보면 무엇을 조회하는지 알 수 있음
- 예: /track?id=123&type=package
POST (전송) = 우체국 창구에 봉투 안에 넣어서 제출
- 봉투 안의 내용은 밖에서 안 보임 (보안!)
- URL에는 안 보임: /send-package
- 로그인, 회원가입처럼 민감한 정보는 POST 사용
비교:
GET: /search?password=1234 ← 비밀번호가 URL에 노출! 위험! ❌
POST: /login (비밀번호는 본문에 숨겨서 전송) ✅
|
HTTP 메서드란?
1
2
3
4
5
6
7
| HTTP 메서드: 클라이언트가 서버에게 "어떤 동작"을 요청하는지 알려주는 방법
🔍 GET - 데이터 조회 (읽기)
📝 POST - 데이터 전송 (쓰기/생성)
✏️ PUT - 데이터 수정 (전체 업데이트)
🔧 PATCH - 데이터 일부 수정
🗑️ DELETE - 데이터 삭제
|
GET vs POST
1
2
3
4
5
6
7
8
9
10
11
12
13
| # GET 메서드 (기본값)
# - URL에 데이터가 노출됨
# - 북마크 가능
# - 캐시 가능
# - 데이터 길이 제한 있음
예: /search?q=python&page=1
# POST 메서드
# - URL에 데이터가 안 보임 (본문에 포함)
# - 북마크 불가
# - 캐시 불가
# - 데이터 길이 제한 없음
예: 로그인, 회원가입, 파일 업로드
|
GET 메서드 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # app.py
from flask import Flask, request
app = Flask(__name__)
# 기본값은 GET 메서드만 허용
@app.route('/search')
def search():
# URL 쿼리 파라미터 읽기
query = request.args.get('q', '') # ?q=python
page = request.args.get('page', 1, type=int) # ?page=2
return f"""
<h1>검색 결과</h1>
<p>검색어: {query}</p>
<p>페이지: {page}</p>
"""
# 테스트:
# /search?q=python → 검색어: python, 페이지: 1
# /search?q=flask&page=2 → 검색어: flask, 페이지: 2
if __name__ == '__main__':
app.run(debug=True)
|
POST 메서드 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # app.py
from flask import Flask, request
app = Flask(__name__)
# GET과 POST 둘 다 허용
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
# GET: 로그인 폼 표시
return """
<h1>로그인</h1>
<form method="POST">
<input type="text" name="username" placeholder="아이디" required>
<input type="password" name="password" placeholder="비밀번호" required>
<button type="submit">로그인</button>
</form>
"""
else: # POST
# POST: 폼 데이터 처리
username = request.form.get('username')
password = request.form.get('password')
# 간단한 인증 로직 (실제로는 데이터베이스 사용)
if username == 'admin' and password == '1234':
return f"<h1>환영합니다, {username}님!</h1>"
else:
return "<h1>로그인 실패</h1><a href='/login'>다시 시도</a>"
if __name__ == '__main__':
app.run(debug=True)
|
여러 메서드 처리하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
# 간단한 데이터 저장소 (실제로는 데이터베이스 사용)
todos = []
@app.route('/api/todos', methods=['GET', 'POST', 'DELETE'])
def handle_todos():
if request.method == 'GET':
# 할 일 목록 조회
return jsonify(todos)
elif request.method == 'POST':
# 새 할 일 추가
todo = request.json.get('todo')
todos.append(todo)
return jsonify({'message': '추가됨', 'todo': todo}), 201
elif request.method == 'DELETE':
# 모든 할 일 삭제
todos.clear()
return jsonify({'message': '모두 삭제됨'}), 200
if __name__ == '__main__':
app.run(debug=True)
|
API 테스트 (curl 사용):
1
2
3
4
5
6
7
8
9
10
| # GET - 목록 조회
curl http://localhost:5000/api/todos
# POST - 추가
curl -X POST http://localhost:5000/api/todos \
-H "Content-Type: application/json" \
-d '{"todo": "Flask 공부하기"}'
# DELETE - 삭제
curl -X DELETE http://localhost:5000/api/todos
|
🎯 학습 목표 3: 리다이렉트와 URL 빌더 사용하기
한 줄 설명
리다이렉트 = 사용자를 다른 페이지로 자동으로 보내는 것 (로그인 후 메인 페이지로 이동하기)
실생활 비유
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| 🏢 병원 접수 시스템:
redirect() = 다른 창구로 안내하기
- 3번 창구에 가려고 했는데 "1번 창구로 가세요~" 하고 안내받음
- 자동으로 올바른 위치로 이동시켜줌
예시:
1. 로그인 안 한 사람이 "마이페이지" 접근 시도
2. 서버: "로그인부터 하세요!" → /login으로 리다이렉트
3. 로그인 완료 후 → /mypage로 다시 리다이렉트
url_for() = 창구 번호 대신 "담당자 이름"으로 찾기
❌ redirect('/user/profile') ← 주소가 바뀌면 모든 코드 수정 필요!
✅ redirect(url_for('user_profile')) ← 함수 이름만 알면 됨!
건물 주소가 바뀌어도, "홍길동 담당자" 이름만 알면 찾아갈 수 있는 것과 같음!
|
redirect() 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # app.py
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def home():
return "홈 페이지"
@app.route('/admin')
def admin():
# 인증 체크 (실제로는 세션 사용)
is_logged_in = False
if not is_logged_in:
# 로그인 페이지로 리다이렉트
return redirect('/login')
return "관리자 페이지"
@app.route('/login')
def login():
return """
<h1>로그인이 필요합니다</h1>
<form>
<button>로그인</button>
</form>
"""
if __name__ == '__main__':
app.run(debug=True)
|
url_for() 함수
왜 url_for()를 사용해야 할까?
1
2
3
4
5
6
7
8
9
| # ❌ 나쁜 방법: 하드코딩된 URL
return redirect('/user/profile')
# URL이 변경되면? /users/profile → 모든 코드를 수정해야 함!
# ✅ 좋은 방법: url_for() 사용
return redirect(url_for('user_profile'))
# 함수 이름만 알면 됨! URL이 변경되어도 코드 수정 불필요
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # app.py
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def home():
return """
<h1>홈 페이지</h1>
<ul>
<li><a href="/user/alice">Alice 프로필 (하드코딩)</a></li>
<li><a href="{{ url_for('user_profile', username='bob') }}">Bob 프로필 (url_for)</a></li>
</ul>
"""
@app.route('/user/<username>')
def user_profile(username):
return f"<h1>{username}의 프로필</h1>"
@app.route('/go-to-profile/<name>')
def go_to_profile(name):
# url_for()로 다른 라우트의 URL 생성
profile_url = url_for('user_profile', username=name)
return redirect(profile_url)
# 테스트:
# /go-to-profile/charlie → /user/charlie로 리다이렉트
if __name__ == '__main__':
app.run(debug=True)
|
실전 예제: 로그인 후 리다이렉트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| # app.py
from flask import Flask, redirect, url_for, request
app = Flask(__name__)
@app.route('/')
def home():
return """
<h1>환영합니다</h1>
<a href="/login">로그인</a>
"""
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
if username == 'admin' and password == '1234':
# 로그인 성공 → 대시보드로 리다이렉트
return redirect(url_for('dashboard', username=username))
else:
return "로그인 실패 <a href='/login'>다시 시도</a>"
return """
<h1>로그인</h1>
<form method="POST">
<input type="text" name="username" placeholder="아이디"><br>
<input type="password" name="password" placeholder="비밀번호"><br>
<button>로그인</button>
</form>
"""
@app.route('/dashboard/<username>')
def dashboard(username):
return f"""
<h1>{username}님의 대시보드</h1>
<p>로그인에 성공했습니다!</p>
<a href="/">홈으로</a>
"""
if __name__ == '__main__':
app.run(debug=True)
|
🎯 학습 목표 4: 요청 데이터와 쿼리 파라미터 다루기
한 줄 설명
request 객체 = 사용자가 보낸 모든 정보를 담고 있는 택배 상자
실생활 비유
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 📦 택배 상자 (request 객체):
request.args = 상자 겉면에 적힌 메모 (URL 뒤의 ?key=value)
- 누구나 볼 수 있음
- 예: /search?q=파이썬&page=2
- request.args.get('q') → "파이썬"
request.form = 상자 안에 든 물건 (폼 데이터)
- 상자를 열어야 보임 (POST 방식)
- 예: 로그인 폼의 아이디, 비밀번호
- request.form.get('password') → 안전하게 전달됨
request.files = 상자 안의 큰 물건 (파일)
- 이미지, 문서 파일 등
- request.files.get('photo') → 업로드된 파일
request.json = 상자 안의 정리된 목록 (API 데이터)
- JSON 형식의 구조화된 데이터
- {"name": "홍길동", "age": 25}
|
request 객체
Flask의 request 객체로 클라이언트의 모든 요청 데이터에 접근할 수 있습니다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| from flask import request
# 쿼리 파라미터: ?key=value
request.args.get('key')
# 폼 데이터: <form method="POST">
request.form.get('key')
# JSON 데이터: Content-Type: application/json
request.json.get('key')
# 파일 업로드
request.files.get('file')
# HTTP 헤더
request.headers.get('User-Agent')
# 요청 메서드
request.method # GET, POST, etc.
# 요청 URL
request.url
request.path
|
쿼리 파라미터 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/search')
def search():
# 쿼리 파라미터 읽기
keyword = request.args.get('q', '') # 기본값: 빈 문자열
page = request.args.get('page', 1, type=int) # 기본값: 1, 타입: int
sort = request.args.get('sort', 'relevance') # 기본값: relevance
# 모든 쿼리 파라미터
all_params = request.args.to_dict()
return f"""
<h1>검색 결과</h1>
<ul>
<li>검색어: {keyword}</li>
<li>페이지: {page}</li>
<li>정렬: {sort}</li>
<li>모든 파라미터: {all_params}</li>
</ul>
"""
# 테스트:
# /search?q=flask&page=2&sort=date
# → 검색어: flask, 페이지: 2, 정렬: date
if __name__ == '__main__':
app.run(debug=True)
|
폼 데이터 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return """
<h1>회원가입</h1>
<form method="POST">
<input type="text" name="username" placeholder="아이디" required><br>
<input type="email" name="email" placeholder="이메일" required><br>
<input type="password" name="password" placeholder="비밀번호" required><br>
<label>
<input type="checkbox" name="agree" value="yes"> 약관 동의
</label><br>
<button>가입하기</button>
</form>
"""
# POST 요청 처리
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
agree = request.form.get('agree') # 체크박스
# 유효성 검사
if not agree:
return "약관에 동의해야 합니다 <a href='/register'>돌아가기</a>"
return f"""
<h1>회원가입 완료!</h1>
<ul>
<li>아이디: {username}</li>
<li>이메일: {email}</li>
<li>약관 동의: {agree}</li>
</ul>
"""
if __name__ == '__main__':
app.run(debug=True)
|
JSON 데이터 처리 (REST API)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/user', methods=['POST'])
def create_user():
# JSON 데이터 파싱
data = request.get_json()
# 필수 필드 체크
if not data or 'username' not in data:
return jsonify({'error': 'username은 필수입니다'}), 400
username = data.get('username')
email = data.get('email')
age = data.get('age')
# 데이터 처리 (실제로는 데이터베이스 저장)
user = {
'id': 1,
'username': username,
'email': email,
'age': age,
'created_at': '2025-06-20'
}
return jsonify(user), 201 # 201 Created
if __name__ == '__main__':
app.run(debug=True)
|
API 테스트:
1
2
3
4
5
6
7
| curl -X POST http://localhost:5000/api/user \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"email": "[email protected]",
"age": 25
}'
|
💻 실전 예제
예제 1: 제품 카탈로그
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| # app.py
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
# 가상의 제품 데이터베이스
products = {
1: {'id': 1, 'name': '노트북', 'price': 1500000, 'category': 'electronics'},
2: {'id': 2, 'name': '마우스', 'price': 30000, 'category': 'electronics'},
3: {'id': 3, 'name': '키보드', 'price': 120000, 'category': 'electronics'},
4: {'id': 4, 'name': '책상', 'price': 250000, 'category': 'furniture'},
}
@app.route('/')
def home():
return """
<h1>제품 카탈로그</h1>
<ul>
<li><a href="/products">모든 제품</a></li>
<li><a href="/products?category=electronics">전자제품</a></li>
<li><a href="/products?category=furniture">가구</a></li>
<li><a href="/products?sort=price">가격순 정렬</a></li>
</ul>
"""
@app.route('/products')
def list_products():
# 쿼리 파라미터
category = request.args.get('category')
sort_by = request.args.get('sort')
# 필터링
filtered = products.values()
if category:
filtered = [p for p in filtered if p['category'] == category]
# 정렬
if sort_by == 'price':
filtered = sorted(filtered, key=lambda x: x['price'])
# HTML 생성
html = "<h1>제품 목록</h1><ul>"
for product in filtered:
html += f"""
<li>
<a href="/products/{product['id']}">
{product['name']} - {product['price']:,}원
</a>
</li>
"""
html += "</ul><a href='/'>홈으로</a>"
return html
@app.route('/products/<int:product_id>')
def product_detail(product_id):
product = products.get(product_id)
if not product:
return "<h1>제품을 찾을 수 없습니다</h1><a href='/products'>목록으로</a>", 404
return f"""
<h1>{product['name']}</h1>
<ul>
<li>가격: {product['price']:,}원</li>
<li>카테고리: {product['category']}</li>
</ul>
<a href="/products">목록으로</a>
"""
if __name__ == '__main__':
app.run(debug=True)
|
예제 2: 간단한 블로그
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| # app.py
from flask import Flask, request, redirect, url_for
from datetime import datetime
app = Flask(__name__)
# 블로그 포스트 저장소
posts = []
post_id_counter = 1
@app.route('/')
def home():
html = "<h1>블로그</h1>"
html += "<a href='/write'>새 글 쓰기</a><br><br>"
if not posts:
html += "<p>아직 게시글이 없습니다.</p>"
else:
html += "<ul>"
for post in reversed(posts): # 최신글이 위로
html += f"""
<li>
<a href="/post/{post['id']}">
{post['title']}
</a>
<small>({post['created_at']})</small>
</li>
"""
html += "</ul>"
return html
@app.route('/write', methods=['GET', 'POST'])
def write():
if request.method == 'POST':
global post_id_counter
title = request.form.get('title')
content = request.form.get('content')
if not title or not content:
return "제목과 내용을 모두 입력해주세요 <a href='/write'>돌아가기</a>"
# 새 포스트 생성
new_post = {
'id': post_id_counter,
'title': title,
'content': content,
'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
posts.append(new_post)
post_id_counter += 1
return redirect(url_for('home'))
return """
<h1>새 글 쓰기</h1>
<form method="POST">
<input type="text" name="title" placeholder="제목" required><br><br>
<textarea name="content" rows="10" cols="50" placeholder="내용" required></textarea><br><br>
<button>게시</button>
<a href="/">취소</a>
</form>
"""
@app.route('/post/<int:post_id>')
def view_post(post_id):
post = next((p for p in posts if p['id'] == post_id), None)
if not post:
return "<h1>게시글을 찾을 수 없습니다</h1><a href='/'>홈으로</a>", 404
return f"""
<h1>{post['title']}</h1>
<p><small>작성일: {post['created_at']}</small></p>
<hr>
<p>{post['content']}</p>
<br>
<a href="/">목록으로</a>
"""
if __name__ == '__main__':
app.run(debug=True)
|
⚠️ 주의사항
1. request 객체는 요청 컨텍스트에서만 사용
1
2
3
4
5
6
7
8
9
10
| from flask import request
# ❌ 잘못됨: 라우트 밖에서 사용
username = request.args.get('username') # 에러!
@app.route('/user')
def user():
# ✅ 올바름: 라우트 안에서 사용
username = request.args.get('username')
return f"User: {username}"
|
2. 사용자 입력은 항상 검증하기
1
2
3
4
5
6
7
8
9
10
11
12
13
| # ❌ 위험: 검증 없이 사용
@app.route('/eval')
def evaluate():
code = request.args.get('code')
return eval(code) # SQL Injection, XSS 등 취약!
# ✅ 안전: 입력 검증
@app.route('/calc')
def calculate():
num = request.args.get('num', type=int)
if num is None or num < 0:
return "잘못된 입력입니다", 400
return f"Result: {num * 2}"
|
3. 리다이렉트 시 상태 코드 명시
1
2
3
4
5
6
7
8
| # 301: 영구 이동 (Permanent Redirect)
return redirect(url_for('new_page'), code=301)
# 302: 임시 이동 (Temporary Redirect, 기본값)
return redirect(url_for('temp_page'), code=302)
# 307: POST 메서드 유지
return redirect(url_for('target'), code=307)
|
🧪 연습 문제
문제 1: 온도 변환기
섭씨를 화씨로, 화씨를 섭씨로 변환하는 Flask 앱을 만드세요.
/convert/c2f/25 → “25°C = 77.0°F” /convert/f2c/77 → “77°F = 25.0°C”
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return """
<h1>온도 변환기</h1>
<p>예시:</p>
<ul>
<li><a href="/convert/c2f/25">/convert/c2f/25</a> - 섭씨 → 화씨</li>
<li><a href="/convert/f2c/77">/convert/f2c/77</a> - 화씨 → 섭씨</li>
</ul>
"""
@app.route('/convert/<string:mode>/<float:temp>')
def convert_temp(mode, temp):
if mode == 'c2f':
result = (temp * 9/5) + 32
return f"{temp}°C = {result:.1f}°F"
elif mode == 'f2c':
result = (temp - 32) * 5/9
return f"{temp}°F = {result:.1f}°C"
else:
return "잘못된 모드입니다. c2f 또는 f2c를 사용하세요.", 400
if __name__ == '__main__':
app.run(debug=True)
|
문제 2: 방명록
방문자가 메시지를 남길 수 있는 방명록을 만드세요.
- GET
/guestbook: 모든 메시지 표시 + 작성 폼 - POST
/guestbook: 새 메시지 추가 후 목록으로 리다이렉트
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| from flask import Flask, request, redirect, url_for
from datetime import datetime
app = Flask(__name__)
messages = []
@app.route('/guestbook', methods=['GET', 'POST'])
def guestbook():
if request.method == 'POST':
name = request.form.get('name')
message = request.form.get('message')
if name and message:
messages.append({
'name': name,
'message': message,
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
return redirect(url_for('guestbook'))
# GET: 방명록 표시
html = "<h1>방명록</h1>"
# 메시지 목록
if messages:
html += "<ul>"
for msg in reversed(messages):
html += f"""
<li>
<strong>{msg['name']}</strong>: {msg['message']}
<br><small>{msg['time']}</small>
</li>
"""
html += "</ul>"
else:
html += "<p>아직 메시지가 없습니다.</p>"
# 작성 폼
html += """
<h2>메시지 남기기</h2>
<form method="POST">
<input type="text" name="name" placeholder="이름" required><br>
<textarea name="message" placeholder="메시지" required></textarea><br>
<button>남기기</button>
</form>
"""
return html
if __name__ == '__main__':
app.run(debug=True)
|
📝 요약
이번 Day 82에서 학습한 내용:
- 동적 라우트: URL 변수, 타입 지정 (
<int:id>, <path:filepath>) - HTTP 메서드: GET, POST 처리,
methods 파라미터 - 리다이렉트:
redirect(), url_for() 함수 - 요청 데이터:
request.args, request.form, request.json
📚 다음 학습
Day 83: 템플릿 엔진 (Jinja2) ⭐⭐⭐⭐
HTML을 Python 코드에 문자열로 작성하는 건 너무 불편합니다! 내일은 Jinja2 템플릿 엔진으로 HTML을 깔끔하게 관리하는 법을 배웁니다!
“좋은 라우팅 설계는 좋은 웹사이트의 시작입니다!” 🚀
| Day 82/100 | Phase 9: 웹 개발 입문 | #100DaysOfPython |