Flask 핵심 아키텍처 분석: CBV, 템플릿, 세션, 요청 확장 가이드

클래스 기반 뷰(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_requestgetattr(self, request.method.lower())를 통해 현재 HTTP 메서드에 맞는 클래스 메서드(get, post 등)를 동적으로 찾아 실행합니다.
  • 엔드포인트 자동 생성: @app.routeadd_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: argsform을 통합한 객체
  • request.cookies / request.headers: 클라이언트 쿠키 및 요청 헤더
  • request.path: 도메인을 제외한 URL 경로
  • request.url: 쿼리 스트링을 포함한 전체 URL
  • request.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_globaltemplate_filter를 통해 백엔드 로직을 프론트엔드 문법으로 자연스럽게 주입할 수 있습니다.

태그: flask MethodView Jinja2 SecureCookieSession RequestHooks

9월 12일 12:34에 게시됨