Génération de PDF par lots via Connect avec suivi de progression
Pilote le traitement d’une liste de documents jusqu’à leur achèvement depuis un seul processus client via NextPDF Connect, la distribution autonome du moteur sous forme de service HTTP. Ce recipe soumet chaque requête de rendu au point de terminaison de jobs asynchrones POST /api/v1/jobs, interroge chaque job avec GET /api/v1/jobs/{id} jusqu’à ce qu’il atteigne un état terminal, lit, pour chaque job, les champs status et progress signalés par le serveur, puis télécharge chaque PDF terminé depuis GET /api/v1/jobs/{id}/result.
Le cycle de vie d’un job est fixe et restreint. Un job est pending, puis running, puis atteint exactement un état terminal : completed, failed ou cancelled. La réponse de statut contient un entier progress de 0 à 100 lorsque le serveur le suit, et chaque réponse de sondage non terminale inclut un en-tête Retry-After qui t’indique combien de temps attendre avant la requête suivante. Identifie chaque soumission avec un Idempotency-Key afin qu’une soumission réessayée renvoie le même job plutôt que de lancer un second rendu.
Ce recipe adopte l’approche bas niveau, au plus près du transport. Il utilise directement la surface REST et ne suppose aucun kit de développement logiciel (SDK) spécifique à un langage, de sorte que le même flux se transpose vers n’importe quel client HTTP.
Installation
Section intitulée « Installation »Côté serveur, c’est la distribution Connect standard :
composer require nextpdf/serverLe client PHP de l’exemple de production ci-dessous utilise un client HTTP et des fabriques de messages conformes à PSR-18 et PSR-17. Installe les implémentations que ton projet utilise déjà, par exemple :
composer require psr/http-client psr/http-factoryVue d’ensemble conceptuelle
Section intitulée « Vue d’ensemble conceptuelle »La surface des jobs asynchrones sépare la soumission de la récupération. Tu ne gardes pas une longue connexion HTTP ouverte par document. À la place, tu soumets un job, tu reçois un identifiant, puis tu interroges un point de terminaison de statut peu coûteux jusqu’à ce que le job se termine. Ce modèle rend le lot maîtrisable : le client suit N jobs indépendants à la fois, sans N connexions bloquées.
Trois points de terminaison portent le flux :
POST /api/v1/jobsaccepte le même corps de requête de rendu que le point de terminaison synchrone/api/v1/render: unpage_size, uneorientation, et un tableauoperationsordonné. Il renvoie201 Createdpour un nouveau job, ou200 OKlorsqu’uneIdempotency-Keycorrespond à un job déjà soumis.GET /api/v1/jobs/{id}renvoie l’enregistrement actuel du job. Pour un job non terminal, il définit aussi un en-têteRetry-After(le serveur utilise un intervalle de 2 secondes) et un champpoll_url. Respecte l’en-tête plutôt que d’interroger dans une boucle serrée.GET /api/v1/jobs/{id}/resultrenvoie le PDF terminé avec le typeapplication/pdf. Il renvoie409 Conflictsi le job n’a pas atteintcompleted, donc ne l’appelle qu’une fois que le sondage de statut a confirmé l’état terminal.
Chaque réponse réussie utilise la même enveloppe : un objet data avec les champs du job, et un objet meta avec le request_id, le timestamp, le duration_ms, et l’api_version. Les champs du job à lire se trouvent sous data : data.status, data.progress, data.job_id, et, sur un job terminé, data.result_url.
Point important sur la version actuelle. Le serveur traite un job soumis de manière synchrone avant de répondre au POST ; en pratique, la réponse de soumission peut donc déjà porter un status terminal, et le résultat peut être prêt dès le premier sondage. Le contrat de sondage et de progression documenté ici est la forme stable de l’API. Le serveur la garde inchangée pendant que le backend de traitement évolue vers un pool de workers en file d’attente, de sorte qu’un client écrit pour interroger est correct aujourd’hui et le restera après ce changement. Garde la boucle de sondage. Ne suppose pas que la première réponse est non terminale, et ne suppose pas non plus qu’elle est terminale.
Surface d’API
Section intitulée « Surface d’API »La surface REST des jobs asynchrones de Connect, définie par le document OpenAPI du serveur et le routage JobHandler :
POST /api/v1/jobs: soumet un job de rendu. En-tête de requêteIdempotency-Keyfacultatif. Le corps est une requête de rendu (operationsest obligatoire et doit contenir au moins une opération). Réponses :201pour un nouveau job,200pour un rejeu idempotent,422pour un corps invalide,409pour un conflit d’idempotence,429en cas de limitation par le débit.GET /api/v1/jobs/{id}: interroge le statut. Réponse200avec l’enregistrement du job ; en-têteRetry-Afterprésent tant qu’il n’est pas terminal ;404si le job n’existe pas ou appartient à un autre client.GET /api/v1/jobs/{id}/result: télécharge le PDF.200application/pdflorsquecompleted;409lorsqu’il n’est pas encore terminé ;404s’il est inconnu.DELETE /api/v1/jobs/{id}: annule un jobpendingourunning, ou supprime un jobcompleted(204).
L’enregistrement du job sous data contient ces champs, exactement tels que le serveur les sérialise.
job_id: l’identifiant (un préfixejob_et 24 caractères hexadécimaux).status: l’un depending,running,completed,failed,cancelled. Les deux premiers sont non terminaux ; les trois derniers sont terminaux.created_at, et, une fois définis,started_atetcompleted_at: des horodatages ISO-8601.progress: un entier de 0 à 100, présent uniquement lorsque le serveur le suit pour le job ; absent (à traiter comme inconnu) sinon.error: une chaîne de message, présente uniquement sur un jobfailed.result_url: présent uniquement sur un jobcompleted; le chemin vers le téléchargement du résultat.poll_url: présent uniquement tant que le job n’est pas terminal.
L’authentification est un jeton porteur dans l’en-tête Authorization : Authorization: Bearer npk_live_{kid}_{secret}.
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »Cet exemple mène un job de bout en bout au niveau du transport, pour te montrer les trois appels et les champs qu’ils renvoient. Il soumet, interroge une fois, puis télécharge. L’exemple de production ci-dessous ajoute la boucle de lots, l’attente Retry-After et une gestion complète des erreurs.
# 1. Submit an async render job. Capture the job_id from data.job_id.curl -sS -X POST "$NEXTPDF_CONNECT_URL/api/v1/jobs" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN" \ -H 'Content-Type: application/json' \ -H "Idempotency-Key: invoice-2026-04-0001" \ -d '{"page_size":"A4","orientation":"portrait","operations":[{"type":"add_text","text":"Invoice 0001"}]}'
# 2. Poll status. Read data.status and data.progress; honour Retry-After.curl -sS "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_0123456789abcdef01234567" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN"
# 3. Once data.status is "completed", download the PDF binary.curl -sS "$NEXTPDF_CONNECT_URL/api/v1/jobs/job_0123456789abcdef01234567/result" \ -H "Authorization: Bearer $NEXTPDF_CONNECT_TOKEN" \ -o invoice-0001.pdfExemple de code — Production
Section intitulée « Exemple de code — Production »Voici un client autonome. Il soumet un lot de requêtes de rendu, plafonne le nombre de jobs simultanément en vol, interroge chaque job à la cadence demandée par le serveur via Retry-After, rapporte le champ progress signalé par le serveur, télécharge chaque PDF terminé et consigne les échecs. Il utilise un client HTTP PSR-18 et des fabriques PSR-17 (le contrat de transport sur lequel les recipes Connect s’alignent), et il intercepte l’exception la plus spécifique que chaque appel peut lever : Psr\Http\Client\ClientExceptionInterface pour un échec de transport, et un BatchJobException typé pour une réponse serveur qui empêche le lot de continuer. Aucun bloc catch n’est vide. Chacun journalise puis relance l’exception, ou enregistre un résultat défini.
Remplace la liste $documents en ligne par tes propres entrées. Injecte le client HTTP et les fabriques concrètes de ton projet là où le constructeur attend les interfaces PSR.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use Psr\Http\Client\ClientExceptionInterface;use Psr\Http\Client\ClientInterface;use Psr\Http\Message\RequestFactoryInterface;use Psr\Http\Message\StreamFactoryInterface;
/** * Raised when a Connect job response prevents the batch from proceeding. * * Distinct from the PSR-18 transport exception: this means the request was * delivered and the server answered, but the answer is one the batch * cannot act on (a non-success status code, or a job that ended in a * terminal failure). */final class BatchJobException extends RuntimeException{}
/** * Drives a batch of async render jobs over the NextPDF Connect REST surface. * * The client submits each render request, polls every job on the cadence * the server requests through Retry-After, and downloads each completed * PDF. It enforces bounded concurrency so a large batch never opens more * in-flight jobs than the host should track at once. */final readonly class ConnectBatchRunner{ /** * @param non-empty-string $baseUrl Connect base URL, no trailing slash * @param non-empty-string $bearerToken Connect API key (npk_live_...) * @param positive-int $maxInFlight Concurrent jobs cap * @param positive-int $maxPolls Per-job poll attempts before giving up */ public function __construct( private ClientInterface $httpClient, private RequestFactoryInterface $requestFactory, private StreamFactoryInterface $streamFactory, private string $baseUrl, private string $bearerToken, private int $maxInFlight = 8, private int $maxPolls = 150, ) {}
/** * Render every document in the batch and write each completed PDF. * * @param array<non-empty-string, array<string, mixed>> $documents * Map of stable document key to render request body. The key * doubles as the Idempotency-Key, so a re-run of the same batch * does not duplicate server-side work. * @param non-empty-string $outputDir Directory for the written PDFs * * @throws BatchJobException When the batch cannot proceed at all * @throws ClientExceptionInterface When the transport cannot send a request * * @return array<non-empty-string, string> Map of document key to a * human-readable outcome line */ public function run(array $documents, string $outputDir): array { $this->assertWritableDir($outputDir);
$outcomes = [];
// Process in bounded windows so the in-flight job count never // exceeds the configured cap, regardless of batch size. foreach (array_chunk($documents, $this->maxInFlight, preserve_keys: true) as $window) { $jobIds = [];
foreach ($window as $key => $body) { $jobIds[$key] = $this->submit($key, $body); }
foreach ($jobIds as $key => $jobId) { $record = $this->pollToTerminal($jobId); $outcomes[$key] = $this->finish($key, $record, $outputDir); } }
return $outcomes; }
/** * Submit one render job and return its identifier. * * @param non-empty-string $idempotencyKey Stable per-document key * @param array<string, mixed> $body Render request body * * @throws BatchJobException * @throws ClientExceptionInterface * * @return non-empty-string The job_id from data.job_id */ private function submit(string $idempotencyKey, array $body): string { $request = $this->requestFactory ->createRequest('POST', $this->baseUrl . '/api/v1/jobs') ->withHeader('Authorization', 'Bearer ' . $this->bearerToken) ->withHeader('Content-Type', 'application/json') ->withHeader('Idempotency-Key', $idempotencyKey) ->withBody($this->streamFactory->createStream($this->encode($body)));
$response = $this->httpClient->sendRequest($request); $status = $response->getStatusCode();
// 201 new job; 200 idempotent replay. Anything else stops the batch. if ($status !== 201 && $status !== 200) { throw new BatchJobException( sprintf('Submit for "%s" returned HTTP %d.', $idempotencyKey, $status), ); }
$data = $this->decodeData($response->getBody()->__toString()); $jobId = $data['job_id'] ?? null;
if (!is_string($jobId) || $jobId === '') { throw new BatchJobException( sprintf('Submit for "%s" returned no job_id.', $idempotencyKey), ); }
return $jobId; }
/** * Poll one job until it reaches a terminal state. * * Honours the Retry-After header on every non-terminal poll. Gives up * after maxPolls attempts and reports the wait as a failure so the * batch records it rather than blocking forever. * * @param non-empty-string $jobId * * @throws BatchJobException * @throws ClientExceptionInterface * * @return array<string, mixed> The terminal job record (data object) */ private function pollToTerminal(string $jobId): array { $url = $this->baseUrl . '/api/v1/jobs/' . rawurlencode($jobId);
for ($attempt = 0; $attempt < $this->maxPolls; $attempt++) { $request = $this->requestFactory ->createRequest('GET', $url) ->withHeader('Authorization', 'Bearer ' . $this->bearerToken);
$response = $this->httpClient->sendRequest($request); $status = $response->getStatusCode();
if ($status !== 200) { throw new BatchJobException( sprintf('Poll for job "%s" returned HTTP %d.', $jobId, $status), ); }
$data = $this->decodeData($response->getBody()->__toString()); $jobStatus = is_string($data['status'] ?? null) ? $data['status'] : 'unknown'; $progress = is_int($data['progress'] ?? null) ? $data['progress'] : null;
$this->logProgress($jobId, $jobStatus, $progress);
// Terminal states: completed, failed, cancelled. if (in_array($jobStatus, ['completed', 'failed', 'cancelled'], strict: true)) { return $data; }
// Non-terminal: wait the interval the server asked for. $this->waitRetryAfter($response->getHeaderLine('Retry-After')); }
throw new BatchJobException( sprintf('Job "%s" did not finish within %d polls.', $jobId, $this->maxPolls), ); }
/** * Act on a terminal job record: download a completed PDF, or report. * * @param non-empty-string $key Document key * @param array<string, mixed> $record Terminal job record (data object) * @param non-empty-string $outputDir Where to write the PDF * * @throws BatchJobException * @throws ClientExceptionInterface * * @return string A human-readable outcome line */ private function finish(string $key, array $record, string $outputDir): string { $jobStatus = is_string($record['status'] ?? null) ? $record['status'] : 'unknown'; $jobId = is_string($record['job_id'] ?? null) ? $record['job_id'] : '';
if ($jobStatus !== 'completed') { // A failed job carries an error message; surface it, do not swallow. $error = is_string($record['error'] ?? null) ? $record['error'] : 'no detail';
return sprintf('%s -> %s (%s)', $key, $jobStatus, $error); }
$path = rtrim($outputDir, '/\\') . DIRECTORY_SEPARATOR . $key . '.pdf'; $this->download($jobId, $path);
return sprintf('%s -> completed, written to %s', $key, $path); }
/** * Download a completed job result and write it to a server-derived path. * * @param non-empty-string $jobId * @param non-empty-string $path Caller-controlled output path * * @throws BatchJobException * @throws ClientExceptionInterface */ private function download(string $jobId, string $path): void { $request = $this->requestFactory ->createRequest('GET', $this->baseUrl . '/api/v1/jobs/' . rawurlencode($jobId) . '/result') ->withHeader('Authorization', 'Bearer ' . $this->bearerToken);
$response = $this->httpClient->sendRequest($request);
if ($response->getStatusCode() !== 200) { throw new BatchJobException( sprintf('Result download for job "%s" returned HTTP %d.', $jobId, $response->getStatusCode()), ); }
$bytes = $response->getBody()->__toString();
if (!str_starts_with($bytes, '%PDF')) { throw new BatchJobException( sprintf('Result for job "%s" is not a PDF.', $jobId), ); }
if (file_put_contents($path, $bytes) === false) { throw new BatchJobException(sprintf('Could not write result to "%s".', $path)); } }
/** * Sleep for the server-requested interval, with a safe floor and ceiling. */ private function waitRetryAfter(string $retryAfter): void { $seconds = ctype_digit($retryAfter) ? (int) $retryAfter : 2; // Clamp to a sane band so a hostile header cannot stall or busy-loop. $seconds = max(1, min(30, $seconds)); sleep($seconds); }
/** * Emit a progress line. Replace with your logger. */ private function logProgress(string $jobId, string $jobStatus, ?int $progress): void { $pct = $progress === null ? 'n/a' : $progress . '%'; fwrite(STDERR, sprintf("[%s] status=%s progress=%s\n", $jobId, $jobStatus, $pct)); }
/** * Decode a response envelope and return its data object. * * @throws BatchJobException When the body is not the expected envelope * * @return array<string, mixed> */ private function decodeData(string $json): array { try { /** @var mixed $decoded */ $decoded = json_decode($json, true, 32, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new BatchJobException('Response body is not valid JSON.', previous: $e); }
if (!is_array($decoded) || !isset($decoded['data']) || !is_array($decoded['data'])) { throw new BatchJobException('Response is missing the data envelope.'); }
/** @var array<string, mixed> $data */ $data = $decoded['data'];
return $data; }
/** * @param array<string, mixed> $body * * @throws BatchJobException */ private function encode(array $body): string { try { return json_encode($body, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES); } catch (JsonException $e) { throw new BatchJobException('Render request body is not encodable.', previous: $e); } }
/** * @param non-empty-string $dir * * @throws BatchJobException */ private function assertWritableDir(string $dir): void { if (!is_dir($dir) || !is_writable($dir)) { throw new BatchJobException(sprintf('Output directory "%s" is not writable.', $dir)); } }}
// ---------------------------------------------------------------------------// Wiring. Provide your project's concrete PSR-18 client and PSR-17 factories.// ---------------------------------------------------------------------------
/** @var ClientInterface $httpClient *//** @var RequestFactoryInterface $requestFactory *//** @var StreamFactoryInterface $streamFactory */
$baseUrl = getenv('NEXTPDF_CONNECT_URL');$token = getenv('NEXTPDF_CONNECT_TOKEN');
if ($baseUrl === false || $baseUrl === '' || $token === false || $token === '') { fwrite(STDERR, "Set NEXTPDF_CONNECT_URL and NEXTPDF_CONNECT_TOKEN.\n"); exit(2);}
/** @var array<non-empty-string, array<string, mixed>> $documents */$documents = [ 'invoice-0001' => [ 'page_size' => 'A4', 'orientation' => 'portrait', 'operations' => [ ['type' => 'add_text', 'text' => 'Invoice 0001'], ], ], 'invoice-0002' => [ 'page_size' => 'A4', 'orientation' => 'portrait', 'operations' => [ ['type' => 'add_text', 'text' => 'Invoice 0002'], ], ],];
$runner = new ConnectBatchRunner( httpClient: $httpClient, requestFactory: $requestFactory, streamFactory: $streamFactory, baseUrl: rtrim($baseUrl, '/'), bearerToken: $token, maxInFlight: 8,);
try { $outcomes = $runner->run($documents, getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: sys_get_temp_dir());} catch (BatchJobException $e) { fwrite(STDERR, 'Batch stopped: ' . $e->getMessage() . "\n"); exit(1);} catch (ClientExceptionInterface $e) { fwrite(STDERR, 'Transport failure: ' . $e->getMessage() . "\n"); exit(1);}
foreach ($outcomes as $line) { echo $line, "\n";}STDOUT attendu, une ligne par document ; les chemins dépendent de ton répertoire de sortie :
invoice-0001 -> completed, written to /tmp/invoice-0001.pdfinvoice-0002 -> completed, written to /tmp/invoice-0002.pdfCas limites et pièges
Section intitulée « Cas limites et pièges »- Lis les champs du job sous
data, pas au niveau supérieur. Chaque réponse réussie est placée dans une enveloppe{ "data": ..., "meta": ... }.data.statusetdata.progresssont les champs sur lesquels tu agis ;metaporte lerequest_idpour la corrélation avec le support. progresspeut être absent. Le serveur n’inclutprogressque lorsqu’il le suit pour ce job. Traite un champ manquant comme « inconnu », pas comme zéro, et pilote ta boucle à partir destatus, qui est toujours présent.- La soumission peut déjà être terminale. Dans la version actuelle, le serveur effectue le rendu de manière synchrone avant de répondre au
POST, donc la réponse de soumission peut porterstatus: completedet le résultat peut être prêt dès le premier sondage. Ta boucle de sondage doit accepter un état terminal dès la tentative zéro plutôt que d’exiger d’abord unpending. - Respecte
Retry-After. Les réponses de statut non terminales incluentRetry-After(un intervalle de 2 secondes). Interroger plus vite gaspille des requêtes et t’expose à un429. Borne la valeur dans une plage raisonnable plutôt que de lui faire aveuglément confiance. /resultavant l’achèvement est un409. N’appelle le point de terminaison de résultat qu’après que le sondage de statut a montrécompleted. Un409 Conflictsignifie que le job n’est pas terminé ; ce n’est pas une erreur de transport.- Idempotency-Key empêche le travail dupliqué. Une soumission réessayée avec la même clé renvoie le job d’origine (
200au lieu de201). Utilise une clé stable par document pour qu’un réessai réseau ne lance jamais un second rendu. Une clé réutilisée avec un corps différent est un conflit409. - Les jobs sont limités à leur propriétaire. Un job soumis sous une clé d’API est invisible pour une autre ; un
GETentre propriétaires renvoie404, et non403. Interroge avec la même clé d’API que celle utilisée pour la soumission. - Un job
failedporte un messageerror. Lisdata.errorsur un statut terminalfailedet consigne-le. Ne réessaie pas aveuglément.
Performances
Section intitulée « Performances »Le coût d’un lot est la somme des rendus plus la surcharge de sondage. Deux leviers côté client le contrôlent. D’abord, borne la concurrence : le plafond maxInFlight détermine combien de jobs sont suivis à la fois, ce qui maintient le nombre de requêtes ouvertes et la mémoire du client constants, quelle que soit la taille du lot. Aligne-le sur le nombre de workers du serveur, pas plus ; plus de jobs en vol que de workers ne fait qu’allonger l’attente en file de chaque job. Ensuite, respecte l’intervalle de sondage : chaque sondage est une lecture de statut peu coûteuse, mais une boucle serrée multiplie le volume de requêtes et déclenche le limiteur de débit. Le Retry-After de 2 secondes du serveur est la bonne valeur par défaut, et le runner le borne dans une plage de 1 à 30 secondes pour qu’un seul job lent ne puisse ni boucler à vide ni bloquer la fenêtre.
Pour de très grands lots, traite par fenêtres (le runner utilise array_chunk) plutôt que de tout soumettre d’emblée. Cela borne à la fois l’état suivi par le client et la profondeur de file du serveur, de sorte qu’un lot malformé ou surdimensionné échoue à l’intérieur d’une fenêtre plutôt qu’après des milliers de soumissions.
Notes de sécurité
Section intitulée « Notes de sécurité »- Garde le jeton porteur hors des journaux et des URL. La clé d’API circule uniquement dans l’en-tête
Authorization. Ne la place jamais dans une chaîne de requête, une ligne de journal ou un artefact écrit. Le runner consigne lejob_idet lestatus, jamais l’identifiant secret. - Dérive les chemins de sortie à partir de clés contrôlées côté client. Le runner construit chaque chemin de sortie à partir de la clé de document choisie par ton code, jointe à un répertoire de sortie fixe, jamais à partir d’une valeur renvoyée par le serveur. N’interpole pas un champ de job dans un chemin du système de fichiers : cela ouvrirait la voie à une traversée de chemin.
- Valide les octets téléchargés. Le runner vérifie un
200de/resultet l’en-tête%PDFavant d’écrire le fichier. Un statut de téléchargement réussi ne prouve pas à lui seul que le corps est un PDF. - Traite le résultat comme non fiable jusqu’à inspection. Un job terminé signifie que le serveur a généré des octets, pas que ces octets sont sûrs à transmettre. Fais passer les résultats par une étape d’inspection structurelle avant de les remettre à un client ou à un système en aval.
- Utilise une clé à moindre privilège. La surface de jobs asynchrones relève du rendu de niveau Core. Attribue au lot une clé dont la portée se limite exactement aux opérations dont il a besoin, et renouvelle-la selon le calendrier défini par ta politique de gestion des secrets.
- Borne le budget de sondage.
maxPollsempêche un job bloqué de retenir le client indéfiniment. Le lot enregistre le dépassement de délai comme un résultat plutôt que de bloquer, ce qui empêche un seul job défaillant de priver les autres du service.
Conformité
Section intitulée « Conformité »Ce recipe n’émet aucune revendication normative de conformité à des normes. Il utilise les points de terminaison REST des jobs asynchrones de NextPDF Connect (POST /api/v1/jobs, GET /api/v1/jobs/{id}, GET /api/v1/jobs/{id}/result) et lit les champs de l’enregistrement du job définis par le serveur (status, progress, error, result_url, poll_url). La vérification de l’en-tête %PDF sur un résultat téléchargé confirme seulement que la réponse commence par le marqueur PDF ; ce n’est pas une détermination de validité ou de conformité. Pour une vérification de conformité aux normes sur un ensemble de documents, utilise l’outil de conformité par lots Enterprise. Voir Vérification de conformité aux normes par lots via Connect, qui décrit une surface différente des jobs de rendu couverts ici.
Voir aussi
Section intitulée « Voir aussi »- Hello world via Connect : le rendu unique minimal avant de passer aux lots.
- Conventions des recipes Connect : le contrat de transport, d’authentification et de conformité que chaque recipe Connect partage.
- Gestion des erreurs orientée exceptions via Connect : la façon dont le serveur signale les erreurs et dont un client devrait réagir.
- Vérification de conformité aux normes par lots via Connect : la surface de conformité Enterprise, distincte de ces jobs de rendu.