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'е разработчиков мне не дали нормального решения данной проблемы.
    Впрочем, и у данного решения есть минус, а именно варьирование ограничения и количество оперативной памяти.

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

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 5

      +2
      Картинка очень символична!
        0
        Странно, у меня MAX_CONTENT_LENGTH работал нормально. Только это было на версии 0.8
        Плюс я подкручивал nginx, но по другой части — увеличивал предел загружаемого файла.
          0
          Flask 0.10, MAX_CONTENT_LENGTH выставлен в 8 мегабайт — полет нормальный.
            0
            Как перехватываете RequestEnityTooLarge?
              0
              Собственно, согласно документации:

              @ app.errorhandler(413)
              def err_413(error):
              return «OOPS!»

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое