web-dev-qa-db-fra.com

Comment puis-je mettre en œuvre un "sondage long" de base?

Je peux trouver beaucoup d’informations sur le fonctionnement de Long Polling (Par exemple, this , et this ), mais pas simple exemples de mise en oeuvre dans le code.

Tout ce que je peux trouver, c'est cometd , qui repose sur le framework Dojo JS et sur un système serveur assez complexe.

Fondamentalement, comment pourrais-je utiliser Apache pour répondre aux requêtes et comment pourrais-je écrire un script simple (en PHP, par exemple) qui interrogerait le serveur à la recherche de nouveaux messages?

L'exemple ne doit pas nécessairement être évolutif, sécurisé ou complet, il doit simplement fonctionner!

764
dbr

C'est plus simple que je ne le pensais initialement. En gros, vous avez une page qui ne fait rien jusqu'à ce que les données que vous souhaitez envoyer soient disponibles (disons qu'un nouveau message arrive).

Voici un exemple très basique, qui envoie une chaîne simple après 2-10 secondes. 1 chance sur 3 de renvoyer une erreur 404 (pour montrer la gestion des erreurs dans le prochain exemple Javascript)

msgsrv.php

<?php
if(Rand(1,3) == 1){
    /* Fake an error */
    header("HTTP/1.0 404 Not Found");
    die();
}

/* Send a string after a random number of seconds (2-10) */
sleep(Rand(2,10));
echo("Hi! Have a random number: " . Rand(1,10));
?>

Remarque: avec un site réel, son exécution sur un serveur Web classique tel qu'Apache liera rapidement tous les "threads de travail" et le laissera incapable de répondre aux autres demandes. Il existe des solutions, mais il est recommandé d'écrire un "serveur à longue interrogation" ressemblant beaucoup à twisted de Python, qui ne repose pas sur un thread par requête. cometD est populaire (disponible en plusieurs langues), et Tornado est un nouveau cadre spécialement conçu pour ce type de tâches (conçu pour le code à longue interrogation de FriendFeed) ... mais à titre d'exemple, Apache est plus que suffisant! Ce script pourrait facilement être écrit dans n’importe quel langage (j’ai choisi Apache/PHP car ils sont très courants et j’ai eu à les exécuter localement)

Ensuite, en Javascript, vous demandez le fichier ci-dessus (msg_srv.php) et attendez une réponse. Lorsque vous en obtenez un, vous agissez sur les données. Ensuite, vous demandez le fichier et attendez à nouveau, agissez sur les données (et répétez)

Ce qui suit est un exemple d’une telle page. Lorsque la page est chargée, la demande initiale du fichier msgsrv.php est envoyée. Si cela réussit, nous ajoutons le message au #messages div, puis Après 1 seconde, nous appelons à nouveau la fonction waitForMsg, ce qui déclenche l'attente.

La 1 seconde setTimeout() est un limiteur de débit vraiment basique, cela fonctionne très bien sans cela, mais si msgsrv.php toujours revient instantanément (avec une erreur de syntaxe, par exemple) - vous inondez le navigateur et il peut geler rapidement. Il serait préférable de vérifier si le fichier contient une réponse JSON valide et/ou de conserver un total cumulé de demandes par minute/seconde et de faire une pause appropriée.

Si la page est erronée, l'erreur est ajoutée au #messages div, attend 15 secondes, puis réessaie (identique à la manière dont nous attendons 1 seconde après chaque message).

La bonne chose à propos de cette approche est qu’elle est très résistante. Si la connexion Internet des clients meurt, le délai d'attente expire, puis essayez de vous reconnecter. Cela est inhérent à la durée de l'interrogation. Aucune procédure compliquée de gestion des erreurs n'est requise.

Quoi qu'il en soit, le code long_poller.htm, à l'aide du framework jQuery:

<html>
<head>
    <title>BargePoller</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

    <style type="text/css" media="screen">
      body{ background:#000;color:#fff;font-size:.9em; }
      .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
      .old{ background-color:#246499;}
      .new{ background-color:#3B9957;}
    .error{ background-color:#992E36;}
    </style>

    <script type="text/javascript" charset="utf-8">
    function addmsg(type, msg){
        /* Simple helper to add a div.
        type is the name of a CSS class (old/new/error).
        msg is the contents of the div */
        $("#messages").append(
            "<div class='msg "+ type +"'>"+ msg +"</div>"
        );
    }

    function waitForMsg(){
        /* This requests the url "msgsrv.php"
        When it complete (or errors)*/
        $.ajax({
            type: "GET",
            url: "msgsrv.php",

            async: true, /* If set to non-async, browser shows page as "Loading.."*/
            cache: false,
            timeout:50000, /* Timeout in ms */

            success: function(data){ /* called when request to barge.php completes */
                addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
                setTimeout(
                    waitForMsg, /* Request next message */
                    1000 /* ..after 1 seconds */
                );
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                addmsg("error", textStatus + " (" + errorThrown + ")");
                setTimeout(
                    waitForMsg, /* Try again after.. */
                    15000); /* milliseconds (15seconds) */
            }
        });
    };

    $(document).ready(function(){
        waitForMsg(); /* Start the inital request */
    });
    </script>
</head>
<body>
    <div id="messages">
        <div class="msg old">
            BargePoll message requester!
        </div>
    </div>
</body>
</html>
503
dbr

J'ai un exemple de chat très simple dans le cadre de slosh .

Edit : (puisque tout le monde colle son code ici)

Il s’agit de la discussion multi-utilisateurs complète basée sur JSON et utilisant une interrogation longue et slosh . Ceci est une démonstration de la façon de faire les appels, donc ignorez les problèmes XSS. Personne ne devrait déployer ceci sans l'assainir au préalable.

Notez que le client a toujours une connexion au serveur et que dès que quelqu'un envoie un message, tout le monde devrait le voir instantanément.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2008 Dustin Sallings <[email protected]> -->
<html lang="en">
  <head>
    <title>slosh chat</title>
    <script type="text/javascript"
      src="http://code.jquery.com/jquery-latest.js"></script>
    <link title="Default" rel="stylesheet" media="screen" href="style.css" />
  </head>

  <body>
    <h1>Welcome to Slosh Chat</h1>

    <div id="messages">
      <div>
        <span class="from">First!:</span>
        <span class="msg">Welcome to chat. Please don't hurt each other.</span>
      </div>
    </div>

    <form method="post" action="#">
      <div>Nick: <input id='from' type="text" name="from"/></div>
      <div>Message:</div>
      <div><textarea id='msg' name="msg"></textarea></div>
      <div><input type="submit" value="Say it" id="submit"/></div>
    </form>

    <script type="text/javascript">
      function gotData(json, st) {
        var msgs=$('#messages');
        $.each(json.res, function(idx, p) {
          var from = p.from[0]
          var msg = p.msg[0]
          msgs.append("<div><span class='from'>" + from + ":</span>" +
            " <span class='msg'>" + msg + "</span></div>");
        });
        // The jQuery wrapped msgs above does not work here.
        var msgs=document.getElementById("messages");
        msgs.scrollTop = msgs.scrollHeight;
      }

      function getNewComments() {
        $.getJSON('/topics/chat.json', gotData);
      }

      $(document).ready(function() {
        $(document).ajaxStop(getNewComments);
        $("form").submit(function() {
          $.post('/topics/chat', $('form').serialize());
          return false;
        });
        getNewComments();
      });
    </script>
  </body>
</html>
41
Dustin

Tornado est conçu pour les longues interrogations et inclut un minimum (quelques centaines de lignes de Python) application de chat dans/ examples/chatdemo , y compris le code serveur et le code client JS. Cela fonctionne comme ceci:

  • Les clients utilisent JS pour demander des mises à jour car (numéro du dernier message), le serveur URLHandler les reçoit et ajoute un rappel pour répondre au client dans une file d'attente.

  • Lorsque le serveur reçoit un nouveau message, l'événement onmessage est déclenché, parcourt les rappels et envoie les messages.

  • Le JS côté client reçoit le message, l'ajoute à la page, puis demande des mises à jour depuis ce nouvel ID de message.

32
mikemaccana

Je pense que le client ressemble à une demande asynchrone normale AJAX, mais vous vous attendez à ce que cela prenne "longtemps".

Le serveur ressemble alors à ceci.

while (!hasNewData())
    usleep(50);

outputNewData();

Ainsi, la demande AJAX est envoyée au serveur, avec probablement un horodatage de la dernière mise à jour, de sorte que votre hasNewData() sache quelles données vous avez déjà obtenues. Le serveur reste ensuite en boucle jusqu'à ce que de nouvelles données soient disponibles. Pendant tout ce temps, votre demande AJAX est toujours connectée, en attente de données. Enfin, lorsque de nouvelles données sont disponibles, le serveur les transmet à votre demande AJAX et ferme la connexion.

24
Greg

Here Voici certaines classes que j'utilise pour l'interrogation longue en C #. Il y a essentiellement 6 classes (voir ci-dessous).

  1. Contrôleur : traite les actions requises pour créer une réponse valide (opérations de base de données, etc.)
  2. Processeur : gère la communication asynchrone avec la page Web (elle-même)
  3. IAsynchProcessor : le service traite les instances qui implémentent cette interface.
  4. Sevice : Traite les objets de requête qui implémentent IAsynchProcessor
  5. Request : le wrapper IAsynchProcessor contenant votre réponse (objet)
  6. Réponse : contient des objets personnalisés ou des champs
17
Prisoner ZERO

Ceci est un joli screencast de 5 minutes sur la façon de mener une longue interrogation en utilisant PHP & jQuery: http://screenr.com/SNH

Le code est assez similaire à l'exemple de dbr ci-dessus.

16
Sean O

Voici exemple simple d'interrogation longue dans PHP par Erik Dubbelboer à l'aide de l'en-tête Content-type: multipart/x-mixed-replace:

<?

header('Content-type: multipart/x-mixed-replace; boundary=endofsection');

// Keep in mind that the empty line is important to separate the headers
// from the content.
echo 'Content-type: text/plain

After 5 seconds this will go away and a cat will appear...
--endofsection
';
flush(); // Don't forget to flush the content to the browser.


sleep(5);


echo 'Content-type: image/jpg

';

$stream = fopen('cat.jpg', 'rb');
fpassthru($stream);
fclose($stream);

echo '
--endofsection
';

Et voici une démo:

http://dubbelboer.com/multipart.php

12
Jasdeep Khalsa

J'ai utilisé this pour me familiariser avec Comet, j'ai également configuré Comet à l'aide du serveur Glassfish Java et trouvé de nombreux autres exemples en vous abonnant à cometdaily.com.

11
adam

Jetez un oeil à cet article de blog qui contient le code d'une application de chat simple en Python/Django/ gevent .

9
Denis Bilenko

Vous trouverez ci-dessous une solution d'interrogation longue que j'ai développée pour Inform8 Web. En gros, vous substituez la classe et implémentez la méthode loadData. Lorsque le paramètre loadData renvoie une valeur ou que l'opération a expiré, le résultat est retourné.

Si le traitement de votre script peut durer plus de 30 secondes, vous devrez peut-être modifier l'appel set_time_limit () pour le remplacer par quelque chose de plus long.

Licence Apache 2.0. Dernière version sur github https://github.com/ryanhend/Inform8/blob/master/Inform8-web/src/config/lib/Inform8/longpoll/LongPoller.php

Ryan

abstract class LongPoller {

  protected $sleepTime = 5;
  protected $timeoutTime = 30;

  function __construct() {
  }


  function setTimeout($timeout) {
    $this->timeoutTime = $timeout;
  }

  function setSleep($sleep) {
    $this->sleepTime = $sleepTime;
  }


  public function run() {
    $data = NULL;
    $timeout = 0;

    set_time_limit($this->timeoutTime + $this->sleepTime + 15);

    //Query database for data
    while($data == NULL && $timeout < $this->timeoutTime) {
      $data = $this->loadData();
      if($data == NULL){

        //No new orders, flush to notify php still alive
        flush();

        //Wait for new Messages
        sleep($this->sleepTime);
        $timeout += $this->sleepTime;
      }else{
        echo $data;
        flush();
      }
    }

  }


  protected abstract function loadData();

}
9
Ryan Henderson

Merci pour le code, dbr. Juste une petite faute de frappe dans long_poller.htm autour de la ligne

1000 /* ..after 1 seconds */

Je pense que ça devrait être

"1000"); /* ..after 1 seconds */

pour que cela fonctionne.

Pour ceux que ça intéresse, j'ai essayé un équivalent Django. Commencez un nouveau projet Django, disons lp pour une interrogation longue:

Django-admin.py startproject lp

Appelez l'application msgsrv pour le serveur de messages:

python manage.py startapp msgsrv

Ajoutez les lignes suivantes à settings.py pour avoir un modèle ) répertoire:

import os.path
PROJECT_DIR = os.path.dirname(__file__)
TEMPLATE_DIRS = (
    os.path.join(PROJECT_DIR, 'templates'),
)

Définissez vos modèles d'URL dans urls.py en tant que tel:

from Django.views.generic.simple import direct_to_template
from lp.msgsrv.views import retmsg

urlpatterns = patterns('',
    (r'^msgsrv\.php$', retmsg),
    (r'^long_poller\.htm$', direct_to_template, {'template': 'long_poller.htm'}),
)

Et msgsrv/ views.py devrait ressembler à ceci:

from random import randint
from time import sleep
from Django.http import HttpResponse, HttpResponseNotFound

def retmsg(request):
    if randint(1,3) == 1:
        return HttpResponseNotFound('<h1>Page not found</h1>')
    else:
        sleep(randint(2,10))
        return HttpResponse('Hi! Have a random number: %s' % str(randint(1,10)))

Enfin, les templates/ long_poller.htm devraient être les mêmes que ci-dessus avec une faute de frappe corrigée. J'espère que cela t'aides.

8
xoblau

C’est l’un des scénarios pour lesquels PHP est un très mauvais choix. Comme mentionné précédemment, vous pouvez attacher très rapidement tous vos employés Apache à faire quelque chose comme ça. PHP est construit pour démarrer, exécuter, arrêter. Ce n'est pas construit pour démarrer, attendez ... exécutez, arrêtez. Vous allez encombrer votre serveur très rapidement et constater que vous avez des problèmes de mise à l'échelle incroyables.

Cela dit, vous pouvez toujours le faire avec PHP et ne pas tuer votre serveur avec le nginx HttpPushStreamModule: http://wiki.nginx.org/HttpPushStreamModule

Vous installez nginx devant Apache (ou autre) et il se chargera de maintenir ouvertes les connexions simultanées. Vous répondez simplement avec une charge utile en envoyant des données à une adresse interne que vous pouvez utiliser avec un travail en arrière-plan ou tout simplement en envoyant les messages aux personnes en attente à chaque nouvelle demande. Cela empêche les processus PHP de rester en attente. pendant les longues élections.

Ceci n'est pas exclusif à PHP et peut être fait avec nginx avec n'importe quel langage backend. La charge des connexions ouvertes simultanées est égale à Node.js, donc le principal avantage est de vous sortir de besoin de Node] pour quelque chose comme ceci.

Vous voyez beaucoup d'autres personnes qui citent d'autres bibliothèques de langues pour leurs longs sondages, et ce pour de bonnes raisons. PHP n’est tout simplement pas bien construit pour ce type de comportement naturellement.

7
brightball

Pourquoi ne pas envisager les sockets Web au lieu de longues interrogations? Ils sont beaucoup efficaces et faciles à installer. Cependant, ils ne sont supportés que par les navigateurs modernes. Voici une référence rapide .

4
shasi kanth

Le groupe WS-I a publié un document appelé "Profil sécurisé fiable" qui possède un poisson en verre et implémentation .NET qui apparemment interopère .

Avec un peu de chance, une implémentation de Javascript est également disponible.

Il existe également une implémentation Silverlight qui utilise HTTP Duplex. Vous pouvez connecter du javascript à Silverlight pour obtenir des rappels lorsqu’un Push a lieu.

Il existe également versions payantes commerciales également.

3
goodguys_activate

Pour une implémentation ASP.NET MVC, consultez SignalR disponible sur NuGet .. Notez que NuGet est souvent obsolète par rapport à Git source , qui reçoit des commits très fréquents. .

En savoir plus sur SignalR sur un blog de Scott Hanselman

2
goodguys_activate

Vous pouvez essayer icomet ( https://github.com/ideawu/icomet ), un serveur comète C1000K C++ construit avec libevent. icomet fournit également une bibliothèque JavaScript, il est facile à utiliser aussi simple que

var comet = new iComet({
    sign_url: 'http://' + app_Host + '/sign?obj=' + obj,
    sub_url: 'http://' + icomet_Host + '/sub',
    callback: function(msg){
        // on server Push
        alert(msg.content);
    }
});

icomet prend en charge un large éventail de navigateurs et de systèmes d'exploitation, notamment Safari (iOS, Mac), IE (Windows), Firefox, Chrome, etc.

2
ideawu

ok, je ne sais pas ce qu’ils pensent, mais je peux vous offrir un avis pur, autochtone et brillant.

1) faites votre ajax toutes les 5 secondes pour que chaque utilisateur appelle un fichier texte. La valeur est parfois 0 et parfois 1 2) si quelqu'un envoie votre message client, créez un fichier dont le nom est id + nom d'utilisateur.txt par php (c'est un fichier qui est appelé dans la première étape) 3) lorsque le message est envoyé dans une base de données, il insère également la valeur 1 dans un fichier texte 4) si la valeur du fichier texte est égale à 1, le côté client frappe un côté serveur pour obtenir un message. 5) après tout, la fonction d’appel client qui insère 1 dans un fichier texte.

0
Ilia Weber

NodeJS le plus simple

const http = require('http');

const server = http.createServer((req, res) => {
  SomeVeryLongAction(res);
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(8000);

// the long running task - simplified to setTimeout here
// but can be async, wait from websocket service - whatever really
function SomeVeryLongAction(response) {
  setTimeout(response.end, 10000);
}

En ce qui concerne la production dans Express, par exemple, vous obtiendrez response dans le middleware. Faites-vous ce que vous devez faire, pouvez définir toutes les méthodes longues interrogées pour mapper ou quelque chose (visible pour d’autres flux), et invoquer <Response> response.end() chaque fois que vous êtes prêt. Il n'y a rien de spécial dans les longues connexions interrogées. Reste est juste comment vous structurez normalement votre application.

Si vous ne savez pas ce que je veux dire par définition, cela devrait vous donner une idée.

const http = require('http');
var responsesArray = [];

const server = http.createServer((req, res) => {
  // not dealing with connection
  // put it on stack (array in this case)
  responsesArray.Push(res);
  // end this is where normal api flow ends
});

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

// and eventually when we are ready to resolve
// that if is there just to ensure you actually 
// called endpoint before the timeout kicks in
function SomeVeryLongAction() {
  if ( responsesArray.length ) {
    let localResponse = responsesArray.shift();
    localResponse.end();
  }
}

// simulate some action out of endpoint flow
setTimeout(SomeVeryLongAction, 10000);
server.listen(8000);

Comme vous le voyez, vous pouvez vraiment répondre à toutes les connexions, une, faites ce que vous voulez. Il y a id pour chaque demande, vous devriez donc pouvoir utiliser la carte et accéder à des appels spécifiques en dehors de l'api.

0
sp3c1