Datei-Uploads in PHP mit Session Upload Progress in Echtzeit verfolgen
Den Fortschritt von Datei-Uploads zu verfolgen, ist entscheidend für eine gute Nutzererfahrung, insbesondere bei großen Dateien. PHP bietet mit seiner Session-Upload-Progress-Funktion eine integrierte Lösung, die den Fortschritt zuverlässig und effizient verfolgt, ohne zusätzliche Abhängigkeiten.
Anforderungen an die Serverkonfiguration
Bevor Sie die Fortschrittsverfolgung für Uploads implementieren, stellen Sie sicher, dass Ihr Server richtig konfiguriert ist:
; php.ini configuration
session.upload_progress.enabled = On
session.upload_progress.cleanup = On
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.freq = "1%"
session.upload_progress.min_freq = "1"
upload_max_filesize = 10M
post_max_size = 12M
Erlauben Sie Multipart-Overhead oberhalb des Limits von 10 MiB pro Datei. Fügen Sie dies bei Nginx
mit PHP-FPM in die bestehende PHP-Location ein, die upload.php ausliefert, zusammen mit fastcgi_pass und ihren FastCGI-Parametern:
client_max_body_size 12M;
client_body_buffer_size 128k;
fastcgi_request_buffering off;
proxy_request_buffering gilt für HTTP-Proxying, nicht für PHP-FPM. Vorgeschaltete Proxys müssen
die Anfrage ebenfalls durchreichen, ohne sie vollständig zu puffern. Legen Sie /var/lib/php-upload-demo außerhalb
des Webroots an, im Besitz des PHP-Workers und mit dem Modus 0700; machen Sie es nicht über einen Webserver-Alias zugänglich.
Den Upload-Handler implementieren
Erstellen Sie einen sicheren Upload-Handler, der Dateien verarbeitet und eine ordnungsgemäße Validierung umsetzt:
<?php
// upload.php
declare(strict_types=1);
session_start();
session_write_close();
class FileUploadHandler {
private const EXTENSIONS = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'application/pdf' => 'pdf'];
private const MAX_FILE_SIZE = 10485760; // 10 MiB
private const UPLOAD_DIR = '/var/lib/php-upload-demo';
public function __construct() {
if (!is_dir(self::UPLOAD_DIR) || !is_writable(self::UPLOAD_DIR)) {
throw new RuntimeException('Private upload storage is not available');
}
}
public function handleUpload(): array {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
return ['error' => 'Invalid request method'];
}
if (!isset($_FILES['file'])) {
return ['error' => 'No file uploaded'];
}
$file = $_FILES['file'];
try {
$extension = $this->validateUpload($file);
$filename = bin2hex(random_bytes(16)) . '.' . $extension;
$destination = self::UPLOAD_DIR . '/' . $filename;
if (!move_uploaded_file($file['tmp_name'], $destination)) {
throw new RuntimeException('Failed to move uploaded file');
}
chmod($destination, 0600);
return [
'success' => true,
'filename' => $filename,
'size' => $file['size']
];
} catch (Exception $e) {
error_log('Upload failed: ' . get_class($e));
http_response_code(400);
return ['error' => 'The file could not be uploaded. Check its type and size.'];
}
}
private function validateUpload(array $file): string {
if (!isset($file['error'], $file['size'], $file['tmp_name']) ||
!is_int($file['error']) || !is_int($file['size']) || !is_string($file['tmp_name'])) {
throw new RuntimeException('Invalid upload fields');
}
if ($file['error'] !== UPLOAD_ERR_OK) {
throw new RuntimeException($this->getUploadErrorMessage($file['error']));
}
if ($file['size'] > self::MAX_FILE_SIZE) {
throw new RuntimeException('File exceeds maximum size limit');
}
if (!is_uploaded_file($file['tmp_name'])) {
throw new RuntimeException('Invalid upload source');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);
if (!isset(self::EXTENSIONS[$mimeType])) {
throw new RuntimeException('Invalid file type');
}
return self::EXTENSIONS[$mimeType];
}
private function getUploadErrorMessage(int $error): string {
return match($error) {
UPLOAD_ERR_INI_SIZE => 'File exceeds upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => 'File exceeds MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
UPLOAD_ERR_NO_FILE => 'No file was uploaded',
UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload',
default => 'Unknown upload error'
};
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json');
try {
$handler = new FileUploadHandler();
echo json_encode($handler->handleUpload(), JSON_THROW_ON_ERROR);
} catch (Throwable $e) {
error_log('Upload handler failed: ' . get_class($e));
http_response_code(500);
echo json_encode(['error' => 'The upload service is unavailable.']);
}
exit;
}
?>
Implementierung der Fortschrittsverfolgung
Erstellen Sie einen Endpunkt für die Fortschrittsverfolgung, der den Upload-Fortschritt sicher abruft:
<?php
// progress.php
declare(strict_types=1);
session_start();
header('Content-Type: application/json');
$id = $_GET['id'] ?? '';
$key = ini_get('session.upload_progress.prefix') . (is_string($id) ? $id : '');
$progress = [];
if (isset($_SESSION[$key])) {
$current = $_SESSION[$key];
$total = (int) $current['content_length'];
$progress = [
'lengthComputable' => $total > 0,
'loaded' => $current['bytes_processed'],
'total' => $total,
'percentage' => $total > 0 ? min(100, ($current['bytes_processed'] / $total) * 100) : 0
];
}
session_write_close();
echo json_encode($progress);
Clientseitige Integration
Implementieren Sie ein responsives Upload-Formular mit Fortschrittsverfolgung:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>File Upload with Progress</title>
<style>
.progress {
width: 100%;
height: 20px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-bar {
width: 0;
height: 100%;
background: #4caf50;
transition: width 0.3s ease;
}
</style>
</head>
<body>
<form id="uploadForm" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" id="progress-id" />
<input type="file" name="file" required />
<button type="submit">Upload</button>
<div class="progress">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div id="status"></div>
</form>
<script>
const form = document.getElementById('uploadForm')
const progressBar = document.getElementById('progress-bar')
const status = document.getElementById('status')
const progressId = document.getElementById('progress-id')
form.addEventListener('submit', async (e) => {
e.preventDefault()
// Generate unique ID for this upload
const uploadId = Date.now().toString()
progressId.value = uploadId
const formData = new FormData(form)
let tracker
try {
// Establish the session cookie before the upload and polling requests.
const sessionResponse = await fetch('progress.php')
if (!sessionResponse.ok) throw new Error('Could not initialize upload tracking')
// Start progress tracking
tracker = trackProgress(uploadId)
// Perform upload
const response = await fetch('upload.php', {
method: 'POST',
body: formData,
})
if (!response.ok) throw new Error('Upload failed. Check the file type and size.')
const result = await response.json()
if (result.error) {
throw new Error(result.error)
}
status.textContent = 'Upload complete!'
progressBar.style.width = '100%'
} catch (error) {
status.textContent = `Error: ${error.message}`
progressBar.style.backgroundColor = '#f44336'
} finally {
clearInterval(tracker)
}
})
function trackProgress(uploadId) {
return setInterval(async () => {
try {
const response = await fetch(`progress.php?id=${uploadId}`)
if (!response.ok) throw new Error('Could not read upload progress')
const progress = await response.json()
if (progress.lengthComputable) {
const percentage = Math.round(progress.percentage)
progressBar.style.width = `${percentage}%`
status.textContent = `Uploading: ${percentage}%`
}
} catch (error) {
console.error('Progress tracking error:', error)
}
}, 1000)
}
</script>
</body>
</html>
Sicherheitshinweise
Die MIME-Erkennung schränkt die akzeptierten Formate ein, belegt aber nicht, dass eine Datei harmlos ist. Halten Sie hochgeladene Inhalte privat, bis alle erforderlichen Prüfungen oder Verarbeitungsschritte abgeschlossen sind. Dieses Beispiel verfolgt den Fortschritt; es bietet weder fortsetzbare Uploads noch eine anwendungsspezifische Authentifizierung und Autorisierung.
Browserkompatibilität
Diese Implementierung funktioniert in allen modernen Browsern, die Folgendes unterstützen:
- FormData API
- Fetch API
- CSS-Transitions
- JavaScript-Funktionen ab ES6+
Für ältere Browser sollten Sie Polyfills ergänzen oder einen klassischeren Ansatz mit XMLHttpRequest verwenden.
Zusammenfassung
Session Upload Progress in PHP bietet eine zuverlässige Möglichkeit, Datei-Uploads ohne externe Abhängigkeiten zu verfolgen. In Kombination mit geeigneten Sicherheitsmaßnahmen und einer sauberen Fehlerbehandlung können Sie ein robustes Upload-System erstellen, das Nutzern in Echtzeit Rückmeldung gibt.
Für fortgeschrittenere Upload-Funktionen, einschließlich Chunked Uploads und Wiederaufnahme, lohnt sich ein Blick auf die tus-Protokoll-Implementierung für PHP.
Viel Spaß beim Programmieren!
