Effiziente Flask-Datei-Uploads: Schritt-für-Schritt-Anleitung
Datei-Uploads sind eine zentrale Funktion moderner Webanwendungen, denn sie ermöglichen es Nutzern, Daten effizient zu teilen und zu speichern. Flask, ein leichtgewichtiges und zugleich leistungsfähiges Python-Webframework, bietet robuste Möglichkeiten, Datei-Uploads sicher und effizient zu verarbeiten. In dieser Schritt-für-Schritt-Anleitung zeigen wir, wie Sie Datei-Uploads in Flask-Anwendungen umsetzen, und behandeln dabei bewährte Verfahren, Sicherheitsmaßnahmen und fortgeschrittene Techniken.
Ihre Flask-Umgebung einrichten
Für den Einstieg in Datei-Uploads mit Flask richten wir eine einfache Flask-Anwendung mit den nötigen Abhängigkeiten ein. Installieren Sie zunächst die Systemabhängigkeiten für python-magic:
# For Debian/Ubuntu
sudo apt-get install libmagic1
# For macOS
brew install libmagic
# For Windows
# Install libmagic DLLs separately as described in python-magic's Windows instructions.
Installieren Sie nun Flask und seine Abhängigkeiten mit pip:
pip install flask==3.1.0 flask-wtf==1.2.2 python-magic==0.4.27
export FLASK_SECRET_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"
Folgen Sie unter Windows der Installationsanleitung für python-magic,
um kompatible libmagic-Binaries zu erhalten; sie werden nicht automatisch installiert. Setzen Sie
FLASK_SECRET_KEY in Ihrer Shell oder in der Secret-Konfiguration Ihres Deployments und
halten Sie den Wert über alle Anwendungs-Worker hinweg stabil.
Legen wir eine einfache Flask-Anwendungsstruktur in einer Datei mit dem Namen
app.py an:
from flask import Flask
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ['FLASK_SECRET_KEY']
app.config['UPLOAD_FOLDER'] = os.path.join(app.instance_path, 'uploads')
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16 MiB total request limit
# Ensure the upload folder exists
os.makedirs(app.config['UPLOAD_FOLDER'], mode=0o700, exist_ok=True)
Konfiguration erklärt:
SECRET_KEY: Ein geheimer Schlüssel, den Flask-WTF zum sicheren Signieren des Session-Cookies und für weitere sicherheitsrelevante Zwecke verwendet.UPLOAD_FOLDER: Privater Speicherort außerhalb des statischen Verzeichnisses von Flask. Geben Sie das Instanzverzeichnis nicht über Ihren Webserver frei.MAX_CONTENT_LENGTH: Die maximale Gesamtgröße einer Anfrage (16 MiB), einschließlich Multipart-Feldern und Overhead. Die zulässige Dateigröße liegt geringfügig darunter.
Das Formular für Datei-Uploads erstellen
Um Datei-Uploads zu ermöglichen, erstellen wir ein Flask-WTF-Formular, das die hochgeladenen Dateien validiert.
Erstellen Sie zunächst eine Formularklasse in forms.py:
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired, FileAllowed
class UploadForm(FlaskForm):
file = FileField('File', validators=[
FileRequired(),
FileAllowed(['jpg', 'jpeg', 'png', 'pdf'], 'Allowed file types are jpg, jpeg, png, pdf')
])
Das Formular verwendet FileField für die Dateieingabe und enthält Validatoren,
die sicherstellen, dass eine Datei übergeben wird und dass sie eine zulässige Dateiendung hat.
Erstellen Sie als Nächstes unter templates/upload.html eine Vorlage für das Upload-Formular:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Upload File</title>
</head>
<body>
<h1>Upload File</h1>
<form method="POST" enctype="multipart/form-data">
{{ form.hidden_tag() }} {{ form.file.label }} {{ form.file }}
{% for errors in form.errors.values() %}
{% for error in errors %}<p role="alert">{{ error }}</p>{% endfor %}
{% endfor %}
<input type="submit" value="Upload" />
</form>
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %}
<ul>
{% for category, message in messages %}
<li class="{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %} {% endwith %}
</body>
</html>
Diese Vorlage rendert das Formular und enthält einen Bereich, in dem Flash-Nachrichten als Rückmeldung für Nutzer angezeigt werden.
Die Verarbeitung von Datei-Uploads implementieren
Nun verarbeiten wir Datei-Uploads in unserer Flask-Anwendung mit robuster MIME-Typ-Validierung,
Prüfung der Dateiendungen und Fehlerbehandlung. Ergänzen Sie in app.py die
nötigen Importe und Routen:
from flask import render_template, redirect, url_for, flash, request, jsonify
from werkzeug.exceptions import HTTPException, RequestEntityTooLarge
from forms import UploadForm
import magic
import tempfile
ALLOWED_TYPES = {
'image/jpeg': ('jpg', 'jpeg'),
'image/png': ('png',),
'application/pdf': ('pdf',),
}
def save_upload(file):
if file is None or not file.filename:
raise ValueError('No file selected')
extension = file.filename.rsplit('.', 1)[-1].lower()
file_type = magic.from_buffer(file.read(2048), mime=True)
file.seek(0)
extensions = ALLOWED_TYPES.get(file_type)
if extensions is None or extension not in extensions:
raise ValueError('File type or extension not allowed')
# Exclusive temporary-file creation gives unique names and mode 0600.
with tempfile.NamedTemporaryFile(
dir=app.config['UPLOAD_FOLDER'], prefix='upload-',
suffix='.' + extensions[0], delete=False,
) as target:
try:
file.save(target)
except Exception:
os.unlink(target.name)
raise
return os.path.basename(target.name)
# Error handler for file size limit
@app.errorhandler(RequestEntityTooLarge)
def handle_file_too_large(e):
return jsonify({'error': 'The upload request exceeds 16 MiB.'}), 413
@app.route('/', methods=['GET', 'POST'])
def upload():
form = UploadForm()
if form.validate_on_submit():
try:
save_upload(form.file.data)
flash('File uploaded successfully', 'success')
except ValueError as e:
flash(str(e), 'danger')
except Exception as e:
app.logger.error('Upload failed (%s)', type(e).__name__)
flash('The file could not be saved. Please try again.', 'danger')
return redirect(url_for('upload'))
return render_template('upload.html', form=form)
Sicherheitsmerkmale:
- MIME-Sniffing prüft die Signatur einer Datei, beweist aber nicht, dass die Datei harmlos ist. Scannen oder verarbeiten Sie nicht vertrauenswürdige Inhalte angemessen, bevor Sie sie anderen Nutzern zugänglich machen.
- Die Hilfsfunktion prüft, ob die Dateiendung zum erkannten Typ passt.
- Serverseitig erzeugte Namen und Dateiendungen verhindern, dass von Nutzern gewählte Dateinamen lokale Pfade bestimmen.
- Ein durchgesetztes Limit für die Dateigröße schützt vor Ressourcenerschöpfung.
- Fehlermeldungen an Clients werden bereinigt; die Server-Diagnose protokolliert nur den Ausnahmetyp.
Einen Endpunkt für die REST API erstellen
Für programmatische Datei-Uploads implementieren wir einen Endpunkt der REST API:
@app.route('/api/upload', methods=['POST'])
def api_upload():
try:
filename = save_upload(request.files.get('file'))
return jsonify({
'message': 'File uploaded successfully',
'filename': filename
}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400
Den API-Endpunkt testen:
Nachdem Sie die Routen und den Fehlerhandler in app.py ergänzt haben, starten
Sie Flask und testen den API-Endpunkt anschließend mit cURL in einem weiteren Terminal:
flask --app app run
curl -F 'file=@/path/to/your/file.jpg' http://localhost:5000/api/upload
Große Datei-Uploads verarbeiten
Für die effiziente Verarbeitung großer Datei-Uploads bieten sich diese Strategien an:
nginx für große Uploads konfigurieren
Wenn Sie Nginx als Reverse Proxy einsetzen, ergänzen Sie Ihre Konfiguration um diese Einstellungen:
http {
client_max_body_size 16M;
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
}
Chunk-Uploads implementieren
Bei großen Datei-Uploads empfiehlt sich ein Ansatz mit Chunk-Uploads. Das tus-Protokoll ist dafür eine ausgezeichnete Wahl, da es fortsetzbare Uploads ermöglicht.
Asynchrone Verarbeitung nutzen
Celery verlagert die Verarbeitung in einen Worker, nachdem der HTTP-Upload gespeichert wurde; das Limit für die Anfragegröße hebt es nicht auf. Dieses optionale Worker-Beispiel richtet sich an Linux/macOS. Installieren Sie Celery mit dem Redis-Transport, installieren Sie Redis und betreiben Sie einen lokalen Redis-Broker:
pip install 'celery[redis]==5.6.3'
redis-server
Hängen Sie diesen Code an app.py an. Das Beispiel berechnet einen
SHA-256-Digest in einem Worker:
from celery import Celery
import hashlib
celery = Celery('tasks', broker='redis://localhost:6379/0')
@celery.task
def process_uploaded_file(file_path):
digest = hashlib.sha256()
with open(file_path, 'rb') as source:
for chunk in iter(lambda: source.read(1024 * 1024), b''):
digest.update(chunk)
return digest.hexdigest()
@app.route('/upload-large', methods=['POST'])
def upload_large_file():
try:
filename = save_upload(request.files.get('file'))
except ValueError as e:
return jsonify({'error': str(e)}), 400
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
# Queue the file for processing
task = process_uploaded_file.delay(file_path)
return jsonify({'message': 'File uploaded and queued for processing', 'task_id': task.id}), 202
Führen Sie diese Befehle in getrennten Terminals aus dem Projektverzeichnis aus, wobei
FLASK_SECRET_KEY in beiden gesetzt sein muss:
celery -A app:celery worker --loglevel=INFO
flask --app app run
Webprozess und Worker müssen dasselbe private Upload-Verzeichnis nutzen. Dieses Beispiel hat weder ein Result-Backend noch einen Status-Endpunkt. Bei einem Ausfall des Brokers wird ein bereinigter Fehler zurückgegeben; die gespeicherte Datei bleibt zur Bereinigung durch den Betrieb erhalten. Halten Sie Redis privat. Die API-Beispiele veranschaulichen die Validierung; ergänzen Sie daher Authentifizierung, Autorisierung und Rate Limits Ihrer Anwendung, bevor Sie sie öffentlich zugänglich machen.
Fehlerbehandlung und Validierung
Implementieren Sie eine umfassende Fehlerbehandlung, um die Zuverlässigkeit zu erhöhen:
@app.errorhandler(Exception)
def handle_unexpected_error(error):
if isinstance(error, HTTPException):
return error
app.logger.error('Request failed (%s)', type(error).__name__)
return jsonify({'error': 'An unexpected error occurred'}), 500
Fazit
Sichere und effiziente Datei-Uploads in Flask erfordern sorgfältige Aufmerksamkeit für Sicherheit, Performance und Nutzererlebnis. Wenn Sie bewährte Verfahren befolgen und robuste Validierung sowie Fehlerbehandlung einbauen, entsteht ein zuverlässiges System für Datei-Uploads in Ihren Flask-Anwendungen.
Für fortgeschrittenere Upload-Funktionen bieten sich Open-Source-Tools wie tus oder Uppy an, die sich in Flask integrieren lassen und Funktionen wie fortsetzbare Uploads und Fortschrittsanzeige unterstützen. Darüber hinaus bietet Transloadit umfassende Dienste für das Hochladen und Verarbeiten von Dateien.
