Apprendre et créer
AccueilProgrammationCréer une API PHP et javascriptAPI listener en PHP grâce à session_write_close()

API listener en PHP grâce à session_write_close()

Le 13-01-2020...

Exposition du problème

Voici un problème qui m'a pris la tête un moment. Aussi bien que j'ai cru qu'il serait impossible de réaliser cet combinaison PHP/Javascript d'API listener.

Mais quel est ce problème ?

Lorsque je lançais mon script, le listener fonctionnait parfaitement.

Par contre, quand j'essayais d’accéder à l'API quand le listener était lancé, rien ne se passait.

Comme si l'API ne pouvait être lancée deux fois en simultanée et que le listener bloquait tout autre utilisation du fichier.

Explication du problème

Après de nombreuses errances à la recherche d'une explication et d'une solution, j'ai enfin trouvé d'où venait ce problème grâce à un article de blog : https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/

Merci beaucoup à l'auteur !

Le problème vient du blocage de SESSION en PHP.

En résumé, quand on fait un session_start(), PHP accède au fichier qui stock les données de $_SESSION et applique blocage en lecture et écriture du fichier.

Quand le script se termine, PHP enregistre les changements dans le fichier de SESSION et enlève le blocage du fichier, ce qui permet à d'autres scripts de faire appelle aux SESSIONS.

Le problème c'est que notre API est tout le temps en cours de chargement donc le fichier de SESSION est constamment bloqué, ce qui ne permet aucun autre script de charger les SESSIONS.

Heureusement il existe un moyen de contourner ce blocage. Tout simplement en forçant PHP à enregistrer les modifications de SESSION et à lever le blocage du fichier.

Voici les fichiers de mon système PHP/Javascript d'API listener

Pour tester le système, j'ai réalisé un petit script qui permet de savoir combien de fois le bouton clic a été cliqué.

A chaque clic, l'API est appelée et ajoute 1 au nombre de clics.

Et quand un changement à eu lieu, le serveur renvoie la donnée au listener, puis une nouvelle requête est lancée.

Mais quel intérêt ?? Je vous l'accorde, réaliser un compteur qui s'incrémente en cliquant sur un bouton ne requière pas tant de code, il est même réalisable en Javascript pure...

Mais pour comprendre la puissance de ce code, imaginez que si deux personnes sont sur le site, chacun peut voir en temps réel quand l'autre vient de cliquer sur le bouton... ! Et cela sans spammer le serveur ! C'est ça que permet ce script !!

Index.php

<!DOCTYPE html>
<html lang="fr">
<head>
    <title>Test API PHP Javascript</title>
</head>
<body>
    Nombre de clics : <span id = "data"></span>
    <button onClick = "newClick()">Clic</button>
    <script type="text/javascript" src="javascript.js"></script>
    <script>
        getServerData();
    </script>
    
</body>
</html>

javascript.js

function getServerData() {
    let xhr = new XMLHttpRequest();
    // pour forcer chargement si contenu vide (cas de l'actualisation de la page)
    let forceGetServerData = document.querySelector('#data').innerHTML == '' ? 1 : 0;
    xhr.open('GET', 'api.php?getServerData=' + forceGetServerData);
    xhr.send();
    
    xhr.onload = function () {
        let response = xhr.responseText;
        if( response != '' ) {
            document.querySelector('#data').innerHTML = response;
        }
        setTimeout(getServerData, 30);
    };
    
}
function newClick() {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', 'api.php?newClick');
    xhr.send();
}

api.php

<?php
// session start puis close pour charger les varibles de session puis lever le blocage du fichier de session.
session_start();
session_write_close();
if( isset($_GET['getServerData']) ) {
    $_SESSION['lastDataSent'] = $_SESSION['lastDataSent'] ?? '';
    
    $data = getServerData();
    // Si $_GET['getServerData'] est à 0 on entre dans la boucle, à 1 on force l'envoi des données
    $startMicrotime = microtime(TRUE);
    while( $_GET['getServerData'] == 0 AND $data == $_SESSION['lastDataSent'] AND (microtime(TRUE) - $startMicrotime) < 60) {
        usleep(20000);
        $data = getServerData();
    }
    
    if( $_GET['getServerData'] == 1 OR $data != $_SESSION['lastDataSent'] ) {
        session_start();
        $_SESSION['lastDataSent'] = $data;
        echo $data;
    }
}
if( isset($_GET['newClick']) ) {
    $clickNumber = file_get_contents('clickNumber.txt');
    $clickNumber++;
    file_put_contents('clickNumber.txt', $clickNumber);
}
function getServerData() {
    $clickNumber = file_get_contents('clickNumber.txt');
    return $clickNumber;
}

clickNumber.txt

0

Et voilà, en lisant le code vous pouvez voir comment j'ai utilisé la fonction session_write_close() pour bloquer au minimum le fichier de session.

Avec cette base nous allons pouvoir améliorer le script de jeu de Morpion pour qu'il soit aussi économique de ressources que possible.