# Lavorare con file di grandi dimensioni

Che cosa cambiare quando i PDF diventano grandi: streaming, timeout, nuovi tentativi, limiti di dimensione e quando conviene dividere prima.

I PDF di grandi dimensioni danno luogo alla stessa richiesta di quelli piccoli
(un file in input, un file in output), ma l’approccio ingenuo di leggere
l’intero documento in memoria e affidarsi a un timeout predefinito crolla man
mano che i file crescono. Eseguire in streaming l’upload e il download, lasciare
alla richiesta il tempo di concludersi e ritentare in caso di errori transitori.
Poiché l’API è *stateless*, una richiesta fallita non lascia nulla dietro di sé,
quindi i nuovi tentativi sono sicuri.

## Usare lo streaming, non il buffer

Leggere l’input dal disco a blocchi mentre lo si invia e scrivere la risposta sul
disco a blocchi man mano che arriva. Non tenere mai in memoria l’intero
documento. Ogni esempio qui sotto usa lo streaming in entrambe le direzioni e
appone una filigrana su `big.pdf`, che sta al posto di qualsiasi azione a output
singolo.

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

## Impostare timeout generosi

Un documento di grandi dimensioni richiede più tempo per l’upload e per
l’elaborazione, quindi un timeout predefinito del client, spesso di 30 secondi o
meno, interrompe una richiesta che sarebbe altrimenti andata a buon fine.
Conviene dividere il budget:

- Un breve timeout di **connessione** (pochi secondi) fallisce subito quando
  l’host è irraggiungibile.
- Un lungo timeout di **lettura** dà all’elaborazione il tempo di concludersi.
  Gli esempi qui sopra usano dieci minuti: dimensionarlo sul documento più grande
  che ci si aspetta.

Non impostare un timeout illimitato: serve comunque che una connessione bloccata
prima o poi si arrenda, in modo che un nuovo tentativo possa subentrare.

## Ritentare in caso di errori transitori

I reset di rete, i timeout di connessione e (una volta in vigore) le risposte
`429` o `5xx` sono transitori: la stessa richiesta può benissimo riuscire un
istante dopo. Ritentare con un backoff esponenziale e un po’ di jitter. Qui
ritentare è sicuro proprio perché l’API è *stateless*: ogni chiamata è una
funzione pura dei suoi input, senza scritture parziali da annullare.

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

Non ritentare un `400`: un errore di validazione non si corregge da solo e il
corpo `application/problem+json` indica quale campo correggere. Vedere
[Errori](/docs/api/errors) per il catalogo. Dalla riga di comando cURL ha già
questo meccanismo integrato: `--retry 3 --retry-delay 2 --retry-all-errors`.

## Limiti di dimensione e rate limit

<Note>
  I limiti di dimensione delle richieste, i rate limit e i relativi codici di
  stato `413`, `429` e affini sono una parte riservata del contratto dell’API e
  **non sono ancora applicati**. Non è pubblicata alcuna soglia fissa.
  Progettare per il contratto anziché per un numero preciso.
</Note>

Per restare robusti quando i limiti entreranno in vigore, mantenere le singole
richieste a una dimensione ragionevole, trattare `413` e `429` come casi da
ritentare con backoff (oppure, per `413`, come il segnale che occorre dividere
l’input) e rispettare l’intestazione `Retry-After` quando è presente. Vedere
[Rate limit e utilizzo](/docs/api/rate-limits-and-usage) per sapere come viene
misurato l’utilizzo.

## Dividere prima i file molto grandi

Se un singolo documento è troppo grande o troppo lento da elaborare in un’unica
chiamata, dividerlo in parti più piccole, elaborare ciascuna in modo indipendente
e (se serve riavere un solo file) unire i risultati. Questo limita la memoria e
il tempo richiesti da ogni singola richiesta e permette di elaborare le parti in
parallelo.

<Steps>

<Step title="Dividere in parti">
  Usare [Dividere per dimensione del file](/docs/api/split-pdf-by-file-size) o
  [Dividere per numero di pagine](/docs/api/split-pdf-by-page-count) per
  produrre blocchi gestibili. Vedere [Dividere un
  PDF](/docs/api/splitting-a-pdf) per sapere come spacchettare l’output.
</Step>

<Step title="Elaborare ogni parte">
  Eseguire la propria azione su ogni parte, in parallelo se si vuole, dato che
  le chiamate sono indipendenti.
</Step>

<Step title="Unire se serve">
  Ricombinare le parti elaborate con [Unire documenti
  PDF](/docs/api/merge-pdf-documents).
</Step>

</Steps>

## Vedi anche

<CardGroup cols={2}>

<Card title="Rate limit e utilizzo" href="/docs/api/rate-limits-and-usage">
  Come viene misurato l’utilizzo e il contratto riservato sui limiti.
</Card>

<Card title="Errori" href="/docs/api/errors">
  I codici di stato, la forma problem+json e che cosa ritentare.
</Card>

<Card title="Dividere un PDF" href="/docs/api/splitting-a-pdf">
  Dividere prima un documento enorme in parti più piccole.
</Card>

<Card title="Concatenare le azioni" href="/docs/api/chaining-actions">
  Eseguire in streaming una pipeline in più passaggi senza file temporanei.
</Card>

</CardGroup>
