2012-01-30 10 views
12

Sto cercando di fare i conti con gli eventi lato server, dato che si adattano perfettamente alle mie esigenze e sembrano come dovrebbero essere semplici da implementare, tuttavia non riesco a superare un errore vago e quello che sembra che la connessione venga ripetutamente chiusa e riaperto. Tutto ciò che ho provato è basato su this e altri tutorial.Creazione di prototipi HTML5 Server-Sent Events - errore ambiguo e polling ripetuto?

Il PHP è un singolo script:

<?php 
header('Content-Type: text/event-stream'); 
header('Cache-Control: no-cache'); 

function sendMsg($id, $msg) { 
    echo "id: $id" . PHP_EOL; 
    echo "data: $msg" . PHP_EOL; 
    echo PHP_EOL; 
    ob_flush(); 
    flush(); 
} 

$serverTime = time(); 
sendMsg($serverTime, 'server time: ' . date("h:i:s", time())); 
?> 

e il codice JavaScript assomiglia a questo (eseguito su carico del corpo):

function init() { 

    var source; 
    if (!!window.EventSource) { 
     source = new EventSource('events.php'); 
     source.addEventListener('message', function(e) { 
      document.getElementById('output').innerHTML += e.data + '<br />'; 
     }, false); 
     source.addEventListener('open', function(e) { 
      document.getElementById('output').innerHTML += 'connection opened<br />'; 
     }, false); 
     source.addEventListener('error', function(e) { 
      document.getElementById('output').innerHTML += 'error<br />'; 
     }, false); 
    } 
    else { 
     alert("Browser doesn't support Server-Sent Events"); 
    } 
} 

Ho cercato un po 'intorno, ma non riesce a trovare le informazioni su

  1. Se Apache necessita di una configurazione speciale per supportare eventi inviati dal server e
  2. Come posso avviare un push dal server con questo tipo di configurazione (ad es. posso semplicemente eseguire lo script PHP dalla CLI per dare una spinta al browser già connesso?)

Se eseguo questo JS in Chrome (16.0.912.77) apre la connessione, riceve l'ora, quindi errori (senza informazioni utili nell'oggetto error), quindi ricollega in 3 secondi e passa attraverso lo stesso processo. In Firefox (10.0) ottengo lo stesso comportamento.

EDIT 1: Ho pensato che il problema potesse essere correlato al server che stavo usando, quindi ho provato su un'installazione XAMPP vanilla e lo stesso errore si è verificato. Una configurazione di base del server dovrebbe essere in grado di gestirlo senza modifiche/configurazione aggiuntiva?

EDIT 2: Il seguente è un esempio di output dal browser:

connection opened 
server time: 01:47:20 
error 
connection opened 
server time: 01:47:23 
error 
connection opened 
server time: 01:47:26 
error 

qualcuno può dirmi dove questo sta andando male? Le esercitazioni che ho visto fanno sembrare che l'SSE sia molto semplice. Anche eventuali risposte alle mie due domande numerate sopra sarebbero davvero utili.

Grazie.

+1

puoi pubblicare il tuo cronometro, il codice fornito è privo di errori. Si rilascia l'oggetto EventSource in qualsiasi momento? –

+1

@tnt Non potrò postare il codice fino a lunedì, ma non esiste un ciclo a tempo - il comportamento predefinito di EventSource è di riconnettersi 3 secondi dopo la perdita della connessione. Una singola funzione (init(), sopra) viene chiamata una volta sul carico del corpo che crea la connessione, quindi il browser inserisce il suo ciclo di connessione-errore-riconnessione tutto da solo. – tomfumb

+1

Da W3C: i server proxy legacy sono noti per, in alcuni casi, rilasciare connessioni HTTP dopo un breve timeout. Per proteggersi da tali server proxy, gli autori possono includere una riga di commento (una che inizia con un carattere ':') ogni 15 secondi circa. –

risposta

21

Il problema è il tuo php.

Con il modo in cui è scritto lo script php, viene inviato un solo messaggio per esecuzione. È così che funziona se accedi direttamente al file php, ed è così che funziona se accedi al file con un EventSource. Quindi, per fare in modo che lo script php invii più messaggi, è necessario un ciclo.

<?php 
header('Content-Type: text/event-stream'); 
header('Cache-Control: no-cache'); 

function sendMsg($id, $msg) { 
    echo "id: $id" . PHP_EOL; 
    echo "data: $msg" . PHP_EOL; 
    echo PHP_EOL; 
    ob_flush(); 
    flush(); 
} 
while(true) { 
    $serverTime = time(); 
    sendMsg($serverTime, 'server time: ' . date("h:i:s", time())); 
    sleep(1); 
} 
?> 

Ho cambiato il codice per includere un ciclo infinito che attende 1 secondo dopo ogni messaggio inviato (dopo un esempio trovato qui: Using server-sent events).

Questo tipo di ciclo è quello che sto attualmente utilizzando ed ha eliminato la caduta costante della connessione e riconnettersi ogni 3 secondi. Tuttavia (e ho solo provato questo in chrome), le connessioni sono ora mantenute in vita per 30 secondi. Continuerò a capire perché questo è il caso e pubblicherò una soluzione quando trovo uno, ma fino ad allora questo dovrebbe almeno avvicinarti al tuo obiettivo.

Speranza che aiuta,

Edit:

Al fine di mantenere la connessione aperta per ridicolmente lunghi tempi con PHP, è necessario impostare il max_execution_time (Grazie a tomfumb per questo). Questo può essere ottenuto in almeno tre modi:

  1. Se è possibile modificare il vostro php.ini, modificare il valore per "max_execution_time." Ciò consentirà a tutti gli script di essere eseguiti per il tempo specificato dall'utente.
  2. Nello script che si desidera eseguire per un lungo periodo, utilizzare la funzione ini_set (chiave, valore), dove la chiave è 'max_execution_time' e il valore è il tempo in secondi in cui si desidera eseguire lo script.
  3. Nello script che si desidera eseguire per un lungo periodo, utilizzare la funzione set_time_limit (n) dove n è il numero di secondi che si desidera eseguire lo script.
+1

superbo, è esattamente quello che volevo. Ho impostato il sonno su 3 e la connessione si interrompe dopo 60 secondi (probabilmente in relazione al timeout della connessione in httpd.conf o al tempo massimo di esecuzione di PHP?). L'ho impostato come risposta, ma se riesci a capire l'errore di connessione, metterò volentieri un'altra taglia sulla domanda e invierò se la tua strada. Devo ammettere che la mia prossima domanda sarà come inviare nuovi eventi al client dopo la connessione iniziale - ora vedo che il PHP non deve mai smettere di eseguire. – tomfumb

+0

Grazie, tomfumb! Quella possibilità parentetica era esattamente ciò di cui avevo bisogno per capirlo. Non c'era nulla in nessuno dei miei file di configurazione di Apache, ma quel max_execution_time era abbastanza facile da trovare. Ho modificato la mia risposta includendo opzioni per estendere la vita di ogni connessione EventSource. –

1

Il problema non è un problema lato server, tutto ciò accade sul client e fa parte delle specifiche (so che sembra strano).

http://dev.w3.org/html5/eventsource/

"Quando un agente utente è di ristabilire la connessione, il programma utente deve eseguire le seguenti operazioni. Questi passaggi vengono eseguiti in modo asincrono, non come parte di un'operazione. (I compiti che le code, di Naturalmente, vengono eseguiti come attività normali e non in modo asincrono)"

  1. coda un compito di eseguire le seguenti operazioni:.
    1. Se l'attributo readyState è impostato su CLOSED, interrompere l'operazione.
    2. Impostare l'attributo readyState su CONNECTING.
    3. Attivare un evento semplice denominato errore nell'oggetto EventSource.

non riesco a vedere alcuna necessità di avere un errore qui, quindi ho modificato la funzione Init per filtrare l'evento di errore sparato mentre la connessione.

function init() { 
       var CONNECTING = 0; 
       var source; 
       if (!!window.EventSource) { 
        source = new EventSource('events.php'); 
        source.addEventListener('message', function (e) { 
         document.getElementById('output').innerHTML += e.data + '
'; }, false); source.addEventListener('open', function (e) { document.getElementById('output').innerHTML += 'connection opened
'; }, false); source.addEventListener('error', function (e) { if (source.readyState != CONNECTING) { document.getElementById('output').innerHTML += 'error
'; } }, false); } else { alert("Browser doesn't support Server-Sent Events"); } }
+0

Sono confuso - non sei solo nascondendo il problema? Da quello che ho capito la connessione EventSource dovrebbe rimanere aperta finché non la chiudo. Il problema qui è che la connessione viene regolarmente chiusa e quindi riaperta. Dici che il comportamento che vedo è corretto quando il client ristabilisce la connessione, ma perché è necessario ristabilire in primo luogo? Se il client si riconnette al server ogni 3 secondi questo non è diverso dal polling e nega totalmente i vantaggi di push. Ho modificato la mia domanda per includere l'output del browser. – tomfumb

+0

Ok da quello che ho letto il server deve mantenere aperta la connessione e inviare i dati al client, nel caso di PHP, svuotando il buffer. Se il server non mantiene la connessione, l'origine dell'evento riproverà, inviando l'ultimo ID evento fornito dal server, questo è ciò che causa il polling osservato. Attualmente le specifiche sono in bozza e non so perché non ci sia un altro evento per distinguere tra un problema reale e la riconnessione. – Robert

+0

Anche in base al collegamento fornito da @unludo, PHP non sembra una buona soluzione per l'origine degli eventi se si intende mantenere aperta la connessione poiché sarà necessario mantenere un thread PHP per richiesta. – Robert

0

Non c'è alcun problema con il codice, che posso vedere. La risposta selezionata come corretta è quindi errata.

Questo riassume il comportamento menzionato nella domanda (http://www.w3.org/TR/2009/WD-html5-20090212/comms.html):

"Se un tale risorsa (con il tipo MIME corretto) completa il caricamento (ovvero viene ricevuto l'intero corpo della risposta HTTP o la connessione stessa si chiude), l'agente utente deve richiedere nuovamente la risorsa sorgente evento dopo un ritardo pari al tempo di riconnessione dell'origine evento. applicare per i casi di errore che sono elencati di seguito. "

Il problema sta nel flusso. Ho mantenuto con successo un singolo EventStream aperto in perl; invia semplicemente le intestazioni HTTP appropriate e inizia a inviare i dati del flusso; mai spegnere il lato del server di streaming. Il problema è che sembra che la maggior parte delle librerie HTTP tentano di chiudere il flusso dopo essere stato aperto. Ciò farà sì che il client tenti di riconnettersi al server, che è pienamente conforme allo standard.

Ciò significa che apparirà che il problema è risolto eseguendo un ciclo while, per un paio di motivi:

A) Il codice continuerà a inviare i dati, come se fosse spingendo fuori un file di grandi dimensioni B) Il codice (server php) non avrà mai la possibilità di tentare di chiudere la connessione

Tuttavia, il problema qui è ovvio: per mantenere attivo lo stream, è necessario inviare un flusso costante di dati. Ciò si traduce in uno spreco di utilizzo delle risorse e annulla tutti i benefici che il flusso SSE dovrebbe fornire.

Non sono abbastanza un php guru per sapere, ma immagino che qualcosa nel server php/più avanti nel codice stia prematuramente chiudendo il flusso; Ho dovuto manipolare il flusso a livello Socket con Perl per tenerlo aperto, dal momento che HTTP :: Response stava chiudendo la connessione e facendo in modo che il browser client tentasse di riaprire la connessione. In Mojolicious (un altro framework web Perl), questo può essere fatto aprendo un oggetto Stream e impostando il timeout a zero, in modo che lo streaming non scada mai.

Quindi, la soluzione corretta qui non è quella di utilizzare un ciclo while; è chiamare le funzioni php appropriate per aprire e mantenere aperto un flusso php.

0

Sono riuscito a farlo implementando un ciclo di eventi personalizzato. Sembra che questa funzione html5 non sia pronta per niente e abbia problemi di compatibilità anche con l'ultima versione di google chrome. Eccolo, lavorando su firefox (impossibile ottenere il messaggio inviato correttamente su chrome):

var source; 

function Body_Load(event) { 
    loopEvent(); 
} 

function loopEvent() { 
    if (source == undefined) { 
     source = new EventSource("event/message.php"); 
    } 
    source.onmessage = function(event) { 
     _e("out").value = event.data; 
     loopEvent(); 
    } 
} 

P.S. : _e è una funzione che chiama document.getElementById (id);

0

Secondo la specifica, la riconnessione di 3 secondi è di progettazione quando la connessione è chiusa. PHP con un ciclo dovrebbe teoricamente fermarlo, ma lo script PHP verrà eseguito indefinitamente e sprecando risorse. Dovresti cercare di evitare l'uso di apache e php per SSE a causa di questo problema.

La risposta http standard deve chiudere una connessione una volta inviata la risposta. È possibile modificare questo con l'intestazione "connessione: keep-alive" che dovrebbe dire al browser che la connessione è destinata a rimanere aperta anche se questo può causare problemi se si sta utilizzando i proxy.

node.js o qualcosa di simile è un motore migliore da utilizzare per SSE piuttosto che apache/php e dal momento che è fondamentalmente JavaScript, è piuttosto facile da afferrare.

0

Server Sent Event come suggerisce il nome i dati dovrebbero viaggiare da un server all'altro se deve riconnettersi ogni tre secondi per recuperare i dati dal server, quindi non è diverso da altri meccanismi di polling. Lo scopo di SSE è di avvisare il client non appena ci sono nuovi dati a cui il client non è consapevole. Dal momento che il server chiude la connessione anche se l'intestazione è keep-alive non c'è altro modo che eseguire lo script php in loop infinito ma con un thread thread considerevole per evitare oneri sul server. Fino ad ora non vedo altra via d'uscita ed è meglio dello spamming server ogni 3 secondi per i nuovi dati.

2

Gli eventi inviati dal server sono facili solo quando si tratta della parte Javascript. Prima di tutto un sacco di tutorial su SSE in internet stanno chiudendo le loro connessioni nella parte server. Che si tratti di esempi PHP o Java. Questo è davvero sorprendente perché quello che ottieni è solo un modo diverso di implementare un sistema di "Ajax Polling" con una struttura del payload rigorosamente definita (e alcune funzionalità minori come i valori di retry del client impostati dal lato server). Puoi facilmente implementarlo con poche righe di jQuery. Quindi non c'è bisogno di SSE.

Secondo le specifiche di SSE, direi che il tentativo non dovrebbe essere il modo normale di implementare un ciclo lato client. Per me SSE è un metodo di streaming a senso unico che si basa su un server back-end che non chiude la connessione dopo aver spinto i primi dati al client.

In Java è utile utilizzare le specifiche Async Servlet3 per liberare immediatamente il thread di richiesta e eseguire l'elaborazione/streaming in un thread diverso. Funziona così lontano, ma ancora non mi piace la durata della connessione di 30 secondi per la richiesta EventSource. Anche se spingo i dati ogni 5 secondi, la connessione verrà interrotta dopo 30 secondi (chrome, firefox). Naturalmente SSE si ricollegherà per impostazione predefinita dopo 3 secondi, ma ancora non penso che questo sia il modo in cui dovrebbe essere.

Un problema è che alcuni framework Java MVC non hanno la possibilità di mantenere la connessione aperta dopo l'invio dei dati, in modo da terminare la codifica con la semplice API Servlet. Dopo 24 ore su prototipi di codifica in Java, sono più o meno deluso perché il guadagno su un tradizionale jQuery-Ajax-loop non è COSÌ tanto. E il problema con il polyfilling della funzionalità SSE è anche esistente.

+0

sì, un po 'frustrantemente molti autori di tutorial presumono che tu stia usando Node nel back-end e c'è poca considerazione su come lavorare con altri framework. L'SSE sembra un'idea fantastica, ma per qualche motivo dietro di esso sembra un po 'debole. – tomfumb

0

Sto provando la stessa cosa. Con diversi gradi di successo.

  1. Aveva lo stesso problema con Firefox, eseguendo lo stesso codice js come accennato. Usando il server Nginx e alcuni PHP che sono usciti (cioè senza ciclo continuo), ho potuto richiamare i messaggi su una "Richiesta" da firefox solo una volta che il PHP è uscito. Esegui il PHP come script in PHP.exe e tutto va bene sul concole, le punture vengono stampate quando vengono scaricate. Tuttavia, Nginx non invia i dati fino al completamento del PHP. Ho cercato di aggiungere extra \ r \ n \ r \ n e flush() o ob_flush() non ha aiutato. Non c'è alcuna spinta dei dati, come mostrato nei registri di Wireshark, solo un pacchetto di risposta ritardata al GET.

Leggere che ho bisogno di un modulo "push" per Nginx che richiede una ricostruzione dalla sorgente.

Quindi questo è sicuramente un problema Nginx.

  1. Utilizzando un socket in "C", ho potuto inviare dati a Firefox come previsto, e il socket è stato tenuto aperto e nessun messaggio è stato perso. Tuttavia questo ha lo svantaggio che ho bisogno di server il page.html e gli eventi/stream dallo stesso socket o firefox non si connetteranno a causa di problemi di URL del sito incrociato. Ci sono alcuni modi per aggirare questo in certe situazioni, ma non per un iframe in un sistema di menu. Questo approccio ha dimostrato il punto che l'SSE funziona con Firefox e ci sono pacchetti spinti nel log di wireshark. Dove l'opzione 1 aveva solo pacchetti di richiesta/risposta.

Tutto ciò detto, non ho ancora una soluzione. Ho provato a rimuovere il buffering su PHP e Nginx. Ma ancora niente fino al termine di PHP. Ho provato diverse opzioni di intestazione, ad es. Anche i blocchi non sono stati d'aiuto. Non mi va di scrivere un server http in piena regola in "C", ma questa sembra essere l'unica opzione che funziona per me al momento. Sto per provare Apache, ma la maggior parte delle registrazioni suggeriscono che questo è peggio di Nginx in questo lavoro.

+0

Dal post ho trovato alcuni link ./scrive che hanno aiutato molto. – GSEmbedded

+0

Dal post ho trovato alcuni link ./scrive che hanno aiutato molto. [http://html5doctor.com/server-sent-events/] Un sacco di pagine di esempio e informazioni utili su questo argomento. https: //pushmodule.slact.net/il modulo Push per Nginx. – GSEmbedded