Tout est dans le titre et nous allons voir ici différentes méthodes pour en savoir un peu plus sur la localisation géographique de nos visiteurs. A la base, on ne sait que deux choses, l'IP à tous les coups et parfois le hostname, le tout grâce aux variables d'environement CGI, REMOTE_ADDR et REMOTE_HOST.
Cette méthode est très simple, implique un code un peu long mais est très économe pour ce qui est de l'utilisation des ressources du serveur et n'utilise pas de bande passante ni de modules exotiques absents de la distribution standard de perl. On récupère la variable d'environment $ENV{'REMOTE_HOST'};, on fait des vérifications puis on en extrait l'extension qui correspond au TLD utilisé par le visiteur. Ensuite on fait appel à un hash assez important grâce auquel on fait la conversion. Dans mon exemple j'ai récupéré une liste des TLDs qui n'est peut-être pas très à jour mais qui sera suffisante.
# initialisation du hash constant de conversion
# c'est ici un extrait, le code complêt est dans l'archive indiquée en bas
%pays = ("ca"=>"Canada","fr"=>"France","nl"=>"Netherlands","no"=>"Norway");
$host = lc $ENV{'REMOTE_HOST'};
# vérification que le host est bien utilisable, ni vide, ni IP numérique
$host =~ /(\D)/;
print geo() if (($host) and ($1));
# le sub geo
sub geo {
# regexp d'extraction du TLD
$host =~ /.*\.(\D+)$/;
$wz = $1;
if ($pays{$wz}) {
# cas où le pays est connu de notre hash
return "Hello ".$pays{$wz};
} else {
# si absent du hash, je m'envoie un mail pour que je pense à mettre
# à jour mon script.
return "Je ne sais pas";
open(SENDMAIL, "|/usr/lib/sendmail -t");
print SENDMAIL <<EOF;
From: robot\@habett.org
To: habett\@habett.org
Subject: Nouvelle extension
X-Mailer: HKMail subset aka mailme.cgi
Que faire de $wz ?
EOF
close(SENDMAIL);
}
}
Je mentionne cette méthode qui est évidente car le code est plutôt long à taper en raison du hash que je n'ai pas reproduit ici en intégralité mais qui est présent dans l'archive que vous trouverez en bas de page. Le seul intérêt est la petite procédure de notification par e-mail du webmaster dans le cas où le TLD nous est inconu. Ces choses évoluent vite et nous disposons ainsi d'un très bon moyen de se tenir informé des évolutions en la matière. Rien qu'à cette fin ce script pourait être utilisé.
J'aime bien cette méthode et je l'utilise souvent tout comme le font la plupart des logiciels d'analyse de logs de serveurs web. Elle est fiable mais est un peu limitée. C'est encore la Technodicée du simple.
Son problème reste néanmoins simple à comprendre et pour vous l'expliquer je vais me baser sur les logs d'un de mes autres serveurs (habett.org) : 24% des visiteurs n'ont pas de nom d'hôte mais une simple IP. Si l'on rajoute tous les TLDs non liés à un pays particulier, cela nous fait environ 60% des visiteurs sans réelle identification géographique. C'est à la fois peu et beaucoup selon que l'on conssidére le but à atteindre ou la parcimonie du code.
Qui dit technodicée dit opposition de styles. Nous avons vu les classiques, passons maintenant aux baroques.
Je commence par télécharger un fichier de 437Ko sur le site http://ip-to-country.webhosting.info. Une fois dézippé, on obtient un fichier Coma Separated Values (CSV) de 2,6 Mo. Chaque ligne ressemble à ça:
"33996344","33996351","GB","GBR","UNITED KINGDOM"
En fait, il décrit des plages d'IP avec les deux premiers chiffres et retourne le pays en dernier élément. Les deux valeurs intermédiaires sont simplement des codes pays, eux aussi ISO 3166 qui est la norme internationale correspondante. Toutes les IP n'y sont pas mais cela donne déjà une idée.
Par simple curiosité, nous allons calculer le nombre d'IP présentes dans ce fichier. Si je ne me trompe pas, on peut écrire :
$total = 0;
open (FILE,"ip-to-country.csv");
while (<FILE>) {
($beg,$end) = split (/,/,$_);
$beg =~ s/"//g;
$end =~ s/"//g;
$total += $end-$beg;
}
close (FILE);
print $total;
On obtient alors le nombre de 2 634 190 229 IP avec leur correspondance géographique ! Si l'on fait des petits calculs, il y a un potentiel de 256*256*256*256 IP possibles, soit 4 294 967 296 et ce jusqu'à l'avénement du protocole IPv6. Il faudrait exclure de ce second total les plages d'IP qui ne sont pas routables genre 10.x.x.x, 192.x.x.x et les autres. En tout cas, on doit avoir un couverture largement supérieure à 50%.
Avant d'aller plus loin, demandons-nous si cela vaut la peine en pensant bien à la méthode que nous avons évoqué en début d'article. Je jette un coup d'oeil à l'analyse de mes logs qui sont peu significatifs mais donnent peut-être des ordres de grandeur. Je vois que :
Cela nous fait un total de plus de 60% donc cela veut dire que la méthode dont nous parlons dans ce paragraphe vaut la peine d'être explorée, surtout que l'on peut imaginer qu'en recoupant les deux méthodes il y ait des cas où une seule fonctionne.
Prennons pour exemple l'IP temporaire qu'a eut un de mes amis, 217.13.4.77 et voyons ce que nous pouvons en faire. On commence par la convertir du format IP en format numérique intégral soit (((( 217 * 256) + 13) * 256) +4) * 256 + 77 égal 3 641 508 941. Je regarde dans ip-to-country.csv et je lis que cette IP est en Norvège ce qui est correct, hors, cette IP avait pour hostname un .com !
Dans l'exemple précédent j'ai cherché à la main dans le fichier CSV mais perl peut très bien le faire pour moi ainsi que tous les calculs :
$ip = shift;
($a,$b,$c,$d) = split(/\./,$ip);
$n = ((((($a * 256) + $b) * 256) + $c) * 256) +$d;
open (FILE,"< ip-to-country.csv");
while (<FILE>) {
($beg,$end,$country) = (split (/,/,$_))[0,1,4];
$beg =~ s/"//g;
$end =~ s/"//g;
$isit = (($beg <= $n) and ($end >= $n));
if ($isit) {
$country =~ s/"//g;
$country = lc $country;
$country = "\u$country";
print $country;
}
next if ($isit);
}
exit(0);
Cela fonctionne à merveille mais c'est lent. Une charge de travail importante que tous les serveurs ne peuvent pas se permettre. Il y a bien des moyens d'optimiser tout cela mais restons concentrés sur notre simple objectif car nous ne cherchons pas un application industrielle. C'est juste un petit jeux entre nous.
Ne reste plus qu'à faire l'assemblage final. On commence par la méthode décrite en premier car elle s'éxécute plus rapidement mais on aura préalablement expurgé de la liste des TLDs ceux qui ne correspondent pas à un pays. Ensuite, si l'on n'a pas de résultat alors on passe à la seconde méthode. Je code le tout en perl qui s'éxécutera en mode CGI pour produire le résultat suivant:
Si votre pays n'est pas marqué alors contactez moi. Merci.
L'archive suivante contient les deux méthodes et le script cgi final.