URL: https://linuxfr.org/news/epub-le-convertisseur-epub3-a-la-volee-de-linuxfr-org Title: epub, le convertisseur EPUB3 à la volée de LinuxFr.org Authors: Benoît Sibaud Julien Jorge Date: 2024-11-03T16:58:41+01:00 License: CC By-SA Tags: epub, linuxfr, docker, hurl, nginx et docker-compose Score: 3 Le site LinuxFr.org utilise divers logiciels libres pour son fonctionnement et ses services : une large majorité provient de projets tiers (_Debian_, _MariaDB_, _Redis_ - version d’avant le changement de licence, _nginx_, _Postfix_, conteneurs _LXC_ et _Docker_, _Ruby On Rails_, _Sympa_, etc.) et d’autres composants sont développés pour nos propres besoins. Cette dernière catégorie comprend le [code principal du site web](https://github.com/linuxfrorg/linuxfr.org/) en Ruby On Rails, et principalement 5 services autour : le [cache d’images _img_](https://github.com/linuxfrorg/img-LinuxFr.org), la [tribune](https://github.com/linuxfrorg/board-sse-linuxfr.org) _board_, le [convertisseur EPUB 3](https://github.com/linuxfrorg/epub-LinuxFr.org) _epub_, le [partageur sur les réseaux sociaux](https://github.com/linuxfrorg/share-LinuxFr.org) _share_ et le [convertisseur LaTeX vers SVG](https://github.com/linuxfrorg/svgtex) _svg_. Cette dépêche va s’intéresser à _epub_, un code sous AGPLv3. Elle est née d’une envie personnelle d’expliquer, documenter et montrer ce qui a été fait sur le convertisseur EPUB3 à la volée de LinuxFr.org, et elle vient accompagner la précédente sur [img, le cache d’images sur LinuxFr.org](https://linuxfr.org/news/img-le-cache-d-images-sur-linuxfr-org). ---- ---- # Des EPUB de vos contenus et commentaires LinuxFr.org vous permet de libre les contenus et commentaires du site, au [format EPUB3](https://fr.wikipedia.org/wiki/EPUB_(format)), par exemple dans votre liseuse préférée. Il y a une exception à cela, les [liens](https://linuxfr.org/liens), parce que certes ça ferait des EPUB tout mignons, mais surtout petits voire un poil inutiles. Le lien EPUB est présent automatiquement sur chaque contenu (hormis les liens donc). Le principe est simple : on donne un lien vers un contenu HTML à _epub_, il le demande à la partie _Ruby on Rails_ du site, ainsi que les images associées, convertit le tout au format EPUB3 et le renvoie à la personne qui l’a demandé. Techniquement _epub_ n'est pas exposé frontalement mais se trouve derrière un _nginx_. # Côté code _Ruby on Rails_ C’est assez basique : on ajoute juste sur chaque contenu un lien pour télécharger au format EPUB. Ainsi, y compris sur cette dépêche, vous allez trouver un lien à la fin pour récupérer le tout au format EPUB (et un autre pour récupérer le source en Markdown mais c’est un autre sujet). ```ruby app/views/news/_news.atom.builder: epub = content_tag(:div, link_to("Télécharger ce contenu au format EPUB", "#{url}.epub")) app/views/polls/_poll.atom.builder: epub = content_tag(:div, link_to("Télécharger ce contenu au format EPUB", "#{url}.epub")) app/views/posts/_post.atom.builder: epub = content_tag(:div, link_to("Télécharger ce contenu au format EPUB", "#{url}.epub")) app/views/nodes/_actions.html.haml: = link_to "EPUB", "#{path_for_content node.content}.epub", title: "Télécharger ce contenu au format EPUB", class: "action download" app/views/diaries/_diary.atom.builder: epub = content_tag(:div, link_to("Télécharger ce contenu au format EPUB", "#{url}.epub")) app/views/wiki_pages/_wiki_page.atom.builder: epub = content_tag(:div, link_to("Télécharger ce contenu au format EPUB", "#{url}.epub")) ``` # Côté _epub_ Le service est plutôt simple, par rapport à _img_, car il n’a pas de dépendance sur _redis_ par exemple, et qu’il a, au final, peu de paramétrage (un couple adresse+port d’écoute, un fichier de trace et un hôte pour aller chercher les contenus). Il est possible de faire un _GET /status_ et on obtient une réponse HTTP 200 avec un contenu _OK_. C’est utile pour tester que le service est lancé (depuis l’intérieur de la plateforme). Sinon on lui demande une dépêche, un journal, une entrée de forum, un sondage, une entrée de suivi ou une page wiki en prenant le chemin sur LinuxFr.org et ajoutant un petit `.epub` à la fin, et il va renvoyer un fichier EPUB. Ou bien il va répondre un contenu non trouvé HTTP 404 s’il y a un souci. Et vu son fonctionnement, si on a un souci de HTML non valide ou si _img_ a un problème avec une image, alors derrière _epub_ pourrait avoir le même souci. _epub_ est un binaire dynamique en Go. Il impose le _https_ pour l’hôte (du coup on aura tous les liens en HTTPS en interne normalement). Il ne peut pas vraiment être compilé statiquement (on a besoin de _libxml2_, _libonig2_ et de la même version de la _libc_ au déploiement). Il ne gère pas les images _in-line_. Dans les logs on va trouver des infos comme : ``` 2024/11/03 16:34:02 Status code of http:/example.invalid/exemple.png is: 404 (…) 2024/11/03 16:38:23 Fetch https://linuxfr.org/news/capitole-du-libre-2024-au-programme-du-16-et-17-novembre 2024/11/03 16:38:24 Fetch https://linuxfr.org/users/liberf0rce/journaux/libreast-2006-is-out-of-order ``` # Historique _epub_ a été créé par [Bruno Michel](https://linuxfr.org/users/nono) en 2013 et Bruno est le seul à travailler dessus (48 commits) jusqu’en 2018. Comme _img_, on peut considérer que _epub_ a fait le job pendant ce temps-là, sans besoin de retouche. Mon premier commit de 2021 concerne la gestion d’un [cas de collision de nommages des images](https://linuxfr.org/suivi/telechargement-contenu-au-format-epub-pb-avec-la-gestion-des-images). En 2022, Bruno quitte l’équipe du site, et par ailleurs il y a des montées de versions et des migrations à faire sur les serveurs de LinuxFr.org, et _epub_ fait partie des services à reprendre en main. Ce qui veut dire le comprendre, le documenter et au besoin l’améliorer. Bref je décide de me plonger dans _epub_ (2022-2024), dans la foulée de _img_, car a priori ce n’est pas un composant compliqué du site (il vit dans son coin, il offre une interface, c’est du Go, donc on a un binaire seulement à gérer - _divulgâchage en fait non pas seulement_). Le choix est le même que pour _img_ (cf la dépêche précédente) : ajouter un Dockerfile permettant de recompiler _epub_ dans un conteneur, en contrôlant la version de _Go_ utilisée, en effectuant une détection d’éventuelles vulnérabilités au passage avec _govulncheck_. Cela me permet de valider que l’on sait produire le binaire d’une part, et que l’on offre à tout le monde la possibilité de contribuer facilement sur ce composant. Et de découvrir qu’une version statique n’est pas facilement envisageable. Puis je vais tester le composant pour vérifier qu’il fonctionne comme je le pense et qu’il fait ce qu’on attend de lui. Je vais ajouter une suite des tests qui couvrent les différentes fonctionnalités et les vérifient en IPv4 et en IPv6, en HTTP 1.1 et en HTTP 2.0. Les tests utilisent [_Hurl_](https://hurl.dev/) et _docker-compose_, et encore une fois l’idée de donner la possibilité de contribuer facilement. Ils comprennent des tests de types de contenus non pris en charge, le test de la limite à 5 MiB, différents types de contenus, le test de vie, des appels erronés (mauvais chemin, mauvaise méthode, etc). Et surtout de vérifier avec [epubcheck](https://www.w3.org/publishing/epubcheck/) que le fichier epub produit est correct. Le choix des cas de tests est basé sur le trafic réellement constaté sur le serveur de production, sur les différents cas dans le code et un peu sur l’expérience du testeur. Les différents travaux effectués vont permettre de détecter et corriger quelques soucis : - le besoin de pouvoir choisir l’hôte (histoire de ne pas toujours tester sur la production) - mieux gérer certaines erreurs comme les réponses inattendues - corriger les [zip sans extension unix](https://linuxfr.org/suivi/validation-des-epub), [le logo en couverture](https://linuxfr.org/suivi/non-validation-des-epub-et-erreur-sur-l-image-de-couverture) - documenter les cas pouvant être mieux gérer : [image trop grande, non récupérable, format inconnu, etc.](https://linuxfr.org/suivi/image-trop-grande-non-recuperable-format-inconnu-etc) en fournissant les tests en attendant la correction Et à la fin, j’écris une dépêche pour parler de tout cela. # Évolutions récentes ## Dockerfile Le fichier [Dockerfile](https://github.com/linuxfrorg/img-LinuxFr.org/blob/main/Dockerfile) du projet permet : - de partir d’une image officielle _Go_ d’une version donnée, basée sur une distribution _Debian_ (en raison des dépendances) - de l’utiliser pendant la construction en prenant la liste des dépendances de compilation, en les téléchargeant, en prenant l’unique fichier source _epub.go_ et en le compilant dynamiquement avec l’option pour retirer les chemins de compilation - de rechercher les éventuelles vulnérabilités avec _govulncheck_ - de tester avec [golangci/golangci-lint](https://github.com/golangci/golangci-lint) le code (fait à la construction de l’image, car on dispose de toutes les dépendances à ce moment-là) - de repartir d’une base _Debian_ en y mettant les autorités de certification, les dépendances de fonctionnement et le binaire issus de la partie construction, de déclarer le port d’écoute et de lancer le binaire avec des variables disposant de valeurs par défaut. ## La suite de tests Pour l’utiliser, c’est assez simple, il faut aller dans le répertoire _tests_ et lancer un _docker-compose up --build_, qui va produire le conteneur contenant _epub_, et démarrer le _nginx-cert_ qui fournit les certificats et le _nginx_ préconfiguré pour les tests. Si tout va bien, on attend, et au bout d’un moment il s’affiche : ``` linuxfr.org-epub-test_1 | All tests look good! tests_linuxfr.org-epub-test_1 exited with code 0 ``` Rentrons un peu dans les détails. D’abord un fichier [_docker-compose.yaml_](https://github.com/linuxfrorg/epub-LinuxFr.org/blob/main/tests/docker-compose.yaml) qui décrit le réseau IPv4/IPv6 utilisé pour les tests, l’image _nginx-cert_ qui sera utilisée pour créer une autorité de certification et un certificat serveur de test, l’image _nginx_ qui sera utilisée avec sa configuration et ses fichiers à servir pour les tests, l’image _epub_ et son paramétrage (dont l’accès au _nginx_) ainsi que le répertoire de l’autorité de certification de tests et enfin l’image de la suite de tests qui est construit avec son _Dockerfile_ et son répertoire de dépôt des fichiers EPUB. Le [_Dockerfile_](https://github.com/linuxfrorg/epub-LinuxFr.org/blob/main/tests/Dockerfile) de tests est basé sur une image _Hurl_ (un outil pour faire des tests HTTP). On ajoute les fichiers de tests en _.hurl_, le script shell qui pilote le tout, on prévoit d’avoir les paquets dont on aura besoin : _bash_ (pas par défaut dans les Alpine), _curl_, _openjdk17_ (pour _epubcheck_), _openssl_, _unzip_ (transitoirement), _bind-tools_ et _shellcheck_. On installe _epubcheck_. Et on lance les tests par défaut. La [configuration _nginx_ de test](https://github.com/linuxfrorg/epub-LinuxFr.org/blob/main/tests/nginx.conf) écoute en HTTP sur le port 80 en IPV4 et IPv6 et permet de définir des chemins avec des réponses en HTTP 301, 302, 308, 400, 401, 403, etc. jusqu’à 530 et même 666 pour les codes invalides, ainsi qu’une redirection infinie. Dans les [données de tests servies par _nginx_](https://github.com/linuxfrorg/epub-LinuxFr.org/tree/main/tests/data-web), on trouve des contenus du mauvais type, des contenus dans divers formats, une image très grande et des images qui ne seront pas accessibles. Sont aussi présents deux fichiers de tests avec une extension en _.hurl_ : - le test de vie et les chemins hors des contenus autorisés - les tests sur les contenus Vient enfin le [script shell qui pilote le tout](https://github.com/linuxfrorg/epub-LinuxFr.org/blob/main/tests/epub-tests.sh) : - on définit les variables pour les cibles IPv4/IPv6 que l’on veut utiliser dans les autres conteneurs _Docker_ - on purge le stockage des EPUB sur disque - on lance les premiers tests (en IPv4 et IPv6, en HTTP 1.1 et en HTTP 2.0) - sur chaque EPUB produit, on lance _epubcheck_ et on regarde si la validation donne le résultat attendu (succès ou échec) - si on est arrivé jusque-là on écrit que tout va bien et on déclenche un sourire de satisfaction. # Les problématiques restantes Il y a quelques entrées encore ouvertes dans le suivi : - [les images trop grandes (en octet), non récupérables, de format inconnu, etc.](https://linuxfr.org/suivi/image-trop-grande-non-recuperable-format-inconnu-etc) : la suite de tests actuelle « couvre » le cas des images de plus de 5 MiB ou non récupérables, avec des tests qui échouent, comme prévu, vu que c’est _img_ qui est censé faire le job de les éviter. Cependant il pourrait être sympa de remplacer toute image non disponible/invalide par une image de remplacement « Image indisponible » du bon Content-Type et du bon nom (vu qu’elle est déclarée dans le MANIFEST). - [les images trop grandes (en pixel)](https://linuxfr.org/suivi/taille-des-images-dans-les-fichiers-epub) : globalement on revient à la question des images que laisse passer _img_ - [les epub non fonctionnels en rédaction et modération](https://linuxfr.org/suivi/generation-epub-non-fonctionnelle-en-redaction-et-moderation) : pour des questions de droits, la génération EPUB ne marche pas dans les espaces de rédaction et de modération, à voir si on trouve un contournement ou si on évite de proposer le lien. Il y a la question habituelle de la montée de versions des dépendances (pour nous actuellement contraintes celles du code _Ruby on Rails_). Et des questions à se poser sur l’avenir de _nginx_ ?. Les dépendances pendant le fonctionnement amènent aussi leur lot de contraintes. # Conclusion ? Encore une fois, sans surprise et me répétant, _il reste des problématiques et du code à faire pour les gérer (c’est rare un composant sans demandes d’évolution ou de correction). Yapuka (mais probablement plus tard, il faut aussi partager le temps avec les autres composants, ou avoir plus de contributions)._ _epub_ rend la fonction que l’on attend de lui, même si on pourrait faire un peu mieux. Plonger dans ce composant s’est avéré assez intéressant et formateur (et nécessaire) : techniquement cela a été l’occasion de faire du _Go_, du _docker_ et du _docker-compose_, du _nginx_, du _hurl_, de l’HTTP et de gérer des problématiques statique/dynamique et des dépendances. Il s’agissait encore de _comprendre ce que faisait un code écrit par une autre personne, de se poser des questions pour choisir les tests et le contenu de la documentation, de se demander pour quelles raisons tel ou tel choix a été fait, de rendre ce composant plus « contribuable », et de compléter le tout de façon détaillée avec une dépêche._