ファイルのアップロード Uploading Files

確かに、ファイルのアップロードは昔ながらの問題です。ファイルのアップロードの基本的な考えは非常に単純です。基本的に以下のように働きます: Ah yes, the good old problem of file uploads. The basic idea of file uploads is actually quite simple. It basically works like this:

  1. <form>タグはenctype=multipart/form-dataで印付けられ、そのformの中に<input type=file>が置かれます。 A ``<form>`` tag is marked with ``enctype=multipart/form-data`` and an ``<input type=file>`` is placed in that form.

  2. アプリケーションは、リクエストのオブジェクトのfilesにあるdictionaryからファイルにアクセスします。 The application accesses the file from the :attr:`~flask.request.files` dictionary on the request object.

  3. ファイルのsave()メソッドを使って、ファイルシステムのどこかへ永続的にファイルを保存します。 use the :meth:`~werkzeug.datastructures.FileStorage.save` method of the file to save the file permanently somewhere on the filesystem.

やさしいイントロダクション A Gentle Introduction

特定のアップロード・フォルダへファイルをアップロードし、ユーザへファイルを表示する、非常に基本的なアプリケーションから始めましょう。アプリケーションの手始めのコードを見てみましょう: Let's start with a very basic application that uploads a file to a specific upload folder and displays a file to the user. Let's look at the bootstrapping code for our application::

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 = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

最初にいくつかimportが必要です。ほとんどは直感的なはずですが、secure_filename()はもう少し後で説明します。UPLOAD_FOLDERはアップロードされたファイルを格納する場所で、ALLOWED_EXTENSIONSは許可されたファイル拡張子です。 So first we need a couple of imports. Most should be straightforward, the :func:`werkzeug.secure_filename` is explained a little bit later. The ``UPLOAD_FOLDER`` is where we will store the uploaded files and the ``ALLOWED_EXTENSIONS`` is the set of allowed file extensions.

なぜ許可される拡張子を制限するのでしょうか?恐らくあなたは、サーバがクライアントへ直接データを送信する場合、そのサーバへユーザがなんでもアップロードできるようにはしたくないでしょう。そうすれば、ユーザがXSSの問題(クロスサイトスクリプティング(Cross-Site Scripting)(XSS)を見てください)を起こしかねないHTMLファイルをアップロードできないことを確実にできます。もしサーバが.phpファイルを実行する場合はphpファイルのアップロードを許さないことも確実にするでしょうが、(Flaskがあるのに)誰が自分のサーバにPHPをインストールするんでしょうか?:) Why do we limit the extensions that are allowed? You probably don't want your users to be able to upload everything there if the server is directly sending out the data to the client. That way you can make sure that users are not able to upload HTML files that would cause XSS problems (see :ref:`security-xss`). Also make sure to disallow ``.php`` files if the server executes them, but who has PHP installed on their server, right? :)

次は、拡張子が適切かチェックし、ファイルをアップロードし、ユーザをアップロードされたファイルのURLへリダイレクトする関数です: Next the functions that check if an extension is valid and that uploads the file and redirects the user to the URL for the uploaded file::

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 the user does not select a file, the browser submits an
        # empty file without a 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('download_file', name=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()関数は何を実際にはするのでしょうか?この時の問題は「ユーザのインプットは決して信用するな」といわれる原則があることです。これはアップロードされたファイルのファイル名についても当てはまります。提供されるformのデータはすべて偽造されている可能性があり、ファイル名が危険である可能性があります。今のところはただ覚えておくようにしてください: ファイルシステムへ直接格納する前に、常にファイル名を安全にする関数を使用してください。 So what does that :func:`~werkzeug.utils.secure_filename` function actually do? Now the problem is that there is that principle called "never trust user input". This is also true for the filename of an uploaded file. All submitted form data can be forged, and filenames can be dangerous. For the moment just remember: always use that function to secure a filename before storing it directly on the filesystem.

プロ向けの情報 Information for the Pros

このsecure_filename()関数が何をして、それを使わない場合は何が問題なのか、興味を持ちましたね?では、誰かが以下のような情報をfilenameとして自分のアプリケーションへ送信することを、ただ想像してください: So you're interested in what that :func:`~werkzeug.utils.secure_filename` function does and what the problem is if you're not using it? So just imagine someone would send the following information as `filename` to your application::

filename = "../../../../home/username/.bashrc"

../の数が適切であり、これをUPLOAD_FOLDERと結合すると仮定したとき、ユーザはサーバのファイルシステムにある本来変更すべきでないファイルを変更できる能力を持つかもしれません。これはアプリケーションがどのようなものかについての知識をいくらか要求しますが、信じてください、ハッカーは我慢強いのです:) Assuming the number of ``../`` is correct and you would join this with the ``UPLOAD_FOLDER`` the user might have the ability to modify a file on the server's filesystem he or she should not modify. This does require some knowledge about how the application looks like, but trust me, hackers are patient :)

それでは、この関数(secure_filename)がどのように働くかを見てみましょう: Now let's look how that function works:

>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'

ユーザがダウンロードできるように、アップロードされたファイルを提供可能にしたいでしょう。(以下の例では)nameによってアプロードのフォルダにあるファイルを提供する、download_fileというviewを定義します。url_for("download_file", name=name)が、ダウンロード用URLを生成します。 We want to be able to serve the uploaded files so they can be downloaded by users. We'll define a ``download_file`` view to serve files in the upload folder by name. ``url_for("download_file", name=name)`` generates download URLs.

from flask import send_from_directory

@app.route('/uploads/<name>')
def download_file(name):
    return send_from_directory(app.config["UPLOAD_FOLDER"], name)

もしファイルを提供するのにミドルウェアまたはHTTPサーバーを使っている場合、url_forがview関数なしで働くように、download_fileエンドポイントをbuild_onlyとして登録できます。 If you're using middleware or the HTTP server to serve files, you can register the ``download_file`` endpoint as ``build_only`` so ``url_for`` will work without a view function.

app.add_url_rule(
    "/uploads/<name>", endpoint="download_file", build_only=True
)

アップロードの改善 Improving Uploads

Changelog

バージョン 0.6 で追加.

それでは、正確にはFlaskはどのようにファイルのアップロードを処理するのでしょうか?もしファイルが妥当な小ささであればwebサーバのメモリ内に、そうでない場合は(tempfile.gettempdir()で返される)一時的な場所に、Flaskはファイルを格納します。しかし、それを超えたらアップロードが中止される、最大ファイルサイズはどのように指定するのでしょうか?既定の動作では、Flaskはファイルのアップロードをメモリ容量無制限に喜んで受け入れますが、MAX_CONTENT_LENGTHの設定キーを設定することで制限することができます: So how exactly does Flask handle uploads? Well it will store them in the webserver's memory if the files are reasonably small, otherwise in a temporary location (as returned by :func:`tempfile.gettempdir`). But how do you specify the maximum file size after which an upload is aborted? By default Flask will happily accept file uploads with an unlimited amount of memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH`` config key::

from flask import Flask, Request

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000

上記のコードは許可される最大のペイロードを16メガバイトに制限します。もしもっと大きなファイルが伝送された場合、FlaskはRequestEntityTooLarge例外を発生させます。 The code above will limit the maximum allowed payload to 16 megabytes. If a larger file is transmitted, Flask will raise a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception.

接続中止の問題 Connection Reset Issue

ローカルの開発サーバ(訳注: Flaskで最初から用意され使用可能な開発サーバのことだと思います)を使用しているとき、(HTTPステータスコードが)413のレスポンスの代わりに接続中止エラー(conneciton reset error)を得る場合があります。アプリを製品版のWSGIサーバで走らせたときは、適切なステータスのレスポンスを得られるようになります。 When using the local development server, you may get a connection reset error instead of a 413 response. You will get the correct status response when running the app with a production WSGI server.

この目玉機能はFlask 0.6で追加されましたが、リクエストのオブジェクトをサブクラス化することで古いバージョンでも同様に達成可能です。このことに関してのさらなる情報は、ファイル処理についてのWerkzeugのドキュメントを調べてください。 This feature was added in Flask 0.6 but can be achieved in older versions as well by subclassing the request object. For more information on that consult the Werkzeug documentation on file handling.

アップロードのプログレスバー Upload Progress Bars

しばらく前には、アップロードの進み具合をクライアントからJavaScriptでポーリングできるようにするために、多くの開発者は受信ファイルを小さな断片にして読み込み、アップロードの進み具合をデータベースに格納することを考えていました。クライアントは5秒ごとにサーバにどれくらいファイルを伝送済みかを問い合わせるのですが、それはクライアントが既に知っているべきことです。 A while ago many developers had the idea to read the incoming file in small chunks and store the upload progress in the database to be able to poll the progress with JavaScript from the client. The client asks the server every 5 seconds how much it has transmitted, but this is something it should already know.

より簡単な解決策 An Easier Solution

今ではより高速に働いてより信頼性のある、より良い解決策があります。プログレス・バーの構築を容易にするformのプラグインを持つ、jQueryのようなJavaScriptのライブラリがあります。 Now there are better solutions that work faster and are more reliable. There are JavaScript libraries like jQuery_ that have form plugins to ease the construction of progress bar.

ファイルのアップロードについてのありがちなパターンはアップロードを扱う全てのアプリケーションで殆ど変わることなく存在するため、Flask拡張にも、どのファイル拡張子はアップロードを許可するか制御できる、十分に成熟したアップロードの仕組みを実装したものがいくつかあります。 Because the common pattern for file uploads exists almost unchanged in all applications dealing with uploads, there are also some Flask extensions that implement a full fledged upload mechanism that allows controlling which file extensions are allowed to be uploaded.