web2print-service/guides/IMPLEMENTATION_GUIDE.md
isUnknown e3534dcefb
All checks were successful
Deploy / deploy (push) Successful in 8s
classify guides
2026-03-05 07:11:08 +01:00

20 KiB

Guide d'implémentation - Service Web2Print

Guide étape par étape pour déployer le service de conversion HTML vers PDF sur le VPS Infomaniak.

Prérequis

  • Accès SSH au VPS
  • Accès à l'interface Infomaniak
  • Domaine web2print.studio-variable.com pointant vers le VPS

Phase 1 : Préparation du VPS

1.1 Connexion et vérification

# Vérifier Node.js et npm
node --version
npm --version

# Vérifier Apache et PHP
apache2 -v
php -v

1.2 Installation de Node.js (si nécessaire)

# Si Node.js n'est pas installé ou version < 14
sudo apt update
sudo apt install curl -y

# Installer Node.js via NodeSource (version LTS recommandée)
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
sudo apt install -y nodejs

# Vérifier l'installation
node --version
npm --version

1.3 Installation de Paged.js CLI

# Installer pagedjs-cli globalement
sudo npm install -g pagedjs-cli

# Vérifier l'installation (pagedjs-cli n'a pas d'option --version)
npm list -g pagedjs-cli
which pagedjs-cli

# Vérifier le chemin d'installation (noter pour la config PHP)
which pagedjs-cli

1.4 Tester Paged.js CLI

# Créer un fichier HTML de test
cat > /tmp/test.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <style>
    @page { size: A4; margin: 2cm; }
    body { font-family: Arial, sans-serif; }
    h1 { color: #333; }
  </style>
</head>
<body>
  <h1>Test Paged.js CLI</h1>
  <p>Ceci est un test de génération PDF avec Paged.js.</p>
</body>
</html>
EOF

# Générer un PDF de test
pagedjs-cli /tmp/test.html -o /tmp/test.pdf

# Vérifier que le PDF est créé
ls -lh /tmp/test.pdf
file /tmp/test.pdf

Phase 2 : Structure du projet

2.1 Créer la structure des dossiers

# Créer le dossier du projet
sudo mkdir -p /var/www/web2print
cd /var/www/web2print

# Créer la structure
sudo mkdir -p {public,config,logs,tmp}
sudo mkdir -p src/{Controllers,Services,Middleware}

# Structure finale :
# /var/www/web2print/
# ├── public/
# │   └── index.php
# ├── src/
# │   ├── Controllers/
# │   ├── Services/
# │   └── Middleware/
# ├── config/
# │   └── config.php
# ├── logs/
# └── tmp/

2.2 Définir les permissions

# Propriétaire : www-data (utilisateur Apache)
sudo chown -R www-data:www-data /var/www/web2print

# Permissions
sudo chmod -R 755 /var/www/web2print
sudo chmod -R 775 /var/www/web2print/logs
sudo chmod -R 775 /var/www/web2print/tmp

Phase 3 : Configuration Apache

3.1 Configurer le VirtualHost

# Créer le fichier de configuration
sudo nano /etc/apache2/sites-available/web2print.conf

Contenu du fichier web2print.conf :

<VirtualHost *:80>
    ServerName web2print.studio-variable.com
    DocumentRoot /var/www/web2print/public

    <Directory /var/www/web2print/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # Logs
    ErrorLog /var/www/web2print/logs/apache-error.log
    CustomLog /var/www/web2print/logs/apache-access.log combined

    # Sécurité
    <FilesMatch "\.(env|config\.php)$">
        Require all denied
    </FilesMatch>
</VirtualHost>

3.2 Activer le site et modules nécessaires

# Activer le module rewrite
sudo a2enmod rewrite

# Activer le site
sudo a2ensite web2print.conf

# Vérifier la configuration
sudo apache2ctl configtest

# Redémarrer Apache
sudo systemctl restart apache2

3.3 Configuration SSL avec Let's Encrypt (recommandé)

# Installer Certbot
sudo apt install certbot python3-certbot-apache -y

# Obtenir et installer le certificat
sudo certbot --apache -d web2print.studio-variable.com

# Le renouvellement automatique est configuré par défaut

Phase 4 : Configuration DNS (Interface Infomaniak)

4.1 Dans l'interface Infomaniak

  1. Aller dans la gestion des domaines
  2. Sélectionner le domaine studio-variable.com
  3. Accéder aux zones DNS
  4. Ajouter un enregistrement A :
    • Nom : web2print
    • Type : A
    • Valeur : [IP du VPS]
    • TTL : 3600

4.2 Vérifier la propagation

# Attendre quelques minutes puis tester
dig web2print.studio-variable.com
curl -I http://web2print.studio-variable.com

Phase 5 : Implémentation du code PHP

5.1 Fichier de configuration

sudo nano /var/www/web2print/config/config.php

Contenu :

<?php

return [
    // Clés API autorisées
    'api_keys' => [
        'votre-cle-api-secrete-generee-aleatoirement',
        // Ajouter d'autres clés si nécessaire
    ],

    // Chemins
    'tmp_dir' => '/var/www/web2print/tmp',
    'log_file' => '/var/www/web2print/logs/app.log',

    // Paged.js CLI
    'pagedjs_bin' => '/usr/local/bin/pagedjs-cli', // Vérifier avec: which pagedjs-cli
    'pagedjs_timeout' => 60, // secondes

    // Limites
    'max_html_size' => 5 * 1024 * 1024, // 5 MB
    'max_execution_time' => 90,

    // Options PDF par défaut
    'pdf_defaults' => [
        'format' => 'A4',
        'margin' => '2cm',
    ],
];

5.2 Middleware d'authentification

sudo nano /var/www/web2print/src/Middleware/AuthMiddleware.php

Contenu :

<?php

namespace Web2Print\Middleware;

class AuthMiddleware
{
    private array $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function authenticate(): bool
    {
        $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';

        if (empty($apiKey)) {
            $this->sendError(401, 'API key missing');
            return false;
        }

        if (!in_array($apiKey, $this->config['api_keys'], true)) {
            $this->sendError(403, 'Invalid API key');
            return false;
        }

        return true;
    }

    private function sendError(int $code, string $message): void
    {
        http_response_code($code);
        header('Content-Type: application/json');
        echo json_encode(['error' => $message]);
        exit;
    }
}

5.3 Service de génération PDF

sudo nano /var/www/web2print/src/Services/PdfGenerator.php

Contenu :

<?php

namespace Web2Print\Services;

class PdfGenerator
{
    private array $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function generate(string $html, ?string $css = null, array $options = []): string
    {
        // Créer un fichier HTML temporaire
        $htmlFile = $this->createTempFile($html, $css);
        $pdfFile = tempnam($this->config['tmp_dir'], 'pdf_') . '.pdf';

        try {
            // Construire la commande Paged.js CLI
            $command = $this->buildCommand($htmlFile, $pdfFile, $options);

            // Exécuter Paged.js CLI
            $output = [];
            $returnCode = 0;
            exec($command . ' 2>&1', $output, $returnCode);

            if ($returnCode !== 0) {
                $this->log('Paged.js CLI error: ' . implode("\n", $output));
                throw new \Exception('PDF generation failed: ' . implode("\n", $output));
            }

            // Lire le PDF généré
            if (!file_exists($pdfFile)) {
                throw new \Exception('PDF file not created');
            }

            $pdfContent = file_get_contents($pdfFile);

            return $pdfContent;

        } finally {
            // Nettoyer les fichiers temporaires
            @unlink($htmlFile);
            @unlink($pdfFile);
        }
    }

    private function createTempFile(string $html, ?string $css): string
    {
        $fullHtml = $html;

        // Injecter le CSS dans le HTML si fourni
        if ($css) {
            $styleTag = "<style>{$css}</style>";
            if (stripos($html, '</head>') !== false) {
                $fullHtml = str_ireplace('</head>', $styleTag . '</head>', $html);
            } else {
                // Si pas de <head>, créer une structure HTML complète
                $fullHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'>{$styleTag}</head><body>{$html}</body></html>";
            }
        }

        // S'assurer que le HTML a une structure complète
        if (stripos($fullHtml, '<!DOCTYPE') === false) {
            $fullHtml = "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>{$fullHtml}</body></html>";
        }

        $tempFile = tempnam($this->config['tmp_dir'], 'html_') . '.html';
        file_put_contents($tempFile, $fullHtml);

        return $tempFile;
    }

    private function buildCommand(string $htmlFile, string $pdfFile, array $options): string
    {
        $cmd = escapeshellcmd($this->config['pagedjs_bin']);
        $cmd .= ' ' . escapeshellarg($htmlFile);
        $cmd .= ' -o ' . escapeshellarg($pdfFile);

        // Options supplémentaires pour Paged.js CLI
        // Note: Paged.js utilise les règles CSS @page pour le format
        // Les options sont limitées dans la CLI

        // Timeout (millisecondes)
        if (!empty($options['timeout'])) {
            $cmd .= ' --timeout ' . (int)$options['timeout'];
        } else {
            $cmd .= ' --timeout ' . ($this->config['pagedjs_timeout'] * 1000);
        }

        return $cmd;
    }

    private function log(string $message): void
    {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[{$timestamp}] {$message}\n";
        file_put_contents($this->config['log_file'], $logMessage, FILE_APPEND);
    }
}

5.4 Contrôleur principal

sudo nano /var/www/web2print/src/Controllers/GenerateController.php

Contenu :

<?php

namespace Web2Print\Controllers;

use Web2Print\Services\PdfGenerator;

class GenerateController
{
    private PdfGenerator $generator;
    private array $config;

    public function __construct(PdfGenerator $generator, array $config)
    {
        $this->generator = $generator;
        $this->config = $config;
    }

    public function handle(): void
    {
        // Vérifier la méthode HTTP
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->sendError(405, 'Method not allowed');
            return;
        }

        // Lire le body JSON
        $input = file_get_contents('php://input');

        if (strlen($input) > $this->config['max_html_size']) {
            $this->sendError(413, 'Request too large');
            return;
        }

        $data = json_decode($input, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->sendError(400, 'Invalid JSON');
            return;
        }

        // Valider les données
        if (empty($data['html'])) {
            $this->sendError(400, 'HTML content required');
            return;
        }

        try {
            // Générer le PDF
            $pdf = $this->generator->generate(
                $data['html'],
                $data['css'] ?? null,
                $data['options'] ?? []
            );

            // Retourner le PDF
            header('Content-Type: application/pdf');
            header('Content-Length: ' . strlen($pdf));
            echo $pdf;

        } catch (\Exception $e) {
            $this->sendError(500, 'PDF generation error: ' . $e->getMessage());
        }
    }

    private function sendError(int $code, string $message): void
    {
        http_response_code($code);
        header('Content-Type: application/json');
        echo json_encode(['error' => $message]);
    }
}

5.5 Point d'entrée (index.php)

sudo nano /var/www/web2print/public/index.php

Contenu :

<?php

// Autoloader simple
spl_autoload_register(function ($class) {
    $prefix = 'Web2Print\\';
    $baseDir = __DIR__ . '/../src/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }

    $relativeClass = substr($class, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';

    if (file_exists($file)) {
        require $file;
    }
});

// Charger la configuration
$config = require __DIR__ . '/../config/config.php';

// Définir le temps d'exécution max
set_time_limit($config['max_execution_time']);

// Headers CORS (si nécessaire)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, X-API-Key');

// Gérer preflight OPTIONS
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}

// Authentification
$auth = new \Web2Print\Middleware\AuthMiddleware($config);
if (!$auth->authenticate()) {
    exit;
}

// Router simple
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

if ($uri === '/generate' || $uri === '/') {
    $generator = new \Web2Print\Services\PdfGenerator($config);
    $controller = new \Web2Print\Controllers\GenerateController($generator, $config);
    $controller->handle();
} else {
    http_response_code(404);
    header('Content-Type: application/json');
    echo json_encode(['error' => 'Not found']);
}

5.6 Fichier .htaccess

sudo nano /var/www/web2print/public/.htaccess

Contenu :

# Activer le moteur de réécriture
RewriteEngine On

# Rediriger toutes les requêtes vers index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]

# Sécurité
<Files ".env">
    Require all denied
</Files>

Phase 6 : Génération de la clé API

6.1 Générer une clé sécurisée

# Sur le VPS
openssl rand -hex 32
# Exemple de sortie : a7f8e9d4c2b1a3f5e7d9c4b2a1f8e6d5c3b9a7f4e2d8c6b4a2f9e7d5c3b1a8f6

6.2 Ajouter la clé dans la configuration

sudo nano /var/www/web2print/config/config.php

Remplacer 'votre-cle-api-secrete-generee-aleatoirement' par la clé générée.

6.3 Sécuriser le fichier de configuration

sudo chmod 640 /var/www/web2print/config/config.php
sudo chown www-data:www-data /var/www/web2print/config/config.php

Phase 7 : Tests

7.1 Vérifier le chemin de Paged.js CLI

which pagedjs-cli
# Mettre à jour 'pagedjs_bin' dans config.php si nécessaire

7.2 Test avec curl - HTML simple

# Créer un fichier de test avec CSS Paged Media
cat > /tmp/test-request.json << 'EOF'
{
  "html": "<!DOCTYPE html><html><head><meta charset='utf-8'><style>@page { size: A4; margin: 2cm; } body { font-family: Arial; } h1 { color: #333; }</style></head><body><h1>Test PDF Paged.js</h1><p>Ceci est un test de génération PDF avec Paged.js CLI.</p></body></html>"
}
EOF

# Tester l'API (remplacer YOUR_API_KEY)
curl -X POST https://web2print.studio-variable.com/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d @/tmp/test-request.json \
  --output /tmp/result.pdf

# Vérifier le PDF généré
file /tmp/result.pdf
ls -lh /tmp/result.pdf

7.2b Test avec CSS séparé

# Test avec HTML et CSS séparés
cat > /tmp/test-request-2.json << 'EOF'
{
  "html": "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body><h1>Test avec CSS séparé</h1><p>Le CSS est injecté séparément.</p></body></html>",
  "css": "@page { size: A4; margin: 2cm; } body { font-family: Georgia, serif; color: #444; } h1 { color: #d63031; border-bottom: 2px solid #d63031; padding-bottom: 10px; }"
}
EOF

curl -X POST https://web2print.studio-variable.com/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d @/tmp/test-request-2.json \
  --output /tmp/result-2.pdf

7.3 Test sans clé API (doit échouer)

curl -X POST https://web2print.studio-variable.com/generate \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Test</h1>"}'
# Doit retourner : {"error":"API key missing"}

7.4 Test avec mauvaise clé (doit échouer)

curl -X POST https://web2print.studio-variable.com/generate \
  -H "Content-Type: application/json" \
  -H "X-API-Key: wrong-key" \
  -d '{"html": "<h1>Test</h1>"}'
# Doit retourner : {"error":"Invalid API key"}

Phase 8 : Monitoring et maintenance

8.1 Rotation des logs

sudo nano /etc/logrotate.d/web2print

Contenu :

/var/www/web2print/logs/*.log {
    daily
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data www-data
    sharedscripts
}

8.2 Nettoyage des fichiers temporaires

# Créer un script de nettoyage
sudo nano /var/www/web2print/cleanup.sh

Contenu :

#!/bin/bash
# Nettoyer les fichiers temporaires de plus de 1 heure
find /var/www/web2print/tmp -type f -mmin +60 -delete
# Rendre exécutable
sudo chmod +x /var/www/web2print/cleanup.sh

# Ajouter au cron (toutes les heures)
sudo crontab -e
# Ajouter : 0 * * * * /var/www/web2print/cleanup.sh

8.3 Surveiller les logs

# Logs Apache
sudo tail -f /var/www/web2print/logs/apache-error.log

# Logs applicatifs
sudo tail -f /var/www/web2print/logs/app.log

Phase 9 : Checklist de déploiement

  • Node.js et npm installés
  • Paged.js CLI installé et fonctionnel
  • Structure de dossiers créée avec bonnes permissions
  • VirtualHost Apache configuré
  • SSL/TLS configuré (Let's Encrypt)
  • DNS configuré dans Infomaniak
  • Tous les fichiers PHP créés
  • Chemin pagedjs_bin vérifié dans config.php
  • Clé API générée et configurée
  • Tests d'API réussis (HTML simple + CSS séparé)
  • Rotation des logs configurée
  • Nettoyage automatique des fichiers temporaires configuré
  • Documentation de la clé API sauvegardée en lieu sûr

Phase 10 : Optimisations futures (optionnel)

10.1 Rate limiting

Ajouter un système de limitation de requêtes par clé API pour éviter les abus.

10.2 Cache

Mettre en cache les PDFs générés si le même HTML est demandé plusieurs fois.

10.3 File d'attente

Pour des volumes plus importants, implémenter une file d'attente avec des workers.

10.4 Monitoring avancé

  • Intégrer un système de monitoring (Prometheus, Grafana)
  • Alertes sur échecs de génération
  • Métriques de performance

Ressources et documentation


Support et troubleshooting

Erreur : "pagedjs-cli: command not found"

# Vérifier l'installation
which pagedjs-cli
npm list -g pagedjs-cli

# Réinstaller si nécessaire
sudo npm install -g pagedjs-cli --force

# Si problème de PATH, vérifier où npm installe les binaires
npm config get prefix
# Les binaires sont dans: <prefix>/bin

Erreur : "Permission denied" lors de la génération

# Vérifier les permissions
sudo chown -R www-data:www-data /var/www/web2print
sudo chmod -R 775 /var/www/web2print/tmp
sudo chmod -R 775 /var/www/web2print/logs

PDF vide ou erreur de génération

  • Vérifier les logs : /var/www/web2print/logs/app.log
  • Tester Paged.js CLI manuellement :
    echo '<html><body>Test</body></html>' > /tmp/test.html
    pagedjs-cli /tmp/test.html -o /tmp/test.pdf
    
  • Vérifier que Node.js est à jour (minimum v14)
  • S'assurer que le HTML contient <!DOCTYPE html> et une structure complète

Erreur : "Timeout" lors de génération de gros documents

  • Augmenter pagedjs_timeout dans config.php
  • Augmenter max_execution_time dans config.php
  • Augmenter le timeout Apache si nécessaire :
    sudo nano /etc/apache2/apache2.conf
    # Ajouter/modifier : Timeout 300
    sudo systemctl restart apache2
    

Les styles CSS ne sont pas appliqués

  • Vérifier que le CSS contient les règles @page pour Paged.js
  • S'assurer que les ressources externes (fonts, images) sont accessibles
  • Tester le HTML directement dans un navigateur avec Paged.js
  • Exemple de CSS Paged Media basique :
    @page {
      size: A4;
      margin: 2cm;
    }
    @page :first {
      margin-top: 3cm;
    }
    

Différences de rendu entre front et back

  • Utiliser exactement la même version de Paged.js (front) et Paged.js CLI (back)
  • Vérifier la version installée : npm list -g pagedjs-cli
  • Les fonts doivent être identiques (utiliser web fonts ou fonts système communes)