# 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.

<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>

## Dé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.

```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
```

Ne 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](/docs/api/errors) 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

<Note>
  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.
</Note>

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](/docs/api/rate-limits-and-usage) 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.

<Steps>

<Step title="Divisez en parties">
  Utilisez [Diviser un PDF par taille de
  fichier](/docs/api/split-pdf-by-file-size) ou [Diviser un PDF par nombre de
  pages](/docs/api/split-pdf-by-page-count) pour produire des blocs gérables.
  Consultez [Diviser un PDF et traiter la sortie](/docs/api/splitting-a-pdf)
  pour savoir comment extraire la sortie.
</Step>

<Step title="Traitez chaque partie">
  Exécutez votre action sur chaque partie, en parallèle si vous le souhaitez,
  puisque les appels sont indépendants.
</Step>

<Step title="Fusionnez si nécessaire">
  Recombinez les parties traitées avec [Fusionner des documents
  PDF](/docs/api/merge-pdf-documents).
</Step>

</Steps>

## Voir aussi

<CardGroup cols={2}>

<Card title="Rate limits et utilisation" href="/docs/api/rate-limits-and-usage">
  Comment l’utilisation est mesurée et le contrat de limites réservé.
</Card>

<Card title="Erreurs" href="/docs/api/errors">
  Les codes de statut, la forme problem+json et ce qu’il faut réessayer.
</Card>

<Card title="Diviser un PDF" href="/docs/api/splitting-a-pdf">
  Découpez d’abord un document énorme en parties plus petites.
</Card>

<Card title="Enchaîner les actions" href="/docs/api/chaining-actions">
  Faites circuler en flux un pipeline en plusieurs étapes sans fichiers
  temporaires.
</Card>

</CardGroup>
