概要
Flask でファイルのアップロードをやってみました
環境
- macOS 11.4
- Python 3.8.3
- flask 1.1.2
サンプルコード
- vim app.py
import os
from flask import Flask, flash, request, redirect, url_for, send_from_directory, render_template
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = '/tmp'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.secret_key = 'randomstringyoulike'
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@app.route('/', methods=['POST'])
def upload_file():
# file パートがない場合はアップロード画面にリダイレクト
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# ファイルが選択されていない場合はアップロード画面にリダイレクト
if not file.filename:
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
# マルチバイトなど XSS の可能性のある文字列を変換
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
@app.route('/', methods=['GET'])
def upload_file_view():
return render_template('index.html')
- vim templates/index.html
<!doctype html>
<title>Upload new File</title>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
解説
「/」にブラウザでアクセスした場合にはアップロードフォームを表示します フォームはテンプレートとして管理します
ファイルは multipart/form-data としてアップロードします
Flask では file パートがある場合に request.files
に格納されるためここをチェックします
そもそも file パートがない場合とアップロードフォームでファイルを指定しないでアップロードした場合はエラーとしトップのアップロードフォーム画面にリダイレクトします
今回は flash メソッドを使ってエラー分をテンプレートに表示するような仕組みにしていますがここは好きなようにしてもらって大丈夫です
RuntimeError: The session is unavailable because no secret key was set. が発生する場合は app.secret_key
に好きな暗号化文字列を入力します
flush が session を使う仕組みなので secret_key を設定していない場合に上記のエラーが発生します
アップロードされたファイルは「/tmp」に保存されます
保存する際に XSS 対策としてファイル名を半角英数字のみのファイル名に変換してあげています
ファイルのアップロードに成功した場合はアップロードしたファイルを返しています
flask でファイルの内容をそのまま返したい場合には send_from_directory
を使うのが便利です
もう少し考えるべきこと
今はサイズ制限などしていません
数GBのファイルをアップロードされてサーバ側がパンクする可能性もあるのでファイルサイズのチェックはサーバ側でもするべきです
ちなみにファイルサイズも含めてリクエストのペイロード全体のサイズを制限する場合は app.config['MAX_CONTENT_LENGTH']
を使います
このサイズを超えてアップロードしようとした場合は 413 エラーが flask から返却されます
また拡張子のチェックはしていますが正確なファイルの内容を見ているわけではないので本来であればファイルの内容まで確認した上でアップロードさせるべきです
あとは自分で実装せずともそのあたりのお作法などを実装してくれている Flask-Uploads というモジュールがあるのでこれを使うのも手です
動作確認
- pipenv run flask run
でアプリが起動した localhost:5000 にアクセスするとアップロードフォームが表示されます また curl の場合は以下のようにアップロードできます
-
curl -X POST -F file=@./test.txt localhost:5000
0 件のコメント:
コメントを投稿