Suivre les consultations de son blog

En voilà un autre sujet "épineux"...

Comment avoir une idée du nombre de visites sur mon site sans espionner mes lecteurs ?

Même si de nos jours, l'ajout d'un code de suivi Google Analytics est devenu presque naturel, il est bon de se poser quelques questions.

  • Est-ce que j'en ai besoin ?
  • Est-ce que je veux transmettre à une société tierce un ensemble d'informations sur mes lecteurs ?
  • Est-ce que je veux passer du côté obscur de la force et envoyer des cookies à mes lecteurs ?

Pour une utilisation personnelle, comme la mienne, j'ai envie de répondre non à tout.

Maintenant, il faut trouver une manière de sortir du schéma classique et mettre en place une solution de suivi des consultations.

Je sais qu'il existe déjà des milliers de tentatives réussie, ou non, de système qui correspondent à mon besoin.

Je pourrais très probablement en réutiliser un, mais pour les besoins de cet article, on va faire notre propre sauce. Et puis, c'est un peu ça l'histoire de l'informatique, expérimenter, rencontrer des difficultés, les contourner pour finalement arriver à ses fins. Chaque expérience apporte de nouvelles connaissances.

Mon "cahier des charges"

Avant de commencer le moindre - petit - projet, il faut savoir ce que l'on veut faire, comment on veut le faire et avec quels moyens.

I. Objectifs

Recueillir des informations concernant les visites sur mon blog tout en respectant la vie privée de mes lecteurs et en ne faisant appel à aucune tierce partie.

II. Fonctionnalités

Le script devra, à la connexion d'un utilisateur sur mon blog :

  • Identifier l'adresse IP de l'utilisateur en utilisant $_SERVER['REMOTE_ADDR']
  • Tenter de géo-localiser l'adresse IP
  • Récupérer l'adresse visité par l'utilisateur $_SERVER['REQUEST_URI']
  • Enregistrer dans une base de données :
    • Le compte des origines de connexion et accès par url dans une table hits
  • Le script effectuera une requête asynchrone en AJAX

Le script ne devra pas conserver :

  • Les adresses IP des visiteurs ni aucune autre informations autre que celles citées ci-dessus.

Le script devra vérifier que la requête provient bien de mon serveur. Sinon, je vois déjà les petits malins que vous êtes m'envoyer des fausses requêtes pour flooder ma base :-) L'utilisation de $_SERVER['HTTP_REFERER'] ne sera pas suffisante. En effet, cette variable est envoyée par le client. On peut donc la modifier avant un envoi. On va coupler cela avec la vérification de $_SERVER['HTTP_ORIGIN'] de l'expéditeur qui devra être celle de mon serveur.

III. Réalisation

L'environement de travail :

  • Language serveur : PHP
  • Language client : JavaScript & HTML
  • SGBD : SQLite
  • Outil de géo-localisation : GeoLite Country
  • Librairie côté client : JQuery

En avant !

Premièrement, pour bien séparer mon "module" de statistiques et mon blog, je vais enregistrer un sous-domaine auprès de mon registar Gandi.

J'ai choisis http://stats.lfconsult.work. À présent, je configure mes vHosts Nginx pour faire pointer ce nom de domaine vers un répertoire spécifique sur mon serveur.

Création d'un fichier stats dans /etc/nginx/sites-available qui contient :

server {  
        listen   80;

        root /var/www/stats;
        index index.php;

        server_name stats.lfconsult.work;
}

On crée un lien symbolique vers sites-enabled pour activer le vHost :

ln -s /etc/nginx/sites-available/stats /etc/nginx/sites-enabled/

On redémarre nginx :

sudo service nginx restart

Par défaut, mon Droplets ne permet pas l'exécution de PHP. On va donc installer PHP et ajouter une instruction à nginx pour lui dire de l'envoyer à PHP :

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    [...]

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

On redémarre nginx :

sudo service nginx restart

À présent, notre sous domaine pointe vers le bon répertoire et notre environement côté serveur est prêt. Allons donc coder :-)

On va créer deux répertoire dans notre racine de sous-domaine /var/www/stats, à savoir lib et dat.

On peut utiliser wget pour récupérer directement les fichiers depuis le web :

wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz

Pour extraire une archive en *.GZ, on utlise l'utilitaire gzip :

gzip -d GeoIP.dat.gz

À présent on ajoute notre code dans index.php sans vérification de referer juste pour voir si tout fonctionne :

<?php

// include the php script
include("lib/geoip.inc");

// open the geoip database
$gi = geoip_open("dat/GeoIP.dat",GEOIP_STANDARD);

// to get country code
$country_code = geoip_country_code_by_addr($gi, $_SERVER['REMOTE_ADDR']);
echo "Your country code is: $country_code \n";

// to get country name
$country_name = geoip_country_name_by_addr($gi, $_SERVER['REMOTE_ADDR']);
echo "Your country name is: $country_name \n";

// close the database
geoip_close($gi);

?>

On se rend à l'adresse que nous avons configuré stats.lfconsult.work et voici le résultat :

Your country code is: FR Your country name is: France

Génial. On vient de vérifier l'identification de l'adresse IP, ça fonctionne :-)

Ajoutons notre couche de protection pour éviter les flooders. Si l'origine de la connexion est bien mon domaine alors on renvoie un résultat, sinon, on retourne un code HTTP 401 : Unauthorized :

<?php

if($_SERVER['HTTP_ORIGIN'] == "http://lfconsult.work"){  
        // include the php script
        include("lib/geoip.inc");

        // open the geoip database
        $gi = geoip_open("dat/GeoIP.dat",GEOIP_STANDARD);

        // to get country code
        $country_code = geoip_country_code_by_addr($gi, $_SERVER['REMOTE_ADDR']);
        echo "Your country code is: $country_code \n";

        // to get country name
        $country_name = geoip_country_name_by_addr($gi, $_SERVER['REMOTE_ADDR']);
        echo "Your country name is: $country_name \n";

        $current_page = $_GET['page'];
        echo "Your current page is: $current_page \n";

        // close the database
        geoip_close($gi);

                try{
                    $pdo = new PDO('sqlite:dat/stats.sqlite');
                    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
                    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); // ERRMODE_WARNING | ERRMODE_EXCEPTION | ERRMODE_SILENT
                } catch(Exception $e) {
                    echo "Impossible d'accéder à la base de données SQLite : ".$e->getMessage();
                    die();
                }

                $pdo->query("CREATE TABLE IF NOT EXISTS hits ( 
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    country_code VARCHAR(2),
                    country VARCHAR(255),
                    uri VARCHAR(255)
                );");

                $stmt = $pdo->prepare("INSERT INTO hits (country_code, country, uri) VALUES (:country_code, :country, :uri)");
                try{
                        $result = $stmt->execute(array(
                            'country_code' => $country_code,
                            'country' => $country_name,
                            'uri' => $current_page
                        ));
                } catch(Exception $e) {
                    echo $e->getMessage();
                    die();
                }



}else{
        echo '<h1>Error : HTTP/1.1 401 Unauthorized</h1>';
        http_response_code(401);
}


?>

Si on accède à notre page de stats, on obtient bien :

Error : HTTP/1.1 401 Unauthorized

Si vous n'avez pas autorisé les CORS votre requête sera bloquée depuis votre serveur.

Il faut donc ajouter des petits réglages pour les faire fonctionner :

sudo apt-get install nginx-extras

Puis ajouter le code ci-dessous dans votre fichier de vHost :

add_header Access-Control-Allow-Headers "X-Requested-With";  
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS";  
add_header Access-Control-Allow-Origin "http://lfconsult.work";  

Cela va autoriser les requêtes provenant du domaine spécifié dans Access-Control-Allow-Origin.

Reste à finaliser la partie client. Je vais utiliser JQuery pour la lisibilité mais vous pouvez utiliser ce que vous voulez.

Voici le petit script qui va effectuer notre requête AJAX :

<script type="text/javascript">  
    $.get('https://stats.lfconsult.work/get_my_ip.php', function(ip){
        console.log('Your IP is : ' + ip);
    $.get('https://stats.lfconsult.work/index.php?ip=' + ip + '&page=' + window.location.pathname, function(result){
            console.log(result);
    });
    });
</script>  

En détail, on effectue une requête de type GET pour récupérer l'adresse IP du client. Lorsque nous obtenons l'adresse, on envoye cette dernière au script qui enregistre les hits en lui passant également la page qui est en train d'être vue.

Picture

Oui Scarlett, je sais, c'est incroyable ce que nous venons de faire.

Merci pour ton soutient depuis tant d'années (ah ah ah ) :-)

Bon, c'était un article un peu plus technique mais il en faut pour tout le monde, n'est-ce pas ?

Vous voulez voir le résultat ?

C'est une bonne chose :-) Mais c'est uniquement pour les braves, ceux qui lisent mes articles jusqu'au bout. Ceux qui n'ont pas peur de lire plus de 1.000 mots sur un article d'informatique :-)

Oui, vous, si vous venez de lire tout ceci, vous êtes un brave.

Alors voici l'url : https://stats.lfconsult.work/public.php

Ce lien est à présent disponible dans le menu en haut à droite du blog, bouton "Statistiques".

Deux tableaux avec le récapitulatif des connexion par page et par pays.

J'ai décider de ne pas limiter le compte des hits, pour la simple et bonne raison qu'avec le temps, les résultats vont refléter la vérité vraie. Donc pourquoi se compliquer la vie ?

Bon, sur ce, je vous souhaite un très bon halloween :-)

Picture

*L'abus de boisson alcoolisée ou trop sucrée est dangereux pour la santé. À consommer avec modération.

Sources