Flask 퀵스타트 — 첫 라우팅부터 요청·응답·템플릿까지

Flask 퀵스타트 — 첫 라우팅부터 요청·응답·템플릿까지

Flask로 웹 애플리케이션을 만들 때 가장 먼저 만나는 기본기를 한 번에 훑어보는 페이지예요. 최소 애플리케이션부터 라우팅, 요청 데이터 접근, 응답 만들기, 템플릿 렌더링까지 실제 코드를 따라 하면서 흐름을 잡을 수 있어요. 먼저 공식 설치 가이드로 프로젝트를 준비하고 Flask를 설치해 주세요.

출처: 공식문서 - Quickstart

본문

최소 애플리케이션

최소한의 Flask 애플리케이션은 이렇게 생겼어요.

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

이 코드가 하는 일을 하나씩 볼게요.

  1. 먼저 :class:~flask.Flask 클래스를 임포트했어요. 이 클래스의 인스턴스가 우리의 WSGI 애플리케이션이 돼요.
  2. 다음으로 이 클래스의 인스턴스를 만들었어요. 첫 번째 인자는 애플리케이션의 모듈이나 패키지 이름이에요. __name__은 대부분의 경우에 알맞은 편리한 단축어예요. Flask가 템플릿이나 정적 파일 같은 리소스를 어디서 찾아야 할지 알게 되는 데 필요해요.
  3. 그다음 :meth:~flask.Flask.route 데코레이터로 어떤 URL이 우리 함수를 실행하게 할지 알려줘요.
  4. 함수는 사용자 브라우저에 보여주고 싶은 메시지를 반환해요. 기본 콘텐츠 타입이 HTML이라 문자열 안의 HTML은 브라우저가 그대로 렌더링해요.

이걸 :file:hello.py처럼 저장하세요. 파일 이름을 :file:flask.py로 짓지 마세요 — Flask 자체와 충돌하니까요.

애플리케이션을 실행하려면 flask 명령이나 python -m flask를 써요. --app 옵션으로 애플리케이션이 어디 있는지 알려줘야 해요.

$ flask --app hello run
 * Serving Flask app 'hello'
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

참고로 파일 이름이 app.pywsgi.py라면 --app을 쓰지 않아도 돼요. 자세한 내용은 CLI 문서를 봐요.

이 명령은 아주 단순한 내장 서버를 띄워요. 테스트에는 충분하지만 운영에서 쓰기에는 부족한데, 배포 옵션은 배포 문서에서 다뤄요.

이제 http://127.0.0.1:5000/ 로 들어가면 hello world 인사를 볼 수 있어요.

포트 5000을 다른 프로그램이 이미 쓰고 있다면 서버 시작 시 OSError: [Errno 98]OSError: [WinError 10013]가 나와요. address-already-in-use를 보면 그 상황을 처리하는 방법이 있어요.

외부에서 접근 가능한 서버에 대해 알아둘 게 있어요. 서버를 실행하면 자기 컴퓨터에서만 접근할 수 있고 네트워크의 다른 컴퓨터에서는 접근할 수 없어요. 디버그 모드에서는 애플리케이션 사용자가 당신 컴퓨터에서 임의의 파이썬 코드를 실행할 수 있기 때문이 기본값이 그렇게 돼 있어요. 디버거를 끄거나 네트워크의 사용자를 신뢰한다면 --host=0.0.0.0을 명령줄에 추가해 공개할 수 있어요.

$ flask run --host=0.0.0.0

이렇게 하면 운영체제가 모든 공개 IP에서 수신하도록 만들 수 있어요.

디버그 모드

flask run 명령은 개발 서버를 시작하는 것보다 더 할 수 있어요. 디버그 모드를 켜면 코드가 바뀔 때 서버가 자동으로 리로드되고, 요청 처리 중 오류가 나면 브라우저에서 대화형 디버거를 보여줘요.

디버거는 브라우저에서 임의의 파이썬 코드를 실행할 수 있게 해줘요. PIN으로 보호되긴 하지만 여전히 큰 보안 위험이에요. 운영 환경에서 개발 서버나 디버거를 실행하지 마세요.

디버그 모드를 켜려면 --debug 옵션을 쓰세요.

$ flask --app hello run --debug
 * Serving Flask app 'hello'
 * Debug mode: on
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: nnn-nnn-nnn

HTML 이스케이프

Flask에서 기본 응답 타입인 HTML을 반환할 때는 주입 공격(injection)을 막으려고 출력에 렌더링되는 모든 사용자 제공 값이 이스케이프돼야 해요. 나중에 소개할 Jinja로 렌더링한 HTML 템플릿은 이 작업을 자동으로 해요.

:func:~markupsafe.escape는 아래처럼 수동으로 쓸 수 있어요. 예시들을 간결하게 하려고 대부분 생략하지만, 신뢰할 수 없는 데이터를 어떻게 다루는지 항상 인지하고 있어야 해요.

from flask import request
from markupsafe import escape

@app.route("/hello")
def hello():
    name = request.args.get("name", "Flask")
    return f"Hello, {escape(name)}!"

만약 사용자가 /hello?name=<script>alert("bad")</script>를 보내면, 이스케이프 덕분에 스크립트가 사용자 브라우저에서 실행되지 않고 텍스트로 렌더링돼요.

라우팅

현대 웹 애플리케이션은 의미 있는 URL을 사용해요. 기억하고 직접 재방문할 수 있는 URL이 있으면 사용자가 페이지를 더 좋아하고 다시 찾을 가능성이 커요.

:meth:~flask.Flask.route 데코레이터로 함수를 URL에 묶어요.

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

더 할 수 있어요! URL의 일부를 동적으로 만들고 함수에 여러 규칙을 붙일 수도 있어요.

변수 규칙 (Variable Rules)

URL에 <variable_name>으로 구간을 표시해서 변수 부분을 추가할 수 있어요. 그러면 함수가 <variable_name>을 키워드 인자로 받아요. 선택적으로 <converter:variable_name>처럼 컨버터를 써서 인자의 타입을 지정할 수도 있어요.

from markupsafe import escape

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return f'User {escape(username)}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return f'Post {post_id}'

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return f'Subpath {escape(subpath)}'

컨버터 타입은 이렇게 돼요.

컨버터 의미
string (기본) 슬래시 없는 모든 텍스트를 받아요
int 양의 정수를 받아요
float 양의 부동소수점 값을 받아요
path string과 같지만 슬래시도 받아요
uuid UUID 문자열을 받아요

고유 URL / 리다이렉트 동작

다음 두 규칙은 끝 슬래시 사용에서 차이가 나요.

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

projects 엔드포인트의 정식 URL은 끝 슬래시가 있어요. 파일 시스템의 폴더와 비슷해요. 끝 슬래시 없는 URL(/projects)로 접근하면 Flask가 끝 슬래시 있는 정식 URL(/projects/)로 리다이렉트해요.

about 엔드포인트의 정식 URL은 끝 슬래시가 없어요. 파일의 경로명과 비슷하죠. 끝 슬래시 있는 URL(/about/)로 접근하면 404 "Not Found" 오류가 나요. 이렇게 하면 이 리소스들의 URL을 고유하게 유지해 검색 엔진이 같은 페이지를 두 번 색인하지 않게 도와줘요.

URL 만들기 (URL Building)

특정 함수로 가는 URL을 만들려면 :func:~flask.url_for 함수를 써요. 첫 인자로 함수 이름을 받고, URL 규칙의 변수 부분에 해당하는 만큼의 키워드 인자를 받아요. 알 수 없는 변수 부분은 쿼리 파라미터로 URL에 붙어요.

템플릿에 URL을 하드코딩하는 대신 URL 역산 함수 :func:~flask.url_for로 URL을 만드는 이유가 뭘까요?

  1. 역산은 하드코딩보다 훨씬 설명적이에요.
  2. 하드코딩된 URL을 수동으로 바꿔야 하는 걸 기억할 필요 없이 한 번에 URL을 바꿀 수 있어요.
  3. URL 만들기는 특수 문자의 이스케이프를 투명하게 처리해요.
  4. 생성된 경로는 항상 절대 경로라서 브라우저에서 상대 경로의 예기치 않은 동작을 피할 수 있어요.
  5. 애플리케이션이 URL 루트 밖(/ 대신 /myapplication)에 놓여도 :func:~flask.url_for가 알아서 처리해요.

예를 들어 여기서는 :meth:~flask.Flask.test_request_context 메서드로 :func:~flask.url_for를 시험해 봐요. 이 메서드는 파이썬 셸을 쓰면서도 Flask가 요청을 처리하는 것처럼 동작하게 해요.

from flask import url_for

@app.route('/')
def index():
    return 'index'

@app.route('/login')
def login():
    return 'login'

@app.route('/user/<username>')
def profile(username):
    return f'{username}\'s profile'

with app.test_request_context():
    print(url_for('index'))
    print(url_for('login'))
    print(url_for('login', next='/'))
    print(url_for('profile', username='John Doe'))
/
/login
/login?next=/
/user/John%20Doe

HTTP 메서드

웹 애플리케이션은 URL에 접근할 때 서로 다른 HTTP 메서드를 써요. Flask로 작업하면서 HTTP 메서드에 익숙해져야 해요. 기본적으로 라우트는 GET 요청에만 응답해요. :meth:~flask.Flask.route 데코레이터의 methods 인자로 다른 HTTP 메서드를 처리할 수 있어요.

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

위 예시는 한 함수 안에 라우트의 모든 메서드를 두었어요. 각 부분이 공통 데이터를 쓰면 유용할 수 있어요.

서로 다른 메서드의 뷰를 서로 다른 함수로 분리할 수도 있어요. Flask는 각 공통 HTTP 메서드에 대해 :meth:~flask.Flask.get, :meth:~flask.Flask.post 등으로 그런 라우트를 데코레이팅하는 단축어를 제공해요.

@app.get('/login')
def login_get():
    return show_the_login_form()

@app.post('/login')
def login_post():
    return do_the_login()

GET이 있으면 Flask는 HEAD 메서드 지원을 자동으로 추가하고 HTTP RFC에 따라 HEAD 요청을 처리해요. 마찬가지로 OPTIONS도 자동으로 구현돼요.

정적 파일

동적 웹 애플리케이션도 정적 파일이 필요해요. 보통 CSS와 JavaScript 파일이 여기서 와요. 이상적으로는 웹 서버가 그걸 제공하도록 설정하지만, 개발 중에는 Flask도 할 수 있어요. 패키지나 모듈 옆에 :file:static이라는 폴더만 만들면 애플리케이션에서 /static으로 접근할 수 있어요.

정적 파일의 URL을 만들려면 특별한 'static' 엔드포인트 이름을 써요.

url_for('static', filename='style.css')

파일은 파일시스템의 :file:static/style.css에 저장돼 있어야 해요.

템플릿 렌더링

파이썬 안에서 HTML을 생성하는 건 재미없고 꽤 번거로워요. 애플리케이션을 안전하게 하려고 HTML 이스케이프를 직접 해야 하거든요. 그래서 Flask는 Jinja 템플릿 엔진을 자동으로 설정해 줘요.

템플릿은 모든 종류의 텍스트 파일을 만드는 데 쓸 수 있어요. 웹 애플리케이션에서는 주로 HTML 페이지를 만들지만, 마크다운, 이메일용 일반 텍스트 같은 것도 만들 수 있어요.

HTML·CSS 같은 웹 API 레퍼런스는 MDN Web Docs를 참고해요.

템플릿을 렌더링하려면 :func:~flask.render_template 메서드를 써요. 템플릿 이름과 템플릿 엔진에 전달할 변수들을 키워드 인자로 제공하면 돼요.

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', person=name)

Flask는 :file:templates 폴더에서 템플릿을 찾아요. 애플리케이션이 모듈이면 모듈 옆에, 패키지면 패키지 안에 그 폴더가 있어요.

Case 1: 모듈일 때

/application.py
/templates
    /hello.html

Case 2: 패키지일 때

/application
    /__init__.py
    /templates
        /hello.html

템플릿에서는 Jinja의 모든 기능을 쓸 수 있어요. 더 자세한 내용은 공식 Jinja Template Documentation을 봐요.

예시 템플릿이에요.

<!doctype html>
<title>Hello from Flask</title>
{% if person %}
  <h1>Hello {{ person }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

템플릿 안에서는 :data:~flask.Flask.config, :class:~flask.request, :class:~flask.session, :class:~flask.g 객체와 :func:~flask.url_for, :func:~flask.get_flashed_messages 함수에도 접근할 수 있어요.

상속(inheritance)을 쓰면 템플릿이 특히 유용해요. 어떻게 동작하는지는 템플릿 상속 패턴을 봐요. 기본적으로 템플릿 상속은 헤더·내비게이션·푸터 같은 요소를 각 페이지에 유지할 수 있게 해줘요.

자동 이스케이프가 켜져 있어서 person에 HTML이 들어 있어도 자동으로 이스케이프돼요. 변수를 신뢰할 수 있고 안전한 HTML이라는 걸 안다면(예: 위키 마크업을 HTML로 변환하는 모듈에서 왔다면) :class:~markupsafe.Markup 클래스나 템플릿의 |safe 필터로 안전하다고 표시할 수 있어요.

:class:~markupsafe.Markup 클래스가 어떻게 동작하는지 기본적인 소개예요.

>>> from markupsafe import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup('<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
'Marked up » HTML'

요청 데이터 접근

웹 애플리케이션에서 클라이언트가 서버로 보내는 데이터에 반응하는 건 아주 중요해요. Flask에서 이 정보는 전역 :data:.request 객체로 제공되며, 이 객체는 :class:.Request의 인스턴스예요. 수신한 요청 데이터를 다루는 많은 속성과 메서드가 있지만 여기선 개괄을 살펴볼게요. 먼저 임포트해야 해요.

from flask import request

파이썬에 조금 익숙하다면 Flask가 여러 요청을 동시에 처리할 때 그 객체가 어떻게 전역(global)일 수 있는지 궁금할 거예요. 답은 :data:.request가 사실 프록시(proxy)라는 거예요. 주어진 워커가 현재 처리 중인 요청을 가리키며, 이는 Flask와 파이썬이 내부적으로 관리해요. 더 자세한 내용은 앱·요청 컨텍스트를 봐요.

현재 요청 메서드는 :attr:~.Request.method 속성에 있어요. 폼 데이터(POSTPUT 요청으로 전송된 데이터)에 접근하려면 :attr:~flask.Request.form 속성을 써요. 이 속성은 dict처럼 동작해요.

@app.route("/login", methods=["GET", "POST"])
def login():
    error = None

    if request.method == "POST":
        if valid_login(request.form["username"], request.form["password"]):
            return store_login(request.form["username"])
        else:
            error = "Invalid username or password"

    # Executed if the request method was GET or the credentials were invalid.
    return render_template("login.html", error=error)

form에 키가 없으면 특별한 :exc:KeyError가 발생해요. 일반 KeyError처럼 잡을 수 있고, 그렇지 않으면 HTTP 400 Bad Request 오류 페이지를 반환해요. :meth:~werkzeug.datastructures.MultiDict.get 메서드를 쓰면 오류 대신 기본값을 얻을 수도 있어요.

URL에 담긴 파라미터(?key=value)에 접근하려면 :attr:~.Request.args 속성을 써요. 키 오류는 form과 똑같이 동작해서 잡지 않으면 400 응답을 반환해요.

searchword = request.args.get('key', '')

request 객체의 전체 메서드·속성 목록은 :class:~.Request 문서를 봐요.

파일 업로드

Flask로 업로드된 파일을 쉽게 다룰 수 있어요. HTML 폼에 enctype="multipart/form-data" 속성을 설정하는 걸 잊지 않아야 해요. 그렇지 않으면 브라우저가 파일을 아예 전송하지 않아요.

업로드된 파일은 메모리나 파일시스템의 임시 위치에 저장돼요. 요청 객체의 :attr:~flask.request.files 속성으로 그 파일들에 접근할 수 있어요. 각 업로드 파일은 그 딕셔너리에 저장돼요. 표준 파이썬 :class:file 객체처럼 동작하지만, 서버 파일시스템에 파일을 저장하게 해주는 :meth:~werkzeug.datastructures.FileStorage.save 메서드도 있어요.

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
        ...

클라이언트가 애플리케이션에 업로드하기 전에 파일이 뭐라고 이름 지어졌는지 알고 싶다면 :attr:~werkzeug.datastructures.FileStorage.filename 속성에 접근하면 돼요. 다만 이 값은 위조될 수 있으니 절대 신뢰하지 마세요. 클라이언트의 파일명을 서버에 저장하는 데 쓰고 싶다면, Werkzeug가 제공하는 :func:~werkzeug.utils.secure_filename 함수에 통과시켜야 해요.

from werkzeug.utils import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['the_file']
        file.save(f"/var/www/uploads/{secure_filename(file.filename)}")
        ...

더 나은 예시는 파일 업로드 패턴을 봐요.

쿠키

쿠키에 접근하려면 :attr:~flask.Request.cookies 속성을 써요. 쿠키를 설정하려면 응답 객체의 :attr:~flask.Response.set_cookie 메서드를 써요. 요청 객체의 :attr:~flask.Request.cookies 속성은 클라이언트가 전송한 모든 쿠키가 담긴 딕셔너리예요. 세션을 쓰고 싶다면 쿠키를 직접 쓰지 말고, 쿠키 위에 보안을 더해주는 Flask의 세션 기능을 쓰는 게 좋아요.

쿠키 읽기:

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

쿠키 저장:

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

쿠키는 응답 객체에 설정된다는 걸 기억해요. 보통 뷰 함수에서 문자열만 반환하면 Flask가 그걸 응답 객체로 변환해 주지만, 명시적으로 하려면 :meth:~flask.make_response 함수를 쓰고 그것을 수정하면 돼요.

때로는 응답 객체가 아직 존재하지 않는 시점에 쿠키를 설정하고 싶을 수 있어요. 이건 지연 콜백 패턴을 활용하면 가능해요.

이에 대해서는 응답에 대하여도 참고해요.

리다이렉트와 오류

사용자를 다른 엔드포인트로 리다이렉트하려면 :func:~flask.redirect 함수를, 오류 코드로 요청을 조기에 중단하려면 :func:~flask.abort 함수를 써요.

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

이 예시는 다소 의미가 없어요. 인덱스에서 접근할 수 없는 페이지(401은 접근 거부를 뜻해요)로 리다이렉트하니까요. 그래도 어떻게 동작하는지 보여줘요.

기본적으로 각 오류 코드에 대해 흑백 오류 페이지가 표시돼요. 오류 페이지를 커스터마이즈하려면 :meth:~flask.Flask.errorhandler 데코레이터를 써요.

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

:func:~flask.render_template 호출 뒤의 404를 주목하세요. 이게 Flask에 그 페이지의 상태 코드가 404(찾지 못함)여야 한다고 알려줘요. 기본적으로는 200으로 가정하는데, 이는 모든 게 잘 됐다는 뜻이에요.

자세한 내용은 오류 처리 문서를 봐요.

응답에 대하여 (About Responses)

뷰 함수의 반환 값은 자동으로 응답 객체로 변환돼요. 반환 값이 문자열이면 문자열을 응답 본문으로, 200 OK 상태 코드와 :mimetype:text/html 마임타입을 가진 응답 객체로 변환돼요. 반환 값이 dict나 list면 jsonify를 호출해 응답을 만들어요. Flask가 반환 값을 응답 객체로 변환할 때 적용하는 규칙은 다음과 같아요.

  1. 올바른 타입의 응답 객체를 반환하면 그대로 뷰에서 반환돼요.
  2. 문자열이면 그 데이터와 기본 파라미터로 응답 객체를 만들어요.
  3. 문자열이나 bytes를 반환하는 iterator·generator면 스트리밍 응답으로 취급해요.
  4. dict나 list면 :func:~flask.json.jsonify로 응답 객체를 만들어요.
  5. 튜플을 반환하면 튜플의 항목들이 추가 정보를 줄 수 있어요. 그런 튜플은 (response, status), (response, headers), (response, status, headers) 형태여야 해요. status 값은 상태 코드를 덮어쓰고, headers는 추가 헤더 값의 리스트나 딕셔너리가 될 수 있어요.
  6. 그 어느 것도 아니면 Flask는 반환 값을 유효한 WSGI 애플리케이션으로 가정하고 응답 객체로 변환해요.

뷰 안에서 결과 응답 객체를 직접 얻고 싶다면 :func:~flask.make_response 함수를 써요.

from flask import render_template

@app.errorhandler(404)
def not_found(error):
    return render_template('error.html'), 404

반환 표현식을 :func:~flask.make_response로 감싸고 응답 객체를 얻어 수정한 뒤 반환하기만 하면 돼요.

from flask import make_response

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

JSON을 쓰는 API

API를 작성할 때 흔한 응답 형식이 JSON이에요. Flask로 그런 API를 시작하는 건 쉽습니다. 뷰에서 dictlist를 반환하면 JSON 응답으로 변환돼요.

@app.route("/me")
def me_api():
    user = get_current_user()
    return {
        "username": user.username,
        "theme": user.theme,
        "image": url_for("user_image", filename=user.image),
    }

@app.route("/users")
def users_api():
    users = get_all_users()
    return [user.to_json() for user in users]

이것은 데이터를 :func:~flask.json.jsonify 함수에 전달하는 단축어예요. 지원되는 모든 JSON 데이터 타입을 직렬화해요. 즉 dict나 list 안의 모든 데이터는 JSON 직렬화가 가능해야 해요.

데이터베이스 모델 같은 복잡한 타입은 먼저 직렬화 라이브러리로 유효한 JSON 타입으로 변환해야 해요. 커뮤니티가 관리하는 여러 직렬화 라이브러리와 Flask API 확장이 더 복잡한 애플리케이션을 지원해요.

세션

request 객체 외에도 :class:~flask.session이라는 객체가 있어요. 이 객체는 한 요청에서 다음 요청으로 사용자에게 특정한 정보를 저장하게 해줘요. 쿠키 위에 구현되어 있고 쿠키를 암호화해 서명해요. 즉 서명에 쓰인 시크릿 키를 알지 못하면 사용자가 쿠키 내용을 볼 수는 있어도 수정할 수는 없어요.

세션을 쓰려면 시크릿 키를 설정해야 해요.

from flask import session

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/')
def index():
    if 'username' in session:
        return f'Logged in as {session["username"]}'
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

좋은 시크릿 키를 만드는 방법을 하나 알려드릴게요. 시크릿 키는 가능한 한 무작위여야 해요. 운영체제에 암호화 난수 생성기를 기반으로 꽤 무작위한 데이터를 만드는 방법이 있어요. :attr:Flask.secret_key(또는 :data:SECRET_KEY) 값을 빨리 만들려면 다음 명령을 써요.

$ python -c 'import secrets; print(secrets.token_hex())'
'192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf'

쿠키 기반 세션에 관한 참고사항이에요. Flask는 session 객체에 넣은 값들을 쿠키로 직렬화해요. 어떤 값들이 요청 사이에 유지되지 않는 걸 발견하는데 쿠키가 실제로 켜져 있고 명확한 오류 메시지가 없다면, 페이지 응답의 쿠키 크기를 웹 브라우저가 지원하는 크기와 비교해 보세요.

기본 클라이언트 측 세션 말고 서버 측에서 세션을 처리하고 싶다면 이를 지원하는 몇 가지 Flask 확장이 있어요.

메시지 플래싱

좋은 애플리케이션과 사용자 인터페이스는 모두 피드백에 관한 거예요. 사용자가 충분한 피드백을 받지 못하면 결국 애플리케이션을 싫어하게 될 거예요. Flask는 플래싱(flashing) 시스템으로 사용자에게 피드백을 주는 아주 간단한 방법을 제공해요. 기본적으로 플래싱 시스템은 요청 끝에 메시지를 기록하고 다음(그리고 오직 그 다음) 요청에서 접근하게 해줘요. 보통 레이아웃 템플릿과 결합해 메시지를 노출해요.

메시지를 플래시하려면 :func:~flask.flash 메서드를, 메시지를 얻으려면 :func:~flask.get_flashed_messages를 써요. 이 함수는 템플릿에서도 쓸 수 있어요. 전체 예시는 메시지 플래싱 패턴을 봐요.

로깅

옳아야 하는데 실제로는 그렇지 않은 데이터를 다루는 상황에 처할 때가 있어요. 예를 들어 클라이언트 측 코드가 서버로 HTTP 요청을 보내는데 분명히 잘못된 형식일 때요. 사용자가 데이터를 조작했거나 클라이언트 코드가 실패했을 수 있어요. 대부분의 경우 400 Bad Request로 응답하는 게 괜찮지만, 때로는 그걸로는 부족하고 코드가 계속 동작해야 할 때도 있어요.

뭔가 수상한 일이 있었다는 걸 로그로 남기고 싶을 거예요. 이때 로거가 유용해요. Flask 0.3부터 쓸 수 있는 로거가 미리 설정되어 있어요.

예시 로그 호출을 몇 개 볼게요.

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

붙어 있는 :attr:~flask.Flask.logger는 표준 :class:logging.Logger예요. 자세한 내용은 공식 :mod:logging 문서를 봐요.

WSGI 미들웨어 연결

Flask 애플리케이션에 WSGI 미들웨어를 추가하려면 애플리케이션의 wsgi_app 속성을 감싸요. 예를 들어 Nginx 뒤에서 실행할 때 Werkzeug의 :class:~werkzeug.middleware.proxy_fix.ProxyFix 미들웨어를 적용하려면:

from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

app 대신 app.wsgi_app을 감싸면 app이 여전히 Flask 애플리케이션을 가리키고 미들웨어를 가리키지 않아요. 그래서 app을 계속 직접 사용·설정할 수 있어요.

Flask 확장 사용

확장(extension)은 흔한 작업을 처리하도록 돕는 패키지예요. 예를 들어 Flask-SQLAlchemy는 SQLAlchemy 지원을 제공해서 Flask와 함께 간단·쉽게 쓸 수 있게 해요.

Flask 확장에 대해 더 알아보려면 확장 문서를 봐요.

웹 서버에 배포

새 Flask 앱을 배포할 준비가 됐나요? 배포 문서를 봐요.

더 알아보기