lindev : administration linux , développement php

Aller au contenu | Aller au menu | Aller à la recherche

21 sept. 2009

big brother is watching you !

Surveiller la disponibilité d'un site camera.jpg

Rien de bien extraordinaire , mais le principe m'amuse alors autant partager , et pourquoi pas récupérer des idées dans vos commentaires .

Le principe :

L'on me demande un moyen de vérifier si des sites distants sont disponibles , ou non de façon permanente .

J'ai donc décidé de développer un bout de script , qui ne fait ni plus ni moins que:

  • Tester la résolution DNS ( Type A uniquement pour le moment )
  • récupérer les en-têtes envoyés par le serveur en réponse à une requête HTTP.
  • Récupération de ce qui m'intéresse , à savoir le code de status http , et le temps qu'a mis la réponse à me parvenir .

Ensuite , grâce à ces informations , je peux faire toute sorte de graphiques m'indiquant la disponibilité du/des sites surveillés dans le temps .

Exemple :

viewer.png

Le code

pour récupérer des données le plus proche de la réalité possible Comme j'ai dit plus haut , le code est très simple et rapide à mettre en place , il vous faudra cependant activer curl pour l'utiliser .

Vérifier la résolution de nom

Pour cette étape , nous allons utiliser la fonction native à php 4&5 checkdnsrr() :

checkdnsrr($adresse,"A");

Récupérer les entêtes HTTP

Pas besoin de récupérer la totalité , celà surchargerais inutilement les deux cotés , une première solution consiste à utiliser la fonction get_headers() en natif dans php5 :

get_headers  ( "htp://".$adresse  , true );

L'inconvénient de cette fonction , est qu'elle ne possède pas de timeout hormis celui de php , qui est bien trop long et peut pratique .
Les tests avec cette fonction se sont donc révélé bon , mais a généré l'augmentation de la consommation de mémoire de façon spectaculaire , voyez vous-même !

eluna.png

La solution , est donc d'utiliser curl , qui s'est révélé bien plus efficace , voici donc la fonction de remplacement de get_headers() ( sources fr2.php.net )

function get_headers_curl($url)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL,            $url);
    curl_setopt($ch, CURLOPT_HEADER,         true);
    curl_setopt($ch, CURLOPT_NOBODY,         true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT,        20);

    $r = curl_exec($ch);
    $r = split("\n", $r);
    return $r;
} 

Si vous ne pouvez pas utiliser curl , vous pouvez aussi utiliser la fonction suivante , qui se base sur fsockopen() en natif dans php .

Note:merci à clochix de m'avoir signalé cette autre alternative.

/*
 * Fonction de remplacement de get_headers 
 * Attention à ne pas mettre les http:// devant l'adresse 
 */
function get_headers_sock($adresse, $timeout=15){
    //Tableau qui vat contenir les entêtes
    $Theaders   =   array();
    $UserAgent = "User-Agent : Mozilla/4.0 (compatible; MSIE 5.0; Windows 95)";

    $fp = fsockopen($adresse, 80, $errno, $errstr, $timeout);
    if (!$fp) {
        echo "$errstr ($errno)<br />\n";
    } else {
        $out = "HEAD / HTTP/1.1\r\n";
        $out .= "Host: $adresse\r\n";
        $out .= "$UserAgent\r\n";
        $out .= "Connection : Close\r\n\r\n";

        fwrite($fp, $out);
        while (!feof($fp)) {
            $Theaders[] =   fgets($fp, 128);
        }
        fclose($fp);
    }


    //renvoi des entêtes
    if(!empty($Theaders)){
        return $Theaders;
    }else{
        return null;
    }
    
}

Cette fonction retourne tout comme get_headers() un tableau avec une ligne = une entrée .
Le timeout est ici défini à 20 secondes , car je juge que si l'entête n'est toujours pas là en 20secondes , le site n'est pas navigable normalement , donc indisponible .

Le code de status HTTP

Après avoir reçut la réponse HTTP , l'on décortique l'entrée du tableau n°0 qui correspond normalement à : HTTP/1.1 <code http> <etat>

Une petite expression régulière plus tard , on se retrouve avec le code http seul

//récupération des entêtes
$header = get_headers_curl($adresse);
if(!empty($header){

  //le code http sera disponible dans $retour[1]
  preg_match('#([0-9]{3})#',$header[0],$retour);

}

Le temps pour faire tout ça !

Il ne nous reste plus qu'à récupérer le temps total pour effectuer ces opérations ( temps d'envoi + temps de réception ).
Rien de plus simple , comme dans beaucoup d'exemple sur le net , nous utiliserons microtime()

$debut  = microtime(true);
$header =   get_headers_curl("http://".$adresse) ;
//ou  get_headers_sock($adresse); ( attention à ne pas mettre http devant )
$fin    =  microtime(true);
$temps  =   round($fin-$debut,4);

Et voilà , nous avons toutes les infos nécessaire , ne reste plus qu'à les enregistrer .. en base , fichier .. comme vous voulez .

Exemple de code complet :

<?php
//definition du user_agent

$useragent="Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";

//adresse à surveiller ( sans les http )
$adresse = "lindev.fr";
        
$retour = array(0,0);
$dns        =   0;
$temps      =   0;

//test du dns
if( checkdnsrr($adresse,"A")){


	$debut  = microtime(true);
        $header =   get_headers_curl("http://".$adresse) ;
        $fin    =  microtime(true);
        $temps  =   round($fin-$debut,4);

        preg_match('#([0-9]{3})#',$header[0],$retour);
        $dns    =   1;

}else{
        echo "Résolution DNS impossible";   
}

//enregistrement des resulats
$result =   array(
   "DNS"           =>     $dns,
   "reponse"       =>  $retour[1],
   "tempsReponse"  =>  $temps
);

//si timeout , impose le code 503
if($result['reponse'] == NULL){
   $result['reponse'] = 503;
}

//enregistrement dans la base de données / ou fichier
        ... ... ...
function get_headers_curl($url)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL,            $url);
    curl_setopt($ch, CURLOPT_HEADER,         true);
    curl_setopt($ch, CURLOPT_NOBODY,         true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT,        20);

    $r = curl_exec($ch);
    $r = split("\n", $r);
    return $r;
} 
?>

Mise en place

Il faut maintenant lancer ce script de façon régulière afin de surveiller le site de façon précise .
Je vais donc dans mon cas le lancer toutes les 10min , via cron

Vous voilà avec une table qui se remplis toute seule des informations citées ci-dessus , il ne vous reste plus qu'à en faire ce que bon vous semble avec .

Pour info , les graphiques générés ci-dessus sont fait via JPgraph qui fera pourquoi pas l'objet d'un billet pour son initiation .

Conclusion

Voilà une approche d'un outil qui je pense peut s'avérer utile , à partir du moment ou les sites surveillés ne se trouvent pas au même endroit que ce script de monitoring .
Ensuite , n'a de limite que votre imagination pour le faire évoluer , pour par exemple lancer des alertes au bout de X minutes de non-disponibilités , tester d'autres type de services comme les mails , ftp ect ...

J'attends vos retours et vos critiques sur l'idée ou la façon de faire afin d'améliorer tout ça .

A bientôt ,

Ch.

05 fév. 2009

Php , survolons les sockets

Nous allons voir dans cet article , l'utilisation des sockets en php Dans quel but , et bien , par curiosité .. simplement ..

Pré-requis :

il nous faut pour les sockets avoir compilé php avec l'option --enable-sockets ... Voilà , rien de plus ..

Que faire ?

Nous allons une partie serveur qui va écouter sur un port donné , et distribuer les messages aux clients qui se connectent . Le but n'est pas d'avoir un programme fini , mais bien d'avoir une première approche avec les sockets en PHP.

Partie Serveur:

Entamons donc notre script serveur.php

Création d'une socket

Nous allons ici , creer la socket , et mettre le script en écoute de connexion ...

set_time_limit(0); 
ob_implicit_flush();	

try {
			
	//creation du socket
	$Psock	=	socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
			
	//en cas d'erreur à la creation de la socket
	if ( $Psock	===	FALSE ){
		$msgerror	=	socket_strerror(socket_last_error());
		throw new Exception($msgerror);
	}
			
			
	//nommage de la socket cree
	$ret	=	socket_bind($Psock, '127.0.0.1', 10005);
			
	//en cas d'erreur au nommage de la socket
	if ( $ret	===	FALSE ){
		$msgerror	=	socket_strerror(socket_last_error());
		throw new Exception($msgerror);
	}
			
			
			
			
	//Attente des connexions entrantes
	$ret	=	socket_listen( $Psock, 0 );
			
	//en cas d'erreur
	if ( $ret	===	FALSE ){
		$msgerror	=	socket_strerror(socket_last_error());
		throw new Exception($msgerror);
	}
			
			
			
					
	//Acceptation des connexions entrantes
				
	//endormissement si aucune connexion
	$Csock	=	socket_accept($Psock);
				
	//en cas d'erreur
	if ( $ret	===	FALSE ){
		$msgerror	=	socket_strerror(socket_last_error());
		throw new Exception($msgerror);
	}
			
	//Message lor d'une connexion
	echo "<br><b>Réception d'une connexion ! </b><br>";
				
					
	//Souhaitons la bienvenue au client
	$ret	=	@socket_write($Csock, "Welcome<br>", strlen("Welcome<br>"));
							
			
	//Fermeture de la connexion cliente
        if( is_ressource($Csock) ){
	       socket_close($Csock);
               unset($Csock);
        }
			
	//fermeture du serveur
	if(is_resource($this->Psock)){
		socket_close($this->Psock);
		unset($this->Psock);
	}
			
}catch (Exception $e){
	print_r( $e->getMessage() );
	exit(1);
}

Voilà , pour la partie serveur .. comme vous pouvez le voir , il n'y a rien de vraiment palpitant .. ici , une simple connexion .. et hop , le serveur envoie un message au client , puis il s'arrête aussitôt . Nous aurions trés bien pu mettre une boucle infinie pour accepter autant de connexions que possible , ou un message spécifique stop le serveur . En effet, la fonction socket_accept($Psock); est bloquante , c'est à dire que le script s'arrete à cette ligne tant qu'aucune connexion n'a eu lieu , c'est pourquoi nous avons ajouté en debut de script , la ligne : set_time_limit(0); afin de ne pas être limité dans le temps ... pour la suite des possibilités , je laisse libre votre imagination ..

Partie Client :

Pour la partie client , le tout debut , est le même .. mais moins long tout de même ... regardez client.php

/*
 * Création d'une socket Inet
 */
try{
	
	
	
	$Psock	=	socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
	
	//en cas d'erreur à la creation de la socket
	if ( $Psock	===	FALSE ){
		$msgerror	=	socket_strerror(socket_last_error());
		throw new Exception($msgerror);
	}
	
	
	
	/*
	 * Connexion au serveur 
	 */
	$ret	=	socket_connect($Psock,'127.0.0.1',10005);
	
	//en cas d'erreur à la creation de la socket
	if ( $Psock	===	FALSE ){
		$msgerror	=	socket_strerror(socket_last_error());
		throw new Exception($msgerror);
	}
	
			
	echo $buf = socket_read($Psock, 2048);
	
	socket_close($Psock);
	
	
}catch(Exception $e){
	print_r( $e->getMessage() );
	exit(1);
}

Et voilà pour la partie cliente , comme pour la partie serveur , nous créons une socket via la fonction socket_create() , ensuite , nous tentons une connexion de l'autre coté du "tube" via socket_connect() , si aucune erreur n'est détecté , nous attendons sagement un message du serveur ... socket_read() est aussi bloquant , tant qu'aucun message (ou une chaîne vide) n'arrive , le script reste endormis à cette ligne , toujours sans consommer de ressources ...

Dés réception de données , notre script va simplement l'afficher à l'écran puis se déconnecter ...

Test sans parti serveur

Pour tester trés rapidement les sockets au niveau client , vous pouvez simplement , changer le numéro de port et l'adresse à laquelle vous vous connectez .. exemple , si vous avez ssh sur votre machine , remplacez 10005 par 22 , vous allez voir un message du style :

SSH-2.0-OpenSSH_4.3p2 Debian-9etch3 

Ou sur votre serveur de mail , mettez l'adresse de votre serveur , et le port 25

220 mailhost.creavi.fr ESMTP Postfix (Debian/GNU) 

Et voilà pour le survol des sockets , qui peut aboutir à de nombreuses applications intéressantes , rien que pour leurs conception ...

Christophe.