LoginSignup
19
30

More than 5 years have passed since last update.

Flask サーバに保存せずにファイルをアップロード・ダウンロードする

Last updated at Posted at 2017-11-04

Flaskの使い方の基本を知っていることを前提とする。
ファイルを処理するスクリプトを走らせたいが、ベタ張りで保存したくないのは当然のこと、データベースに格納する必要もないという状況にあった。そこで、一回のPOSTリクエストでファイルをアップロード、処理、ダウンロードまでやってしまえばいいのではということで調べて見た結果のメモ。

アップロード

結論として、IOクラスのオブジェクトとして受け取れば良い123
Formタグでファイルを受け取れば、StringIOのバイナリファイルとしてオブジェクトにできる。4
この段階までを実行するプログラム例:

app.py
from flask import Flask, render_template, request


app = Flask(__name__)


@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        f = request.files['file']
        return f'uploaded {f.filename}'


if __name__ == '__main__':
    app.run(debug=True)
templates/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <body>
    <form action="/" method="POST" enctype="multipart/form-data">
      <input type="file" name="file">
      <input type="submit">
    </form>
  </body>
</html>

ダウンロード

公式ドキュメント5が一番参考になったが、結論として、ダウンロードというより、send_fileを使えば良い
ドキュメントの情報の注意点としては、
StringIOモジュールは3系ではなくなっており、ioクラスからio.StringIOを利用する必要がある。6

また、Python3以降では、Flaskの要求でStringIOではなくBytesIOを用いる

app.py
+ from flask import Flask, render_template, request, send_file


app = Flask(__name__)


@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        f = request.files['file']
        # return f'uploaded {f.filename}'
+       return send_file(f,
+                        attachment_filename=f.filename,
+                        as_attachment=True)


if __name__ == '__main__':
    app.run(debug=True)

サンプルなので上げたファイルをまた落とすだけという中身空っぽな処理になっているが、f = request.files['file']return send_file(...)の間に処理を書けばいい。

デプロイ編

蛇足ながら、uwsgi + nginxで構成したサーバに実装した際に遭遇した問題とその解決を記す。若干手抜き。

nginxでpermission denied

2017/11/01 06:46:19 [crit] 25062#0: *1 open() "/usr/local/var/run/nginx/client_body_temp/0000000001" failed (13: Permission denied), client: 124.27.107.250, server: (your_domain), request: "POST /sbi HTTP/1.1", host: "(your_domain)", referrer: "http://(your_domain)/(uri)"

error_logにこんなのが残っていました。
こちらの記事を参考にしました
https://qiita.com/paranishian/items/5ca43e13e9711a0a1ae6

uwsgiでsend_fileがうまくいかない

エラー内容:

[2017-11-01 06:59:20,352] ERROR in app: Exception on /(uri) [POST]
io.UnsupportedOperation: fileno

The above exception was the direct cause of the following exception:

(中略)
SystemError: <built-in function uwsgi_sendfile> returned a result with an error set
[pid: 25653|app: 0|req: 3/3] 124.27.107.250 () {52 vars in 992 bytes}[Wed Nov  1 06:59:20 2017] POST /(uri) => generated 291 bytes in 81 msecs (HTT
P/1.1 500) 2 headers in 84 bytes (9 switches on core 0)

こちらを参考に、uwsgi.iniを編集しました
https://github.com/unbit/uwsgi/issues/1126#issuecomment-166687767

uwsgi.ini
wsgi-disable-file-wrapper = true

参照・注釈


  1. 公式ドキュメント http://flask.pocoo.org/docs/0.12/patterns/fileuploads/ 

  2. 参考にしたブログ http://nekoyukimmm.hatenablog.com/entry/2016/05/27/162736 

  3. 参考サイト https://www.tutorialspoint.com/flask/flask_file_uploading.htm 

  4. 正確にStringIOなのかBytesIOなのか理解が若干曖昧だが、f = open(fname, 'r')と同じように扱えることは確かだ。 

  5. 公式ドキュメント 一番参考になった http://flask.pocoo.org/snippets/32/ 

  6. https://stackoverflow.com/a/18284900/8776028 

19
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
30