Spec-Zone.ru › Flask

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

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

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

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

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

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

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = {'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 the user does not select a file, the browser submits an
        # empty file without a 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('download_file', name=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form method=post enctype=multipart/form-data>
      <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'

Мы хотим иметь возможность обслуживать загруженные файлы, чтобы пользователи могли их загрузить. Мы определим представление download_file для обслуживания файлов в папке загрузки по имени. url_for("download_file", name=name) генерирует URL для скачивания.

from flask import send_from_directory

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

Если вы используете промежуточное ПО или HTTP-сервер для обслуживания файлов, вы можете зарегистрировать конечную точку download_file как build_only, поэтому url_for будет работать без функции представления.

app.add_url_rule(
    "/uploads/<name>", endpoint="download_file", build_only=True
)

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

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

Добавлен в версии 0.6.

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

from flask import Flask, Request

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

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

Проблема разрыва соединения

При использовании локального сервера разработки вместо ответа 413 вы можете получить ошибку разрыва соединения. Вы получите правильный ответ статуса при запуске приложения с сервером WSGI для производства.

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

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

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

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

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

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

© 2010 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/stable/patterns/fileuploads/

Spec-Zone.ru

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