Flask 概要
Python 用のウェブアプリケーションフレームワーク。
ディレクトリ構成
単純な例。
/
+-- myapp.py
+-- static/
+-- ...
+-- ...
+-- templates/
+-- ...
+-- ...
基本的な使い方
単純な REST API
#!/usr/bin/env python3
from flask import Flask, jsonify
# インスタンス生成
app = Flask(__name__)
# http://localhost:5000/
@app.route('/')
def hello():
"""
単純な文字列を返す
"""
return 'Hello, world!'
# http://localhost:5000/json
@app.route('/json')
def json():
"""
JSON を返す
"""
result = {
'a': 'aiueo',
'b': 12345
}
return jsonify(ResultSet=result)
if __name__ == '__main__':
# リモートからのアクセスを許可して起動
app.run(host='0.0.0.0')
パラメータの受け取り
クエリパラメータ
from flask import jsonify, request
@app.route('/user')
def func():
user_id = request.args.get("id", type=int)
user_name = request.args.get("name", type=str)
result = {
'name': user_name,
'id': user_id
}
return jsonify(ResultSet=result)
パスパラメータ
任意のフォーマットのパラメータを受け取る
(/
を含まない)任意のパスパラメータを変数として受け付ける。
@app.route('/user/<username>')
def func(username):
return 'your user-name is ' + username
ルールでフォーマットを制限する
@app.route('/user/<int:user_id>')
def func(user_id):
return 'your user-id is ' + user_id
<コンバータ:変数名>
という記法で変数書式に制限をかけられる
コンバータ | 説明 | 備考 |
---|---|---|
int |
整数 | |
float |
浮動小数 | int とは排他で整数は受け付けない模様(3.0 は OK だが3 はダメだった) |
path |
/ を含んでも OK |
静的ファイル・テンプレートファイルの使用
静的ファイルをそのまま返す
静的ファイルを static/ に配置(ここでは static/index.html)
<html>
<body>
This is static file.
</body>
</html>
@app.route('/staticfile')
def static_file():
return app.send_static_file('index.html')
$ curl http://127.0.0.1:5000/staticfile
<html>
<body>
This is static file.
</body>
</html>
テンプレートファイルを変数の値でレンダリングして返す
テンプレートファイルを templates/ に配置(ここでは templates/index.html)
<html>
<body>
<ul>
</ul>
</body>
</html>
from flask import render_template
users = {'Taro': 1, 'Jiro': 2, 'Saburo': 3}
@app.route('/userinfo')
def render():
message = 'There are {} users.'.format(len(users))
return render_template('index.html', message=message, users=users)
$ curl http://localhost:5000/userinfo
<html>
<body>
There are 3 users.
<ul>
<li><a href="/user/1">Taro</a></li>
<li><a href="/user/2">Jiro</a></li>
<li><a href="/user/3">Saburo</a></li>
</ul>
</body>
</html>
Tips
app.run() の引数
待ち受けポート指定
app.run(port=80)
複数リクエストの並列処理
app.run(threaded=True)
デバッグモード
app.run(debug=True)
- 元のコードに変更があると自動でリロード
- エラー時のデバッガー機能
リモートからのアクセスを許可
app.run(host='0.0.0.0')
デフォルトでは127.0.0.1
(ローカルループバックアドレス)で待ち受けており、外からのアクセスを受け付けない。
GET 以外のメソッドを受け付ける
@app.route('/path', methods=['POST'])
def func():
pass
エラーハンドル
エラー発生時処理をカスタマイズできる。
from flask import abort
# 強制的に例外を発生させる
@app.route('/exception')
def exception():
raise Exception('hoge')
# 強制的に500ステータスを返す
@app.route('/500')
def internal_server_error():
abort(500)
# 例外をハンドル
@app.errorhandler(Exception)
def handle_exception(e):
return '----- [handled] Exception -----\n{}'.format(e), 500
# 500をハンドル
@app.errorhandler(500)
def internal_error(error):
return '----- [handled] Internal Server Error -----\n{}'.format(error), 500
# 404 をハンドル
@app.errorhandler(404)
def not_found(error):
return '----- [handled] Not Found -----\n{}'.format(error), 404
$ curl --dump-header - http://localhost:5000/exception
HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/html; charset=utf-8
Content-Length: 36
Server: Werkzeug/0.16.0 Python/3.7.6
Date: Sat, 01 Feb 2020 07:36:57 GMT
----- [handled] Exception -----
hoge
$ curl --dump-header - http://localhost:5000/500
HTTP/1.0 500 INTERNAL SERVER ERROR
Content-Type: text/html; charset=utf-8
Content-Length: 225
Server: Werkzeug/0.16.0 Python/3.7.6
Date: Sat, 01 Feb 2020 07:36:59 GMT
----- [handled] Internal Server Error -----
500 Internal Server Error: The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
$ curl --dump-header - http://localhost:5000/abc
HTTP/1.0 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 167
Server: Werkzeug/0.16.0 Python/3.7.6
Date: Sat, 01 Feb 2020 07:37:00 GMT
----- [handled] Not Found -----
404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
リダイレクト
from flask import redirect, url_for
@app.route('/hello')
def func1():
return 'Hello!'
@app.route('/hi')
def func2():
return redirect(url_for('func1'))
$ curl --dump-header - http://localhost:5000/hello
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 6
Server: Werkzeug/0.16.0 Python/3.7.6
Date: Sat, 01 Feb 2020 07:00:30 GMT
Hello!
$ curl --dump-header - http://localhost:5000/hi
HTTP/1.0 302 FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 219
Location: http://localhost:5000/hello
Server: Werkzeug/0.16.0 Python/3.7.6
Date: Sat, 01 Feb 2020 07:00:34 GMT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/hello">/hello</a>. If not click the link.
セッション
ログイン・ログアウト処理を行う例を示す。
static/login.html
<html>
<body>
<form action="/login" method="post">
<p><input type="text" name="username">
<p><input type="submit" value="ログイン">
</form>
</body>
</html>
from flask import Flask, session, redirect, request
import os
app = Flask(__name__)
# 秘密鍵の登録
app.secret_key = os.urandom(24)
@app.route('/home')
def home():
return 'Your name: {}'.format(session['username'])
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect('/home')
else:
return app.send_static_file('login.html')
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect('/home')
localhost:5000/home
localhost:5000/login
localhost:5000/home
localhost:5000/logout
ロギング
app.logger.debug('Debug log.')
app.logger.info('Info log.')
app.logger.warning('Warning log.')
app.logger.error('Error log.')
ルーティング URL が重複したときの優先順位
実験してみた。
- 直接 URL を指定 > コンバータ付きパスパラメータ > コンバータなしパスパラメータ
- パス、コンバータが重複する複数のルートが定義されている場合、先に定義した方が優先
@app.route('/hello')
def func1a():
return 'route 1a'
@app.route('/hello')
def func1b():
return 'route 1b'
@app.route('/<int:param>')
def func2a(param):
return 'route 2a'
@app.route('/<int:param>')
def func2b(param):
return 'route 2b'
@app.route('/<param>')
def func4a(param):
return 'route 4a'
@app.route('/<param>')
def func4b(param):
return 'route 4b'
@app.route('/<float:param>')
def func3(param):
return 'route 3'
$ curl http://localhost:5000/hello
route 1
$ curl http://localhost:5000/helloworld
route 4a
$ curl http://localhost:5000/3
route 2a
$ curl http://localhost:5000/3.1
route 3
$ curl http://localhost:5000/3.0
route 3
$ curl http://localhost:5000/
# 404 Not Found
ファイルのアップロード
static/upload.html
<html>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploaded_file"/>
<input type="submit" value="アップロード">
</form>
</body>
</html>
from flask import request
from werkzeug import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'uploaded_file' not in request.files:
return 'no file'
else:
file = request.files['uploaded_file']
filename = secure_filename(file.filename)
size = len(file.read())
res = {'file_name': filename, 'size': size}
return jsonify(ResultSet=res)
else:
return app.send_static_file('upload.html')
ページアクセス時:
ファイル選択後:
「アップロード」結果:
{
"ResultSet": {
"file_name": "hoge.json",
"size": 21
}
}
URL 末尾のスラッシュの有無
\ | 末尾に/ をつけてリクエスト |
末尾に/ をつけずリクエスト |
---|---|---|
URL 定義末尾に/ あり |
200 OK |
308 PERMANENT REDIRECT |
URL 定義末尾に/ なし |
404 NOT FOUND |
200 OK |
@app.route('/hello')
def no_slash():
return 'slash not in definition'
@app.route('/hi/')
def slash():
return 'slash in definition'
$ curl http://localhost:5000/hello
slash not in definition
$ curl http://localhost:5000/hello/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
$ curl http://localhost:5000/hi
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="http://localhost:5000/hi/">http://localhost:5000/hi/</a>. If not click the link.
$ curl http://localhost:5000/hi/
slash in definition