Spec-Zone.ru › Flask 0.12

Загрузка файлов

Да, давняя проблема загрузки файлов. Основная идея загрузки файлов на самом деле довольно проста. Она работает примерно так:

  1. Тег <form> помечен как enctype=multipart/form-data и в эту форму помещен <input type=file>.
  2. Приложение получает доступ к файлу из словаря files в объекте запроса.
  3. Используйте метод save() файла, чтобы сохранить файл постоянно где-нибудь в файловой системе.

Небольшое введение

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

import os
from flask import Flask, request, redirect, url_for
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])

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

Итак, сначала нам нужно несколько импортов. Большинство должны быть очевидными, werkzeug.secure_filename() поясняется чуть позже. UPLOAD_FOLDER - это место, где мы будем хранить загруженные файлы, а ALLOWED_EXTENSIONS — набор разрешенных расширений файлов.

Почему мы ограничиваем разрешенные расширения? Вы, вероятно, не хотите, чтобы ваши пользователи могли загружать все, что угодно, если сервер напрямую отправляет данные клиенту. Таким образом, вы можете убедиться, что пользователи не могут загружать HTML-файлы, которые могут вызвать проблемы XSS (см. Межсайтовый скриптинг (XSS)). Также убедитесь, что запрещены .php файлы, если сервер их выполняет, но кто устанавливает PHP на свой сервер, да? :)

Далее функции, проверяющие, является ли расширение допустимым, и загружающие файл, перенаправляя пользователя на URL загруженного файла:

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 user does not select file, browser also
        # submit a empty part without 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('uploaded_file',
                                    filename=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form method=post enctype=multipart/form-data>
      <p><input type=file name=file>
         <input type=submit value=Upload>
    </form>
    '''

Что же делает функция secure_filename()? Сейчас проблема в том, что есть принцип «никогда не доверяйте пользовательскому вводу». Это также относится к имени файла загружаемого файла. Все отправленные данные формы могут быть сфальсифицированы, а имена файлов могут быть опасными. На данный момент просто запомните: всегда используйте эту функцию для защиты имени файла перед его непосредственным сохранением в файловой системе.

Информация для профессионалов

Вы заинтересованы в том, что делает функция secure_filename() и в чем проблема, если вы ее не используете? Представьте, что кто-то отправит следующую информацию как filename вашему приложению:

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

Предполагая, что количество ../ правильное и вы бы объединили это с UPLOAD_FOLDER, пользователь мог бы иметь возможность изменить файл в файловой системе сервера, который ему не следует изменять. Для этого потребуются некоторые знания о том, как выглядит приложение, но поверьте мне, хакеры терпеливы :)

Теперь давайте посмотрим, как работает эта функция:

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

Теперь не хватает только предоставления загруженных файлов. В upload_file() мы перенаправляем пользователя на url_for('uploaded_file', filename=filename), то есть /uploads/filename. Таким образом, мы пишем функцию uploaded_file() для возврата файла с этим именем. Начиная с Flask 0.5, мы можем использовать функцию, которая делает это за нас:

from flask import send_from_directory

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

В качестве альтернативы вы можете зарегистрировать uploaded_file как build_only правило и использовать SharedDataMiddleware. Это также работает с более старыми версиями Flask:

from werkzeug import SharedDataMiddleware
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
                 build_only=True)
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
    '/uploads':  app.config['UPLOAD_FOLDER']
})

Если вы теперь запустите приложение, все должно работать как ожидается.

Улучшение загрузки

Журнал изменений

Новое в версии 0.6.

Итак, как именно Flask обрабатывает загрузки? Ну, он будет хранить их в памяти веб-сервера, если файлы достаточно небольшие, в противном случае — во временном месте (как возвращается tempfile.gettempdir()). Но как указать максимальный размер файла, после которого загрузка прерывается? По умолчанию Flask будет с радостью принимать загрузки файлов в неограниченном количестве памяти, но вы можете ограничить это, задав параметр конфигурации MAX_CONTENT_LENGTH:

from flask import Flask, Request

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

Приведенный выше код ограничит максимальный разрешенный размер полезной нагрузки до 16 мегабайт. Если передается файл большего размера, Flask вызовет исключение RequestEntityTooLarge.

Эта функция была добавлена в Flask 0.6, но может быть реализована и в более старых версиях путем наследования объекта запроса. Для получения дополнительной информации ознакомьтесь с документацией Werkzeug по обработке файлов.

Полосы прогресса загрузки

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

Более простое решение

Сейчас есть лучшие решения, которые работают быстрее и надежнее. Существуют JavaScript-библиотеки, такие как jQuery, которые имеют плагины для форм, облегчающие создание полосы прогресса.

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

© 2007–2020 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/0.12.x/patterns/fileuploads/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API