PDF Blocks
PrezziSupporto
Iniziare gratis
Aprire la pagina

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.

cURLbash
# 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
Pythonpython
# 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)
Node.jsjavascript
// 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
Gogo
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
}
C#csharp
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

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.

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

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.

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

Dividere in parti

Usare Dividere per dimensione del file o Dividere per numero di pagine per produrre blocchi gestibili. Vedere Dividere un PDF per sapere come spacchettare l’output.

Elaborare ogni parte

Eseguire la propria azione su ogni parte, in parallelo se si vuole, dato che le chiamate sono indipendenti.

Unire se serve

Ricombinare le parti elaborate con Unire documenti PDF.

Vedi anche