# Mit großen Dateien arbeiten

Was sich ändert, sobald die PDFs groß werden: Streaming, Zeitlimits, Wiederholungen, die Größengrenzen und wann Sie besser vorher aufteilen.

Große PDFs sind dieselbe Anfrage wie kleine (eine Datei hinein, eine Datei
heraus), aber der naive Ansatz, das ganze Dokument in den Speicher zu lesen und
auf ein voreingestelltes Zeitlimit zu warten, bricht zusammen, sobald die
Dateien wachsen. Verarbeiten Sie Upload und Download als Stream, geben Sie der
Anfrage Raum zum Fertigwerden und wiederholen Sie vorübergehende Fehler. Weil
die API *stateless* ist, hinterlässt eine fehlgeschlagene Anfrage nichts, sodass
Wiederholungen sicher sind.

## Als Stream verarbeiten, nicht puffern

Lesen Sie die Eingabe beim Senden blockweise von der Festplatte und schreiben
Sie die Antwort beim Eintreffen blockweise auf die Festplatte. Halten Sie
niemals das ganze Dokument im Speicher. Jedes Beispiel unten verarbeitet beide
Richtungen als Stream und bringt ein Wasserzeichen auf `big.pdf` auf,
stellvertretend für jede Aktion mit einer einzigen Ausgabe.

<CodeGroup>

```bash title="cURL"
# curl streams the upload from disk and the response to disk by default.
curl https://api.pdfblocks.com/v1/add_text_watermark \
  -H 'X-API-Key: your_api_key' \
  -F file=@big.pdf \
  -F line_1='CONFIDENTIAL' \
  --max-time 600 \
  --retry 3 --retry-delay 2 --retry-all-errors \
  -o watermarked.pdf
```

```python title="Python"
# pip install requests requests-toolbelt
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder

with open('big.pdf', 'rb') as file:
    form = MultipartEncoder(fields={
        'file': ('big.pdf', file, 'application/pdf'),  # streamed from disk
        'line_1': 'CONFIDENTIAL',
    })
    with requests.post(
        'https://api.pdfblocks.com/v1/add_text_watermark',
        headers={'X-API-Key': 'your_api_key', 'Content-Type': form.content_type},
        data=form,
        stream=True,
        timeout=(10, 600),  # 10s to connect, 600s to read
    ) as response:
        response.raise_for_status()
        with open('watermarked.pdf', 'wb') as output:
            for chunk in response.iter_content(chunk_size=65536):
                output.write(chunk)
```

```javascript title="Node.js"
// npm install form-data
import { createReadStream, createWriteStream } from 'node:fs';
import { request } from 'node:https';
import FormData from 'form-data';

const form = new FormData();
form.append('file', createReadStream('big.pdf')); // streamed from disk
form.append('line_1', 'CONFIDENTIAL');

const req = request(
  'https://api.pdfblocks.com/v1/add_text_watermark',
  {
    method: 'POST',
    headers: { 'X-API-Key': 'your_api_key', ...form.getHeaders() },
    timeout: 600000,
  },
  (response) => {
    if (response.statusCode !== 200) {
      throw new Error(`Request failed: ${response.statusCode}`);
    }
    response.pipe(createWriteStream('watermarked.pdf')); // streamed to disk
  },
);

form.pipe(req); // stream the multipart body
```

```go title="Go"
package main

import (
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

func main() {
	// Generate the multipart body on the fly with io.Pipe: the file is never
	// held in memory in full.
	pr, pw := io.Pipe()
	form := multipart.NewWriter(pw)

	go func() {
		defer pw.Close()
		defer form.Close()
		file, _ := os.Open("big.pdf")
		defer file.Close()
		part, _ := form.CreateFormFile("file", "big.pdf")
		io.Copy(part, file) // streamed, not buffered
		form.WriteField("line_1", "CONFIDENTIAL")
	}()

	req, _ := http.NewRequest("POST",
		"https://api.pdfblocks.com/v1/add_text_watermark", pr)
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("X-API-Key", "your_api_key")

	client := &http.Client{Timeout: 10 * time.Minute}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, _ := os.Create("watermarked.pdf")
	defer out.Close()
	io.Copy(out, res.Body) // streamed to disk
}
```

```csharp title="C#"
using var client = new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");

using var fileStream = File.OpenRead("big.pdf");
using var form = new MultipartFormDataContent
{
    { new StreamContent(fileStream), "file", "big.pdf" }, // streamed from disk
    { new StringContent("CONFIDENTIAL"), "line_1" },
};

// ResponseHeadersRead returns as soon as the headers arrive, so the body isn't
// buffered before you read it.
using var response = await client.PostAsync(
    "https://api.pdfblocks.com/v1/add_text_watermark",
    form,
    HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();

await using var output = File.Create("watermarked.pdf");
await response.Content.CopyToAsync(output); // streamed to disk
```

</CodeGroup>

## Großzügige Zeitlimits setzen

Ein großes Dokument braucht länger zum Hochladen und zum Verarbeiten, deshalb
schneidet ein voreingestelltes Zeitlimit im Client, oft 30 Sekunden oder
weniger, eine gesunde Anfrage ab. Teilen Sie das Budget auf:

- Ein kurzes Zeitlimit für den **Verbindungsaufbau** (ein paar Sekunden)
  scheitert schnell, wenn der Host nicht erreichbar ist.
- Ein langes Zeitlimit für das **Lesen** gibt der Verarbeitung Zeit zum
  Fertigwerden. Die Beispiele oben verwenden zehn Minuten; bemessen Sie es nach
  dem größten Dokument, das Sie erwarten.

Setzen Sie kein unbegrenztes Zeitlimit; eine hängende Verbindung soll
irgendwann doch aufgeben, damit ein neuer Versuch übernehmen kann.

## Vorübergehende Fehler wiederholen

Zurückgesetzte Netzwerkverbindungen, Zeitüberschreitungen beim
Verbindungsaufbau und (sobald sie durchgesetzt werden) Antworten mit `429` oder
`5xx` sind vorübergehend: Dieselbe Anfrage kann einen Moment später durchaus
gelingen. Wiederholen Sie mit exponentiellem Backoff und ein wenig Jitter. Eine
Wiederholung ist hier genau deshalb sicher, weil die API *stateless* ist: Jeder
Aufruf ist eine reine Funktion seiner Eingaben, ohne teilweise Schreibvorgänge,
die rückgängig zu machen wären.

```python title="Python"
import random
import time
import requests


def send_with_retries(build_request, attempts=4):
    for attempt in range(attempts):
        try:
            response = build_request()
            # Retry only on server-side / throttling responses.
            if response.status_code < 500 and response.status_code != 429:
                return response
        except requests.RequestException:
            if attempt == attempts - 1:
                raise
        # Back off: 1s, 2s, 4s, … plus jitter.
        time.sleep(2 ** attempt + random.uniform(0, 1))
    return response
```

Wiederholen Sie kein `400`: Ein Validierungsfehler behebt sich nicht von selbst,
und der Antworttext vom Typ `application/problem+json` sagt Ihnen, welches Feld
zu korrigieren ist. Den Katalog finden Sie unter [Fehler](/docs/api/errors).
Auf der Kommandozeile bringt cURL das schon mit:
`--retry 3 --retry-delay 2 --retry-all-errors`.

## Größengrenzen und Rate-Limits

<Note>
  Grenzen für die Anfragegröße, Rate-Limits und die zugehörigen Statuscodes
  `413`, `429` und verwandte sind ein reservierter Teil des API-Vertrags und
  werden **noch nicht durchgesetzt**. Es werden keine festen Schwellenwerte
  veröffentlicht. Entwerfen Sie für den Vertrag und nicht für eine bestimmte
  Zahl.
</Note>

Damit Ihr Code robust bleibt, sobald die Grenzen in Kraft treten, halten Sie
einzelne Anfragen in einer vernünftigen Größe, behandeln Sie `413` und `429` als
mit Backoff wiederholbar (bei `413` auch als Signal, die Eingabe aufzuteilen)
und beachten Sie einen Header `Retry-After`, wenn er vorhanden ist. Wie die
Nutzung gemessen wird, steht unter [Rate-Limits und
Nutzung](/docs/api/rate-limits-and-usage).

## Sehr große Dateien zuerst aufteilen

Wenn ein einzelnes Dokument zu groß oder zu langsam ist, um es in einem Aufruf
zu verarbeiten, zerlegen Sie es in kleinere Teile, verarbeiten Sie jeden für
sich und führen Sie die Ergebnisse zusammen, falls Sie am Ende eine einzige
Datei brauchen. Das begrenzt den Speicher und die Zeit, die eine einzelne
Anfrage braucht, und Sie können die Teile parallel verarbeiten.

<Steps>

<Step title="In Teile aufteilen">
  Verwenden Sie [Nach Dateigröße aufteilen](/docs/api/split-pdf-by-file-size)
  oder [Nach Seitenanzahl aufteilen](/docs/api/split-pdf-by-page-count), um
  handhabbare Blöcke zu erzeugen. Wie Sie die Ausgabe auspacken, steht unter
  [Ein PDF aufteilen](/docs/api/splitting-a-pdf).
</Step>

<Step title="Jeden Teil verarbeiten">
  Führen Sie Ihre Aktion auf jedem Teil aus, bei Bedarf auch parallel, denn
  die Aufrufe sind unabhängig.
</Step>

<Step title="Bei Bedarf zusammenführen">
  Setzen Sie die verarbeiteten Teile mit [PDF-Dokumente
  zusammenführen](/docs/api/merge-pdf-documents) wieder zusammen.
</Step>

</Steps>

## Siehe auch

<CardGroup cols={2}>

<Card title="Rate-Limits und Nutzung" href="/docs/api/rate-limits-and-usage">
  Wie die Nutzung gemessen wird, und der reservierte Vertrag über die Grenzen.
</Card>

<Card title="Fehler" href="/docs/api/errors">
  Statuscodes, die Form von problem+json und was zu wiederholen ist.
</Card>

<Card title="Ein PDF aufteilen" href="/docs/api/splitting-a-pdf">
  Ein riesiges Dokument zuerst in kleinere Teile zerlegen.
</Card>

<Card title="Aktionen verketten" href="/docs/api/chaining-actions">
  Eine mehrstufige Pipeline als Stream verarbeiten, ohne temporäre Dateien.
</Card>

</CardGroup>
