Newsgroup: it.comp.www.php
Titolo: Inviare risposta parziale al browser

Salve a tutti,
sto sviluppando un sistema che utilizza AJAX lato client e PHP + MySQL
lato server.
Devo caricare N prodotti su un database in una volta sola.
Questo N può essere 100 ma anche 25000.
L'inserimento di 25000 prodotti è testato e funzionante, ma impiega
*ovviamente* un sacco di tempo, anche perchè deriva da un parsing XML
che ne porta via abbastanza (di tempo).
L'unico problema è che la mia attuale pagina di attesa, prevede la
solita gif animata con una progress bar fasulla che continua a
ricominciare.
Questo può accontentare l'utente fino a 5000 INSERT, ma dopo il tempo
diventa un po' troppo e spontaneamente viene de pensare "si è bloccato
tutto".
Una progress bar *realistica* invece sarebbe la soluzione al problema.
Ora la domanda.
E' possibile da PHP inviare risposte parziali, o comunque dati al
browser in modo che questi possano essere catturati da AJAX? Avete
avuto esperienze in merito?
Grazie 
Franco

 
Questa potrebbe essere una idea ;-P
Se e' possibile da parte tua poter spezzettare tutti questi dati in più 
blocchi di dati (Esempio 1000 record alla volta), allora tramite ajax 
sarebbe possibile mettere su una struttura che effettua una 
pseudo-progressBar in real time.
Fammi sapere e ne discutiamo.
jumpaolo

 pseudo-progressBar in real time.
Già pensato.. e sarebbe fondamentalmente possibile..
solo che i dati da inviare allo sono in formato xml e al momento con
ajax non li prendo direttamente in mano ma invio allo script l'url del
file xml.
Anche perchè un xml di 25000 prodotti è abbastanza pesantino.. quindi
scaricarlo tutto sul client, per poi rispedirlo al server sarebbe una
cosa assurda e estremamente poco efficace.
Però la tua idea di calcolare un "tempo di esecuzione" non è male..
poi con ajax potrei creare 10 piccoli DIV che cotruiscono una barra, e
colorarli mano a mano che il tempo passa!
Però sarebbe una soluzione "di ripiego"..
ho pensato anche un'altra cosa:
lo script php conta i prodotti, ogni prodotto calcola la percentuale di
lavoro effettuato e la scrive su un file sul server.
Ajax, ogni tempo T, legge il file e stampa il risultato..
Anke qui, però, potrebbe succedere di dover fare 1000 richieste ajax,
che sono decisamente troppe..
Cosa ne pensate?

 Questo può accontentare l'utente fino a 5000 INSERT, 
Ogni dato xml è una query insert separata???


 
Ehm... per il momento sì ... :-)

 
e questo va bene. Non devi inviare i dati al client ma devi lavorare solo 
sul server.
Vedo di spiegarmi:
invii l'esecuzione (tramite chiamata AJAX) della prima parte dei dati da 
inserire nel DB (esempio url:inserisciDati.php?step=0&file=xml_da_0_a_100)
a questo punto sul client tramite Javascript visualizzi il div con la 
barra di attesa...
AJAX attende la risposta del Server ed alla risposta del SERVER che ha 
terminato di inserire i primi 100 dati, oppore e
è avvenuto un errore
(lato server invii un: 
..
echo "OK";
exit;
..
se tutto e' andato bene 
oppure 
..
echo "ERRORE";
exit;
..
in caso di errore)
intercetti la stringa di ritorno(AJAX) e se tutto "OK" e sai che devi fare 
partire il successivo inserimento rimandi un'altra richiesta AJAX
(esempio url:inserisciDati.php?step=1&file=xml_da_101_a_200) in questo 
momento aggiorni il div (tramite Javascript) dovi dai all'utente 
l'informazione che i primi 100 record sono stati salvati correttamente e 
gli rivisualizzi la barra di attesa..... e così via fino alla fine....
in questo modo dovresti aver migliorato di molto le prestazioni...
spero di essere stato chiaro. cmq fammi sapere
jumpaolo

 sul server.
sì, potrebbe essere un 'idea a questo punto.. ma lato server come
gestisci i "passi"?
cioè php sa che deve gestire dall'elemento 101, al 200, ad esempio.
Di conseguenza deve *scartare* i primi 100.
Quando dovrò parsare gli ultimi 100 prodotti, però, il parser dovrà
scartare 24900 prodotti.
Dal punto di vista delle prestazioni questo è un problema secondo voi?

 gestisci i "passi"?
io penserei di lavorare in questo modo:
1. quando acquisisco il file XML lo parserizzo immediatamente e 
  a) crerei delle strutture dati separate (esempio array di 1000 elementi) in 
PHP le serializzerei e le salverei in file separati sul server.
  b) andrebbe bene anche la costruzione di piccoli file XML separati sempre di 
un tot di elementi....
2. terrei in un DB le informazioni (numero di file - elementi per file etc.. 
questo lo decidi tu).
3. ora l'utente richiede l'archiviazione dei dati e in questo memento ti 
inserisci i dati del singolo file uno alla volta
in questo modo hai solo il tempo iniziale della parserizzazione della prima 
volta ed unica volta del file XML
fammi sapere se è possibile...
jumpaolo

scartare 24900 prodotti.
No: parsi il file XML tutto insieme e salvi un secondo file temporaneo
eventualmente GIA' spezzandolo in sotto-file, contenente i dati rapidi
(es.: serializzazioni di ciascuna tupla).
Guardi quanto tempo ci hai messo a fare questo (diciamo 40 secondi), e
calcoli quanti record hai da inserire (diciamo 50 file da 500 righe).
Mandi al client una pagina, con scritto "Elaborazione in corso...": e'
la pagina uno, con un redirect alla pagina "loadchunk.php?chunk=1"
La pagina loadchunk vede dalla sessione che sono gia' passati 40" e si
legge il primo chunk, creando l'SQL e inserendolo nel DB (se usi MySQL
puoi usare una INSERT multipla: molto piu' veloce). E quando ha finito
cancella il file chunk.1, e invia un refresh (usando javascript) dando
l'indirizzo di se stessa, con chunk=2.
Dato che in ogni momento sai quanto tempo e' passato (te lo salvi!), e
sai quanti record hai inserito, e sai quanti ce ne sono in tutto, ecco
che la percentuale di avanzamento "temporale" e' del tipo
	tempo_impiegato_finora * 100
	/
	(TempoElabIniziale + (TIF-TEI)*REF/RET)
Quando REF, RecordElaboratiFinora, sia uguale a RecordElaboratiTotali,
avresti che la percentuale e' TIF*100 / (TEI + TIF*1 - TEI*1) = 100.
Per renderizzare la barra usi una GIF con un gradiente verticale, dove
imposterai la larghezza; cosi' la scarichi una sola volta. Es.:
	<td style="background: url(/images/barrasfondo.gif);">
		<img src="/images/barracaricati.gif" width="40%" />
	</td>
(Non so se si possa impostare la width di un img in percento... se non
si puo', ti calcoli i pixel e chissenefrega).
Naturalmente, la barra avanzera' a scatti, e in modo irregolare, pero'
il valore mostrato sara' preciso.
Piu' piccoli sono i chunk in cui dividi l'operazione, e piu' dappresso
la barra seguira' l'avanzamento.
Eventualmente puoi perfino usare chunk piccoli, e loadchunk non guarda
che valore gli passi, ma quanti chunk ci sono (lavori di file_exists),
dopodiche' li elabora uno dopo l'altro, finche' il tempo impiegato non
supera "X" secondi:
	$chunk	= 0;
	$timeout = ($start = time()) + 15; // 15 secondi
	while(time() < $timeout)
	{
		if ($chunk > $_REQUEST['maxchunk'])
			break;
		if (!file_exists($file = 'chunk.' . $chunk++))
			continue;
		// Elaborazione del file $file
		$raw	= file($file);
		$values	= array();
		foreach($raw as $rawtuple)
		{
			$tuple = unserialize($rawtuple);
			// Costruisco l'SQL. Modo bovino DA NON USARE:
			$values[] = '(' . implode(',', $tuple) . ')';
			// (vanno verificati e quotati gli elementi, ecc.)
		}
		SQLExec('INSERT INTO tbl VALUES ' . implode(',', $values)
. ';');
		if (...verifica che tutto sia andato bene...)
		{
			$_SESSION['records_so_far'] += count($values);
			unlink($file);
		}
		else
			break;
	}
	$_SESSION['time_so_far'] += (time() - $start);
	// Abbiamo elaborato X record in Y secondi, quindi X/Y record
	// al secondo. Sappiamo che dobbiamo elaborare N record e che
	// all'inizio abbiamo perso P secondi di parsing.
	// Probabimente ci metteremo P + (X/Y)*N secondi.
	// Di questi, P+Y sono gia' passati
	// Dunque siamo al (P+Y)*100/(P+(X/Y)*N)% del totale.
	// Chiaro che se Y = 0, non sappiamo a che velocita' andiamo,
	// e quindi siamo vittime.
	if (0 != $_SESSION['records_so_far'])
		$_SESSION['time_total']	= $_SESSION['xml_parse_time'] + 
			$_SESSION['records_total'] *
$_SESSION['time_so_far'] / $_SESSION['records_so_far'];
	else
		$_SESSION['time_total'] = 'N/A';
	...
Leonardo e i suoi .02 EUR
-- 
Geordie - he's a man, there's little doot about't,
He's done all he can, wha' can do withoot't?
Doon there cam a blade, linkin' like me lordie,
He wad drive a trade, a' the loom of Geordie.

(es.: serializzazioni di ciascuna tupla).
Whoops. Vedo ora di aver plagiato jumpaolo :-)
Leonardo


 
Beh, visto che s'č una queri per ogni singolo inserimento e che con 
affected_rows puoi stabilire quanti record sono stati "modificati" 
nell'ultima query, io farei una cosa del genere:
1)calcolerei i record da "parsare"
2)calcoli il tempo che ci vuole per una singola query (lo puoi vedere 
anche eseguendo la query direttamente (in phpmyadmin ti dice anche il 
tempo di esecuzione)
3)moltiplichi il tempo di ogni singola query per il numero di query 
necessarie
4)crei una gif che impieghi lo stesso tempo, magari arrotondato per 
eccesso (anche l'idea dei DIV con la costruzione dinamica della barra 
non č male)
Lato server devi solo contare volta per volta i record salvati. In 
questo modo č possibile anche intervenire manualmente se vuoi 
interrompere il procedimento di parsing.
Tutto questo solo in teoria, non ho provato alcun codice, ma credo sia 
possibile da creare, e nemmeno sembra troppo lungo il procedimento. Il 
codice dovrebbe essere ridotto all'osso, nel senso che sono poche le 
funzioni da implementare. E' un'idea.. fammi sapere come risolvi. Ciao.
Kevin.

 ogni settimana, o 2 settimane..
allora, soprattutto se ci impiega tanto tempo, non e' un'operazione da
eseguire via web.
metti un cron ogni giorno od ogni ora o quanto vuoi tu di uno script
che controlla la presenza di un file in una data directory, se il file
c'e' l'esecuzione parte, parsa l'xml e alla fine lo cancella.
lato web nella sezione di aggiornamento visualizzi "nessun
aggiornamento in corso" se il file non e' presente nella directory, o
"aggiornamento in corso" se e' presente
se proprio vuoi mettere una progress bar allo script che parsa l'xml
fai scrivere un file di testo con una sola riga: X:Y
X e' il numero di righe totali
Y il numero di righe a cui e' arrivato
da li' e' semplice creare la progressbar
ovviamente cambia tutta la logica di utilizzo: non e' piu' l'utente che
va su una pagina e fa partire il processo ma e' una sua azione che lo
scatena (mettere il file nella dir). a questo punto non c'e' piu'
bisogno di far aspettare l'utente davanti al browser, ma solo dargli la
possibilita' di controllare quando vuole lo stato del processo, e, se
proprio vuoi fare le cose per bene, notificargli in qualche modo
l'avvenuta fine (via mail, via popup, puoi sbizzarrirti, ti basta
creare un evento controllabile alla fine del script in cron)

 c'e' l'esecuzione parte, parsa l'xml e alla fine lo cancella.
Anche questa è un'opinione corretta, scusa, però mi sfuggono questi
"cron".
Come si implementano in php?
grazie
Franco

 interrompere il procedimento di parsing.
Il problema è : come metto al corrente il client dello stato del
server se non alla fine del parsing?
grazie
Franco

 volta ed unica volta del file XML
Chiaro e fattibile.
Ora sento le altre opzioni e vedo quale posso implementare con meno
lavoro rispetto a quello che ho già fatto :) Dolce voglia di far
niente :P
grazie
Franco

 
i cron sono dei processi che partono in automatico secondo le 
impostazioni che gli dai... per quel che ne sò non si settano in php, ma 
direttamente sulla macchina.

 avuto esperienze in merito?
Lascia perdere le risposte parziali e pensa alla tecnologia che hai a
disposizione: ajax: ASYNCHRONOUS js and xml.
Prepara il form, al submit mandi l'xml (via ajax o come vuoi): lo script
php che lo riceve controlla quanti dati ci sono (calcolando la lunghezza,
o contando il numero di tag <prodotto> o quello che e`, nel file.
Quindi salvi questo valore in sessione ($_SESSION["totrecords"]=25000).
Inizi l'elaborazione, e ad ogni ciclo aggiorni una variabile in sessione
col record in elaborazione ($_SESSION["record"]=1).
Lato client metti una funzione in polling con setTimeout() ogni 2-3
secondi, che richiama un'altro script che legge la $_SESSION["record"] e
la torna al client (magari in json).
Da js aggiorni la barra di progresso in base ai risultati di questo
secondo script.
Alla fine dell'elaborazione metti un valore particolare in record (tipo
"finished") che blocca il setTimeout() del js appena gli arriva, che si
limita a visualizzare "elaborazione completata".
Bye.

 la torna al client (magari in json).
a parte il fatto che pianoska aveva espressamente richiesto soluzioni
che non sovraccaricassero di richieste ajax il server (cosa che invece
la tua soluzione fa), quando l'esecuzione di uno script supera il tempo
di attesa accettabile di caricamento di una pagina (2, 3, 5 secondi?),
significa che non va servita via web, ma lo script relativo va eseguito
altrimenti.

 altrimenti.
Qui parli di frontend.
Su un backend e` accettabilissimo che una pagina impieghi anche 5 minuti
ad essere eseguita. L'importante e` far capire all'utente che sta
succedendo qualcosa.
Bye.

 ad essere eseguita.
sara' accettabile per te.
un webserver non e' pensato per eseguire processi di cinque minuti, se
il tuo script impiega tanto tempo e' bene farlo eseguire in un altro
modo

Allora, grazie mille per tutte le risposte, mi sono state molto
d'aiuto, veramente!
Adesso vi illustro la mia soluzione.
Sono partito da ipotesi che :
1. il numero di prodotti da parsare *medio* per ogni catalogo è di
circa 500. il tempo di esecuzione per questo è nell'ordine dei
secondi.
2. i casi limite sono 100 prodotti (quasi immediato e molto spesso) e
25000 prodotti (quasi infinito ma molto raro )
Quindi ho adottato la soluzione di scrivere una variabile di sessione
ad ogni prodotto da parte di php, e leggerlo ogni 2 secondi da ajax.
Adesso devo fare un po' di test e vi dirò come è andata.
Grazie 
Franco

Ho fatto delle prove, il risultato non è per niente
soddisfacente.........
Allora
Le richieste sono troppe per i miei gusti e per quelli di un qualunque
programmatore, e inoltre essendo ,appunto, *asincrone* le percentuali
salgono e scendono a caso. Non parliamone se non si dispone di una
connessione molto veloce, in tal caso i numeri sono proprio pseudo
casuali.
Penso sia un affare troppo grosso per quel che vale. A meno che non
venga a qualcuno un idea brillante, lascierò l'utente ad ipnotizarsi
davanti ad una gif che scorre avanti e indietro per fargli passare i 5
minuti.......
La soluzione dei cron mi piaceva, ma non ho accesso al server in quanto
ho acquistato solo lo spazio e il dominio.
Faccio il punto della situazione così magari può saltar fuori
un'idea.
parser.php -> riceve l'url dell'xml da cui ricavare i prodotti e ne fa
il parsing, creando una INSERT sql ad ogni prodotto
admin.php -> è l'interfaccia dell'applicazione e dialoga con tutti gli
altri moduli tramite ajax
il problema è tenere aggiornato l'utente sullo stato del parser senza
però effettuare 200 richieste ajax al server.
Mi viene in mente una cosa. Può essere una soluzione aggiornare lo
stato solo quando l'utente clicca su un pulsante "controlla stato
dell'upload"?
Non si farebbero troppe richeste e se l'utente è via dal pc non si
spreca banda per inviargli lo stato...
Che ve ne pare?

 ho acquistato solo lo spazio e il dominio.
resta pero' l'unica soluzione corretta, per cui se devi fare degli
sforzi, sforzati di implementare questa.
il cron potresti metterlo in locale, richiamando una url sul server
dietro autenticazione. ovviamente alla url dovrebbe rispondere uno
script che si comporti secondo certe logiche, lascio a te immaginare
quali

il parsing, creando una INSERT sql ad ogni prodotto
Devi riuscire innanzitutto a ridurre le operazioni in unita' distinte
facilmente separabili, che occupino DI SICURO meno memoria, risorse e
tempo di qualsiasi limite ragionevole possa entrare in ballo.
Per esempio, potresti creare prima tutte le INSERT senza eseguirle, e
poi eseguirle in un secondo tempo.
Addirittura, quando parsi l'XML, *non* formare l'SQL ma immagazzina i
dati del prodotto in un array, che penso sia piu' veloce - senza fare
alcun controllo su cosa immagazzini.
A questo punto hai tre "blocchi":
1) Parsing dell'XML
	Crea un file temporaneo sul server, contenente gli array di
	ogni prodotto, uno per riga, serializzato.
	Conta le righe e le mette in sessione.
2) Serialize -> SQL
	Converte ogni riga in una riga SQL, effettuando i controlli
	necessari.
3) SQL -> Database
	Effettua gli inserimenti
I blocchi 2 e 3 sono suddivisibili in sottoblocchi di N righe. Forse,
dipende dalla struttura dell'XML, puoi suddividere anche il primo.
Dividendo tutta l'operazione in tocchetti, ciascuno puo' essere breve
quanto basta per non dare fastidi.
A questo punto ti basta salvare in sessione "dove sei", e calcolarti,
approssimativamente, dove devi arrivare e quanto ci metterai... cosi'
hai i dati per la barra di scorrimento.
Lo script che aggiorna la barra di scorrimento, in realta', e' quello
che fa le cose: a seconda dei casi, converte 50 righe, oppure immette
120 righe di SQL dentro a MySQL e fa vedere una barra un pochino piu'
lunga del giro prima.
Per l'eventualita' che qualcosa vada storto, puoi immettere tutto non
nella tabella normale ma in una di appoggio. Poi quando hai finito, e
tutto e' a posto, esegui una copia. Piu' costoso, certo - pero' cosi'
l'operazione e' "atomica".


 altri moduli tramite ajax
Se te la cavi con JS, potresti anche parsare l'XML sul client, e preparare
i dati in JSON, o addirittura come serie di insert gia` pronte da mandare
al server. Ma in questo caso attento alla sicurezza. In questo modo
scarichi il server di un lavoro non indifferente.
Bye.

 sforzi, sforzati di implementare questa.
Ok. Allora ho deciso di portarmi su questa strada.
D'altronde questi cron e il dividere il catalogo in pezzetti, penso sia
la soluzione ideale.
In questo modo è persino possibile interrompere il lavoro e
ricominciarlo in un altro momento, avendo tanti piccoli file separati
sul server e una tabella "indice" sul database... in questo modo evito
persino le sessioni che mi creavano qualche problema. (sembra che ajax
creasse una sessione separata da quella del parser quando chiedeva lo
stato)
Allora stesso tempo ogni script dura poco (facciamolo di 100 elementi
ad esempio, quindi qualche secondo).
E il client può mostrare la percentuale di lavoro completato.
E' possibile persino aggiungere più cataloghi (altra tabella) e
parsarli uno dopo l'altro, quindi potrei avere una percentuale totale e
parziale dell'upload.
Il problema :
 il cron in locale immagino sia un script javascript che ogni tempo T
(o alla fine di ogni pezzetino?) effettua una richiesta al server.
 Il fatto è che comunque finirei con il fare 100 chiamate per un
catalogo di 10000 prodotti.
 Come posso evitare ?

 Come posso evitare ?
Non puoi.
Comunque, non e' che sarebbero cento chiamate da chissa' che cosa: non
piu' di 200-500 byte in andata e altrettanti in ritorno.
In teoria potresti tentare con multipart/x-mixed-replace, una chiamata
sola e tanto ottimismo, ma e' un cercarsi rogne col lanternino - e non
so come sia supportata dai browser moderni, come interagisca con tutti
i vari schemi di caching e proxying, e cosi' via.
Ti meriterebbe senz'altro vedere se riesci a procurarti un cron locale
(quello remoto e' un ripiego che spesso funziona male, per esempio non
funziona se eccedi eventuali time_limit forzati server side)... Quella
e' la soluzione CORRETTA.
Poi ci sono vari ripieghi e rappezzi, ma e' chiaro che ciascuno ha dei
problemi: se no la soluzione buona non sarebbe quell'altra, no?


> Non e' che ti scordavi di passargli il session ID, eh? :-)
Ops...... :-(
Comunque domani pomeriggio provo a sistemare un po' in questa direzione
il mio codice.. anzi.. più che sistemare "rivoluzionare"! Per fortuna
mi rimane buona la parte del parser.. che però mi scriverà i file
spezzettati..
Grazie a tutti intanto!
Franco