Travailler avec de gros fichiers
Ce qu’il faut changer une fois que les PDF deviennent volumineux : le streaming, les délais d’attente, les nouvelles tentatives, les limites de taille et le moment où il vaut mieux diviser d’abord.
Les gros PDF donnent lieu à la même requête que les petits (un fichier en entrée, un fichier en sortie), mais l’approche naïve qui consiste à charger tout le document en mémoire et à s’en remettre au délai d’attente par défaut s’écroule à mesure que les fichiers grossissent. Envoyez et téléchargez en flux, laissez à la requête le temps d’aboutir et réessayez après les échecs transitoires. Comme l’API est stateless, une requête qui échoue ne laisse rien derrière elle, donc les nouvelles tentatives sont sans risque.
Travailler en flux, pas en mémoire tampon
Lisez l’entrée depuis le disque par blocs au fur et à mesure de l’envoi, et
écrivez la réponse sur le disque par blocs à mesure qu’elle arrive. Ne gardez
jamais tout le document en mémoire. Chaque exemple ci-dessous fonctionne en
flux dans les deux sens et appose un filigrane sur big.pdf, qui tient lieu de
n’importe quelle action à sortie unique.
# 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 diskDéfinir des délais d’attente généreux
Un gros document met plus de temps à être envoyé et à être traité, si bien qu’un délai d’attente client par défaut, souvent de 30 secondes ou moins, interrompra une requête pourtant saine. Répartissez le budget :
- Un court délai de connexion (quelques secondes) échoue vite lorsque l’hôte est injoignable.
- Un long délai de lecture laisse au traitement le temps de se terminer. Les exemples ci-dessus utilisent dix minutes ; dimensionnez-le en fonction du plus gros document que vous attendez.
Ne définissez pas de délai illimité : vous voulez tout de même qu’une connexion bloquée finisse par abandonner, pour qu’une nouvelle tentative prenne le relais.
Réessayer après les échecs transitoires
Les réinitialisations réseau, les délais de connexion dépassés et (une fois
appliquées) les réponses 429 ou 5xx sont transitoires : la même requête a de
bonnes chances d’aboutir un instant plus tard. Réessayez avec un backoff
exponentiel et un peu de jitter. Réessayer est ici sans risque précisément
parce que l’API est stateless : chaque appel est une fonction pure de ses
entrées, sans écriture partielle à annuler.
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 responseNe réessayez pas après un 400 : une erreur de validation ne se corrigera pas
toute seule, et le corps application/problem+json vous indique quel champ
corriger. Consultez Erreurs pour le catalogue. En ligne de
commande, cURL intègre ce mécanisme : --retry 3 --retry-delay 2 --retry-all-errors.
Limites de taille et rate limits
Les limites de taille de requête, les rate limits et leurs codes de statut
413, 429 et apparentés font partie du contrat de l’API à titre réservé et
ne sont pas encore appliqués. Aucun seuil fixe n’est publié : concevez
pour le contrat plutôt que pour un chiffre précis.
Pour rester robuste à mesure que les limites entrent en vigueur, gardez chaque
requête à une taille raisonnable, traitez 413 et 429 comme des cas à
réessayer avec backoff (ou, pour 413, comme le signal qu’il faut diviser
l’entrée) et respectez un en-tête Retry-After lorsqu’il est présent.
Consultez Rate limits et utilisation pour
savoir comment l’utilisation est mesurée.
Diviser d’abord les fichiers très volumineux
Si un document unique est trop gros ou trop lent à traiter en un seul appel, découpez-le en parties plus petites, traitez chacune indépendamment et, s’il vous faut récupérer un seul fichier, fusionnez les résultats. Cela plafonne la mémoire et le temps qu’exige une requête donnée, et vous permet de traiter les parties en parallèle.
Utilisez Diviser un PDF par taille de fichier ou Diviser un PDF par nombre de pages pour produire des blocs gérables. Consultez Diviser un PDF et traiter la sortie pour savoir comment extraire la sortie.
Exécutez votre action sur chaque partie, en parallèle si vous le souhaitez, puisque les appels sont indépendants.
Recombinez les parties traitées avec Fusionner des documents PDF.
Voir aussi
Comment l’utilisation est mesurée et le contrat de limites réservé.
Les codes de statut, la forme problem+json et ce qu’il faut réessayer.
Découpez d’abord un document énorme en parties plus petites.
Faites circuler en flux un pipeline en plusieurs étapes sans fichiers temporaires.