http_url="https://gestion.bibli.fr/genes/sip2.php"; $s->http_url_login="https://gestion.bibli.fr/genes/main.php"; $s->http_port=80; $s->http_use_cookie=true; $s->http_cookie_login=array("user"=>"admin","password"=>"admin","database"=>"bibli"); $s->http_renew_pattern="/class\=\'erreur\'/"; $s->http_use_ssl=true; $s->http_ssl_crt="/home/ftetart/.ssl/certificat.crt"; $s->http_ssl_key="/home/ftetart/.ssl/certificat.key"; $s->socket_bind_address="192.168.1.65"; $ret=$s->start_bind(); if (!$ret) print $s->error_message."\n"; \endverbatim */ /** * \brief Passerelle socket vers un serveur http * * La classe implément un serveur de socket qui transfère à un serveur http les données reçues via cette socket.\n * La réponse du serveur http est retransmise intégralement au client de la socket. * @author ftetart * \date Février 2008 * \ingroup server */ class socket2http { public $http_url="http://localhost/pmb"; /*!< \brief Adresse du serveur http a contacter */ public $http_url_login="http://localhost/pmb/main.php"; /*!< \brief URL du serveur http a contacter pour l'autentification par cookies */ public $http_port=""; /*!< \brief Port http du serveur */ public $http_use_cookie=false; /*!< \brief Gestion d'une session par cookie sur le serveur : false=non, true=oui */ /** * \brief Variables de login par cookie * * Variables a passer par POST au départ pour initier la session par cookie. C'est un tableau "nom de variable"=>"valeur"\n * Exemple : array("user"=>"ftetart","password"=>"xxxxx");\n * passe au serveur http les variables de connexion "user" et "password" */ public $http_cookie_login=array(); public $http_cookie_renew_pattern=""; /*!< \brief Expression régulière qui permet de détecter qu'une session par cookie a expiré */ public $http_use_ssl=false; /*!< \brief Utiliser une connexion sécurisée par ssl : false=non, true=oui */ public $http_ssl_key=""; /*!< \brief Clé privée pour la connexion ssl*/ public $http_ssl_crt=""; /*!< \brief Clé publique pour l'autentification */ /** * \brief Cookies reçus après l'authentification par session * \private */ public $http_cookies=array(); /** * \brief Corps de la réponse http * \private */ public $http_core=""; /** * \brief Hearder de la réponse http * \private */ public $http_header=""; /** * \brief Lien curl courant * \private */ public $curl_link; public $socket_max_connections=10; /*!< \brief Nombre maximum de connexions autorisés au serveur de socket */ public $socket_port=6001; /*!< \brief Numéro de port a écouter */ public $socket_bind_address="127.0.0.1"; /*!< \brief Adresse d'écoute */ /** * \brief Tableau des clients socket connectés * \private */ public $socket_clients=array(); /** * \brief Socket serveur pour création d'une connexion * \private */ public $socket_server=""; public $error=false; /*!< \brief Si true, il y a eu une erreur lors d'un traitement */ public $error_message=""; /*!< \brief Si ::error = true, message d'explication de l'erreur */ /** * \brief Constructeur * * Ne prend aucun argument, tout est exécuté par la méthode start_bind . */ public function __construct() { } /** * \brief Gestion de l'erreur socket * * Positionne le flag error à vrai et affecte error_message avec la dernière erreur de socket * @return vide rien * \private */ public function make_socket_error() { $this->error=true; $this->error_message="Erreur ouverture du serveur de socket : ".socket_strerror(socket_last_error()); } /** * \brief Initialisation du serveur de socket * * Création de la socket serveur dans $this->socket_server. La fonction ne prend aucun argument mais utilise des variables internes * @param string ::socket_bind_address Adresse d'écoute de la scoket * @param integer ::socket_port Port d'écoute * @return boolean true : la création de la socket a fonctionné, false : la création a échoué (sockets2http::error est à true) * \note la variable interne ::socket_server contient la ressource de la socket serveur * \private */ public function init_socket() { // Création d'une "TCP Stream socket" $this->socket_server = socket_create(AF_INET, SOCK_STREAM, 0); if (!$this->socket_server) { $this->make_socket_error(); return false; } else { // Ecoute de la socket serveur sur l'adresse/port if (!socket_bind($this->socket_server, $this->socket_bind_address, $this->socket_port)) { $this->make_socket_error(); return false; } else { // Start listening for connections if (!socket_listen($this->socket_server)) { $this->make_socket_error(); return false; } } } return true; } /** * \brief Enregistrement des données de la réponse http dans ::http_core * @param ressource $curl_ressource ressource curl qui gère la requête * @param string $data contenu du corps de la réponse * @return integer longueur des données reçues * \private * \ingroup http */ public function get_http_core($curl_ressource,$data) { $this->http_core.=$data; return strlen($data); } /** * \brief Enregistrement des entêtes de la réponse http dans ::http_header * @param ressource $curl_ressource ressource curl qui gère la requête * @param string $data contenu partiel des entêtes * @return integer longueur des données reçues * \note Si l'entête passée dans $data est un cookie (Set-Cookie: ...), le cookie est stocké dans le tableau ::http_cookies * \private */ public function get_http_header($curl_ressource,$data) { if (strpos($data,"Set-Cookie:")!==false) { $this->http_cookies[]=trim(substr($data,12)); } $this->http_header.=$data; return strlen($data); } /** * \brief Intialise et prépare les options curl pour la connexion http * @param array $http_params tableau des options curl a initialiser sous la forme : "NOM_OPTION"=>"valeur".\n * \note Exemple de tableau d'option array("URL"=>"http://localhost/pmb/main.php") sera traduit en * curl_setopt($this->curl_link,CURLOPT_URL,"http://localhost/pmb/main.php") * \private */ public function prepare_http($http_params) { //Initialisation de la connexion $this->curl_link = curl_init(); curl_setopt($this->curl_link, CURLOPT_WRITEFUNCTION,array(&$this,"get_http_core")); curl_setopt($this->curl_link, CURLOPT_HEADERFUNCTION,array(&$this,"get_http_header")); curl_setopt($this->curl_link, CURLOPT_HEADER, 0); curl_setopt($this->curl_link, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->curl_link, CURLOPT_TIMEOUT,30); //Gestion du SSL if ($this->http_use_ssl) { curl_setopt($this->curl_link,CURLOPT_SSL_VERIFYPEER,false); curl_setopt($this->curl_link,CURLOPT_SSLCERT,$this->http_ssl_crt); curl_setopt($this->curl_link,CURLOPT_SSLKEY,$this->http_ssl_key); } if (count($this->http_cookies)) { curl_setopt($this->curl_link,CURLOPT_COOKIE,implode(";",$this->http_cookies)); } foreach ($http_params as $param=>$value) { curl_setopt($this->curl_link,constant("CURLOPT_".$param),$value); } } /** * \brief Fermeture de la connexion curl * \private */ public function close_http() { curl_close($this->curl_link); } /** * \brief fait une requête http en vérifiant la session par cookie * * Fait une requête http en postant le paramètre message. La fonction vérifie que la session cookie est toujours valide * et se reconnecte si nécéssaire. * @param string $message message a poster au serveur http (le serveur recevra message=$message) * @return boolean true : La requête a réussie (la réponse est dans ::http_core), false : la requête a échoué * \private */ public function make_logged_http_request($message,$id_client) { global $protocol_prolonge; //Initialisation de la requête $http_params=array( "URL"=>$this->http_url, "POST"=>true, "POSTFIELDS"=>"message=".rawurlencode($message)."&id=".rawurlencode($id_client).$protocol_prolonge ); $this->prepare_http($http_params); if ($this->make_http_request()) { //Requete OK, on cherche un statut "erreur de session" si il y a une session cookie if ($this->http_use_cookie) { //Recherche du pattern erreur $p=preg_match($this->http_renew_pattern,$this->http_core); //Si session expirée, reconnexion if ($p) { //Reconnexion if ($this->http_do_login()) { $this->prepare_http($http_params); if ($this->make_http_request()) { //On s'est reconnecté, OK ! return true; } else { //Erreur ! $this->error=true; $this->error_message="Impossible d'obtenir une réponse à l'URL : ".$this->http_url; return false; } } else { //Erreur ! $this->error=true; $this->error_message="Impossible de recréer une session valide"; return false; } } } } else { //Requete pas OK $this->error=true; $this->error_message="Impossible d'obtenir une réponse à l'URL : ".$this->http_url; return false; } return true; } /** * \brief Exécute la requête http préparée par ::http_prepare() * @return boolean true : la requête a réussi, false : la requête a échoué * \private */ public function make_http_request() { $this->http_headers=""; $this->http_core=""; $cexec=curl_exec($this->curl_link); $this->close_http(); return $cexec; } /** * \brief Login pour une session cookie * * Autentification sur le serveur http et ouverture d'une session par cookie si nécessaire * @param array ::http_cookie_login paramètres a poster pour la connexion, tableau sous la forme :\n * "nom_parametre"=>"valeur" * @param string ::http_url_login adresse a appeler pour le login session * * \note L'adresse de login est appellée avec les paramètres de connexion (dans ::http_cookie_login) en POST. * La connexion est considérée comme réussie si on reçoit au moins un cookie en réponse dans l'entête.\n * Les cookies sont stockés dans le tableau ::http_cookies sous la forme "nom_cookie"=>"valeur" * * @return boolean true : la connexion/ouverture de la session a réussi, true : la connexion a échoué * \private */ public function http_do_login() { //Y-a-t-il une autentification par cookies ? if ($this->http_use_cookie) { //Préparation de la requête POST avec les éléments de login $post_vars=array(); foreach($this->http_cookie_login as $key=>$val) { $post_vars[]=$key."=".rawurlencode($val); } //Initialisation de la requête http $http_params=array( "POST"=>true, "URL"=>$this->http_url_login, "POSTFIELDS"=>implode("&",$post_vars) ); $this->prepare_http($http_params); //Remise à zero des cookies $this->http_cookies=array(); //Autentification if ($this->make_http_request()) { //A-t-on reçu des cookies ? if (count($this->http_cookies)) { return true; } else { //L'autentification a échoué $this->error=true; $this->error_message="La session http n'a pû être créée"; return false; } } else { //La requête POST n'a pas marché $this->error=true; $this->error_message="Impossible de s'autentifier sur le serveur http !"; return false; } } else return true; } /** * \brief Test de la connexion http * * Fait une requête http a l'adresse ::http_url pour vérifier que le serveur http répond * @return boolean true : le serveur répond bien, false : le serveur ne répond pas * \private */ public function test_http_connection() { //Initialisation de la connexion $http_params=array( "URL"=>$this->http_url ); $this->prepare_http($http_params); //Test de la liaison if ($this->make_http_request()) { return true; } else { $this->error=true; $this->error_message="Impossible d'obtenir une réponse à l'URL : ".$this->http_url; return false; } } /** * \brief Boucle infinie du serveur de socket * * Accepte les connexions jusqu'à concurrence de ::socket_max_connections par la socket serveur * et gère les transferts des données reçues sur les différentes sockets vers le serveur http. * \private */ public function make_loop() { // $exec_cmd est la commande permettant de lancer le serveur de la borne de prêt, une fois ce service actif. // Affecté dans init_automate.php global $exec_cmd,$socket_write_type; if ($exec_cmd) exec($exec_cmd); //Boucle continue d'attente des évênements print "Entrée dans la boucle d'écoute du serveur de socket\n"; while (true) { //Affectation de la socket principale à l'élément 0 du tableau des sockets actives $read[0] = $this->socket_server; //On effecte au reste du tableau read les sockets en cours ouvertes for ($i = 0; $i < $this->socket_max_connections; $i++) { if ($this->socket_clients[$i]['sock'] != null) $read[$i + 1] = $this->socket_clients[$i]['sock'] ; } //Préparation d'un appel bloquant à socket_select $write=NULL; $except=NULL; $ready = socket_select($read,$write,$except,null); //Si une nouvelle connextion est demandée, on l'enregistre dans le tableau des connexions courantes if (in_array($this->socket_server, $read)) { //Recherche d'une case vide... for ($i = 0; $i < $this->socket_max_connections; $i++) { if ($this->socket_clients[$i]['sock'] == null) { $this->socket_clients[$i]['sock'] = socket_accept($this->socket_server); $this->socket_clients[$i]['id'] = microtime(); //Dit "Hello !" //socket_write($this->socket_clients[$i]['sock'],"Connecte a ".$this->http_url_login."\r\n"); break; } elseif ($i == $this->socket_max_connections - 1) print ("Le nombre maximum de client est atteint, connexion refusée !"); } //Si l'appel à socket_select a échoué, on sort de la boucle if (--$ready <= 0) continue; } //Fin de la gestion d'une nouvelle connexion // Si un client tente d'écrire, on le gère ici for ($i = 0; $i < $this->socket_max_connections; $i++) { // pour chaque client if (in_array($this->socket_clients[$i]['sock'] , $read)) { //Lecture des données (4096 car au max) $input = socket_read($this->socket_clients[$i]['sock'] , 4096,PHP_BINARY_READ); if ($input == null) { //Si données = null, la socket client est déconnectée, on déconnecte socket_close($this->socket_clients[$i]['sock']); unset($this->socket_clients[$i]); } //On nettoie les caractères blancs $n = trim($input); if ($n == 'exit') { //Si on reçoit exit, on déconnecte le client socket_close($this->socket_clients[$i]['sock']); unset($this->socket_clients[$i]); } elseif ($n) { //print "Client ".$i." : ".strlen($input)." - ".(strlen($input)>1?$input:dechex(ord($input)))."\n"; //Envoi du message if ($this->make_logged_http_request($input,$this->socket_clients[$i]['id'])) { print "Client ".$this->socket_clients[$i]['id']." / Received : ".$input; //print "Response ".$i." : ".$this->http_core."\n"; //Ecriture de la réponse print "Client ".$this->socket_clients[$i]['id']." / Response : ".$this->http_core."\n"; if($socket_write_type) socket_write($this->socket_clients[$i]['sock'],ltrim($this->http_core).""); else socket_write($this->socket_clients[$i]['sock'],$this->http_core."\r\n"); } else { //Si on a pas réussi a avoir une réponse du serveur http, on déconnecte socket_close($this->socket_clients[$i]['sock']); unset($this->socket_clients[$i]); } } } } } //Fin de la boucle //Fermeture de la socket serveur socket_close($this->socket_server); } /** * \brief Lancement du service * * Teste la connexion http, réalise l'autentification pour la session cookie si nécéssaire, * lance le serveur de socket et gère les évènements socket * @return boolean false : Il y a eu une erreur lors du lancement du service le flag sockets2http::error est à true, sinon ne retourne jamais ! */ public function start_bind() { //Tests de connexion au serveur http if (!$this->test_http_connection()) return false; else //Si c'est ok, test de connexion à la session par cookie if (!$this->http_do_login()) return false; else { print "Serveur HTTP ".$this->http_url_login." contacte\n"; //Si c'est OK, on initialise le serveur de socket if (!$this->init_socket()) return false; else { $this->make_loop(); } } } } $s=new socket2http(); // Fichier de paramétrage require_once("init_automate.php"); $ret=$s->start_bind(); if (!$ret) print $s->error_message."\n"; ?>