Trabalhar com arquivos grandes
O que mudar quando os PDFs ficam grandes: streaming, tempos limite, novas tentativas, os limites de tamanho e quando dividir antes.
PDFs grandes são a mesma requisição que os pequenos (um arquivo entra, um arquivo sai), mas a abordagem ingênua de ler o documento inteiro na memória e esperar por um tempo limite padrão desmorona à medida que os arquivos crescem. Envie e baixe em fluxo, dê à requisição espaço para terminar e repita as falhas transitórias. Como a API é stateless, uma requisição que falha não deixa nada para trás, então repetir é seguro.
Trabalhe em fluxo, não em buffer
Leia a entrada do disco em blocos enquanto a envia, e grave a resposta no disco
em blocos à medida que ela chega. Nunca mantenha o documento inteiro na memória.
Todos os exemplos abaixo trabalham em fluxo nos dois sentidos e carimbam uma
marca-d’água em big.pdf, que serve de substituto para qualquer ação de saída
única.
# 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 diskDefina tempos limite generosos
Um documento grande demora mais para ser enviado e processado, então um tempo limite padrão do cliente, muitas vezes de 30 segundos ou menos, vai interromper uma requisição saudável antes da hora. Divida o orçamento:
- Um tempo limite curto de conexão (alguns segundos) falha rápido quando o host está inacessível.
- Um tempo limite longo de leitura dá ao processamento tempo para terminar. Os exemplos acima usam dez minutos; dimensione-o pelo maior documento que você espera receber.
Não defina um tempo limite ilimitado: você ainda quer que uma conexão travada desista em algum momento, para que uma nova tentativa assuma.
Repita após falhas transitórias
Resets de rede, tempos limite de conexão e (quando forem aplicadas) respostas
429 ou 5xx são transitórios: a mesma requisição pode muito bem ter sucesso um
instante depois. Repita com backoff exponencial e um pouco de jitter. Repetir é
seguro aqui justamente porque a API é stateless: cada chamada é uma função pura
das suas entradas, sem escritas parciais a desfazer.
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 responseNão tente de novo depois de um 400: um erro de validação não vai se corrigir
sozinho, e o corpo application/problem+json informa qual campo corrigir.
Consulte Erros para o catálogo. Na linha de comando, o cURL
já traz isso embutido: --retry 3 --retry-delay 2 --retry-all-errors.
Limites de tamanho e rate limits
Os limites de tamanho de requisição, os rate limits e seus códigos de status
413, 429 e relacionados são uma parte reservada do contrato da API e
ainda não são aplicados. Nenhum limiar fixo foi publicado. Projete para o
contrato, e não para um número específico.
Para continuar robusto conforme os limites entrarem em vigor, mantenha cada
requisição em um tamanho razoável, trate 413 e 429 como casos a repetir com
backoff (ou, no caso do 413, como um sinal para dividir a entrada) e respeite
um cabeçalho Retry-After quando ele estiver presente. Consulte Rate limits e
uso para saber como o uso é medido.
Divida primeiro os arquivos muito grandes
Se um único documento for grande ou lento demais para ser processado em uma chamada, divida-o em partes menores, processe cada uma de forma independente e (se você precisar de um arquivo só de volta) mescle os resultados. Isso limita a memória e o tempo que qualquer requisição isolada exige, e permite processar as partes em paralelo.
Use Dividir por tamanho de arquivo ou Dividir por número de páginas para produzir blocos gerenciáveis. Consulte Dividir um PDF para saber como descompactar a saída.
Execute a sua ação em cada parte, em paralelo se quiser, já que as chamadas são independentes.
Recombine as partes processadas com Mesclar documentos PDF.