Python
Flask
upload
アップロード

Flaskでmultipart/form-dataのファイルアップロードを実現する方法

1. はじめに

今回はflaskでmultipart/form-dataによるファイルアップロードの方法について説明したいと思います。わざわざmultipart/form-dataと書いているのはRest API(json形式)におけるファイルアップロードという選択肢もあるからです。こちらについては次回説明する予定です。
なお、クライアント側については「Pythonのrequestsを利用してmultipart/form-dataのFormにファイルアップロードする方法」を参照ください。

2. ソースコード

multipartFileUploadApp.py
# -*- coding: utf-8 -*-
from flask import Flask, request, make_response, jsonify
import os
import werkzeug
from datetime import datetime

# flask
app = Flask(__name__)

# ★ポイント1
# limit upload file size : 1MB
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024

# ★ポイント2
# ex) set UPLOAD_DIR_PATH=C:/tmp/flaskUploadDir
UPLOAD_DIR = os.getenv("UPLOAD_DIR_PATH")

# rest api : request.files with multipart/form-data
# <form action="/data/upload" method="post" enctype="multipart/form-data">
#   <input type="file" name="uploadFile"/>
#   <input type="submit" value="submit"/>
# </form>
@app.route('/data/upload', methods=['POST'])
def upload_multipart():

    # ★ポイント3
    if 'uploadFile' not in request.files:
        make_response(jsonify({'result':'uploadFile is required.'}))

    file = request.files['uploadFile']
    fileName = file.filename
    if '' == fileName:
        make_response(jsonify({'result':'filename must not empty.'}))

    # ★ポイント4
    saveFileName = datetime.now().strftime("%Y%m%d_%H%M%S_") \
        + werkzeug.utils.secure_filename(fileName)
    file.save(os.path.join(UPLOAD_DIR, saveFileName))
    return make_response(jsonify({'result':'upload OK.'}))

# ★ポイント5
@app.errorhandler(werkzeug.exceptions.RequestEntityTooLarge)
def handle_over_max_file_size(error):
    print("werkzeug.exceptions.RequestEntityTooLarge")
    return 'result : file size is overed.'

# main
if __name__ == "__main__":
    print app.url_map
    app.run(host='localhost', port=3000)
static/upload.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<!-- ★ポイント6 -->
<form action="/data/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="uploadFile"/>
  <input type="submit" value="submit"/>
</form>
</body>
</html>

★ポイント1

ファイルアップロードにおけるセキュリティ対策としてデータサイズの制限は重要です。
flaskでは標準の機能でデータサイズの上限(正確にはHTTPリクエストのContent-Length)を設定することができます。
単位はByteで、サンプルでは1MBを上限としました。

If set to a value in bytes, Flask will reject incoming requests with a content length greater than this by returning a 413 status code.

★ポイント2

アップロードされたファイルをファイルシステムに保存する場合、Webからアクセス可能な場所にするかアクセス不可能な場所にするか業務要件によって変わるかと思います。
サンプルでは環境に応じて変更できるように、環境変数から取得するようにしました。

★ポイント3

<input type="file">のデータはflaskではrequest.filesに格納されます。Formのフィールド名(input要素のname属性の値)でデータが存在する(送信されている)か確認します。
特定のアップロードファイルにはrequest.filesからFormのフィールド名をキーとしてアクセスできます。
ファイルの実体はwerkzeug.datastructures.FileStorageです。FileStorageにはファイル名やmimetype等の情報が格納されています。

  • filename : 送信時のファイル名
  • name : Formのフィールド名
  • headers : HTTPリクエストのヘッダ情報(flaskのheaderオブジェクト)
  • content_length : HTTPリクエストのcontent-length
  • mimetype : mimetype

詳しくは公式ガイドラインを参照ください。
サンプルではファイル名が設定されているかチェックしています。

★ポイント4

save()でアップロードされたファイルを、引数で指定したファイルパスにファイルとして保存します。この際、ファイル名の重複すると上書きになるため注意が必要です。
サンプルでは★ポイント2で取得したディレクトリに、werkzeug.utils.secure_filename()を利用してOSセーフなファイル名にコンバートした後、年月日時分秒のプレフィックスを付与して保存しています。

(注意)完全に一意なファイル名で保存する場合、アップロードされたファイル名を利用せず、システムにて生成した値を利用すべきです。

なお、ファイルのバイナリデータをDB等に格納したい場合、save()ではなくstreamを利用して直接インプットストームを操作することができます。

★ポイント5

★ポイント1で設定したMAX_CONTENT_LENGTHを超過したHTTPリクエストの場合、werkzeug.exceptions.RequestEntityTooLarge 例外が発生します。
必要に応じてRequestEntityTooLargeのエラーハンドリングを行ってください。

★ポイント6

flaskのデフォルト設定はstaticディレクトリがWebに公開されます。詳細については「Flaskで静的ファイルの格納ディレクトリとURLを変更する」を参照ください。
動作確認用にこの機能を利用してファイルのアップロード画面(static/upload.html)を作成しました。

  • アクションのURL : /data/upload
  • HTTPメソッド : POST
  • Formのエンコード : multipart/form-data
  • fileのフィールド名 : uploadFile

Webブラウザではなくpythonのプログラムでアップロードする場合、「Pythonのrequestsを利用してmultipart/form-dataのFormにファイルアップロードする方法」を参照ください。

3. さいごに

今回はflaskでmultipart/form-dataによるファイルアップロードの方法について説明しました。
flask(werkzeug)の標準機能で対応できるため、簡単にファイルアップロードを実装できることが理解できたかと思います。
冒頭にも書きましたが、次回はFlaskでRest API(json形式)によるファイルアップロードを実現する方法について説明したいと思います。