ファイルのアップロード
はい、ファイルアップロードの古き良き問題です。 ファイルのアップロードの基本的な考え方は、実際には非常に単純です。 基本的には次のように機能します。
<form>
タグはenctype=multipart/form-data
でマークされ、<input type=file>
はその形式で配置されます。- アプリケーションは、リクエストオブジェクトの
files
ディクショナリからファイルにアクセスします。 - ファイルの
save()
メソッドを使用して、ファイルをファイルシステムのどこかに永続的に保存します。
優しい紹介
特定のアップロードフォルダにファイルをアップロードし、ユーザーにファイルを表示する非常に基本的なアプリケーションから始めましょう。 アプリケーションのブートストラップコードを見てみましょう。
import os
from flask import Flask, flash, request, redirect, url_for
from werkzeug.utils import secure_filename
UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
したがって、最初にいくつかのインポートが必要です。 ほとんどは簡単なはずです。werkzeug.secure_filename()
については少し後で説明します。 UPLOAD_FOLDER
はアップロードされたファイルを保存する場所であり、ALLOWED_EXTENSIONS
は許可されたファイル拡張子のセットです。
許可される拡張機能を制限するのはなぜですか? サーバーがクライアントにデータを直接送信している場合は、ユーザーがそこにすべてをアップロードできないようにする必要があります。 これにより、ユーザーがXSSの問題を引き起こす可能性のあるHTMLファイルをアップロードできないようにすることができます(クロスサイトスクリプティング(XSS)を参照)。 また、サーバーが.php
ファイルを実行する場合は、必ず許可しないようにしてください。ただし、サーバーにPHPがインストールされているのは誰ですか。 :)
次に、拡張子が有効かどうかをチェックし、ファイルをアップロードして、アップロードされたファイルのURLにユーザーをリダイレクトする関数は次のとおりです。
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# if user does not select file, browser also
# submit an empty part without filename
if file.filename == '':
flash('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file',
filename=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
では、そのsecure_filename()
関数は実際に何をするのでしょうか? ここで問題となるのは、「ユーザー入力を絶対に信用しない」という原則があるということです。 これは、アップロードされたファイルのファイル名にも当てはまります。 送信されたすべてのフォームデータは偽造される可能性があり、ファイル名は危険な場合があります。 今のところ覚えておいてください。ファイルシステムに直接保存する前に、常にその関数を使用してファイル名を保護してください。
プロのための情報
それで、あなたはそのsecure_filename()
関数が何をするのか、そしてあなたがそれを使わない場合の問題は何であるのかに興味がありますか? したがって、誰かが次の情報を filename としてアプリケーションに送信すると想像してみてください。
filename = "../../../../home/username/.bashrc"
../
の数が正しく、これをUPLOAD_FOLDER
と結合すると、ユーザーはサーバーのファイルシステム上のファイルを変更できない可能性があります。 これには、アプリケーションがどのように見えるかについての知識が必要ですが、私を信じてください、ハッカーは忍耐強いです:)
次に、その関数がどのように機能するかを見てみましょう。
>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'
最後にもう1つ欠けているのは、アップロードされたファイルの提供です。 upload_file()
では、ユーザーをurl_for('uploaded_file', filename=filename)
、つまり/uploads/filename
にリダイレクトします。 そこで、uploaded_file()
関数を記述して、その名前のファイルを返します。 Flask 0.5以降、それを行う関数を使用できます。
from flask import send_from_directory
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
または、 uploaded_file を build_only ルールとして登録し、SharedDataMiddleware
を使用することもできます。 これは、古いバージョンのFlaskでも機能します。
from werkzeug.middleware.shared_data import SharedDataMiddleware
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
build_only=True)
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
'/uploads': app.config['UPLOAD_FOLDER']
})
ここでアプリケーションを実行すると、すべてが期待どおりに機能するはずです。
アップロードの改善
バージョン0.6の新機能。
では、Flaskはアップロードをどのように正確に処理しますか? ファイルが適度に小さい場合はWebサーバーのメモリに保存され、そうでない場合は一時的な場所に保存されます(tempfile.gettempdir()
によって返されます)。 しかし、アップロードが中止されるまでの最大ファイルサイズをどのように指定しますか? デフォルトでは、Flaskは無制限の量のメモリへのファイルのアップロードを喜んで受け入れますが、MAX_CONTENT_LENGTH
構成キーを設定することでそれを制限できます。
from flask import Flask, Request
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
上記のコードは、最大許容ペイロードを16メガバイトに制限します。 より大きなファイルが送信されると、FlaskはRequestEntityTooLarge
例外を発生させます。
接続リセットの問題
ローカル開発サーバーを使用すると、413応答ではなく接続リセットエラーが発生する場合があります。 本番WSGIサーバーでアプリを実行すると、正しいステータス応答が得られます。
この機能はFlask0.6で追加されましたが、リクエストオブジェクトをサブクラス化することで、古いバージョンでも実現できます。 詳細については、ファイル処理に関するWerkzeugのドキュメントを参照してください。
プログレスバーをアップロードする
しばらく前、多くの開発者は、受信ファイルを小さなチャンクで読み取り、アップロードの進行状況をデータベースに保存して、クライアントからJavaScriptを使用して進行状況をポーリングできるようにすることを考えていました。 簡単に言うと、クライアントは5秒ごとにサーバーに送信済みの量を尋ねます。 あなたは皮肉に気づいていますか? クライアントは、すでに知っておくべきことを求めています。
より簡単なソリューション
現在、より高速に動作し、より信頼性の高い、より優れたソリューションがあります。 プログレスバーの作成を容易にするフォームプラグインを備えた jQuery のようなJavaScriptライブラリがあります。
ファイルアップロードの一般的なパターンは、アップロードを処理するすべてのアプリケーションでほとんど変更されていないため、 Flask-Uploads と呼ばれるFlask拡張機能もあり、拡張機能のホワイトリストやブラックリストなどを備えた本格的なアップロードメカニズムを実装しています。