Asynchrone PHP-Integration für effiziente Transloadit-Nutzung
Nutzende müssen warten, bis ein Upload abgeschlossen ist, den Browser aber während der Bildverarbeitung nicht geöffnet lassen. Mit Assembly Notifications teilt Transloadit Ihrem PHP-Backend mit, wann die Verarbeitung endet. Ihr Backend kann dieses Ergebnis unabhängig vom Browser erfassen.

Diese Anleitung aktualisiert Josephs Anleitung vom Juli 2021 mit dem Dashboard und dem Transloadit-Plugin von Uppy und ersetzt den eingestellten Robodog-Dateiuploader. Wir erstellen eine kleine PHP-Anwendung für eine einzelne betreibende Person mit einer MySQL-Tabelle für Empfangsbestätigungen, serverseitig signierten Uploads und einem authentifizierten Callback. Sie verwendet PHP 8.2 oder neuer mit PDO MySQL und bewahrt alle geheimen Zugangsdaten des Kontos auf dem Server auf.
Website einrichten
Erstellen Sie ein Projekt mit common.php und einem Verzeichnis
public/. Nur public/ ist das Dokumentenstammverzeichnis.
Die betreibende Person meldet sich per HTTP-Basic-Authentifizierung über HTTPS bei der Anwendung an;
Notifications authentifizieren sich dagegen mit der Signatur von Transloadit. So bleiben sowohl
das Signieren als auch das Anzeigen der Empfangsbestätigungen zugriffsgeschützt, ohne dass für das
Tutorial ein vollständiges Benutzerkontensystem nötig ist.
Stellen Sie PHP diese Umgebungsvariablen über den Secret Manager Ihrer Bereitstellung zur Verfügung:
TRANSLOADIT_KEY,TRANSLOADIT_SECRETundTRANSLOADIT_TEMPLATE_IDaus Ihrem Konto.APP_USERund einen langen, zufälligen Wert fürAPP_PASSWORDfür die betreibende Person in diesem Tutorial.APP_ORIGIN: der exakte HTTPS-Origin der App ohne abschließenden Schrägstrich.TRANSLOADIT_NOTIFY_URL: dieser Origin, gefolgt von/notify.php.DB_DSN: zum Beispielmysql:host=127.0.0.1;dbname=transloadit;charset=utf8mb4.DB_USERundDB_PASSWORD: ein Datenbankkonto, das aufSELECTundINSERTfür die Tabelle mit den Empfangsbestätigungen beschränkt ist.
Bewahren Sie Dateien mit geheimen Zugangsdaten außerhalb von public/ und der
Versionsverwaltung auf. Ersetzen Sie in einer Anwendung für mehrere Nutzende die
Basic-Authentifizierung durch Ihre bestehende Sitzungsautorisierung, beschränken Sie den Zugriff
auf Empfangsbestätigungen auf deren Eigentümer und setzen Sie am Signier-Endpunkt Upload-Kontingente
und Ratenlimits pro nutzender Person durch.
Datenbank einrichten
Erstellen Sie die Datenbank und die Tabelle über eine administrative MySQL-Verbindung:
CREATE DATABASE transloadit CHARACTER SET utf8mb4;
USE transloadit;
CREATE TABLE assemblies (
id CHAR(32) CHARACTER SET ascii COLLATE ascii_bin PRIMARY KEY,
status VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
http_code SMALLINT UNSIGNED NOT NULL,
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Die Assembly ID ist der eindeutige Schlüssel der Empfangsbestätigung. Erneute Zustellversuche von Notifications dürfen weder zusätzliche Zeilen erzeugen noch eine Geschäftsaktion wiederholen. Dieses Beispiel speichert nur den Endstatus; es veröffentlicht keine Ergebnisdateien und führt keine nachgelagerten Jobs aus.
Unser Template erstellen
Erstellen Sie in Ihrem Konto ein Template mit diesen Instructions und kopieren Sie seine ID in die Serverkonfiguration. Das Callback-Ziel wird vom Signier-Endpunkt vorgegeben, nicht von einem Browserfeld oder einem Template-Ausdruck, der auf Benutzereingaben basiert.
{
"steps": {
":original": { "robot": "/upload/handle" },
"resized": {
"robot": "/image/resize",
"use": ":original",
"width": 500,
"format": "jpeg",
"resize_strategy": "fit",
"imagemagick_stack": "v3"
}
}
}
Aktivieren Sie Signature Authentication für Ihren Auth Key, damit
veränderte oder unsignierte Instructions abgelehnt werden. Der Server signiert die exakte
serialisierte Zeichenfolge params mit HMAC-SHA384 und verwendet dabei dasselbe
Format wie @transloadit/utils.
Lokale Notifications während der Entwicklung erreichbar machen
Verwenden Sie für diese Anleitung einen Cloudflare Quick Tunnel.
Starten Sie nach der Installation von cloudflared den Tunnel:
cloudflared tunnel --url http://127.0.0.1:8000
Setzen Sie APP_ORIGIN auf den zugewiesenen HTTPS-Origin und
TRANSLOADIT_NOTIFY_URL auf dessen URL für /notify.php.
Starten Sie anschließend PHP aus dem Projektverzeichnis, nachdem Sie die übrigen Umgebungsvariablen
konfiguriert haben:
php -d display_errors=0 -d log_errors=1 -d post_max_size=512K \
-d upload_max_filesize=256K -d max_input_vars=10 -d file_uploads=0 \
-S 127.0.0.1:8000 -t public
Öffnen Sie die HTTPS-Tunnel-URL in Ihrem Browser. Uppy verwendet weiterhin
https://api2.transloadit.com; Uploads umgehen PHP. Beenden Sie nach dem Testen beide Prozesse mit
Ctrl+C. Aktualisieren Sie die beiden URL-Einstellungen und starten Sie PHP neu, sobald sich der
temporäre Tunnel-Hostname ändert.
Das vom Anbieter bereitgestellte Notification-Relay ist eine weitere Option für die Entwicklung: Es muss den Datenverkehr zur Erstellung von Assemblies über seinen lokalen Proxy empfangen, den Assembly Status abfragen und weitergeleitete Notifications mit Ihrem Auth Secret signieren. Wenn Sie es lediglich starten, während Uppy den normalen API-Endpunkt verwendet, werden die Notifications dieser App nicht weitergeleitet. Die obige Tunnelkonfiguration benötigt weder das Relay noch einen localhost-Callback im Template.
Verbindung mit unserer Datenbank herstellen
Speichern Sie dies als common.php außerhalb des öffentlichen
Dokumentenstammverzeichnisses. Die Datei stellt Konfiguration, Anmeldung, Datenbankzugriff und eine
zentrale Fehlerbehandlung für alle vier PHP-Dateien bereit, die sensible Fehlerdetails zurückhält.
<?php
declare(strict_types=1);
set_exception_handler(function (Throwable $error): void {
error_log('Notification application request failed.');
respond(500, 'Request could not be completed.');
});
function respond(int $status, string $message): never {
http_response_code($status);
header('Content-Type: text/plain; charset=utf-8');
header('Cache-Control: no-store');
echo $message;
exit;
}
function setting(string $name): string {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException('Missing server configuration.');
}
return $value;
}
function requireOperator(): void {
if (!hash_equals(setting('APP_USER'), $_SERVER['PHP_AUTH_USER'] ?? '') ||
!hash_equals(setting('APP_PASSWORD'), $_SERVER['PHP_AUTH_PW'] ?? '')) {
header('WWW-Authenticate: Basic realm="Upload demo", charset="UTF-8"');
respond(401, 'Sign in to continue.');
}
header('Cache-Control: no-store');
}
function database(): PDO {
return new PDO(setting('DB_DSN'), setting('DB_USER'), setting('DB_PASSWORD'), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
Speichern Sie den Signier-Endpunkt als public/sign.php. Er akzeptiert keine vom
Aufrufer übergebenen Instructions. Die Prüfungen des Origin-Headers und des benutzerdefinierten
Headers schützen diesen POST mit Zugangsdaten vor Formularübermittlungen von fremden Websites;
fügen Sie keine freizügigen CORS-Header hinzu. Die kurze Gültigkeitsdauer und die signierte
Begrenzung der Upload-Größe gelten auch dann, wenn jemand die clientseitigen Beschränkungen von
Uppy umgeht.
<?php
declare(strict_types=1);
require dirname(__DIR__) . '/common.php';
requireOperator();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
respond(405, 'Use POST.');
}
$origin = setting('APP_ORIGIN');
if (!preg_match('~\Ahttps://[a-zA-Z0-9.-]+(?::[0-9]+)?\z~D', $origin) ||
setting('TRANSLOADIT_NOTIFY_URL') !== $origin . '/notify.php') {
throw new RuntimeException('Invalid server URL configuration.');
}
if (($_SERVER['HTTP_ORIGIN'] ?? '') !== $origin ||
($_SERVER['HTTP_X_UPLOAD_REQUEST'] ?? '') !== '1') {
respond(403, 'Request not allowed.');
}
if (!preg_match('/\A[a-f0-9]{32}\z/D', setting('TRANSLOADIT_TEMPLATE_ID'))) {
throw new RuntimeException('Invalid Template configuration.');
}
$params = json_encode([
'auth' => [
'key' => setting('TRANSLOADIT_KEY'),
'expires' => gmdate('Y-m-d\TH:i:s\Z', time() + 300),
'max_size' => 10 * 1024 * 1024,
],
'template_id' => setting('TRANSLOADIT_TEMPLATE_ID'),
'notify_url' => setting('TRANSLOADIT_NOTIFY_URL'),
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
$signature = 'sha384:' . hash_hmac('sha384', $params, setting('TRANSLOADIT_SECRET'));
header('Content-Type: application/json');
echo json_encode(['params' => $params, 'signature' => $signature], JSON_THROW_ON_ERROR);
Daten zur Datenbank hinzufügen
Speichern Sie dies als public/notify.php. Überprüfen Sie die
ursprünglichen Bytes des Formularfelds transloadit, bevor Sie JSON decodieren.
Wenn Sie JSON zuerst erneut codieren, würde sich die signierte Nachricht ändern. Die Liste der
zulässigen Algorithmen umfasst für die Kompatibilität mit Notifications auch das ältere SHA-1-Format
des SDK; die neuen Upload-Instructions oben verwenden SHA384. Unbekannte Algorithmen, fehlerhaft
formatierte Felder und ungültige Signaturen führen zur Ablehnung.
<?php
declare(strict_types=1);
require dirname(__DIR__) . '/common.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
respond(405, 'Use POST.');
}
$raw = $_POST['transloadit'] ?? null;
$signature = $_POST['signature'] ?? null;
if (!is_string($raw) || strlen($raw) > 262144 || !is_string($signature) ||
strlen($signature) > 140 || count($_POST) !== 2 || count($_FILES) !== 0) {
respond(400, 'Invalid notification.');
}
$parts = explode(':', $signature, 2);
[$algorithm, $digest] = count($parts) === 2 ? $parts : ['sha1', $signature];
$lengths = ['sha1' => 40, 'sha256' => 64, 'sha384' => 96, 'sha512' => 128];
if (!isset($lengths[$algorithm]) || strlen($digest) !== $lengths[$algorithm] ||
!ctype_xdigit($digest) ||
!hash_equals(hash_hmac($algorithm, $raw, setting('TRANSLOADIT_SECRET')), $digest)) {
respond(403, 'Invalid notification signature.');
}
try {
$assembly = json_decode($raw, true, 32, JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
respond(400, 'Invalid notification JSON.');
}
if (!is_array($assembly) || array_is_list($assembly)) {
respond(400, 'Invalid notification.');
}
$id = $assembly['assembly_id'] ?? null;
$status = $assembly['error'] ?? $assembly['ok'] ?? null;
$httpCode = $assembly['http_code'] ?? null;
if (!is_string($id) || !preg_match('/\A[a-f0-9]{32}\z/D', $id) ||
!is_string($status) || !preg_match('/\A[A-Z][A-Z0-9_]{0,63}\z/D', $status) ||
!is_int($httpCode) || $httpCode < 100 || $httpCode > 599 ||
(!isset($assembly['error']) && !in_array($status, ['ASSEMBLY_COMPLETED', 'ASSEMBLY_CANCELED'], true))) {
respond(400, 'Invalid terminal Assembly status.');
}
$db = database();
try {
$insert = $db->prepare('INSERT INTO assemblies (id, status, http_code) VALUES (?, ?, ?)');
$insert->execute([$id, $status, $httpCode]);
} catch (PDOException $error) {
// MySQL error 1062 is the unique receipt key, not a successful new delivery.
if (($error->errorInfo[1] ?? null) !== 1062) {
throw $error;
}
$existing = $db->prepare('SELECT status, http_code FROM assemblies WHERE id = ?');
$existing->execute([$id]);
$row = $existing->fetch(PDO::FETCH_ASSOC);
if (!$row || $row['status'] !== $status || (int) $row['http_code'] !== $httpCode) {
respond(409, 'Conflicting notification receipt.');
}
}
respond(200, 'Notification recorded.');
Ein identischer erneuter Zustellversuch erhält nach Bestätigung des gespeicherten Ergebnisses 200. Ein widersprüchliches Ergebnis für dieselbe Assembly ID erhält zur Untersuchung 409; Datenbankfehler liefern einen generischen Fehler 500, damit der Absender die Zustellung erneut versuchen kann. Der Primärschlüssel schützt auch bei gleichzeitigen Zustellungen. Wenn Sie nachgelagerte Jobs hinzufügen, tragen Sie einen Job in derselben Datenbanktransaktion wie die Empfangsbestätigung in eine Outbox ein und verarbeiten Sie ihn anschließend mit einem idempotenten Worker. Versenden Sie keine E-Mails und veröffentlichen Sie keine Dateien, bevor Sie die Empfangsbestätigung gespeichert haben.
Daten aus der Datenbank abrufen
Speichern Sie diese vollständige Upload-Seite als public/index.php. Sie zeigt der
authentifizierten betreibenden Person die fünf zuletzt empfangenen Assembly-Ergebnisse an und
maskiert jeden Datenbankwert vor der Darstellung. Das
Transloadit-Plugin von Uppy fordert signierte Optionen von unserem
Backend an.
<?php
declare(strict_types=1);
require dirname(__DIR__) . '/common.php';
requireOperator();
$rows = database()->query(
'SELECT id, status, received_at FROM assemblies ORDER BY received_at DESC, id DESC LIMIT 5'
)->fetchAll(PDO::FETCH_ASSOC);
function escape(string $value): string {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asynchronous image uploads</title>
<link rel="stylesheet" href="https://releases.transloadit.com/uppy/v5.2.1/uppy.min.css">
</head>
<body>
<h1>Upload an image</h1>
<p>Wait until the upload finishes. Processing continues after you close this page.</p>
<div id="dashboard"></div>
<h2>Recent processing outcomes</h2>
<p>Refresh this page to see newly received notifications.</p>
<ul>
<?php foreach ($rows as $row): ?>
<li><?= escape($row['id']) ?> — <?= escape($row['status']) ?> — <?= escape($row['received_at']) ?></li>
<?php endforeach; ?>
</ul>
<?php if (count($rows) === 0): ?><p>No notifications received yet.</p><?php endif; ?>
<script type="module">
import { Uppy, Dashboard, Transloadit } from 'https://releases.transloadit.com/uppy/v5.2.1/uppy.min.mjs'
new Uppy({ restrictions: { maxNumberOfFiles: 1, maxFileSize: 10 * 1024 * 1024, allowedFileTypes: ['image/*'] } })
.use(Dashboard, { target: '#dashboard', inline: true })
.use(Transloadit, {
waitForEncoding: false,
async assemblyOptions() {
const response = await fetch('/sign.php', {
method: 'POST',
credentials: 'same-origin',
headers: { 'X-Upload-Request': '1' },
})
if (!response.ok) throw new Error('Could not authorize this upload.')
return response.json()
},
})
</script>
</body>
</html>
Testen
Prüfen Sie zuerst, dass nicht authentifizierte Anfragen an die Seite und den Signier-Endpunkt 401
erhalten und dass ein unsignierter POST an /notify.php mit 403 beantwortet wird,
ohne eine Zeile einzufügen. Eine fehlende oder fehlerhaft formatierte Payload erhält 400. Auch das
Senden einer Assembly ID mit SQL-Syntax muss bei der Validierung scheitern.
Für einen optionalen Ende-zu-Ende-Test mit Ihrem eigenen Konto laden Sie ein kleines Testbild über die HTTPS-Seite hoch, warten Sie auf den Abschluss des Uploads und aktualisieren Sie die Seite nach der Verarbeitung. Eine Empfangsbestätigung für den erfolgreichen Abschluss sollte auch dann erscheinen, wenn der Upload-Tab geschlossen wurde. Ein Verarbeitungsfehler muss als Fehlerstatus angezeigt werden, nicht als fertig verarbeitetes Bild. Wenn Sie eine Notification über die Assembly-Seite erneut senden, sollte weiterhin nur eine Empfangsbestätigung vorhanden sein.
Testen Sie auch lokal mit signierten synthetischen Notifications: Unveränderte erneute Zustellversuche sollten 200 erhalten, ein geänderter Body mit der alten Signatur sollte 403 erhalten und ein signiertes widersprüchliches Endergebnis sollte 409 erhalten. Deaktivieren Sie niemals die Signaturprüfung, damit ein Tunnel-Test erfolgreich ist. Die obigen Größenlimits für Request-Bodys während der Entwicklung eignen sich für dieses Beispiel mit einem Bild. Legen Sie explizite Reverse-Proxy- und PHP-Limits fest, die zu Ihren Notification-Payloads im Produktionsbetrieb passen, und überwachen Sie abgelehnte Zustellungen.
Abschluss
Der Browser übernimmt nun das Hochladen, während PHP authentifizierte Verarbeitungsergebnisse erfasst. Bewahren Sie Empfangsbestätigungen lange genug für erneute Zustellversuche von Notifications und Ihre eigenen Wiederholungen auf und überwachen Sie fehlgeschlagene Notifications in Ihrem Konto. Signature Authentication stellt die Identität des Absenders fest; Anwendungsautorisierung, die Zuordnung von Empfangsbestätigungen zu ihren Eigentümern und idempotente nachgelagerte Verarbeitung bleiben Aufgaben Ihres Backends.
