Exemples d’intégration

Python et Node.js : déposer, suivre une opération et préparer la recette.

Les exemples ci-dessous s’utilisent côté serveur, avec des identifiants fournis par votre gestionnaire de secrets. Ils ne créent pas de boucle de reprise automatique. Enregistrez le payload exact et la clé d’idempotence avant le premier POST.

Python : dépôt contrôlé et suivi

Cet exemple utilise la bibliothèque standard. Il refuse les redirections et conserve le code HTTP, les en-têtes et le JSON pour permettre une décision métier explicite. Une erreur réseau laisse le résultat du dépôt indéterminé : consultez son statut avec la clé d’origine.

import base64
import hashlib
import json
import os
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

def ccfn_request(method, path, payload=None, key=None):
    base = os.environ['CCFN_BASE_URL'].rstrip('/')
    url = urllib.parse.urlsplit(base)
    if (url.scheme != 'https' or not url.netloc or url.path not in ('', '/')
            or url.username or url.query or url.fragment):
        raise ValueError('CCFN_BASE_URL doit être une origine HTTPS')
    credentials = (os.environ['CCFN_USER'] + ':' + os.environ['CCFN_PASSWORD']).encode()
    headers = {
        'Authorization': 'Basic ' + base64.b64encode(credentials).decode(),
        'Accept': 'application/json'
    }
    if key is not None:
        headers['Idempotency-Key'] = key
    body = None
    if payload is not None:
        headers['Content-Type'] = 'application/json'
        body = json.dumps(payload).encode()
    req = urllib.request.Request(base + '/api/ccfn/v1' + path, data=body, headers=headers, method=method)
    try:
        response = urllib.request.build_opener(NoRedirect).open(req, timeout=300)
    except urllib.error.HTTPError as exc:
        response = exc
    with response:
        raw = response.read()
        try:
            data = json.loads(raw)
        except (ValueError, UnicodeError):
            data = {'non_json_response': True}
        return response.code, dict(response.headers), data

def make_payload(filename, space_id, external_id, retention_days):
    content = Path(filename).read_bytes()
    if len(content) > 52_428_800:
        raise ValueError('Fichier supérieur à la limite par défaut de 50 Mio')
    return {
        'content_base64': base64.b64encode(content).decode(),
        'original_filename': Path(filename).name,
        'media_type': 'application/pdf',
        'space_id': space_id,
        'external_id': external_id,
        'retention_days': retention_days,
        'digest_algorithm': 'sha256',
        'digest': hashlib.sha256(content).hexdigest(),
        'signature_validation': 'none'
    }

# Après sauvegarde durable du payload et de la clé :
# status, headers, result = ccfn_request('POST', '/object', payload, saved_key)
# Pour lever une ambiguïté ou suivre un 202 :
# status, headers, result = ccfn_request('GET', '/object/deposit/status', key=saved_key)

Les exceptions réseau sont propagées au code appelant. N’en déduisez pas que le serveur n’a pas reçu le POST. Le délai de 300 secondes est un choix d’exemple à adapter à votre environnement. media_type doit correspondre au fichier réel si vous utilisez un autre format que PDF.

Node.js : même contrat de requête

Ce code utilise les fonctions natives fetch et AbortSignal.timeout. Il doit rester sur votre serveur ; les secrets ne sont pas destinés à une application navigateur.

import { createHash } from 'node:crypto';

export function makePayload(content, options) {
  if (!Buffer.isBuffer(content)) throw new TypeError('Buffer attendu');
  if (content.length > 52_428_800) throw new Error('Limite par défaut : 50 Mio');
  return {
    content_base64: content.toString('base64'),
    original_filename: options.filename,
    media_type: options.mediaType,
    space_id: options.spaceId,
    external_id: options.externalId,
    retention_days: options.retentionDays,
    digest_algorithm: 'sha256',
    digest: createHash('sha256').update(content).digest('hex'),
    signature_validation: 'none'
  };
}

export async function ccfnRequest(method, path, payload, savedKey) {
  const url = new URL(process.env.CCFN_BASE_URL);
  if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash || url.pathname !== '/') {
    throw new Error('Une origine HTTPS est requise');
  }
  const credentials = process.env.CCFN_USER + ':' + process.env.CCFN_PASSWORD;
  const headers = {
    Authorization: 'Basic ' + Buffer.from(credentials).toString('base64'),
    Accept: 'application/json'
  };
  if (savedKey !== undefined) headers['Idempotency-Key'] = savedKey;
  if (payload !== undefined) headers['Content-Type'] = 'application/json';
  const response = await fetch(url.origin + '/api/ccfn/v1' + path, {
    method, headers,
    body: payload === undefined ? undefined : JSON.stringify(payload),
    signal: AbortSignal.timeout(300_000),
    redirect: 'error'
  });
  const raw = await response.text();
  let data;
  try { data = JSON.parse(raw); } catch { data = { non_json_response: true }; }
  return { status: response.status, headers: Object.fromEntries(response.headers), data };
}

// Après persistance de payload et savedKey :
// const result = await ccfnRequest('POST', '/object', payload, savedKey);
// const state = await ccfnRequest('GET', '/object/deposit/status', undefined, savedKey);

Décision de reprise commune

RésultatDécision
POST 201 ou reprise 200 avec objetEnregistrer l’UUID et les métadonnées
POST 202Attendre Retry-After, puis GET du statut avec la même clé
Réponse réseau absenteGET du statut avec la même clé, sans créer une nouvelle opération
GET statut 404 unknownRéémettre le même POST avec la même clé et le même payload
GET statut 200 succeededExploiter l’UUID retourné et lire les métadonnées si nécessaire
GET statut 200 failedExaminer failure_code ; décider explicitement d’une nouvelle opération
409 conflit de payloadRéparer la correspondance entre l’opération persistée et sa clé

Pour un GET de statut, un code 200 ne suffit pas : inspectez data.status. Le protocole n’établit pas une transaction distribuée entre votre application, le CCFN et les prestataires de preuve. Prévoyez un rapprochement des opérations restées ambiguës et une limite de consultation.

Recette de votre intégration

Testez un dépôt contrôlé valide, une empreinte différente, une reprise identique, un changement de payload sous la même clé et une interruption réseau. Vérifiez la séparation des rôles et la restitution des octets. Testez aussi la limite Base64, une prolongation refusée et une destruction bloquée avant échéance avec des fichiers de recette.

Ces extraits illustrent le contrat et ne constituent pas un SDK officiel. Validez leurs appels réseau sur votre environnement activé avant usage en production.

À lire ensuite : Idempotence, Référence.


Did this page help you?