클래스 기반 뷰(CBV) 구현 및 라우팅 바인딩
Flask에서 클래스 기반 뷰를 활용하면 HTTP 메서드별로 로직을 명확히 분리할 수 있습니다. MethodView를 상속받아 구현하며, as_view() 메서드를 통해 라우터에 등록합니다.
from flask import Flask
from flask.views import MethodView
app = Flask(__name__)
app.config['DEBUG'] = True
class UserEndpoint(MethodView):
def get(self):
return "GET 요청 처리 완료"
def post(self):
return "POST 데이터 수신 완료"
# as_view()를 통해 클래스를 뷰 함수로 변환 후 라우팅 등록
# endpoint 인자는 라우트 식별자로 반드시 명시하는 것이 안전함
app.add_url_rule('/users', view_func=UserEndpoint.as_view('user_api'))
if __name__ == '__main__':
app.run()
CBV 내부 동작 원리 분석
MethodView의 핵심은 as_view()와 dispatch_request()에 있습니다.
- as_view() 실행 결과: 내부적으로
view라는 클로저 함수를 생성하여 반환합니다. 이 함수가 실제 라우팅 테이블에 등록되는 뷰 함수입니다. - 요청 디스패칭: 요청이 들어오면
view함수가 실행되고, 내부에서self.dispatch_request()를 호출합니다.dispatch_request는getattr(self, request.method.lower())를 통해 현재 HTTP 메서드에 맞는 클래스 메서드(get,post등)를 동적으로 찾아 실행합니다. - 엔드포인트 자동 생성:
@app.route나add_url_rule에서endpoint를 생략하면, 등록된 뷰 함수의__name__이 자동으로 엔드포인트로 할당됩니다.as_view('name')에 인자를 전달하지 않으면 모든 CBV가 동일한 내부 함수명을 공유하게 되어 라우팅 충돌이 발생합니다. - 데코레이터 적용: 클래스 기반 뷰에 미들웨어 성격의 데코레이터를 적용할 때는
decorators = [auth_required, logging]형태로 클래스 속성에 리스트를 정의합니다. 프레임워크가as_view생성 시 해당 데코레이터를 순차적으로 래핑합니다.
템플릿 렌더링 및 데이터 전달
Flask는 Jinja2 템플릿 엔진을 기본으로 사용합니다. render_template을 통해 컨텍스트 데이터를 전달하며, XSS 방지를 위해 자동으로 HTML 이스케이프가 적용됩니다.
from flask import Flask, render_template, Markup
# 템플릿 및 정적 파일 디렉토리 커스터마이징 가능
app = Flask(__name__, template_folder='views', static_folder='assets')
def multiply(x, y):
return x * y
@app.route('/dashboard')
def show_dashboard():
raw_html = '<button>클릭</button>'
# Markup을 사용하면 safe 필터 없이도 이스케이프를 우회할 수 있음
safe_html = Markup(raw_html)
return render_template('main.html', username='admin', btn_html=safe_html, calc=multiply)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>대시보드</title>
</head>
<body>
<h1>정적 파일 참조</h1>
<img src="/assets/logo.png" alt="로고">
<h1>조건문 제어</h1>
{% if username %}
<p>환영합니다, {{ username }}님</p>
{% else %}
<p>로그인이 필요합니다.</p>
{% endif %}
<h1>HTML 렌더링 및 함수 호출</h1>
{{ btn_html }}
<p>계산 결과: {{ calc(5, 7) }}</p>
</body>
</html>
요청 객체 속성 및 응답 커스터마이징
Flask의 request는 스레드 로컬 기반의 전역 객체로, 현재 요청의 모든 메타데이터를 담고 있습니다. 응답은 문자열, 튜플, 또는 make_response로 생성한 객체를 반환할 수 있습니다.
request 주요 속성
request.method: HTTP 메서드 (GET, POST 등)request.args: URL 쿼리 파라미터 (ImmutableMultiDict)request.form: POST 폼 데이터request.values:args와form을 통합한 객체request.cookies/request.headers: 클라이언트 쿠키 및 요청 헤더request.path: 도메인을 제외한 URL 경로request.url: 쿼리 스트링을 포함한 전체 URLrequest.host: 호스트명 및 포트 정보
응답 객체 조작
from flask import Flask, request, make_response, render_template
app = Flask(__name__)
@app.route('/process', methods=['GET', 'POST'])
def handle_data():
method_type = request.method
query_params = request.args
full_url = request.url
# make_response를 통해 응답 객체를 명시적으로 생성
resp = make_response(render_template('result.html'))
resp.set_cookie('tracking_id', 'abc123', max_age=3600)
resp.headers['X-Custom-Header'] = 'processed'
return resp
세션 저장 메커니즘과 인터페이스 분석
Flask의 세션은 서버 측 저장소를 기본으로 사용하지 않습니다. 대신 데이터를 직렬화하고 암호화 서명한 후 클라이언트 쿠키에 저장하는 Client-Side Session 방식을 채택합니다. 따라서 app.secret_key 설정이 필수적입니다.
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'complex_random_string_for_signing'
@app.route('/auth')
def login():
session['user_id'] = 9988
session['role'] = 'editor'
return '세션 데이터 저장됨'
@app.route('/me')
def profile():
uid = session.get('user_id', 'guest')
return f'현재 사용자 ID: {uid}'
세션 인터페이스 내부 동작
app.session_interface는 세션의 생명주기를 관리합니다. 주요 메서드는 다음과 같습니다.
- open_session(app, request): 요청 진입 시 실행됩니다. 쿠키에서 세션 값을 추출하고, 서명을 검증한 뒤 디코딩하여 세션 딕셔너리 객체를 반환합니다. 쿠키가 없거나 서명이 유효하지 않으면 빈 세션 객체를 생성합니다.
- save_session(app, session, response): 응답 반환 직전 실행됩니다. 세션 데이터를 직렬화하고 비밀키로 서명한 후,
response.set_cookie()를 통해 클라이언트에 전달합니다.
Redis나 데이터베이스에 세션을 저장하려면 SessionInterface를 상속받아 위 두 메서드를 재정의하거나, Flask-Session 같은 확장 라이브러리를 사용합니다.
플래시 메시지 활용
플래시(Flash)는 일회성 알림 메시지를 전달할 때 사용됩니다. 내부적으로 세션에 데이터를 저장하며, 한 번 읽히면 자동으로 제거됩니다.
from flask import Flask, flash, get_flashed_messages
app = Flask(__name__)
app.secret_key = 'flash_secret_key'
@app.route('/notify')
def set_msg():
flash('작업이 성공적으로 완료되었습니다.', 'success')
flash('경고: 디스크 용량 부족', 'warning')
return '메시지 등록 완료'
@app.route('/read')
def show_msg():
# category_filter를 통해 특정 레벨의 메시지만 추출
msgs = get_flashed_messages(category_filter=['success'])
return str(msgs)
요청 라이프사이클 훅과 템플릿 확장
Flask는 미들웨어 대신 요청 전후로 실행되는 콜백 함수(훅)를 제공합니다. 실행 순서와 역할에 따라 적절히 활용해야 합니다.
| 훅(Hook) | 실행 시점 및 특징 |
|---|---|
before_request | 뷰 함수 실행 전 호출. 응답을 반환하면 이후 로직을 중단하고 바로 응답함. |
after_request | 뷰 함수 실행 후, 응답이 클라이언트로 가기 전 호출. response 객체를 인자로 받아 반드시 반환해야 함. |
teardown_request | 요청 컨텍스트가 제거될 때 호출. 예외 발생 여부와 관계없이 항상 실행되며 DB 연결 해제 등에 적합. |
errorhandler(code) | 특정 HTTP 상태 코드나 예외 클래스를 감지하여 전역 에러 페이지를 처리. |
from flask import Flask, request, render_template
app = Flask(__name__)
@app.before_request
def check_auth():
print('[Before] 인증 검사 시작')
# return 'Blocked' 시 요청 체인 중단
@app.after_request
def add_timestamp(response):
print('[After] 응답 헤더 수정')
response.headers['X-Processed-Time'] = '0.02s'
return response
@app.teardown_request
def cleanup_db(exception):
print(f'[Teardown] 리소스 정리 (에러: {exception})')
@app.errorhandler(404)
def page_not_found(e):
return '리소스를 찾을 수 없습니다.', 404
# 템플릿 전역 함수: {{ format_currency(15000) }} 형태로 직접 호출 가능
@app.template_global()
def format_currency(amount):
return f"{amount:,}원"
# 템플릿 필터: {{ "hello" | reverse_str }} 형태로 파이프라인 사용
@app.template_filter()
def reverse_str(s):
return s[::-1]
@app.route('/')
def home():
return render_template('index.html')
before_request는 등록된 순서대로 실행되며, after_request는 역순으로 실행됩니다. 템플릿 확장은 template_global과 template_filter를 통해 백엔드 로직을 프론트엔드 문법으로 자연스럽게 주입할 수 있습니다.