Загрузка файлов
Да, старая добрая проблема загрузки файлов. Основная идея загрузки файлов на самом деле довольно проста. Она работает примерно так:
- Тег
<form>помечен какenctype=multipart/form-data, и<input type=file>размещен в этой форме. - Приложение получает доступ к файлу из словаря
filesобъекта запроса. - Используйте метод
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)
Если вы используете middleware или 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, которые реализуют полную систему загрузки, которая позволяет управлять разрешёнными расширениями загружаемых файлов.
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.3.x/patterns/fileuploads/