20 KiB
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.compointant 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
- Aller dans la gestion des domaines
- Sélectionner le domaine
studio-variable.com - Accéder aux zones DNS
- Ajouter un enregistrement A :
- Nom :
web2print - Type :
A - Valeur :
[IP du VPS] - TTL :
3600
- Nom :
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_binvé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
- Paged.js : https://pagedjs.org/
- Paged.js CLI : https://gitlab.coko.foundation/pagedjs/pagedjs-cli
- CSS Paged Media : https://www.w3.org/TR/css-page-3/
- PHP exec() : https://www.php.net/manual/fr/function.exec.php
- Apache VirtualHost : https://httpd.apache.org/docs/2.4/vhosts/
- Let's Encrypt : https://letsencrypt.org/
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_timeoutdansconfig.php - Augmenter
max_execution_timedansconfig.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
@pagepour 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)