Загрузка файлов
Да, старая добрая проблема загрузки файлов. Основная идея загрузки файлов на самом деле довольно проста. В принципе, она работает так:
- Тег
<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)
Если вы используете промежуточное ПО или 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.2.x/patterns/fileuploads/