Flask 퀵스타트 — 첫 라우팅부터 요청·응답·템플릿까지
Flask 퀵스타트 — 첫 라우팅부터 요청·응답·템플릿까지
Flask로 웹 애플리케이션을 만들 때 가장 먼저 만나는 기본기를 한 번에 훑어보는 페이지예요. 최소 애플리케이션부터 라우팅, 요청 데이터 접근, 응답 만들기, 템플릿 렌더링까지 실제 코드를 따라 하면서 흐름을 잡을 수 있어요. 먼저 공식 설치 가이드로 프로젝트를 준비하고 Flask를 설치해 주세요.
본문
최소 애플리케이션
최소한의 Flask 애플리케이션은 이렇게 생겼어요.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
이 코드가 하는 일을 하나씩 볼게요.
- 먼저
:class:~flask.Flask클래스를 임포트했어요. 이 클래스의 인스턴스가 우리의 WSGI 애플리케이션이 돼요. - 다음으로 이 클래스의 인스턴스를 만들었어요. 첫 번째 인자는 애플리케이션의 모듈이나 패키지 이름이에요.
__name__은 대부분의 경우에 알맞은 편리한 단축어예요. Flask가 템플릿이나 정적 파일 같은 리소스를 어디서 찾아야 할지 알게 되는 데 필요해요. - 그다음
:meth:~flask.Flask.route데코레이터로 어떤 URL이 우리 함수를 실행하게 할지 알려줘요. - 함수는 사용자 브라우저에 보여주고 싶은 메시지를 반환해요. 기본 콘텐츠 타입이 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.py나 wsgi.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을 만드는 이유가 뭘까요?
- 역산은 하드코딩보다 훨씬 설명적이에요.
- 하드코딩된 URL을 수동으로 바꿔야 하는 걸 기억할 필요 없이 한 번에 URL을 바꿀 수 있어요.
- URL 만들기는 특수 문자의 이스케이프를 투명하게 처리해요.
- 생성된 경로는 항상 절대 경로라서 브라우저에서 상대 경로의 예기치 않은 동작을 피할 수 있어요.
- 애플리케이션이 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 <blink>hacker</blink>!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup('<blink>hacker</blink>')
>>> Markup('<em>Marked up</em> » HTML').striptags()
'Marked up » HTML'
요청 데이터 접근
웹 애플리케이션에서 클라이언트가 서버로 보내는 데이터에 반응하는 건 아주 중요해요. Flask에서 이 정보는 전역 :data:.request 객체로 제공되며, 이 객체는 :class:.Request의 인스턴스예요. 수신한 요청 데이터를 다루는 많은 속성과 메서드가 있지만 여기선 개괄을 살펴볼게요. 먼저 임포트해야 해요.
from flask import request
파이썬에 조금 익숙하다면 Flask가 여러 요청을 동시에 처리할 때 그 객체가 어떻게 전역(global)일 수 있는지 궁금할 거예요. 답은 :data:.request가 사실 프록시(proxy)라는 거예요. 주어진 워커가 현재 처리 중인 요청을 가리키며, 이는 Flask와 파이썬이 내부적으로 관리해요. 더 자세한 내용은 앱·요청 컨텍스트를 봐요.
현재 요청 메서드는 :attr:~.Request.method 속성에 있어요. 폼 데이터(POST나 PUT 요청으로 전송된 데이터)에 접근하려면 :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가 반환 값을 응답 객체로 변환할 때 적용하는 규칙은 다음과 같아요.
- 올바른 타입의 응답 객체를 반환하면 그대로 뷰에서 반환돼요.
- 문자열이면 그 데이터와 기본 파라미터로 응답 객체를 만들어요.
- 문자열이나 bytes를 반환하는 iterator·generator면 스트리밍 응답으로 취급해요.
- dict나 list면
:func:~flask.json.jsonify로 응답 객체를 만들어요. - 튜플을 반환하면 튜플의 항목들이 추가 정보를 줄 수 있어요. 그런 튜플은
(response, status),(response, headers),(response, status, headers)형태여야 해요.status값은 상태 코드를 덮어쓰고,headers는 추가 헤더 값의 리스트나 딕셔너리가 될 수 있어요. - 그 어느 것도 아니면 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를 시작하는 건 쉽습니다. 뷰에서 dict나 list를 반환하면 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 앱을 배포할 준비가 됐나요? 배포 문서를 봐요.
더 알아보기
- 앱·요청 컨텍스트 — request·g·session 프록시가 동작하는 원리
- 템플릿 (Jinja) — 자동 이스케이프와 컨텍스트 프로세서
- 블루프린트 — 애플리케이션을 모듈로 나누기
- 폼 검증 (WTForms) — 폼을 클래스로 정의하고 검증하기