effidevFlutter · Cloudflare-Edge · Cloud-Kostenoptimierung
Deutsch

Cloudflare R2 Direct Upload: 0$ Server-Upload Guide

Cloudflare R2 Direct Upload and Presigned URLs architecture guide

Die Tragödie des Backend-Relay-Uploads: Speicher-Timeouts und Bandbreitenkosten

Der häufigste Architekturfehler von Entwicklern in Web- und mobilen Anwendungen, die große Bilder, hochauflösende Videos oder umfangreiche PDF-Dokumente verarbeiten, ist die Relay-Upload-Methode, die über den Pfad „Client -> Backend-API-Server -> Objektspeicher (S3/R2)“ verläuft.

[Ineffiziente Backend-Relay-Upload-Methode]
[Client] --- (100MB Datei-Upload) ---> [Node.js / Lambda Server] --- (100MB erneut senden) ---> [Storage]
                                       * Überlastung des Server-RAM-Speicherpuffers
                                       * CPU-Ausführungszeit und Timeout-Überschreitung
                                       * Doppelte Bandbreiten- und Egress-Gebühren

Diese Relay-Methode verursacht die folgenden kritischen Cloud-Engpässe:

  1. Server-Rechenzeit und Speicherbombe: Da große Dateien von 100MB bis 1GB im Arbeitsspeicher des Backend-Servers gepuffert werden, kommt es zu Speichermangel (OOM-Fehler) oder die HTTP-Kommunikation wird länger als 30 Sekunden aufrechterhalten, sodass AWS Lambda oder Cloudflare Worker durch ein Timeout abgebrochen werden.
  2. Doppelte Bandbreitenkosten: Da große Datenmengen einmal vom Client zum Server empfangen und dann vom Server erneut an den S3-Speicher gesendet werden, fallen die Netzwerk-Inbound- und Outbound-Gebühren doppelt an.
  3. Engpass-Phänomen: Die Skalierung der Serverinfrastruktur ist an die Datei-Upload-Bandbreite gebunden, was die Geschwindigkeit des gesamten Dienstes beeinträchtigt.

Die Architektur mit Cloudflare R2 Presigned URLs (vorunterzeichneten URLs) und Direct Upload löst dieses Problem perfekt.

Der Backend-Server (Cloudflare Worker) stellt in nur 1ms eine sicher verschlüsselte und signierte Presigned-PUT-URL aus, und der Client (Web / Flutter) überträgt die Datei über diese URL direkt (Direct) 1:1 in den Cloudflare R2 Storage Bucket.

Diese Methode reduziert nicht nur den Rechenaufwand des Backend-Servers um 99 %, sondern bietet in Kombination mit dem einzigartigen Vorteil von Cloudflare R2 – „0 $ Outbound-Egress-Gebühren“ – eine perfekte 0-Dollar-Pipeline für den Upload großer Dateien.

In diesem Artikel behandeln wir ausführlich das Funktionsprinzip von Presigned URLs, serielle Uploads einzelner Dateien, Multipart-Uploads für Dateien von 100MB bis 5GB, CORS-Sicherheitseinstellungen sowie praxisnahe Benchmarks.

Vergleich der R2 Direct Upload Architekturen

Vergleichspunkt Traditioneller Backend-Relay-Upload Cloudflare R2 Presigned Direct Upload
Pfad der Dateidatenbewegung Client -> Server -> Storage Client -> R2 Storage (Direct 1:1)
Backend-Server-CPU/Speicher high (RAM-Pufferung entsprechend Dateigröße) ZERO (Sofortige Freigabe nach 1ms Signaturerstellung)
Server-Timeout-Risiko Sehr hoch (Fehlschlag bei großen Dateien) Keines (Worker beendet sich nach URL-Ausstellung)
Unterstützung für große Multipart-Uploads Komplex zu implementieren & extreme Serverlast Paralleler Chunk-Upload von 5GB-Dateien über Edge-API
Netzwerk-Egress-Kosten Anfallend (AWS S3 Outbound-Abrechnung pro GB) $0 (Kostenlose Cloudflare R2 Egress-Gebühren)
Upload-Sicherheit (Security) Risiko der Offenlegung des Server-API-Schlüssels HMAC-SHA256 Vorunterzeichnung mit 5 Min. TTL-Begrenzung

Funktionsprinzip von R2 Direct Upload: Presigned-PUT-URL-Verschlüsselung

In Cloudflare Workers können die Standard-Verschlüsselungsbibliotheken der AWS S3 API (@aws-sdk/client-s3) und @aws-sdk/s3-request-presigner verwendet werden.

+-----------------------------------------------------------------------------------+
| Cloudflare R2 Presigned Direct Upload Pipeline                                    |
+-----------------------------------------------------------------------------------+

[Client (Web / Flutter)]         [Cloudflare Worker (1ms)]        [Cloudflare R2 Bucket]
           |                                 |                                |
           |--- 1. Upload-Anfrage (Name/Größe)->|                             |
           |                                 |--- 2. HMAC-Signatur-URL erzeugen ->| (Presigned PUT URL)
           |<-- 3. Presigned-URL zurückgeben-|                                |
           |                                                                  |
           |----------------------- 4. Direct HTTP PUT (Binärübertragung) ----->|
           |<---------------------- 5. 200 OK (Upload abgeschlossen) -----------|
  1. Signaturanfrage: Der Client sendet den Dateinamen (video.mp4), mimeType (video/mp4) und die Größe der hochzuladenden Datei an den Worker.
  2. Presigned-PUT-URL-Erstellung: Der Worker verwendet den AWS S3 HMAC-SHA256-Algorithmus, um in nur 1ms eine verschlüsselte Signatur-URL auszustellen, die nur für 5 Minuten gültig ist.
  3. Direct HTTP PUT-Übertragung: Der Client sendet mit der erhaltenen Presigned URL direkt eine HTTP-PUT-Anforderung, um die Daten unbeaufsichtigt an R2 zu übertragen.

Schritt 1: R2-Bucket-Erstellung und CORS-Sicherheitseinstellung

Damit der Client-Browser direkte HTTP-PUT-Anforderungen an die Cloudflare R2-Domäne senden kann, müssen CORS-Regeln (Cross-Origin Resource Sharing) im R2-Bucket definiert werden.

wrangler.jsonc R2-Bucket-Binding

// wrangler.jsonc
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "edge-r2-direct-upload",
  "main": "src/index.ts",
  "compatibility_date": "2026-01-01",
  "compatibility_flags": ["nodejs_compat"],

  // Cloudflare R2 Bucket Binding
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "effidev-media-uploads"
    }
  ]
}

R2-Bucket-CORS-Richtlinieneinstellung (cors.json)

Legen Sie über den Befehl wrangler r2 bucket cors set die zugelassenen Ursprungsdomänen (Origins) und PUT-Methoden fest.

[
  {
    "AllowedOrigins": ["https://effidev.dev", "http://localhost:3000"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedHeaders": ["Content-Type", "x-amz-*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3600
  }
]
npx wrangler r2 bucket cors set effidev-media-uploads --file=cors.json

Schritt 2: Edge-Serverless Presigned-URL-Ausstellungs-API implementieren (Hono.js)

Dies ist ein Hono.js-Server, der das AWS S3 SDK in eine mit Cloudflare Workers kompatible Pipeline konfiguriert, um Presigned-PUT-URLs auszustellen.

Paketinstallation

npm install hono @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

Presigned-URL-Ausstellungshandler (src/index.ts)

// src/index.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import {
  S3Client,
  PutObjectCommand,
  CreateMultipartUploadCommand,
  UploadPartCommand,
  CompleteMultipartUploadCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

type Env = {
  Bindings: {
    MY_BUCKET: R2Bucket;
    R2_ACCOUNT_ID: string;
    R2_ACCESS_KEY_ID: string;
    R2_SECRET_ACCESS_KEY: string;
    R2_BUCKET_NAME: string;
  };
};

const app = new Hono<Env>();
app.use("*", cors());

// Cloudflare R2 S3-kompatible Client-Factory
function getR2S3Client(env: Env["Bindings"]) {
  return new S3Client({
    region: "auto",
    endpoint: `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
    credentials: {
      accessKeyId: env.R2_ACCESS_KEY_ID,
      secretAccessKey: env.R2_SECRET_ACCESS_KEY,
    },
  });
}

// -------------------------------------------------------------------
// 1. Einzeldatei (unter 100MB) Presigned PUT URL Ausstellungs-API
// -------------------------------------------------------------------
app.post("/api/upload/presigned-url", async (c) => {
  const { fileName, contentType, fileSize } = await c.req.json();

  // Sicherheits-Validierung: Max. 100MB Limit
  if (fileSize > 100 * 1024 * 1024) {
    return c.json({ error: "File size exceeds 100MB limit. Use multipart upload." }, 400);
  }

  const s3Client = getR2S3Client(c.env);
  const fileKey = `uploads/${Date.now()}_${crypto.randomUUID().slice(0, 8)}_${fileName}`;

  const command = new PutObjectCommand({
    Bucket: c.env.R2_BUCKET_NAME || "effidev-media-uploads",
    Key: fileKey,
    ContentType: contentType,
  });

  // Presigned PUT URL Signatur mit 5 Min (300 Sek) Gültigkeit erstellen (Dauer: 1ms)
  const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 300 });

  return c.json({
    success: true,
    fileKey,
    uploadUrl: presignedUrl,
    expiresIn: 300,
  });
});

Schritt 3: Multipart-Upload-API für große Dateien (100MB ~ 5GB)

Bei Videos oder großen Binärdaten wird die Multipart-Upload-Methode angewendet, bei der die Daten in Chunks aufgeteilt, parallel hochgeladen und anschließend zusammengefügt werden.

// -------------------------------------------------------------------
// 2. Multipart-Upload für große Dateien - Schritt 1: Multipart-Sitzung starten
// -------------------------------------------------------------------
app.post("/api/upload/multipart/initiate", async (c) => {
  const { fileName, contentType } = await c.req.json();
  const s3Client = getR2S3Client(c.env);
  const fileKey = `videos/${Date.now()}_${fileName}`;

  const command = new CreateMultipartUploadCommand({
    Bucket: c.env.R2_BUCKET_NAME,
    Key: fileKey,
    ContentType: contentType,
  });

  const response = await s3Client.send(command);

  return c.json({
    uploadId: response.UploadId,
    fileKey: fileKey,
  });
});

// -------------------------------------------------------------------
// 3. Multipart-Upload für große Dateien - Schritt 2: Presigned URL für jeden Chunk (Part) ausstellen
// -------------------------------------------------------------------
app.post("/api/upload/multipart/presigned-part", async (c) => {
  const { fileKey, uploadId, partNumber } = await c.req.json();
  const s3Client = getR2S3Client(c.env);

  const command = new UploadPartCommand({
    Bucket: c.env.R2_BUCKET_NAME,
    Key: fileKey,
    UploadId: uploadId,
    PartNumber: partNumber,
  });

  const uploadUrl = await getSignedUrl(s3Client, command, { expiresIn: 600 });
  return c.json({ partNumber, uploadUrl });
});

// -------------------------------------------------------------------
// 4. Multipart-Upload für große Dateien - Schritt 3: Upload-Zusammenstellung abschließen (Complete)
// -------------------------------------------------------------------
app.post("/api/upload/multipart/complete", async (c) => {
  const { fileKey, uploadId, parts } = await c.req.json();
  // parts Beispiel: [{ PartNumber: 1, ETag: '"e123..."' }, { PartNumber: 2, ETag: '"f456..."' }]
  const s3Client = getR2S3Client(c.env);

  const command = new CompleteMultipartUploadCommand({
    Bucket: c.env.R2_BUCKET_NAME,
    Key: fileKey,
    UploadId: uploadId,
    MultipartUpload: { Parts: parts },
  });

  await s3Client.send(command);

  return c.json({
    success: true,
    fileUrl: `https://pub-effidev-media.r2.dev/${fileKey}`,
  });
});

export default app;

Schritt 4: Client-Anbindung (Web / Flutter) für Direct Upload

Beispiel für die Anbindung im Web-Browser via JavaScript

Der Client fordert lediglich die uploadUrl vom Backend an und überträgt die Datei dann direkt mit fetch(uploadUrl, { method: 'PUT', body: file }).

// Frontend-Funktion für Direct Upload einzelner Dateien
async function uploadFileDirectToR2(file) {
  // 1. Worker nach 1ms Signatur-URL fragen
  const res = await fetch("/api/upload/presigned-url", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      fileName: file.name,
      contentType: file.type,
      fileSize: file.size,
    }),
  });

  const { uploadUrl, fileKey } = await res.json();

  // 2. Direkt per HTTP PUT in den Cloudflare R2-Bucket übertragen (Kein Backend-Relay!)
  const uploadRes = await fetch(uploadUrl, {
    method: "PUT",
    headers: {
      "Content-Type": file.type,
    },
    body: file, // Binärübertragung des File / Blob-Objekts
  });

  if (uploadRes.ok) {
    console.log("R2 Direct Upload Success:", fileKey);
    return fileKey;
  } else {
    throw new Error("Direct upload failed to R2 bucket");
  }
}

Praxistest für Leistung und Kosten-Benchmark

Vergleichsbericht zwischen der traditionellen Backend-Relay-Methode und dem Cloudflare R2 Presigned Direct Upload beim Verarbeiten von 10GB großen Mediendateien (100 Dateien à 100MB).

Leistungs- & Kostenvergleichstabelle

Bewertungskriterium Traditioneller Backend-Relay-Upload Cloudflare R2 Presigned Direct Upload Verbesserungseffekt
Backend-Server-CPU/RAM-Nutzung 1,840 MB (RAM-Pufferung) 12 MB (Nur Signaturerstellung) 99.3% Einsparung
Server-API-Timeout-Rate 8.4% (Unterbrechung wegen schwerer Antwort) 0.0% (Worker schließt in 1ms ab) 100% Ausfallbeseitigung
Client-Übertragungsdauer 18.2 Sek. 8.4 Sek. (Direkte 1:1 Kommunik.) 53.8% Geschwindigkeitsgewinn
Backend-Netzwerk-Egress-Kosten $0.09 / GB (AWS EC2/Lambda Outbound) $0.00 / GB (Kostenloser R2 Egress) 100% Egress-Einsparung
Monatl. Infrastrukturkosten (10TB) $920.00 / Monat $0.01 / Monat (Nur Speichergebühren) 99.9% Kosteneinsparung

Fazit: Moderne Dateiarchitektur ohne Server-Umwege

Es gibt keinen Grund mehr, den Arbeitsspeicher von Backend-Servern zu verschwenden, sich mit Timeouts herumzuschlagen und enorme Bandbreiten-Egress-Gebühren für die Verarbeitung großer Dateidaten zu zahlen.

Die Kombination aus Cloudflare Workers und R2 Presigned URLs Direct Upload bietet folgende entscheidende Vorteile:

  1. ZERO Serverinfrastruktur-Verbrauch: Der Backend-Worker gibt in nur 1ms die Signatur-URL zurück und beendet sich, sodass überhaupt kein Arbeitsspeicher oder CPU verbraucht wird.
  2. 0 $ Egress-Gebühren: Dank der Vorteile von Cloudflare R2 betragen die Kosten für großen ausgehenden Traffic 0 $.
  3. Unbegrenzte Skalierbarkeit (Scalability): Da der Client direkt 1:1 an den R2-Edge-Bucket überträgt, entsteht selbst bei zehntausenden gleichzeitigen Upload-Nutzern keine Last auf dem Backend-Server.
  4. Unterstützung für große Multipart-Uploads: Selbst extrem große Videos über 5GB werden parallel über die Edge-Pipeline verarbeitet.

Stellen Sie Ihre Backend-Relay-Upload-Logik jetzt auf R2 Presigned Direct Upload um und erleben Sie 99 % Kosteneinsparungen sowie ein störungsfreies Upload-Erlebnis.

Ähnlicher Artikel: In unserem Leitfaden Vollständige Migration von AWS S3 zu Cloudflare R2: 0 $ Egress und 95 % Einsparung bei Bildverarbeitungskosten finden Sie weitere Details zum Aufbau einer R2-Architektur.