1pxsolidblackhttps://1pxsolidblack.pl/2020-05-15T18:14:00+02:00capital FJARR v32020-05-15T18:14:00+02:002020-05-15T18:14:00+02:00François Schmidtstag:1pxsolidblack.pl,2020-05-15:/jarr-v3.html<p>Avant toute chose, voici le lien vers la nouvelle version de l’application : <a href="https://app.jarr.info">app.jarr.info</a></p>
<h4>Préambule</h4>
<p><span class="caps">JARR</span> est un aggrégateur et un lecteur de flux.
<span class="caps">JARR</span> signigie <em>Just Another <span class="caps">RSS</span> Reader</em> et je l’utilise et l’entretient depuis maintenant plusieurs années.</p>
<p>Après une v2 sortie silencieusement il y …</p><p>Avant toute chose, voici le lien vers la nouvelle version de l’application : <a href="https://app.jarr.info">app.jarr.info</a></p>
<h4>Préambule</h4>
<p><span class="caps">JARR</span> est un aggrégateur et un lecteur de flux.
<span class="caps">JARR</span> signigie <em>Just Another <span class="caps">RSS</span> Reader</em> et je l’utilise et l’entretient depuis maintenant plusieurs années.</p>
<p>Après une v2 sortie silencieusement il y a quelques années, cette fois ci je fais une vrai <em>release</em> pour marquer le coup.
Avant de m’étendre sur les nouveautés apportés par cette v3 je vais revenir un peu sur la version précédente. La v2 apportait très discrètement et seulement en opt-in ce que j’ai appelé les <em>clusters</em> : des groupements d’articles.</p>
<h2>Les clusters</h2>
<p>L’idée de base étant que <em>plusieurs flux peuvent référencer une même ressource</em>, j’ai implémenté à l’époque les models et l’interface pour représenter cela.
À chaque création d’article, <span class="caps">JARR</span> list d’autres articles plus ou moins récents de l’utilisateur et vérifiera s’ils ne pointent pas vers la même ressource. Si c’est le cas le nouvel article sera rajouté au regroupement d’un article existant, héritant de fait de son statut (lu / non lu, marqué comme favoris ou non).</p>
<p>Le but premier de la manœuvre étant de réduire un méta-flux (l’ensemble de tous les flux d’un utilisateur), dont le débit peut être important, en éliminant la redondance.
C’est particulièrement utile quand on souscrit à des <a href="https://fr.wikipedia.org/wiki/Planet">planet</a> ou autres aggrégateurs de flux (comme <a href="https://news.ycombinator.com/">Hacker News</a> ou sa contrepartie française <a href="https://www.journalduhacker.net/">le journal du hacker</a>) et même des <em>subreddit</em>.</p>
<p>Pour rajouter à ce groupement basic, uniquement basé sur les liens, un collègue m’a alors proposé d’aller plus loin et de permettre de regrouper des articles <em>parlant</em> de la même chose via <a href="https://fr.wikipedia.org/wiki/TF-IDF"><span class="caps">TF</span>-<span class="caps">IDF</span></a>.
Cela permt de réduire le méta flux créé par plusieurs journaux nationnaux, par exemple, qui traiteraient des mêmes nouvelles.</p>
<h4>Les clusters : retour d’expérience</h4>
<p>Tout cela était plutôt expérimental, je n’ai à l’époque mis qu’une seule option pour activer ou non le regroupement. En effet on s’aperçoit assez rapidement que :</p>
<ul>
<li>
<p>Certains flux renvoient toujours le même lien et mettent à jours la ressource au bout du lien (par exemple <a href="https://www.vigicrues.gouv.fr/rss/?CdEntVigiCru=IF12">vigicrues</a>).
Par conséquent l’intégralité du contenu de ce genre de flux sera regroupé en un seul article.
Il est donc nécessaire de pouvoir désactiver le groupement flux par flux.</p>
</li>
<li>
<p>Classer ses flux en catégorie permet, entre autre, de <em>marquer comme lu</em> (ignorer le contenu) de plusieurs flux à la fois.
Le regroupement d’article étant indépendant des catégories, il arrive que des articles d’autres catégories soient ignorés dans le processus.
Il est donc nécessaire de pouvoir désactiver le groupement pour toute une catégorie.
Il est aussi nécessaire de pouvoir marquer comme lu uniquement les articles qui ne font pas parti d’un groupement.</p>
</li>
<li>
<p>L’inverse est aussi vrai, le regroupement se faisant sur un article déjà lu, le groupement restera invisible car déjà lu.
Par défaut, si un article a été marqué comme lu sans être lu et qu’il est groupé avec un nouvel article, son status lu est changé à <em>non lu</em>.
Comme le reste ce comportement est désactivable flux par flux.</p>
</li>
<li>
<p>Le fonctionnement de l’époque était tout en <span class="caps">HTTP</span> synchrone. Le crawler envoyait une requête et le serveur web créait le nouvel article et faisait le groupement ce qui a plusieurs désavantages :</p>
<ul>
<li>
<p>Le groupement, surtout via <span class="caps">TF</span>-<span class="caps">IDF</span>, est un processus long (potentiellement trop) pour le contexte d’une requête web.</p>
</li>
<li>
<p>Par définition, plusieurs groupements peuvent être exécutés en parallèle ce qui laisse la possibilité que des articles qui, créés en même temps et qui auraient dû être regroupés ensemble ne le soient pas.</p>
</li>
</ul>
</li>
<li>
<p>L’introduction des groupements d’article a apporté son lot de complexité. La remontée la plus fréquente a été que la suppression d’un feed était devenu très longue.
La suppression a donc été rendu asynchrone et est faite par un processus d’arrière plan.</p>
</li>
</ul>
<h1>La v3 : ce qui est nouveau</h1>
<h5>Worker en arrière plan</h5>
<p>D’un point de vu très technique et backend, la nouvelle version de <span class="caps">JARR</span> tourne maintenant via <a href="https://hub.docker.com/repository/docker/jaesivsm/jarr-server">Docker</a>.
Trois pour être précis, <a href="https://hub.docker.com/repository/docker/jaesivsm/jarr-front">un</a> qui sert le Javascript pour l’interface utilisateur, <a href="https://hub.docker.com/repository/docker/jaesivsm/jarr-server">un</a> pour servir les données à cette interface utilisateur et <a href="https://hub.docker.com/repository/docker/jaesivsm/jarr-worker">un</a> worker d’arrière plan multi fonction.</p>
<p>Ce dernier lance un worker <a href="http://www.celeryproject.org/">Celery</a> qui écoute sur une base <a href="https://www.rabbitmq.com/">RabbitMQ</a>.</p>
<p>Son but principal est de rafraichir les flux selon plusieurs options de configuration (délai minimal et maximal de rafraîchissement entre autre).
Ensuite, pour chaque utilisateur, de créer les groupements pour tous les articles qui en sont dépourvus.
Enfin, il s’occupe de la suppression des flux marqués à supprimer. Pour rendre l’opération instantanée pour les utilisateurs, les flux à supprimer sont en effet simplement cachés en attendant que le worker passe pour faire le ménage.</p>
<h5>L’interface</h5>
<p>J’ai écrit la première interface de <span class="caps">JARR</span> sur React 0.14, le temps de m’occuper d’autre chose, react en était déjà à sa version 14. Autant dire que l’ancienne interface était irrécupérable.</p>
<p>J’ai donc entrepris de tout réécrire de zéro, avec cette fois à l’idée une interface compatible avec les smartphones.
Le front n’étant pas mon cœur de métier, je tiens à remercier <a href="https://github.com/clarissecarzon">Clarisse</a> sans qui l’interface ressemblerait toujours à du bootstrap de 2015.</p>
<p>Je tiens aussi à remercier un autre ancien collègue qui m’a apporté une <a href="https://github.com/jaesivsm/JARR/issues/127">code review</a> des plus instructives. Comme d’habitude il faut se pencher sur les détails mais j’ai eu révélation sur révélation en relisant mon code et en comparant avec les points d’amélioration).</p>
<h5>D’un manière générale</h5>
<p>Pour faire une liste plus exhaustive de ce qui a été amélioré :</p>
<p>Expérience utilisateur :</p>
<div>
<img src="https://1pxsolidblack.pl/img/jarr/jarr-v3.jpg"
alt="un aperçu de l'interface utilisateur de la version3"
style="width: 75%; margin: auto; display: block;" />
</div>
<ul>
<li>Meilleur interface pour l’ajout de flux <span class="caps">RSS</span>.
Comme pour la v2, <span class="caps">JARR</span> va tenter de construire un flux <span class="caps">RSS</span> à partir de n’importe quelle url (même si le protocol est manquant : <code>reddit.com/r/france</code>, ou même si la ressource n’est pas un flux <span class="caps">RSS</span> : <code>https://reddit.com/r/france/</code>).
À la différence de la v2, le flux n’est pas créé immédiatement mais un panneau avec le flux préconstruit est affiché de sorte que l’utilisateur puisse l’éditer avant de le créer.</li>
</ul>
<p><img src="https://1pxsolidblack.pl/img/jarr/jarr-v3-feed-building.png"
alt="Capture d'écran de l'interface de construction d'un flux"
style="width: 300px; margin: auto; display: block;" />
</div></p>
<ul>
<li>
<p>Modification de la suppression de flux : la suppression est maintenant instantanée et asynchrone</p>
</li>
<li>
<p>Option de contrôle du groupement d’article au niveau flux, catégories et utilisateur.
Il est désormais possible de choisir si les articles d’un flux, d’une catégorie (ou même tous les articles) peuvent être groupé.
Il est aussi possible de désactiver le groupement par <span class="caps">TFIDF</span> et le réveil (le marquage comme non lu lorqu’il est lu) d’un article par le processus de groupement.</p>
</li>
</ul>
<div>
<img src="https://1pxsolidblack.pl/img/jarr/jarr-v3-edit-cluster-option.jpg"
alt="Capture d'écran du contrôle des options de groupements d'article sur un utilisateur"
style="width: 300px; margin: auto; display: block;" />
</div>
<ul>
<li>Intégration sur mesure (pour l’instant seulement si la ressource pointent vers une image ou une vidéo youtube).
Si un type de contenu supporté est reconnu, l’interface de <span class="caps">JARR</span> créra une intégration sur mesure.</li>
</ul>
<div>
<img src="https://1pxsolidblack.pl/img/jarr/jarr-v3-img-processed-content.jpg"
alt="Capture d'écran de la présentation de contenu prérendu sur JARR"
style="width: 300px; margin: auto; display: block;" />
</div>
<ul>
<li>Interface <em>responsive</em> (le menu des flux est repliable et la listes des articles a deux versions : pour les écrans larges et étroits).</li>
</ul>
<div>
<img src="https://1pxsolidblack.pl/img/jarr/jarr-v3-narrow-view.jpg"
alt="Capture d'écran de JARR en vue étroite"
style="width: 300px; margin: auto; display: block;" />
</div>
<ul>
<li>Intégration limité avec <a href="https://github.com/RSS-Bridge/rss-bridge"><span class="caps">RSS</span>-Bridge</a> afin de fournir des flux <span class="caps">RSS</span> pour des site qui en sont dépourvus.
Sont supporté automatiquement pour l’instant Twitter, Instagram et Soundcloud.</li>
</ul>
<p><img src="https://1pxsolidblack.pl/img/jarr/jarr-v3-rss-bridge-integration.png"
alt="Capture d'écran d'un flux instagram via RSS-Bridge"
style="width: 300px; margin: auto; display: block;" />
</div></p>
<ul>
<li>Édition dans un panneau dédié des options des flux, catégories et de l’utilisateur</li>
</ul>
<div>
<img src="https://1pxsolidblack.pl/img/jarr/jarr-v3-edit-user.jpg"
alt="Capture d'écran de l'édition des options d'un utilisateur"
style="width: 350px; margin: auto; display: block;" />
</div>
<p>Côté server :</p>
<ul>
<li>
<p>Refonte totale de l’<span class="caps">API</span> via <a href="flask-restx.readthedocs.io/">Flask-restx</a></p>
</li>
<li>
<p><span class="caps">API</span> accessible via Swagger sur <a href="https://api.jarr.info/">api.jarr.info</a></p>
</li>
<li>
<p>Suppression de beaucoup de code mort</p>
</li>
<li>
<p>Support des flux Json</p>
</li>
<li>
<p>Refonte totale du <em>crawler</em>, plus facilement intégrable avec d’autres types de resources</p>
</li>
<li>
<p>Abandon de <a href="http://munin-monitoring.org/">munin</a> pour un plug <a href="https://prometheus.io/">prometheus</a></p>
</li>
</ul>
<p>Ce dernier point me permet entre autre de voir d’une façon globale, comment l’application gère le cache et les délais entre deux rafraîchissement d’un flux :</p>
<div>
<img src="https://1pxsolidblack.pl/img/jarr/last-fetched-histogram.jpg"
alt="l'histrogram des délai entre le moment d'un rafraîchissement et la dernière fois que le flux a été rafraîchi"
style="width: 75%; margin: auto; display: block;" />
</div>
<h2>À venir</h2>
<p>Bien entendu ce n’est pas fini et le développement continu. En priorité (pour la <a href="https://github.com/jaesivsm/JARR/milestone/10">v3.1</a>) j’implémenterai des fonctionnalités présentes dans la v2 mais absente de la v3 (par soucis de temps). Entre autre :</p>
<ul>
<li>
<p>L’import et l’export d’archive <span class="caps">OPML</span> (<a href="https://github.com/jaesivsm/JARR/issues/130">github</a>)</p>
</li>
<li>
<p>La recherche d’articles depuis le menu (<a href="https://github.com/jaesivsm/JARR/issues/129">github</a>)</p>
</li>
</ul>
<p>J’ai aussi quelques idées de fonctionnalités comme rendre drag-n-dropable les catégories et pouvoir les ordonner à la main.</p>
<p>Je suis bien entendu ouvert aux suggestions.
N’hésitez pas à commenter ou à ouvrir une <em>issue</em> sur le <a href="https://github.com/jaesivsm/JARR/issues">bug tracker</a> si vous rencontrez un problème ou souhaiteriez une nouvelle fonctionnalité.</p>MindYourNeighbors2017-02-28T23:59:00+02:002017-02-28T23:59:00+02:00François Schmidtstag:1pxsolidblack.pl,2017-02-28:/mindyourneighbors.html<p>J’ai <a href="https://github.com/jaesivsm/MindYourNeighbors/">écrit</a>, <a href="https://pypi.python.org/pypi/MindYourNeighbors/">packagé</a> et finis de <a href="https://travis-ci.org/jaesivsm/MindYourNeighbors">tester</a> <strong>MindYourNeighbors</strong>, un programme qui permet de déclencher des scripts en fonction de son voisinage réseau.</p>
<h3>Pourquoi ?</h3>
<p>Il y a quelques années de ça, j’utilisais <a href="https://transmissionbt.com/">transmission</a> sur une machine chez moi, qui tournait en permanence. Transmission possède un mode “lent” qui permet …</p><p>J’ai <a href="https://github.com/jaesivsm/MindYourNeighbors/">écrit</a>, <a href="https://pypi.python.org/pypi/MindYourNeighbors/">packagé</a> et finis de <a href="https://travis-ci.org/jaesivsm/MindYourNeighbors">tester</a> <strong>MindYourNeighbors</strong>, un programme qui permet de déclencher des scripts en fonction de son voisinage réseau.</p>
<h3>Pourquoi ?</h3>
<p>Il y a quelques années de ça, j’utilisais <a href="https://transmissionbt.com/">transmission</a> sur une machine chez moi, qui tournait en permanence. Transmission possède un mode “lent” qui permet d’économiser les ressources du réseau quand il est activé, et via un script et deux <a href="https://fr.wikipedia.org/wiki/Cron">cron</a>, je m’arrangeais pour que transmission soit lent le soir quand il y avait du monde à la maison, et normal le reste du temps.
Pour différentes raisons, il est devenu de plus en plus malaisé de prévoir les périodes creuses où transmission pourrait être en mode normal et il finissait par être en mode lent la plupart du temps.</p>
<h3>Et donc, pourquoi ?</h3>
<p>Donc, plutôt que d’utiliser cron, j’ai écrit un petit programme qui regarde la table des neighbors connus du kernel et décide si il peut, ou non, sortir transmission de son mode lent.
Puis j’ai simplement ouvert le principe à l’exécution de n’importe quel exécutable selon une configuration.</p>
<p>Après l’avoir laissé tourné dans une version assez peu présentable pendant plutôt longtemps sur mon serveur, j’ai finis par <a href="https://codeclimate.com/github/jaesivsm/MindYourNeighbors">nettoyer</a> un peu tout ça, rajouter des tests, en faire une archive sur <a href="https://pypi.python.org/pypi/MindYourNeighbors/">pypi</a> et intégrer tout ça joliement sur <a href="https://github.com/jaesivsm/MindYourNeighbors">github</a>.</p>
<h3>Comment ?</h3>
<p>Le principe de fonctionnement est assez trivial, le script lance la commande <code>ip neigh show</code> et ligne par ligne analyse la sortie. Si une ligne a le status <code>REACHABLE</code> ou <code>PERMANENT</code> elle est considéré comme un voisin et elle passe ensuite au travers des filtres qui pourrait l’exclure ou l’inclure (sachant que l’exclusion prime).</p>
<p>Les filtres sont définit dans un fichier de configuration dont toutes les sections héritent de la section par défaut.
Voici une version commenté de mon fichier de configuration :</p>
<div class="highlight"><pre><span></span><code><span class="k">[DEFAULT]</span>
<span class="na">threshold</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">4</span>
<span class="c1"># par défaut, seul mon_ordi sera considéré</span>
<span class="na">filter_on_machines</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">mon_ordi</span>
<span class="c1"># on peut aussi faire des filtres plus large</span>
<span class="c1"># filter_on_regex = .*192\.168\.0\..* # filtrera sur toutes les adresses IPv4 de classes B</span>
<span class="c1"># cette section sert juste à renseigner les machines par leurs</span>
<span class="c1"># adresses mac si c'est le mode de filtrage que vous choisissez</span>
<span class="k">[known_machines]</span>
<span class="na">mon_ordi</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s"><mac></span>
<span class="na">mon_tel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s"><mac></span>
<span class="k">[transmission]</span>
<span class="c1"># activer le mode lent quand il y a des voisins</span>
<span class="na">command_neighbor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/my/scripts/transmission-turtle true</span>
<span class="c1"># le désactiver quand il n'y en a pas</span>
<span class="na">command_no_neighbor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/my/scripts/transmission-turtle false</span>
<span class="c1"># capturer la sortie sur une erreur</span>
<span class="na">error_on_stderr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
<span class="k">[bitcoind]</span>
<span class="c1"># Vous pouvez désactiver une section entière sans avoir à l'effacer de la conf</span>
<span class="na">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">false</span>
<span class="na">command_neighbor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">killall bitcoind</span>
<span class="na">command_no_neighbor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">/opt/bitcoind -server -daemon</span>
<span class="c1"># pour les deux sections ci-dessus, les filtres sont hérités de la section par défaut</span>
<span class="k">[wake_computer]</span>
<span class="c1"># mais ici on surcharge la valeur par défaut pour ne filtrer que sur mon_tel</span>
<span class="na">filter_on_machines</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">mon_tel</span>
<span class="c1"># pareil, on veut que un réveil n'attendent pas 4 rotation alors on descends le seuil</span>
<span class="na">threshold</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">2</span>
<span class="c1"># on restreint les horraires d'exécution avec une syntaxe similaire à cron</span>
<span class="na">cron</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">* 18-23 * * 1-5</span>
<span class="na">command_neighbor</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">wake my machine</span>
</code></pre></div>
<p>Un script executable à la main <code>myn</code> exécuter avec les options <code>--output</code> et <code>--verbose</code> permet de facilement debugger sont fichier de configuration en comparant les correspondance obtenue avec celle désirée.</p>
<div class="highlight"><pre><span></span><code><span class="gp"># </span>myn<span class="w"> </span>-o<span class="w"> </span>-v
<span class="go">MindYourNeighbors: INFO - MindYourNeighbors initialized</span>
<span class="go">MindYourNeighbors: DEBUG - 'transmission' - processing section</span>
<span class="go">MindYourNeighbors: DEBUG - EXCLUDED - <mac> - MACHINE: une_machine</span>
<span class="go">MindYourNeighbors: DEBUG - MATCH - <mac> - MACHINE: mon_ordi</span>
<span class="go">MindYourNeighbors: DEBUG - NO_MATCH - <mac> - MACHINE: mon_tel</span>
<span class="go">MindYourNeighbors: DEBUG - cache/transmission/neighbor 2 => 2</span>
<span class="go">MindYourNeighbors: INFO - 'transmission' - cache state: {'results': ['neighbor', 'no_neighbor', 'no_neighbor', 'neighbor'], 'last_command': '/home/jaes/bin/transmission-turtle false'}</span>
<span class="go">MindYourNeighbors: INFO - 'transmission' - cache count hasn't reached threshold yet (2/4)</span>
<span class="go">MindYourNeighbors: DEBUG - section <Section: bitcoind> not enabled</span>
<span class="go">MindYourNeighbors: DEBUG - section <Section: wake_if> disabled for now</span>
</code></pre></div>
<h3>SystemD</h3>
<p>Il est possible de faire que systemd se charge de la <em>dæmonisation</em> de MindYourNeighbors. Pour ce faire il faut placer ce <a href="https://raw.githubusercontent.com/jaesivsm/MindYourNeighbors/master/data/mind-your-neighbors.service">fichier</a> dans <code>/etc/systemd/system/</code> et d’exécuter <code>systemctl daemon-reload</code> et <code>service mind-your-neighbors start</code>.</p>
<p>Malheureusement je n’ai pasa réussi à trouver comment packager de façon correct ce fichier avec l’archive <em>pypi</em>. En effet la mettre dans le <code>setup.py</code> dans l’option <code>data_files</code> résultera en une erreur si on tente une installation sans les droits <em>root</em>. (Oui c’est un appel à l’aide aux bonnes âmes qui me lisent !).</p>
<h3>À venir</h3>
<p>Quelques améliorations sont à venir comme le <a href="https://github.com/jaesivsm/MindYourNeighbors/issues/4">probing</a>, une option pour exécuter en <em>dry run</em> ou juste rajouter assez de test pour avoir une <a href="https://coveralls.io/github/jaesivsm/MindYourNeighbors?branch=master">couverture descente</a>.</p>
<p>Malgré tout je considère le programme comme assez stable pour être publié tel quel.</p>Récupération et expiration en HTTP1.12016-11-19T12:00:00+02:002016-11-19T12:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2016-11-19:/recuperation-et-expiration-en-http11.html<p>Quand je me suis attaqué à <span class="caps">JARR</span>, ma première motivation était d’écrire un crawler qui ne soit pas non seulement rapide mais qui respecte aussi tout un panel de bonnes pratiques et autres RFCs.</p>
<p>Le crawler doit en l’occurence :</p>
<ul>
<li>dans le but de limiter le traffic réseau et …</li></ul><p>Quand je me suis attaqué à <span class="caps">JARR</span>, ma première motivation était d’écrire un crawler qui ne soit pas non seulement rapide mais qui respecte aussi tout un panel de bonnes pratiques et autres RFCs.</p>
<p>Le crawler doit en l’occurence :</p>
<ul>
<li>dans le but de limiter le traffic réseau et soulager en temps <span class="caps">CPU</span> les serveurs distants : <strong>ne récupérer une resource que si elle a expiré</strong></li>
<li>dans le but de limiter la consomation de resources de mon crawler : <strong>vérifier qu’une resource a changé avant de la traiter</strong></li>
</ul>
<p>Exposé ainsi, je pense que ça peut s’appliquer à en fait n’importe quel crawler et pas seulement un qui récupère des flux <span class="caps">RSS</span>, et du coup plus largement n’importe quel client web.
Pour expliquer comment réaliser ces deux fonctionnalités je vais m’appuyer sur différents mécanismes de <span class="caps">HTTP</span> que presque tout le monde implémente plus ou moins bien. Comme base de travail je vais bien sûr utiliser la <a href="https://tools.ietf.org/html/rfc2616"><span class="caps">RFC2616</span></a> qui traitre de <span class="caps">HTTP1</span>.1.</p>
<h2>Expiration d’une ressource</h2>
<p>Il y bien sûr la possibilité de simplement récupérer une ressource en boucle à intervalle de temps régulier. Ça marche plutôt bien et ça reste un fonctionnement par défaut très utile quand une ressource ne dispose d’aucun mécanisme d’expiration.</p>
<p>Comme beaucoup de choses en <span class="caps">HTTP</span> on va passer par des entêtes.</p>
<h4>Cache-Control</h4>
<p>L’entête <a href="https://tools.ietf.org/html/rfc2616#section-14.9"><code>Cache-Control</code></a>, si il est placé dans la réponse, peut préciser différentes choses concernant le contrôle de cache sur un proxy ou par un client. La directive <a href="https://tools.ietf.org/html/rfc2616#section-14.9.3"><code>max-age</code></a>) est celle qui nous intéresse et sert à préciser le délais après laquelle une ressource est considérée comme périmée.</p>
<p>Par exemple le header suivant <code>Cache-Control: max-age=600</code> signifie que la resource expirera dix minutes après avoir reçu la requête (<a href="https://tools.ietf.org/html/rfc2616#section-14.9.3"><code>en gros</code></a>).</p>
<h4>Expires</h4>
<p>On peut aussi utiliser l’entête <a href="https://tools.ietf.org/html/rfc2616#section-14.21"><code>Expires</code></a>. Celui-ci, beaucoup plus simplement, précise directement la date à laquelle la ressource sera considéré comme périmée. La date doit être précisée dans le format spécifié par la <a href="https://tools.ietf.org/html/rfc1123"><span class="caps">RFC1123</span></a>.</p>
<p>Exemple assez explicite : <code>Expires: Sat, 19 Nov 2016 14:32:47 GMT</code>.</p>
<p>Les deux pouvant être présent en même temps la <span class="caps">RFC</span> précise à la fin de cette <a href="https://tools.ietf.org/html/rfc2616#section-14.21">section</a> que la directive <code>max-age</code> de l’entête <code>Cache-Control</code> doit prévaloir.</p>
<h5>Remarques</h5>
<p>Si la date d’expiration de la ressource est inférieure à votre délais de rafraichissement, ce la vous aidera à garder la ressources plus à jours. Si elle est supérieure, cela vous évitera de la récupérer probablement inutilement.</p>
<p>Mais ce qui est important c’est de ne pas oublier que ces valeurs ne sont que des recommandations du serveur. Il n’est pas rare par exemple de voir des valeurs pour <code>Expires</code> plusieurs années dans le passés ou le futur ! C’est parfois dû à la cohabitation de <code>max-age</code> et de <code>Expires</code> mais la conclusion est la même : le serveur peut mentir ou se tromper. Je suggère par conséquent de borner la valeur que vous allez utiliser en y appliquant une limite basse et haute.</p>
<p>Maintenant que vous avez la date d’expiration, il ne vous reste plus qu’à ne planifier la récupération qu’après cette date.</p>
<p>Ensuite, ce n’est pas parce qu’elle a expirée qu’elle a changé.</p>
<h2>Vérifier qu’une ressource a changée</h2>
<p>Pour ce faire on peut tout simplement comparer la réponse obtenue avec le résultat en cache. Mais cette comparaison implique souvent un coût (disque, réseau, base de donnée, etc) qu’on peut éviter avec, encore une fois l’usage de deux entêtes. Si le serveur reconnaît ces entêtes, il répondra avec un <a href="https://tools.ietf.org/html/rfc2616#section-10.3.5">code 304</a> qui en plus d’être vide (et donc d’économiser de la bande passante) indique qu’elle ne nécessite aucun traitement supplémentaire.</p>
<h4>Last-Modified / If-Modified-Since</h4>
<p>Vous pouvez placer l’entête <a href="https://tools.ietf.org/html/rfc2616#section-14.25"><code>If-Modified-Since</code></a> avec comme valeur la date (toujours au format de la <a href="https://tools.ietf.org/html/rfc1123"><span class="caps">RFC1123</span></a>) de la dernière fois que la ressource a été modifié.</p>
<p>Vous pouvez aussi tirer la valeur de n’importe quelle réponse du serveur contenant l’entête <a href="https://tools.ietf.org/html/rfc2616#section-14.29"><code>Last-Modified</code></a>.</p>
<h4>ETag / If-None-Match</h4>
<p>Autrement vous pouvez aussi récupérer la valeur de l’entête <a href="https://tools.ietf.org/html/rfc2616#section-14.19"><code>ETag</code></a> de n’importe quelle réponse qui le contient et le passer dans vos prochaines requêtes avec l’entête <a href="https://tools.ietf.org/html/rfc2616#section-14.26"><code>If-None-Match</code></a>.</p>
<h5>Remarques</h5>
<p>Il se peut qu’une réponse avec un code autre que <code>304</code> qui contienne un <code>ETag</code> correspondant à celui transmit dans la requête. Je n’ai pas trouvé de <span class="caps">RFC</span> spécifiant le comportement à adopter dans ce cas. Pour ce qui est de <a href="https://github.com/jaesivsm/JARR"><span class="caps">JARR</span></a> j’ai pour ma part décidé que cette réponse n’invalidait pas la ressource et ne procède donc à aucune action après cette réponse.</p>
<p>Si possible, il faut préciser <code>If-None-Match</code> et <code>If-Modified-Since</code>. Le serveur suivra différentes <a href="https://tools.ietf.org/html/rfc2616#section-13.3.4">règles</a> pour savoir comment traiter les deux entêtes.</p>JARR: Note de Production2016-10-21T00:00:00+02:002016-10-21T00:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2016-10-21:/2016-10-21-jarr-note-de-production.html<p>Si vous utilisez <a href="https://jarr.info"><span class="caps">JARR</span></a> régulièrement, et plus particulièrement depuis une semaine, vous avez peut-être remarqué quelques changements.</p>
<h2>Clusters (grappes)</h2>
<p>Est en test (puisque oui, la version de <span class="caps">JARR</span> en production est souvent celle de la branche <a href="https://github.com/jaesivsm/JARR/tree/develop">develop</a>) une première version d’une feature que je prépare depuis un moment: les …</p><p>Si vous utilisez <a href="https://jarr.info"><span class="caps">JARR</span></a> régulièrement, et plus particulièrement depuis une semaine, vous avez peut-être remarqué quelques changements.</p>
<h2>Clusters (grappes)</h2>
<p>Est en test (puisque oui, la version de <span class="caps">JARR</span> en production est souvent celle de la branche <a href="https://github.com/jaesivsm/JARR/tree/develop">develop</a>) une première version d’une feature que je prépare depuis un moment: les clusters. J’en ferais un article plus détaillé bientôt, mais le but est, pour l’instant, de grouper les articles de même sources, et bientôt les articles très similaires.</p>
<h2>Les aléas de la bêta</h2>
<p>Hier vous avez aussi pu remarquer quelques problèmes avec vos articles. Il s’avère qu’un vicieux bug qui survient lors de la suppression d’utilisateur (rarement donc) s’était caché dans le code de <span class="caps">JARR</span>.</p>
<p>Ayant détruit les associations articles/clusters j’ai du les reconstruire dans la nuit. Ce fut un peut douloureux pour la base mais tout est maintenant rentré dans l’ordre.</p>
<p>Mais c’était pas sans une petite montée en charge :</p>
<p style="text-align: center; width: 100%; display: block;">
<img src="https://1pxsolidblack.pl/img/jarr/postgres_cache_ALL-day.png" />
<img src="https://1pxsolidblack.pl/img/jarr/postgres_locks_ALL-day.png" />
</p>Mixed Contents2016-07-23T23:00:00+02:002016-07-23T23:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2016-07-23:/mixed-contents.html<p>Dans la série des petits détails méconnus qui vous prennent la tête un moment, avant de se résoudre en une vingtaine de lignes de code ; j’ai envie de parler aujourd’hui des <em>Mixed Content</em>.</p>
<p>Je me suis aperçu que pas mal d’articles étaient mals affichés quand je les …</p><p>Dans la série des petits détails méconnus qui vous prennent la tête un moment, avant de se résoudre en une vingtaine de lignes de code ; j’ai envie de parler aujourd’hui des <em>Mixed Content</em>.</p>
<p>Je me suis aperçu que pas mal d’articles étaient mals affichés quand je les lisais dans <a href="https://jarr.info"><span class="caps">JARR</span></a> (mon agrégateur <span class="caps">RSS</span> pour ceux qui <a href="https://linuxfr.org/users/jaes/journaux/jarr-v1">ne suivrait pas</a>). La solution facile était généralement de se rendre directement sur le site qui héberge l’article en question mais c’est un peu dommage d’en arriver là quand le flux Atom/<span class="caps">RSS</span> est complet et contient toutes les données nécessaires à la lecture de l’article.</p>
<p>Bref, petite enquête et je m’aperçois que dans la console de mon butineur préféré j’ai ça :</p>
<p><img src="https://1pxsolidblack.pl/img/mixed-content/content-blocked.png" style="margin: auto; display: block;" alt="du contenu bloqué (en rouge) car non sécure" /></p>
<p>Petite explication pour ceux qui, comme moi, ignoreraient ce qu’est un <em>mixed content</em> : on parle de <em>mixed content</em> quand une page page sécurisé (comprendre en <span class="caps">HTTPS</span>) sert du contenu non sécurisé (en <span class="caps">HTTP</span> par exemple).
Mozilla explique comment c’est géré dans firefox <a href="https://support.mozilla.org/en-US/kb/mixed-content-blocking-firefox">ici</a>.</p>
<p>Ceci étant, je suis aussi tombé sur du contenu non sécurisé malgré tout affiché par mon navigateur depuis une page sécurisée. Seul un warning était affiché en console mais rien n’était bloqué :</p>
<p><img src="https://1pxsolidblack.pl/img/mixed-content/content-warning.png" style="margin: auto; display: block;" /></p>
<p>C’est là que réside la subtilité, il y a deux type de <em>mixed content</em> et (encore une fois) Mozilla explique les différences et comment elles sont gérées <a href="https://developer.mozilla.org/fr/docs/S%C3%A9curit%C3%A9/MixedContent">ici</a>.
Pour faire court, il y a le contenu <em>passif</em> et le contenu <em>actif</em>. Seul le contenu actif est censé être bloqué. Les <code>img</code> sont catégorisés comme <em>passif</em> et sont censé ne pas être bloqués, même si servit en <span class="caps">HTTP</span> dans une page en <span class="caps">HTTPS</span>. Alors pourquoi <strong>certains</strong> <code>img</code> sont bloquées ?</p>
<p>C’est là qu’il y a une astuce ! Ces <code>img</code> étaient affublés d’un attribut <code>srcset</code> (<a href="http://www.alsacreations.com/article/lire/1621-responsive-images-srcset.html">que j’ai découvert à cette occasion</a>). Cet attribut fait passer les balises <code>img</code> de <em>passive</em> à <em>active</em> et ces dernières se retrouvent donc bloquées.</p>
<p>Pour le besoin de <span class="caps">JARR</span> donc, les images des articles à afficher sont purgés de toute mention de <code>srcset</code>. Voilà qui devrait clore quelques <a href="https://github.com/jaesivsm/JARR/issues/45">petits problème d’utilisabilité</a>.</p>L’impact de la sortie de Jarr2016-04-16T12:00:00+02:002016-04-16T12:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2016-04-16:/jarr-impact-sortie.html<p>J’ai écrit <a href="https://linuxfr.org/users/jaes/journaux/jarr-v1">trois quatre mots</a> sur le fait que <a href="https://github.com/jaesivsm/JARR"><span class="caps">JARR</span></a> est passé dans une version propre et stable. C’est la première release publique dirons-nous, et même si c’est sur une niche (linuxfr n’est pas franchement un média de masse), j’ai quand même eu pas mal …</p><p>J’ai écrit <a href="https://linuxfr.org/users/jaes/journaux/jarr-v1">trois quatre mots</a> sur le fait que <a href="https://github.com/jaesivsm/JARR"><span class="caps">JARR</span></a> est passé dans une version propre et stable. C’est la première release publique dirons-nous, et même si c’est sur une niche (linuxfr n’est pas franchement un média de masse), j’ai quand même eu pas mal de retour intéressants (j’ai mis tout ça sur <a href="https://github.com/jaesivsm/JARR/milestones/1.0.0%20-%20Lisboa">github</a> histoire de retravailler sur tout ça plus tard).</p>
<h2>Inscription</h2>
<p>À l’heure où j’écris ces lignes 39 nouveaux utilisateurs se sont inscrit sur <a href="https://jarr.info/"><span class="caps">JARR</span></a>. La plupart sont des comptes de tests avec seulement un ou deux flux, mais certains on quand même ajouter dans les 20 ou 30 flux.</p>
<p><img src="https://1pxsolidblack.pl/img/jarr/jarr-feeds-release-day.png" style="margin: auto; display: block;" /></p>
<p>Comme vous pouvez le voir ci-dessus, à peu près 100 feeds ont été ajouté dans les heures qui ont suivies (avec un pic à 200 mais l’utilisateur a sûrement dû détruire son compte après son test).</p>
<p>Ce qu’on peu voir sur ce graph, c’est que les retards, c’est à dire les flux qui n’avait pas été mis à jours dans l’heure, ont fait un pic au moment de l’ajout des flux. Pic qui a eux quelques échos dans les heures suivantes. Ces échos ont finit par se tasser naturellement car, le crawler ne prends qu’un nombre limité de flux à rafraîchir et va donc, par construction, répartir la charge dans le temps.</p>
<p>À noter que même si les flux vont finir par se répartir dans le temps, la répartition ne sera jamais égale par la seule action du crawler (une évolution à venir ?). Pour remédier à ça, j’ai écrit un petit <a href="https://github.com/jaesivsm/JARR/blob/master/src/manager.py#L46">utilitaire</a> qui répartira l’intégralité des flux.</p>
<h2>Stress sur la base</h2>
<p style="text-align: center; width: 100%; display: block;">
<img src="https://1pxsolidblack.pl/img/jarr/postgres_scans_release-day.png" />
<img src="https://1pxsolidblack.pl/img/jarr/postgres_size_release-day.png" />
</p>
<p>Bon, postgres a vaguement pris en poids au passage, mais ce n’est rien par rapport à la masse qu’il avait déjà (20M sur 400, moins de 5%) et même si le nombre de requête a explosé aux deux principaux imports de flux (j’imagine lors de l’import de fichiers <span class="caps">OPML</span>), tout est très vite revenu à la normal sans autres impact.</p>
<p>On peut observer que le nombre de flux a fait un cours bon à 600. C’est lors de ce pic que corresponds la prise de 10Mo de poids par la base, Ces flux ayant disparus, la base aurait du maigrir d’autant mais ce n’est pas le cas, je soupçonne une mauvais configuration qui empêcher le <span class="caps">VACUM</span> d’être éxécuté.</p>
<h2>Stress sur la machine</h2>
<p>Et pour finir voyons comment mon serveur s’est comporté :</p>
<p><img src="https://1pxsolidblack.pl/img/jarr/load-release-day.png" style="margin: auto; display: block;" /></p>
<p>Mon server s’en fout ; il n’y a aucun impact sur la charge de la machine ni par le crawling ni par l’ajout massif d’articles.</p>
<p>Bon en même temps mon Xeon à 3.1GHz qui court à côté de s’est 4Go de <span class="caps">RAM</span> m’aurait fait bien de la peine à trimer pour si peu.</p>
<h2>Conclusion</h2>
<p>Certes, c’était un mini stress test sans grands enjeux mais je suis content de voir que <span class="caps">JARR</span> a bien tenu la charge.</p>
<p>Côté crawler, ça semble suffisament optimisé pour la tâche et le fait qu’il soit et multiprocess et multithreadé n’y est sûrement pas pour rien. Je ne pense pas avoir grand chose à améliorer de ce côté là.</p>
<p>Côté server, Postgres est largement suffisant pour la tâche (ce qui n’aurait probablement pas été le cas si j’étais resté sur un sqlite qui luttait déjà avec mon compte et ses 400 flux…). La machine est surdimenssionné aussi pour l’enjeux.</p>
<p>Côté applicatif, je ne sais pas. Je n’ai pas mesuré grand chose, mais vu l’impact sur le reste, je dirais que ça va. Le bench de ce côté là sera sûrement à prévoir pour une prochaine release ou une communication sur le projet via un média peut être plus important.</p>Pélican2016-01-16T21:00:00+02:002016-01-16T21:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2016-01-16:/pelican.html<p>Plus pour réellement m’amuser que par réelle envie de maintenir ce blog à jour, j’ai changé de moteur de blog. Inspiré par d’autres pages personnelles d’<a class="reference external" href="http://kirellbenzi.com/blog/the-infamous-first-blog-post/">autres développeurs</a>, je suis passé à <a class="reference external" href="https://github.com/getpelican/pelican/">pelican</a>.</p>
<p>Par rapport à <a class="reference external" href="https://github.com/lepture/liquidluck">liquidluck</a> que j’utilisais avant il y a quelque changements notables …</p><p>Plus pour réellement m’amuser que par réelle envie de maintenir ce blog à jour, j’ai changé de moteur de blog. Inspiré par d’autres pages personnelles d’<a class="reference external" href="http://kirellbenzi.com/blog/the-infamous-first-blog-post/">autres développeurs</a>, je suis passé à <a class="reference external" href="https://github.com/getpelican/pelican/">pelican</a>.</p>
<p>Par rapport à <a class="reference external" href="https://github.com/lepture/liquidluck">liquidluck</a> que j’utilisais avant il y a quelque changements notables (même si ça ne casse pas trois pattes à un canard, on reste dans le générateur de blog statique qui mange du markdown / restructured pour pondre du html).</p>
<p>Parmi les points à l’avantage de liquidluck, de mémoire (parce que je l’ai installé il y a un moment maintenant) ce dernier était plus facile à installer et son côté “brut de décoffrage” le rendait plus facile à hacker.</p>
<p>Bon par contre son intégration n’était vraiment pas aussi souple. Pélican offre tout un tas d’intégration, la possibilité de déployer via <em>ssh</em>, une configuration bien plus complète. Bref, le choix est sans équivoque !</p>
Jessie, g_slice_set_config et gdm32015-02-05T12:00:00+02:002016-01-17T11:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2015-02-05:/console-kit.html<p>Après avoir mis à jours ma debian au boulot de <em>wheezy</em> à <em>jessie</em> impossible de lancer gdm3. Après quelques errement j’ai découvert que mon <tt class="docutils literal">/var/log/syslog</tt> était rempli de lignes de ce genre :</p>
<div class="highlight"><pre><span></span>Feb<span class="w"> </span><span class="m">5</span><span class="w"> </span><span class="m">14</span>:32:14<span class="w"> </span>evoli<span class="w"> </span>console-kit-daemon<span class="o">[</span><span class="m">4627</span><span class="o">]</span>:<span class="w"> </span><span class="o">(</span>process:4690<span class="o">)</span>:<span class="w"> </span>GLib-CRITICAL<span class="w"> </span>**:<span class="w"> </span>g_slice_set_config:<span class="w"> </span>assertion<span class="w"> </span><span class="s1">'sys_page_size == 0 …</span></pre></div><p>Après avoir mis à jours ma debian au boulot de <em>wheezy</em> à <em>jessie</em> impossible de lancer gdm3. Après quelques errement j’ai découvert que mon <tt class="docutils literal">/var/log/syslog</tt> était rempli de lignes de ce genre :</p>
<div class="highlight"><pre><span></span>Feb<span class="w"> </span><span class="m">5</span><span class="w"> </span><span class="m">14</span>:32:14<span class="w"> </span>evoli<span class="w"> </span>console-kit-daemon<span class="o">[</span><span class="m">4627</span><span class="o">]</span>:<span class="w"> </span><span class="o">(</span>process:4690<span class="o">)</span>:<span class="w"> </span>GLib-CRITICAL<span class="w"> </span>**:<span class="w"> </span>g_slice_set_config:<span class="w"> </span>assertion<span class="w"> </span><span class="s1">'sys_page_size == 0'</span><span class="w"> </span>failed
</pre></div>
<p>Une recherche google d’une partie de cette ligne donne surtout comme conseil de supprimer son <tt class="docutils literal"><span class="pre">~/.profile</span></tt> ce qui n’aide en rien. Une autre, plus approfondie, avec comme paramètre que ma carte graphique est une <em><span class="caps">GT610</span></em> et a donc besoin d’un pilote nvidia, ne donne comme conseil que de <tt class="docutils literal"><span class="pre">apt-get</span> remove <span class="pre">--purge</span></tt> tous les paquets liés de plus ou moins loin à nvidia.</p>
<p>Je le marque ici afin de peut être aidé une âme perdu qui parcourerait les tréfons du net à la recherche d’une solution :</p>
<div class="highlight"><pre><span></span>aptitude<span class="w"> </span>purge<span class="w"> </span>--purge<span class="w"> </span>consolekit
aptitude<span class="w"> </span>install<span class="w"> </span>consolekit
</pre></div>
<p>En effet, c’est ce petit malin qui est à l’origine du fail de gdm3.</p>
<p>Si il doit y avoir une leçon à tirer de cette histoire c’est bien qu’il ne sert à rien de trop chercher sur le net quand on peut lire le nom du fautif directement dans le message de log. Oui, <tt class="docutils literal"><span class="pre">console-kit-daemon</span></tt>, c’est à toi que je parle.</p>
<div class="section" id="mise-a-jours">
<h2>Mise à jours</h2>
<p>La <em>traceback</em> survient toujours sur mon server et c’est plutôt disgracieux, aussi j’ai trouvé ce <a class="reference external" href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=718843">rapport de bug</a> qui stipule :</p>
<blockquote>
<p>ConsoleKit only manages console logins in graphical mode, so it’s
useless on a debian 8 based headless server[1]. The way to remove and
stop console kit:</p>
<p>$ sudo apt-get remove consolekit</p>
</blockquote>
<p>On ne me le demandera pas deux fois.</p>
</div>
Disqus !2014-09-10T15:00:00+02:002014-09-10T15:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2014-09-10:/disqus.html<p>J’utilise <a class="reference external" href="https://github.com/lepture/liquidluck">liquidluck</a> pour générer le rendu de ce blog et ça marche bien pour le peu que j’utilise. Bon, la prise en main n’a pas forcément été des plus facile, mais dans le genre c’est quand même dans les moteurs de blog les plus sympa que …</p><p>J’utilise <a class="reference external" href="https://github.com/lepture/liquidluck">liquidluck</a> pour générer le rendu de ce blog et ça marche bien pour le peu que j’utilise. Bon, la prise en main n’a pas forcément été des plus facile, mais dans le genre c’est quand même dans les moteurs de blog les plus sympa que j’ai trouvé.
Pour le principe : ça marche avec python (indispensable !), ça génère du contenu statique et… et c’est tout.</p>
<p>En tout cas c’est ce que je pensais ! J’ai découvert, à base de lecture de fichier de configuration et de <tt class="docutils literal">grep</tt> au travers du code source, que le moteur de blog supportait l’intégration du moteur de commentaire <a class="reference external" href="http://disqus.com/">Disqus</a>. J’avais vaguement entendu parlé du service auparavant mais c’était le moment de tester.</p>
<p>Le principe est simple : on créé son compte, on intégre leur bout de code et ça roule tout seul. Après avoir jouer peu avec leur code et être vite arrivé à la conclusion qu’il n’y avait pas grand chose à tripoter je vais juste laisser cette histoire tourner par elle même.</p>
<p>J’ai reçu plusieurs mails concernant mon <a class="reference external" href="https://github.com/jaesivsm/ing_chart">appli web pour les graphs <span class="caps">ING</span></a> et je me suis dit que ces mails auraient pu profiter à <a class="reference external" href="https://1pxsolidblack.pl/ing-chart.html">mon post</a> en parlant, au moins sous forme de commentaire. C’était ma principale et unique motivation pour trifouiller <tt class="docutils literal">liquidluck</tt> en fin de compte…</p>
<p>Bref maintenant ça marche et personne n’écrira jamais rien là dedans ! Joie.</p>
ING Chart : Visualiser ses extraits de comptes2014-06-30T12:00:00+02:002014-06-30T12:00:00+02:00François Schmidtstag:1pxsolidblack.pl,2014-06-30:/ing-chart.html<p>Il y a quelque semaines de ça, j’en ai finalement eu marre que les différents graphiques de suivis de budget du site web de ma banque soient cassés.</p>
<p>Sachant que ça faisait déjà près d’un an (<a class="reference external" href="https://communaute.ingdirect.fr/t5/Sur-le-Web/Compte-courant-Fonctions-de-suivi-budget-et-d-export-HS-depuis/td-p/1395">cf le post sur le forum de support</a>) que rien n’était …</p><p>Il y a quelque semaines de ça, j’en ai finalement eu marre que les différents graphiques de suivis de budget du site web de ma banque soient cassés.</p>
<p>Sachant que ça faisait déjà près d’un an (<a class="reference external" href="https://communaute.ingdirect.fr/t5/Sur-le-Web/Compte-courant-Fonctions-de-suivi-budget-et-d-export-HS-depuis/td-p/1395">cf le post sur le forum de support</a>) que rien n’était fait à ce sujet j’ai décidé de coder moi même une petite appli web qui reproduirait les mêmes graphiques. Je me suis servi pour ça de trois bouts de <em>JavaScript</em> collé à la glue de chez <a class="reference external" href="http://jquery.com/">jquery</a> et de la bibliothèque <a class="reference external" href="http://chartjs.org/">Chart.js</a>.</p>
<p>L’appli fonctionne bien et est globalement plus esthétique / ergonomique que les graphs originaux. Pour fonctionner elle ne requiert que les historiques des mouvements de compte qui sont téléchargeables au format <em><span class="caps">CSV</span></em> depuis le site d’<span class="caps">ING</span>.</p>
<p>Le graph est utilisable à partir d’<a class="reference external" href="/ing_chart/">ici</a> et je me fends d’un petit aperçu du rendu :</p>
<a class="reference external image-reference" href="https://1pxsolidblack.pl/img/ing_chart.lines.png"><img alt="Lines chart example" class="align-center" src="https://1pxsolidblack.pl/img/ing_chart.lines.png" style="width: 30%;" /></a>
<p>J’ai aussi implémenté un mode camembert (<em>piechart</em> quoi) parce que j’aurais eu tort de me priver :</p>
<a class="reference external image-reference" href="https://1pxsolidblack.pl/img/ing_chart.pie.png"><img alt="Pie chart example" class="align-center" src="https://1pxsolidblack.pl/img/ing_chart.pie.png" style="width: 30%;" /></a>
<p>Bon. Ce qui est dommage, c’est qu’à l’heure où j’écris ces lignes, un correctif vient tout juste d’être plublié et les graphs fonctionnent de nouveau sur le site d’<span class="caps">ING</span> Direct…</p>
<p>Malgré tout, je trouve mon implémentation bien meilleurs ! Plus flexible, plus jolie, plus lisible, si en plus on compte sur mon rêve simplet de postérité, ça fait pas mal de raisons de laisser ma bidouille en ligne.</p>
<p>Bien entendu, tout ceci est open source et le code est disponible sur <a class="reference external" href="https://github.com/jaesivsm/ing_chart/">github</a>.</p>
<style type='text/css'>
.reference.external.image-reference {
text-align: center; width: 100%; display: block;}
</style>Servir et gérer des fichiers avec WebDav2013-12-09T21:20:00+02:002013-12-09T21:20:00+02:00François Schmidtstag:1pxsolidblack.pl,2013-12-09:/apache-webdav-file-server.html<p>J’ai récement eu besoin de pouvoir stocker des fichiers en ligne. Je voulais un minimum d’authentification, quelque chose qui soit standard, pas de client lourd etc, etc. Bref, après m’être renseigné à minima j’ai décidé de tenter ma chance avec <a href="https://httpd.apache.org/docs/2.4/fr/mod/mod_dav.html">WebDav</a> en l’occurence puisque je …</p><p>J’ai récement eu besoin de pouvoir stocker des fichiers en ligne. Je voulais un minimum d’authentification, quelque chose qui soit standard, pas de client lourd etc, etc. Bref, après m’être renseigné à minima j’ai décidé de tenter ma chance avec <a href="https://httpd.apache.org/docs/2.4/fr/mod/mod_dav.html">WebDav</a> en l’occurence puisque je fais tourner ça avec apache).</p>
<p>Les caractéristiques voulues :</p>
<ul>
<li>accès en <span class="caps">HTTP</span> et HTTPs</li>
<li>un dossier <code>/public/</code> accessible en lecture par tous (<span class="caps">HTTP</span> et HTTPs)</li>
<li>redirection de <span class="caps">HTTP</span> vers HTTPs sinon</li>
<li>authentification via <em>BasicAuth</em></li>
<li>tous les utilisateurs peuvent écrire dans <code>/public/</code></li>
<li>chaque utilisateurs ne peut écrire sur dans l’<span class="caps">URI</span> <code>/<utilisateur>/</code></li>
</ul>
<p>Maintenant la conf et les explications :</p>
<ul>
<li>La conf du <em>VirtualHost</em> pour l’accès en <span class="caps">HTTP</span> :</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nt"><VirtualHost</span><span class="w"> </span><span class="s">*:80</span><span class="nt">></span>
<span class="w"> </span><span class="nb">ServerName</span><span class="w"> </span>my.server.tld
<span class="w"> </span><span class="nb">DocumentRoot</span><span class="w"> </span>/$document_root/
<span class="w"> </span><span class="nt"><Directory</span><span class="w"> </span><span class="s">/$document_root/public/</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Options</span><span class="w"> </span>-Indexes
<span class="w"> </span><span class="nt"></Directory></span>
<span class="w"> </span><span class="nt"><Directory</span><span class="w"> </span><span class="s">/$document_root/public/*/</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Options</span><span class="w"> </span>+Indexes
<span class="w"> </span><span class="nt"></Directory></span>
<span class="w"> </span><span class="nb">RewriteEngine</span><span class="w"> </span><span class="k">On</span>
<span class="w"> </span><span class="c"># Cette RewriteCond vérifie que l'URI de la requête concerne le notre</span>
<span class="w"> </span><span class="c"># dossier /public/ (ou les images qui servent à l'affichage des indexes</span>
<span class="w"> </span><span class="c"># chez apache). La RewriteCond ne s'applique que pour la RewriteRule</span>
<span class="w"> </span><span class="c"># suivante.</span>
<span class="w"> </span><span class="nb">RewriteCond</span><span class="w"> </span>%{REQUEST_URI}<span class="w"> </span>^/(public/(?|.*)|icons/(?|.*)|favicon\.ico|robots\.txt|$)$
<span class="w"> </span><span class="nb">RewriteRule</span><span class="w"> </span>^.*<span class="w"> </span>-<span class="w"> </span>[L]
<span class="w"> </span><span class="c"># Cette dernière RewriteRule s'assure que tous ce qui n'a pas matché</span>
<span class="w"> </span><span class="c"># plus haut est redirigé vers le même domaine en HTTPs</span>
<span class="w"> </span><span class="nb">RewriteRule</span><span class="w"> </span>^/?(.*)<span class="w"> </span>https://%{HTTP_HOST}/$1<span class="w"> </span>[QSA,L,R=301]
<span class="w"> </span><span class="nb">ErrorLog</span><span class="w"> </span>${APACHE_LOG_DIR}/error.log
<span class="w"> </span><span class="nb">CustomLog</span><span class="w"> </span>${APACHE_LOG_DIR}/access.log<span class="w"> </span>vhost_combined
<span class="w"> </span><span class="nb">LogLevel</span><span class="w"> </span><span class="k">warn</span>
<span class="nt"></VirtualHost></span>
</code></pre></div>
<ul>
<li>La conf du <em>VirtualHost</em> pour l’accès en HTTPs :</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nt"><VirtualHost</span><span class="w"> </span><span class="s">*:443</span><span class="nt">></span>
<span class="w"> </span><span class="nb">ServerName</span><span class="w"> </span>my.server.tld
<span class="w"> </span><span class="nb">SSLEngine</span><span class="w"> </span><span class="k">on</span>
<span class="w"> </span><span class="nb">DocumentRoot</span><span class="w"> </span>/$document_root/
<span class="w"> </span><span class="nb">DavLockDB</span><span class="w"> </span><span class="sx">/run/lock/apache_dav</span>
<span class="w"> </span><span class="nb">DAVMinTimeout</span><span class="w"> </span><span class="m">600</span>
<span class="w"> </span><span class="c"># On empêche les robots des snifer le roots où il y a</span>
<span class="w"> </span><span class="c"># la liste des users (leurs dossiers perso en fait)</span>
<span class="w"> </span><span class="nt"><Directory</span><span class="w"> </span><span class="s">/$document_root/</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Options</span><span class="w"> </span>-Indexes
<span class="w"> </span><span class="nb">Dav</span><span class="w"> </span><span class="k">On</span>
<span class="w"> </span><span class="nt"></Directory></span>
<span class="w"> </span><span class="c"># On définit qu'une authentification est nécessaire pour</span>
<span class="w"> </span><span class="c"># accéder à n'importe quel dossier.</span>
<span class="w"> </span><span class="nt"><Directory</span><span class="w"> </span><span class="s">/$document_root/*/</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Options</span><span class="w"> </span>+Indexes<span class="w"> </span>+FollowSymLinks<span class="w"> </span>+MultiViews
<span class="w"> </span><span class="nb">AllowOverride</span><span class="w"> </span><span class="k">None</span>
<span class="w"> </span><span class="nb">Order</span><span class="w"> </span>allow,deny
<span class="w"> </span><span class="nb">Allow</span><span class="w"> </span>from<span class="w"> </span><span class="k">all</span>
<span class="w"> </span><span class="nb">Dav</span><span class="w"> </span><span class="k">On</span>
<span class="w"> </span><span class="nb">AuthType</span><span class="w"> </span>Basic
<span class="w"> </span><span class="nb">AuthName</span><span class="w"> </span><span class="s2">"My Server WebDav"</span>
<span class="w"> </span><span class="nb">AuthUserFile</span><span class="w"> </span><span class="sx">/path/to/htpasswd</span>
<span class="w"> </span><span class="nb">Require</span><span class="w"> </span>valid-user
<span class="w"> </span><span class="nt"></Directory></span>
<span class="w"> </span><span class="c"># On est pas trop méchant et on laisse</span>
<span class="w"> </span><span class="c"># quand même les robots accéder à leurs conf</span>
<span class="w"> </span><span class="nt"><Files</span><span class="w"> </span><span class="s">/$document_root/robots.txt</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Order</span><span class="w"> </span>Allow,Deny
<span class="w"> </span><span class="nb">Allow</span><span class="w"> </span>from<span class="w"> </span><span class="k">all</span>
<span class="w"> </span><span class="nb">Satisfy</span><span class="w"> </span><span class="k">any</span>
<span class="w"> </span><span class="nb">Require</span><span class="w"> </span><span class="k">all</span><span class="w"> </span>granted
<span class="w"> </span><span class="nt"></Files></span>
<span class="w"> </span><span class="c"># On surcharge la précédente définition pour le dossier /public/.</span>
<span class="w"> </span><span class="nt"><Directory</span><span class="w"> </span><span class="s">/$document_root/public/</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Options</span><span class="w"> </span>-Indexes<span class="w"> </span>+FollowSymLinks<span class="w"> </span>+MultiViews
<span class="w"> </span><span class="nb">AllowOverride</span><span class="w"> </span><span class="k">None</span>
<span class="w"> </span><span class="nb">Order</span><span class="w"> </span>Allow,Deny
<span class="w"> </span><span class="nb">Allow</span><span class="w"> </span>from<span class="w"> </span><span class="k">all</span>
<span class="w"> </span><span class="nb">Dav</span><span class="w"> </span><span class="k">On</span>
<span class="w"> </span><span class="nt"><LimitExcept</span><span class="w"> </span><span class="s">GET OPTIONS</span><span class="nt">></span>
<span class="w"> </span><span class="nb">AuthType</span><span class="w"> </span>Basic
<span class="w"> </span><span class="nb">AuthName</span><span class="w"> </span><span class="s2">"My Server WebDav"</span>
<span class="w"> </span><span class="nb">AuthUserFile</span><span class="w"> </span><span class="sx">/path/to/htpasswd</span>
<span class="w"> </span><span class="nb">Require</span><span class="w"> </span>valid-user
<span class="w"> </span><span class="nt"></LimitExcept></span>
<span class="w"> </span><span class="nt"></Directory></span>
<span class="w"> </span><span class="c"># On autorise les indexes sur les dossiers contenu dans /public/</span>
<span class="w"> </span><span class="c"># même si ils ne sont pas autorisés dans ce dernier</span>
<span class="w"> </span><span class="nt"><Directory</span><span class="w"> </span><span class="s">/$document_root/public/*/</span><span class="nt">></span>
<span class="w"> </span><span class="nb">Options</span><span class="w"> </span>+Indexes
<span class="w"> </span><span class="nt"></Directory></span>
<span class="w"> </span><span class="nb">RewriteEngine</span><span class="w"> </span><span class="k">On</span>
<span class="w"> </span><span class="c"># On autorise l'accès au robot.txt sans redirection aucune</span>
<span class="w"> </span><span class="nb">RewriteCond</span><span class="w"> </span>%{REQUEST_URI}<span class="w"> </span>^/robots.txt$
<span class="w"> </span><span class="nb">RewriteRule</span><span class="w"> </span>^/?(.*)<span class="w"> </span>http://%{HTTP_HOST}/robots.txt<span class="w"> </span>[QSA,L,R=301]
<span class="w"> </span><span class="c"># Comme précédemment, on ne touche à rien si</span>
<span class="w"> </span><span class="c"># l'URI de la requête concerne le dossier /public/.</span>
<span class="w"> </span><span class="nb">RewriteCond</span><span class="w"> </span>%{REQUEST_URI}<span class="w"> </span>^/(public/(?|.*)|icons/(?|.*)|favicon\.ico)$
<span class="w"> </span><span class="nb">RewriteRule</span><span class="w"> </span>^.*<span class="w"> </span>-<span class="w"> </span>[L]
<span class="w"> </span><span class="c"># La règle qui empêche un utilisateur d'accéder</span>
<span class="w"> </span><span class="c"># à un dossier qui n'est pas à eux.</span>
<span class="w"> </span><span class="nb">RewriteCond</span><span class="w"> </span>%{LA-U:REMOTE_USER}<span class="w"> </span>^(.+)
<span class="w"> </span><span class="nb">RewriteCond</span><span class="w"> </span>%1:/$1<span class="w"> </span>!^([^:]+):/\<span class="m">1</span>$
<span class="w"> </span><span class="nb">RewriteRule</span><span class="w"> </span>^/([^/]*)<span class="w"> </span>-<span class="w"> </span>[F,L]
<span class="w"> </span><span class="nb">ErrorLog</span><span class="w"> </span>${APACHE_LOG_DIR}/error.log
<span class="w"> </span><span class="nb">CustomLog</span><span class="w"> </span>${APACHE_LOG_DIR}/access.log<span class="w"> </span>vhost_combined
<span class="w"> </span><span class="nb">LogLevel</span><span class="w"> </span><span class="k">warn</span>
<span class="nt"></VirtualHost></span>
</code></pre></div>
<p>Les dernières <em>RewriteCond</em> sont utilisées pour s’assurer que chaque utilisateur n’accèdera qu’à son dossier utilisent <code>LA-U:</code>. Ce préfix est décrit dans la documentation de <a href="http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritecond">mod_rewrite</a>.</p>
<p>L’idée derrière cette subtilité est que les règles de réécriture sont exécutées avant que l’authentification n’ait lieu. La variable <code>REMOTE_USER</code>, elle, définie lors de l’authentification, n’est pas disponible pour la comparaison avec l’<strong><span class="caps">URI</span></strong> de la requête. <code>LA-U:</code> permet de prefetch cette valeur. La dernière condition de réécriture véréfie que cette valeur correspond bien au début de l’<span class="caps">URI</span> de la requête.</p>
<p>La syntaxe de la condition de réécriture est assez compliquée (et honteusement copiée de <a href="https://maze.io/2010/02/09/restricting-directory-in-apache-per-logged-in-user/">cet article</a>). Elle doit sa structure au fait que apache ne permet pas à une valeur d’être à droite de la comparaison du <em>RewriteCond</em>.</p>