Flask и загрузка файлов: success story

от автора


Добрый вечер, Хабраюзер. Хочу поделиться небольшим опытом ограничения размера загружаемого файла. Весь опыт получен эмпирическим путем.

Как выглядело ТЗ:

  1. В шаблоне создаем формочку с полем для файлов
  2. На сервере проверяем расширение файла и его размер
  3. Если все условия удовлетворяются — загружаем файл и сохраняем ссылку в БД

Все выглядит просто, но печаль настигла меня при проверке размера файла. Как я решил эту проблему? Добро пожаловать под хабракат.

Начало

По началу все шло просто отлично — и шаблон сделал, и серверную часть накодил, и все загружается — любо-дорого смотреть. Но тут я понимаю, что мне абсолютно не надо, чтообы какой-нибудь добрый человече начал сливать мне 4 гб. траффика на сервер. Гуглим и находим официальный док, в котором видим волшебные строчки:

By default Flask will happily accept file uploads to 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 * 1024 * 1024

Отлично! Добавляем строчку в конфиг, запускаем, и… И получаем ошибку 413… Беда… Гуглим, находим werkzeug.exceptions и RequestEnityTooLarge. Пытаемся завести конструкцию вида

try:     file = request.files["file"] except RequestEnityTooLarge as e:     return "ERROR", 200 

но без результата — браузер не хочет возвращать что-то кроме ошибки. Даже хваленый

@app.errorhandler(413)

ничем мне не помог, вывод в консоль не в счет — клиенту это по-барабану.

Продолжение

Ломал голову я дней 5, пока не добрался до начинкиFileStorage. Этот класс наследуется от object и имеет метод read, который производит чтение содержимого в оперативную память. А у этого метода есть необязательный аргумент size, который устанавливает максимальный размер считываемой информации. Отсюда и пляшем.

Набрасываем черновик:

from flask import Flask, render_template, request   MAX_FILE_SIZE = 1024 * 1024 + 1  app = Flask(__name__)   @app.route("/", methods=["POST", "GET"]) def index():     args = {"method": "GET"}     if request.method == "POST":         file = request.files["file"]         if bool(file.filename):             file_bytes = file.read(MAX_FILE_SIZE)             args["file_size_error"] = len(file_bytes) == MAX_FILE_SIZE         args["method"] = "POST"     return render_template("index.html", args=args)  if __name__ == "__main__":     app.run(debug=True) 

где в переменной MAX_FILE_SIZE указываем максимальный размер файла + 1 байт для отлова превышения. Остальное, думаю, пояснять не надо, если что — воопросы в комментарии.

Набрасываем шаблон:

<!doctype html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>Uploads</title> </head> <body>     {% if args["method"] == "POST" %}         {% if args["file_size_error"] %}             <h1>Размер файла превышает 1мб.!</h1>         {% else %}             <h1>Файл успешно загружен.</h1>         {% endif %}     {% endif %}     <form action="/" method="POST" enctype="multipart/form-data">         <input type="file" name="file">         <button type="submit">Загрузить</button>     </form> </body> </html> 

Запускаем, проверяем. При выдаче файла размером более 1мб. на нас будут кричать, орать и материться, а мы можем довольно потереть руки. Все работает.

Итоги

Что можно сказать про итоги? Возможно, это костыли. Возможно, я чайник, ламер и дно. Но даже в maillist’е разработчиков мне не дали нормального решения данной проблемы.
Впрочем, и у данного решения есть минус, а именно варьирование ограничения и количество оперативной памяти.

На сей ноте хочу распрощаться. Спасибо за то, что прочли, всю критику буду рад выслушать в комментариях. Всем удачи!

ссылка на оригинал статьи http://habrahabr.ru/post/201386/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *