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.
# 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# 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)// 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 bodypackage 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
}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 diskGroß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.
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 responseWiederholen 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.
Auf der Kommandozeile bringt cURL das schon mit:
--retry 3 --retry-delay 2 --retry-all-errors.
Größengrenzen und Rate-Limits
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.
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.
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.
Verwenden Sie Nach Dateigröße aufteilen oder Nach Seitenanzahl aufteilen, um handhabbare Blöcke zu erzeugen. Wie Sie die Ausgabe auspacken, steht unter Ein PDF aufteilen.
Führen Sie Ihre Aktion auf jedem Teil aus, bei Bedarf auch parallel, denn die Aufrufe sind unabhängig.
Setzen Sie die verarbeiteten Teile mit PDF-Dokumente zusammenführen wieder zusammen.
Siehe auch
Wie die Nutzung gemessen wird, und der reservierte Vertrag über die Grenzen.
Statuscodes, die Form von problem+json und was zu wiederholen ist.
Ein riesiges Dokument zuerst in kleinere Teile zerlegen.
Eine mehrstufige Pipeline als Stream verarbeiten, ohne temporäre Dateien.