web-dev-qa-db-fra.com

Transmission de données au navigateur de l'utilisateur depuis le serveur

Je veux pousser des données du serveur au navigateur. Je suis déjà au courant de la fonction php ob_flush() qui envoie un tampon de sortie. J'ai besoin d'aide avec un peu de logique. J'utilise l'API en temps réel de Facebook et je souhaite donc transmettre des données à l'utilisateur chaque fois que Facebook visite mon site Web.

Voici mon code que j'essaye de transmettre des données au navigateur, mais cela ne fonctionne pas.

<?php
header('Access-Control-Allow-Origin: *');
header('Content-Type: text/event-stream');
ini_set("log_errors", 1);
ini_set("error_log", "php-error.log");
error_log( "LOGS STARTS FROM HERE" );
if(isset($_GET['hub_challenge'])){
    echo $_GET['hub_challenge'];    
}
if($_SERVER['REQUEST_METHOD'] == "POST"){
    $updates = json_decode(file_get_contents("php://input"), true); 
    // Replace with your own code here to handle the update 
    // Note the request must complete within 15 seconds.
    // Otherwise Facebook server will consider it a timeout and 
    // resend the Push notification again.
    print_r($updates);
    ob_flush();
    flush();
    //file_put_contents('fb.log', print_r($updates,true), FILE_APPEND);     
    //error_log('updates = ' . print_r($updates, true));              
}
?>
21
Akash Kumar

Comme @som l'a suggéré, vous pouvez simplement utiliser des demandes avec un intervalle entre elles, vous n'avez pas besoin d'utiliser de sockets.

Mais le fait est que vous essayez de recevoir des données de l'API et de les transmettre directement au navigateur, en une seule fois. Il est préférable de séparer ces deux étapes.

Dans le script qui reçoit les données de Facebook, stockez ces données dans une base de données ou ailleurs:

if($_SERVER['REQUEST_METHOD'] == "POST"){
    $updates = json_decode(file_get_contents("php://input"), true); 

    insertDataToDatabase($updates); // you'll have to implement this.
}

Ensuite, une configuration page de surveillance:

monitor.php

<script>
lastId = 0;

$(document).ready(function() {
    getNewDataAtInterval();
});

function getNewDataAtInterval() {
    $.ajax({
        dataType: "json",
        url: "getData.php",
        data: {lastId: lastId}
    }).done(function(data) {
        for(i=0; i<data.messages.length; i++) {
            $("#messages").append("<p>" + data.messages[i]['id'] + ": " + data.messages[i]['message'] + "</p>");
            if (data.messages[i]['id'] > lastId) lastId = data.messages[i]['id'];
        }

        setTimeout(getNewDataAtInterval, 10000);
    }).fail(function( jqXHR, textStatus ) {
        alert( "Request failed: " + jqXHR.responseText );
    });
}
</script>

<div id="messages"></div>

Enfin, créez un script côté serveur pour renvoyer un JSON avec les nouveaux messages chargés à partir de la base de données.

getData.php

$lastId = $_GET['lastId'];
$newMessages = getUpdatesFromDatabase($lastId);

exit(json_encode(array("messages"=>$newMessages)));

function getUpdatesFromDatabase($lastId) {
    // I'm using this array just as an example, so you can see it working.
    $myData = array(
        array("id"=>1,"message"=>"Hi"),
        array("id"=>2,"message"=>"Hello"),
        array("id"=>3,"message"=>"How are you?"),
        array("id"=>4,"message"=>"I'm fine, thanks")
    );

    $newMessages = array();
    foreach($myData as $item) {
        if ($item["id"] > $lastId) {
            $newMessages[] = $item;
            $newLastId = $item["id"];
        }
    }

    return $newMessages;
}
8
Marcos Dimitrio

L'utilisation de Comet ou Prototype sera la meilleure pratique ici. Ajax augmentera la charge du serveur. Il interrogera le serveur fréquemment. Voici quelques informations nécessaires sur l’utilisation de comet.

Voici l'exemple de la mise en œuvre de comet with php pour envoyer des données en temps réel.

3
Pratik Soni

J'ai trouvé Pusher et Redis pour les données Push de serveur à navigateur alors que je cherchais la meilleure solution.

Poussoir

Vous pouvez facilement utiliser les événements diffusés à l'aide du pilote Pusher à l'aide du SDK JavaScript de Pusher.

this.pusher = new Pusher('pusher-key');

this.pusherChannel = this.pusher.subscribe('reference_id');

this.pusherChannel.bind('SomeEvent', function(message) {
    console.log(message.user);
});

Redis

Si vous utilisez le diffuseur Redis, vous devez écrire votre propre consommateur/sous-consommateur Redis pour recevoir les messages et les diffuser à l'aide de la technologie websocket de votre choix. Par exemple, vous pouvez choisir d'utiliser la bibliothèque Socket.io bien connue, écrite en nœud.

À l'aide des bibliothèques de nœuds socket.io et ioredis, vous pouvez écrire rapidement un diffuseur d'événements pour publier tous les événements diffusés par votre application:

var app = require('http').createServer(handler);
var io = require('socket.io')(app);

var Redis = require('ioredis');
var redis = new Redis();

app.listen(6001, function() {
    console.log('Server is running!');
});

function handler(req, res) {
    res.writeHead(200);
    res.end('');
}

io.on('connection', function(socket) {
    //
});

redis.psubscribe('*', function(err, count) {
    //
});

redis.on('pmessage', function(method, channel, message) {
    message = JSON.parse(message);
    io.emit(channel + ':' + message.event, message.data);
});
2
Pratik Soni

Pourquoi avoir besoin de pousser? Peut-être me manque-t-il un indice, mais sinon, est-ce le seul moyen de résoudre ce problème? Vous voulez pouvoir définir le texte d'une dise que nous appelons statusUpdate, qui montre les nouveaux statuts de facebook lorsqu'ils sont postés? Ensuite, vous pourriez:

Divisez le processus en un thread de collection d’état qui s’exécute en tant que démon et tente continuellement d’extraire à partir de l’API du FB (vous ne connaissez aucune spécification ni n’avez aucune connaissance de l’API du FB, mais je ne peux qu’imaginer qu’il existe des appels à rechercher s'il y a de nouveaux statuts).

Peu importe que l'API soit en flux continu ou que nous devions nous connecter toutes les X secondes pour que nous puissions en tenir compte. Je voudrais installer un démon en php avec puis l'exécuter avec SSH avec la commande: Nohup php daemon.php pour démarrer un script avec une boucle sans fin comme celle-ci:

Define('SLEEP', 1);  // loop every second

while (true) {  

   // do your thing, dont exit

   if( $fbMonkey->checkNewStatus() ){
        $fbMonkey->toDatabase(new_statuses);
  }

   if( some_reason_to_exit() == TRUE ){
      exit;
   }

    sleep(SLEEP);  
}
// While ends with break

Vous pouvez ensuite inclure dans le code HTML de l’utilisateur cible (fin du navigateur) une fonction JavaScript qui lit dans la table les états remplis par le démon, puis ceux qui n’ont pas été marqués comme étant vus (par des utilisateurs ou quelque chose de similaire) les statuts non lus au navigateur. Si nous créons une boucle sans fin dans le navigateur et laissons la mise à jour de div statusUpdate et du nouveau contenu (html ou tekst ne compte pas?). Je laisserais un appel comme celui-ci s'attarder, vérifier toutes les 20 secondes environ et mettre à jour le div.

http://www.w3schools.com/ajax/tryit.asp?filename=tryajax_xml2

function loadXMLDoc(url)
{
var xmlhttp;
var txt,xx,x,i;
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
xmlhttp.onreadystatechange=function()
  {
  if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
    txt="<table border='1'><tr><th>Title</th><th>Artist</th></tr>";
    x=xmlhttp.responseXML.documentElement.getElementsByTagName("CD");
    for (i=0;i<x.length;i++)
      {
      txt=txt + "<tr>";
      xx=x[i].getElementsByTagName("TITLE");
        {
        try
          {
          txt=txt + "<td>" + xx[0].firstChild.nodeValue + "</td>";
          }
        catch (er)
          {
          txt=txt + "<td>&nbsp;</td>";
          }
        }
    xx=x[i].getElementsByTagName("ARTIST");
      {
        try
          {
          txt=txt + "<td>" + xx[0].firstChild.nodeValue + "</td>";
          }
        catch (er)
          {
          txt=txt + "<td>&nbsp;</td>";
          }
        }
      txt=txt + "</tr>";
      }
    txt=txt + "</table>";
    document.getElementById('txtCDInfo').innerHTML=txt;
    }
  }
xmlhttp.open("GET",url,true);
xmlhttp.send();
}

Ou suis-je complètement de la marque ici?

1
Daniel Mulder

Avec le protocole HTTP conventionnel, les navigateurs Web demandent toujours une réponse à votre serveur. Une fois la réponse envoyée, le serveur ferme la connexion.

La véritable technologie de connexion persistante telle que WebSocket est l’une des exceptions dans laquelle un navigateur établit un type de connexion spécifique au serveur et, ayant l’intention de l’intention, le serveur maintiendrait la connexion ouverte. De cette façon, dès que des données doivent être "transmises" au navigateur, la connexion est toujours prête. En utilisant cette approche, il n’est pas nécessaire de sauvegarder les données temporairement, car il vous suffit de les "transmettre".

Les techniques d'interrogation (y compris l'interrogation longue, dans laquelle le serveur continue d'envoyer des "pulsations" au client comme si une réponse non significative s'écoulait lentement) peuvent être utilisées comme solution de contournement, mais il y a toujours un intervalle de temps où la connexion n'est plus. ouvert, jusqu'au cycle suivant. Lorsque la connexion est absente, votre seule option est de sauvegarder les données temporairement afin que, lorsque votre navigateur revient, vous pouvez envoyer les données en attente. Si vous envisagez de la stocker dans une variable temporaire, tenez compte du fait qu'avec le script PHP, une fois l'exécution terminée, toutes les données allouées en mémoire associées à cette étendue sont récupérées.

1
andy

Bonne discussion. Moi J'aime: D @andy: génial, pour moi la première fois que quelqu'un pourrait expliquer la différence très précisément et je l'obtenir à: D @Marcos Dimitrio, je suis d'accord

Moi-même, je gère un pool de threads vachement méchant de démons d'API Twitter qui font exactement cela, à l'exception de la fonction $ _POST Push de facebook si je comprends bien. Il surveille les tweets en temps réel pour les tableaux de centaines/milliers de groupes de mots-clés via l’API streamin Firehose. C’est la voie à suivre ou subir une terrible défaite autrement: D IMHO bien sûr. Il s'agit de la moitié de deux démons appelés getTweets et parseTweets.

<?php
ob_start();
require_once('config/phirehose-config.php');
require_once('lib.php');
$oDB = new db;

// run as a daemon aka background process
while (true) {

  // Process all statuses
  $query = 'SELECT cache_id, raw_Tweet ' .
    'FROM json_cache';
  $result = $oDB->select($query);
  while($row = mysqli_fetch_assoc($result)) {

    $cache_id = $row['cache_id'];
//    $status = unserialize(base64_decode($row['raw_Tweet']));
    $Tweet_object = json_decode($row['raw_Tweet'],false);


    // JSON payload for statuses stored in the database  
    // serialized base64 raw data

      // Delete cached copy of Tweet
      //    $oDB->select("DELETE FROM json_cache WHERE cache_id = $cache_id");

        // Limit tweets to a single language,
        // such as 'en' for English
        //if ($Tweet_object->lang <> 'nl') {continue;}

    // Test status update before inserting
    $Tweet_id = $Tweet_object->id_str;

    if ($oDB->in_table('tweets','Tweet_id=' . $Tweet_id )) {continue;}

    $Tweet_text = $oDB->escape($Tweet_object->text);    
    $created_at = $oDB->date($Tweet_object->created_at);
    if (isset($Tweet_object->geo)) {
      $geo_lat = $Tweet_object->geo->coordinates[0];
      $geo_long = $Tweet_object->geo->coordinates[1];
    } else {
      $geo_lat = $geo_long = 0;
    } 
    $user_object = $Tweet_object->user;
    $user_id = $user_object->id_str;
    $screen_name = $oDB->escape($user_object->screen_name);
    $name = $oDB->escape($user_object->name);
    $profile_image_url = $user_object->profile_image_url;


    // Add a new user row or update an existing one
    $field_values = 'screen_name = "' . $screen_name . '", ' .
      'profile_image_url = "' . $profile_image_url . '", ' .
      'user_id = ' . $user_id . ', ' .
      'name = "' . $name . '", ' .
      'location = "' . $oDB->escape($user_object->location) . '", ' . 
      'url = "' . $user_object->url . '", ' .
      'description = "' . $oDB->escape($user_object->description) . '", ' .
      'created_at = "' . $oDB->date($user_object->created_at) . '", ' .
      'followers_count = ' . $user_object->followers_count . ', ' .
      'friends_count = ' . $user_object->friends_count . ', ' .
      'statuses_count = ' . $user_object->statuses_count . ', ' . 
      'time_zone = "' . $user_object->time_zone . '", ' .
      'last_update = "' . $oDB->date($Tweet_object->created_at) . '"' ;     

    if ($oDB->in_table('users','user_id="' . $user_id . '"')) {
      $oDB->update('users',$field_values,'user_id = "' .$user_id . '"');
    } else {            
      $oDB->insert('users',$field_values);
    }

    // percist status to database

    $field_values = 'Tweet_id = ' . $Tweet_id . ', ' ....


    //... Somethings are to be for da cook alone, its hard work          

            foreach ($entities->hashtags as $hashtag) {

      $where = 'Tweet_id=' . $Tweet_id . ' ' .
        'AND tag="' . $hashtag->text . '"';     

      if(! $oDB->in_table('Tweet_tags',$where)) {

        $field_values = 'Tweet_id=' . $Tweet_id . ', ' .
          'tag="' . $hashtag->text . '"';   

        $oDB->insert('Tweet_tags',$field_values);
      }
    }
    foreach ($entities->urls as $url) {

      if (empty($url->expanded_url)) {
        $url = $url->url;
      } else {
        $url = $url->expanded_url;
      }

      $where = 'Tweet_id=' . $Tweet_id . ' ' .
        'AND url="' . $url . '"';       

      if(! $oDB->in_table('Tweet_urls',$where)) {
        $field_values = 'Tweet_id=' . $Tweet_id . ', ' .
          'url="' . $url . '"'; 

        $oDB->insert('Tweet_urls',$field_values);
      }
    }       
  } 

  if(DEBUG){ 
     echo ob_get_contents();
     ob_clean();
  }else{
     ob_clean();
  }

  // Longer sleep equals lower server load
  sleep(1);
}
?>

Cela fonctionne aussi très bien pour les araignées et les chenilles pour lesquels j'ai mon propre équipage. Montrez-moi un meilleur moyen de le faire, tout ce qui est considéré comme les ressources et l’évolutivité en tant que widget de site Web connecté de manière permanente pour les mises à jour de statut FB revient à utiliser Echelon comme télécommande à nouveau imho).

0
Daniel Mulder

Envoyez une demande uniquement lorsque vous récupérez les données du serveur. Et sur le serveur, dormez jusqu'à ce que vous trouviez quelque chose de nouveau à envoyer et à renvoyer la réponse.

Continuez ce qui précède jusqu'à ce que votre page soit active et que vous voyiez les données transférées. Cela évite d'envoyer une requête ping au serveur à intervalles réguliers.

Je ne sais pas s'ils font référence à cela ici/ https://en.wikipedia.org/wiki/Comet_(programming)

0
Manoj Kumar

Si vous avez juste besoin d'une solution simple et que vous n'êtes pas inquiet à propos de la compatibilité des navigateurs plus anciens et que vous avez un trafic faible, les événements envoyés par le serveur pourraient faire l'affaire.

Vous l'instanciez avec une ligne si votre script de génération de message Push est sur le même serveur.

var evtSource = new EventSource("messages.php");

Puis une fonction pour gérer les messages entrants.

    evtSource.onmessage = function(e) {

       console.log(e.data);
    }

messages.php doit avoir l'en-tête défini comme

header("Content-Type: text/event-stream\n\n");

Ensuite, faites une boucle infinie définie à un intervalle comme vous le souhaitez.

exemple:

header("Content-Type: text/event-stream\n\n");
date_default_timezone_set("Pacific/Auckland"); // as required
$sleepTime = 8; // # of seconds delayed between notifications

while (1) 
{   

   // Send a message at $sleepTime second intervals.
   echo 'data: The time is ' . date("H:i:s") . "\n\n";

   ob_end_flush();
   flush();
   sleep($sleepTime);
}

Évidemment, vous devez lire les messages quelque part et les présenter au besoin, mais au moins cet exemple vous donne une idée de la création d'un flux d'événements sans avoir à entrer dans la complexité relative de WebSocket.

Avertissement: je ne suis pas très expérimenté avec PHP mais cette solution semble fonctionner pour moi. Si cela vous pose problème, je serais ravi de l'entendre.

0
ekendra

Choix 1 (Si vous voulez un système fiable): Une combinaison de serveur de file d’attente (comme AMQP ou MQTT, etc.) et de client Websocket comme http://www.hivemq.com/full-featured-mqtt-client-browser/ sera robuste. Presque toutes les langues prennent en charge AMQP, par exemple. dans PHP ( https://pecl.php.net/package/amqp ).

Choix 2: Utilisez ajax et indiquez à votre navigateur pour obtenir les mises à jour du serveur dans un intervalle périodique (comme déjà mentionné dans les commentaires)

0
Hemant Kumar