Téléchargeur Asynchrone v3


24 janvier 2016

Présentation

On sera tous d'accord pour dire que mon téléchargeur asynchrone d'il y a quelques mois était plus que moyen.

PHP étant toujours aussi inutile pour du code concurrent et Node étant toujours aussi instable / horrible / immaintenable / hideux / random, je suis resté sur du Go qui permet une concurrence élégante et un comportement déterministe.

Quand j'utilise le mot déterministe je ne décris pas des goroutine qui s'exécuteraient toujours dans le même ordre, mais le fait que le programme parvienne toujours à une exécution complète, avec un ordre interne et une durée d'exécution variables bien évidemment. Et oui, j'oppose ça à un code Node qui fait totalement ce qu'il veut, tel un zébulon sous amphèt pris d'une quinte de toux tenant une grenade...

Voici donc une nouvelle version, toujours en Go, disponible sur GitHub et qui implémente exactement ce dont j'avais besoin :

  • possibilité de le nourrir avec beaucoup d'URLs à télécharger

  • des groupes d'adresses concurrents

  • respect des serveurs des sites que j'interroge (via 2 types de délais)

Ainsi, 10 URLs pointant vers des sites différents pourront être téléchargées en même temps mais 10 URLs pointant vers le même site seront téléchargées les unes à la suite des autres pour éviter de se faire bannir et pour ne pas surcharger les serveurs distants.

J'explique un peu mieux les détails d'utilisation dans la suite.

Installation / Compilation

Après un "git clone" on se retrouve avec un simple fichier go.

Je compile ce code en mode release histoire d'avoir un binaire qui ne m'impose pas de délai au démarrage :

go build -ldflags "-s -w" downloaderv3.go

Le fichier produit, downloaderv3, peut être utilisé comme décrit dans la partie suivante.

Utilisation

Comment l'utiliser ? On va supposer que le fichier go a été compilé et qu'une série d'URLs à télécharger sont contenues dans le fichier urls.

Le plus simple

Télécharger dans le dossier destination/ toutes les ressources HTTP dont les URLs sont contenues dans le fichier "urls"

downloaderv3 --urls-file urls --output-dir destination/

Le programme impose un délai par défaut de 1 seconde entre deux téléchargements d'un même site. Ce délai est modifiable avec l'option --global-delay qui attend le nouveau délai.

Le fichier "urls" contient une ligne par adresse à télécharger, suivant les format suivant :

page1.html:http://www.example.org/f1.html
page2.html:http://www.example.org/f2.html

Délais différents pour certains sites

On veut parfois être encore plus prudent avec certains sites qui sont très chatouilleux avec les requêtes successives provenant d'une même IP. Aussi, pour éviter de se faire bannir on peut augmenter le délai par défaut pour ce site spécifiquement. A l'inverse, certains sites plus permissifs et qui n'ont pas de problème de bande passante nous autorisent des délais plus courts.

downloaderv3 --urls-file urls --output-dir destination/ --global-delay 2 --specific-delay www.liberation.fr 3 --specific-delay www.lefigaro.fr 1

On impose ici un délai de trois secondes entre chaque page de Libération, 1 seconde pour Le Figaro et 2 secondes pour tous les autres sites.

Proxy

Certains sites, s'ils connaissent le serveur qui télécharge, peuvent contrarier ses téléchargements. Il faut donc parfois utiliser des proxys pour se camoufler (oui, ça m'est déjà arrivé). Quelque soit la raison, vous pouvez utiliser un proxy par lequel passer pour vos requêtes :

downloaderv3 --urls-file urls --output-dir destination/ --proxy 10.0.0.5:3128

Détails supplémentaires

  • Modifier son User-agent : --user-agent "Firefox (v3.6)"

  • En cas de retour HTTP de type 4XX ou 5XX, le programme écrit directement ce code dans le fichier correspondant à la page dans le dossier de destination. Ces fichiers auront donc une taille de 3 octets.

  • Téléchargement d'une seule URL : --single-url URL

  • Télécharger qu'un nombre limité des URLs passées en paramètre : --limit 100

  • Pour suivre le fonctionnement du programme (debug surtout) : --verbose

Ce que j'en retiens

Si vous examinez le code sur Github, vous trouverez peut-être que ce n'est pas le plus élégant que j'ai écrit et j'imagine que des vétérans de Go trouveraient à y redire, mais pour un outil stand-alone ça me va parfaitement : simple à maintenir, lisible et avec un périmètre clair de fonctionnalités.

Après avoir implémenté cet outil qui me convient parfaitement et qui fonctionne idem, voici quelques pensées concernant le côté brouillon du Go :

  • Les classes qui n'en sont pas (pas de this donc...), les scopes ne sont donc pas clairement définis.

  • les fonctions globales au lieu de méthodes (len, append etc).

  • make à la place d'un constructeur. A chaque fois que j'appelle cette fonction j'ai l'impression de mettre une rustine sur un pneu crevé.

  • l'utilisation des pointeurs est pénible : une variable de type map ou pointeur DOIT être initialisée avec make pour être utilisée.

  • bêtement strict : une variable non-utilisé empêche l'exécution, tout comme un import... Ceci aboutit à des allers-retours incessants et frustrants. Ã?a n'a pas lieu d'être, des avertissements suffiraient avec un comportement bloquant uniquement quand on utilise "build".

  • les binaires produits sont énormes ! Tellement énorme que ça en est ridicule : 6.8Mo pour les 400 lignes du présent downloader.

  • les retours multiples sont très pratiques et élégants mais quand on veut seulement détecter si une clé existe dans une map c'est vraiment lourd et verbeux inutilement.

  • un énorme manque global de cohérence et, moins grave, de finition. Go renvoie une impression de langage-prototype, pas encore au stade alpha. C'est embêtant pour une technologie qui prétend être production-ready.

  • détail : la casse des noms de fonctions, de méthodes etc. Ã?a ne me va pas du tout ! Mais je suis conscient que c'est subjectif.

Tout ça aboutit à du C concurrent, ce qui est vraiment rafraîchissant, mais tout ce qui précède m'empêche d'utiliser ce langage dans des projets plus importants... Le Go est un langage "gentil" pour des petits projets mais qui devient parfaitement illisible en ce qui me concerne dès qu'on dépasse 1000 lignes. A moins de mettre en place des coding practices draconiennes qui feraient passer les étudiants de l'ENA pour des permissifs...

Les points positifs de Go : les goroutines et les channels. Ces deux concepts sont magnifiques et tous les langages devraient copier ces deux idées ! Une syntaxe extrêmement légère, presque invisible, pour des fonctions/résultats fabuleux.

On peut donc réaliser des projets concurrents infiniment mieux qu'en C/C++, PHP, Perl ou Node mais programmer en Swift avec ces goroutines et ces channels me séduit infiniment plus.

Le gros avantage pour l'instant en ce qui me concerne, c'est que ce téléchargeur fonctionne correctement et qu'il n'y a pas de cas étranges comme avec Node, ni d'arrêts brutaux injustifiés. Le programme fait ce qu'il doit faire, il est prévisible et c'est un bienfait monumental après s'être sali les doigts avec ce joujou amusant mais déjà périmé qu'est Node.

Accueil