Monthly Archives: October 2009

Ubuntu 9.10 e PHP 4

Proprio ieri ho aggiornato alla ultimissima Ubuntu, appena uscita. E proprio oggi si è avverato uno dei peggiori incubi del programmatore: rimettere le mani in un’applicazione rimasta inerte per 5 anni, perché all’improvviso si è presentato un bacherozzo bloccante. Ogni tentativo è stato inutile: l’applicazione non è compatibile con PHP5. Ergo, occorreva trovare un sistema (veloce) per far funzionare il PHP4, correggere il baco e poi buttare tutto.

Ecco come ho fatto (nota per gli amici Debianisti: ho più pratica nella compilazione del php/apache che nei vari giochini a colpi di dpkg; abbiate pazienza):

  • Primo problema: trovare i sorgenti di PHP4. Il sito ufficiale non li ha più disponibili, per cui occorre trovarseli altrove; io sono andato a prendere il pacchetto di Debian Etch.
  • PHP andrà compilato solo come CGI (l’idea è quella di usare lighttpd + fastcgi). Scompatta i sorgenti e poi dai un bel ./configure –with-mysql=/usr  –enable-fastcgi. Attenzione che non è sufficiente il solo –with-mysql, perché altrimenti verrà compilato il supporto mysql “vecchio”, che non funzionerebbe con i server 5.x. Probabilmente il configure si lamenterà della mancanza di qualcosa, la qual cosa provvederete ad installare (es: libmysqlclient-dev).
  • Apt-get install lighttpd (probabilmente non partirà se avrai attivo Apache, ma non ti preoccupare) e poi:
    • Modifica /etc/lighttpd/lighttpd.conf per farlo andare su un’altra porta che non sia l’80 (dove potresti avere Apache). Io ho usato l’81.
    • Esegui lighttpd-enable-mod fastcgi
    • Modifica /etc/lighttpd/conf-enabled/10-fastcgi.conf e cambia il “bin-path” del php, per farlo puntare a <dirSorgentiPhp4>/sapi/cgi/php
    • fai (ri)partire lighttpd /etc/init.d/lighttpd start

A questo punto i file php letti da localhost:81 verranno interpretati dal php4 fastcgi, via lighttpd mentre tutto il resto continuerà a funzionare come prima via Apache e php5 DSO :)

Se vuoi usare un sistema del tipo VirtualHost di Apache con lighttpd, ti copio la mia configurazione da mettere nel tuo /etc/lighttpd/lighttpd.conf


$HTTP["host"] =~ "miodominio\.local(\:[0-9]*)?$" {
server.document-root = "/home/claudioc/Sites/miodominio"
}

La cosa a cui fare attenzione è che anche se la chiave si chiama “host”, in realtà nella regexp occorre fornire il match anche per una eventuale porta (come nel caso di esempio).


Upload progressivo in PHP con APC e JavaScript – Parte 3

Terza parte (leggi la prima parte, leggi la seconda parte)

Dopo aver visto come si debba configurare il sistema per far funzionare l’upload progressivo e aver buttato giù una form HTML e un po’ di PHP, vediamo adesso di chiudere il cerchio parlando dell’ultima cosa di cui abbiamo bisogno: un po’ di JavaScript.

Il flusso delle operazioni sarà il seguente:

  • l’utente seleziona il file da spedire ed inizia il caricamento verso il server (preme il tasto Submit, per intenderci)
  • lato server, il PHP inizia a ricevere il file e APC traccia in tempo reale lo stato di avanzamento (come visto nella seconda parte)
  • sul browser, non appena l’utente ha premuto il tasto di submit della form, uno script JavaScript interrogherà ogni X secondi uno script PHP che gli fornirà le informazioni sullo stato di avanzamento (it’s Ajax, baby)
  • lo script JavaScript utilizzerà queste informazioni per aggiornare l’interfaccia utente (visualizzazione della percentuale di caricamento effettuato)
  • al termine (o all’interruzione) del caricamento il JavaScript eseguirà delle istruzioni

Premetto che negli esempi che seguono userò la sintassi jQuery. Spero comunque siano chiari anche a chi non è un esperto di questa libreria.

Vediamo come potrebbe apparire lo script più semplice possibile (seguite i commenti):

$('#frm-upload').submit(function() {

  var apcid = $(this).find('input[name=APC_UPLOAD_PROGRESS]').val();
  var $status = $('#uploadStatus');

  setTimeout(pollStatus, 2000);

  var polls = 0;

  function pollStatus() {
    $.getJSON("progress.php?apcid=" + apcid, function(data) {

      /* Se la prima volta non otteniamo risultati, qualcosa è andato storto */
      if (!data &amp;&amp; polls == 0) {
        $status.html("Errore nel caricamento, il file potrebbe essere troppo grande. Premi 'stop' nel browser e riprova.");
        try {
          stop();
        } catch(e) {
          document.execCommand('Stop');
        }
      }

      /* Verifichiamo se l'upload è terminato oppure cancellato */
      if (data && (data.cancel_upload || data.done)) {
        if (data.cancel_upload) {
          /* Upload cancellato */
        }
        return;
      }

      if (data) {
        /* Aggiorniamo la percentuale di caricamento */
        $status.html(data.filename + ": " + data.percent + "% @ ~" + data.rate + "KB/s (" + data.ETA + " min.)");
      }

      /* Riattiviamo il polling tra 2 secondi */
      setTimeout(pollStatus, 2000);

      polls++;
    });
  }
}

Quello che viene fatto non è altro che temporizzare (ogni due secondi) una richiesta al server, chiedendo lo stato di avanzamento del nostro file. Vediamo un po’ di cose interessanti:

  • viene agganciato l’evento submit della form; è da quel momento che ha inizio il nostro monitoraggio
  • per la visualizzazione dei dati, viene utilizzato un elemento all’interno della pagina con id “status”
  • la prima richiesta al server potrebbe fallire (da qui la necessità di contare i “poll”). Ho notato che questo succede quando per esempio il file è troppo grande rispetto alle dimensioni massime concesse per l’upload
  • quando qualcosa è andato storto (file troppo grande), allora conviene premere virtualmente il tasto “stop” del browser per chiudere la connessione al server. Ci sono un paio di modi per farlo, uno IE e uno “tutti gli altri” (da qui il try/catch)
  • In questo caso trovo più pratica la setTimeout rispetto alla setInterval. In questo modo sono sicuro che in caso di problemi non mi rimanga un timer attivo a massacrarmi il server.
  • “cancel_upload” e “done” sono chiavi della struttura dati che vengono valorizzate automaticamente da APC al termine dell’upload (infatti non erano state descritte nella seconda parte di questo articolo)

OK, fin qui tutto bene (citando un gran bel film).

I problemi arrivano quando l’upload termina. Cosa succede infatti – solitamente – quando si è finito un upload? Si effettua un redirect HTTP da qualche altra parte. In questo caso però – e non ho capito neanche troppo bene perché – il redirect sembra funzionare bene soltanto con Firefox. Gli altri browser si “imbambolano” e, finito l’upload, niente accade. Questo non è un grande problema perché alla fine tutte le altre librerie o gli altri sistemi che ho indagato fanno tutti la stessa cosa, sia per girare intorno a questo problema, sia per ottenere un altro effetto molto simpatico.

Quello che solitamente si fa in questo caso è fare in modo che il target della form sia un IFRAME.

Lo pseudocodice è dunque il seguente:

  • si crea un iframe vuoto (src=”about: blank”) e gli si dà un name
  • si imposta l’attibuto TARGET del tag FORM al name del nostro IFRAME
  • si aggancia l’evento LOAD dell’IFRAME. Sarà questo il modo con cui potremo comunicare delle informazioni dallo script di upload al nostro script JavaScript. L’evento load verrà infatti generato non appena l’upload è terminato.
  • all’interno del event handler del load dell’iframe potremo accedere al contenuto (ovvero ad eventuali dati fornitici dallo script di upload) con $iframe.contents().text()
  • sempre nell’event handler, modificando la location.href potremmo portare il browser sulla pagina di nostra scelta

Intuitivamente si capisce come un altro vantaggio dell’utilizzo dell’iframe è ottenere l’effetto degli upload di file SENZA fare il submit della form; è quello che succede per esempio quando si allegano dei file ad una mail scritta con GMail.

E per adesso è tutto :)


Upload progressivo in PHP con APC e JavaScript – Parte 2

Seconda parte (leggi la prima parte, leggi la terza parte)

In questa seconda parte dell’articolo inizieremo a scrivere un po’ di codice e a chiarire altre cosette.

Nella prima parte dell’articolo eravamo rimasti alla installazione dell’estensione APC di PHP.

Il sistema che ci interessa attivare in APC non è però attivo per default (probabilmente “costa” qualche ciclo di CPU): occorre configurare APC in modo da renderlo disponibile. Apriamo il file di configurazione di APC (in Ubuntu è /etc/php5/apache2/conf.d/apc.ini, in altre distribuzioni è sufficiente modificare il php.ini) e aggiungiamo quanto segue:

apc.enabled = 1
apc.rfc1867 = 1

Aldilà della mera attivazione di APC nel suo complesso, la chiave che qui ci interessa particolarmente è “rfc1867″. Indovinate un po’ infatti di cosa parla l’rfc1867… Rifate partire Apache e saremo pronti per il passo successivo.

Abbiamo visto che APC è necessario in quanto tramite di esso è possibile interrogare il PHP per sapere a che punto sia arrivato l’upload. La prima cosa fare, per innescare questo meccanismo, è informare APC che una nostra form ha bisogno del servizio di “monitoraggio upload”. Effettueremo questa operazione inserendo, tra i campi della nostra form, anche un campo “civetta” che servirà proprio a svegliare il prezioso meccanismo di APC.

Ecco dunque una ipotetica form di upload di un file che vuole sfruttare questo meccanismo:


<form action="upload.php" method="post" enctype="multipart/form-data">
  <div>
    <input type="hidden" id="apcid" name="APC_UPLOAD_PROGRESS" value="<?php print uniqid() ?>">
    <input type="file" size="40" id="userfile" name="userfile">
    <input type="submit" value="Inizia il caricamento">
  </div>
</form>

La form è una normalissima form impostata per l’upload di un file, con più uno strano campo. Il suo nome deve essere necessariamente “APC_UPLOAD_PROGRESS” e il valore dello stesso dovrà essere quanto più possibile univoco (vedremo perché). Uno modo semplice semplice per generare un valore univoco è quello di usare time() oppure, ancora meglio, uniqid() (come nel caso di esempio); il significato di questo campo è abbastanza intuitivo: quel valore servirà ad APC come “chiave” univoca per identificare il nostro file. Il solo nome del file non sarebbe stato ovviamente sufficientemente sicuro (more on this later).

Adesso, non appena spediremo la form al server, APC prenderà in carico l’onere di tenerci aggiornati sull’avanzamento dell’upload.

Già, ma come? È il momento di spostarci lato server.

Il file upload.php, menzionato come “action” della nostra form, non ci interessa minimanente. Quello farà ancora il suo solito mestiere, preoccupandosi di gestire eventuali errori e alla fine del caricamento spostare il file dove meglio crede.

Quello che ci serve, invece, è un nuovo script – che chiameremo progress.php – il quale ci permetterà di sapere lo stato di avanzamento.

Qualcosa che più o meno faccia così:


<?php

print_r(getUploadStatus());

function getUploadStatus() {
    if (!extension_loaded('apc') || !function_exists('apc_fetch'))
      return NULL;

    if (!ini_get('apc.enabled') || !ini_get('apc.rfc1867'))
      return NULL;

    $apcid = trim($_GET['apcid']);

    if (empty($apcid))
      return NULL;

    $status = apc_fetch('upload_' . $apcid);

    if (!$status) {
      return NULL;
    }

    if ($status['total'] > 0) {
      $status['percent'] = ceil($status['current'] / $status['total'] * 100);
      $bytes_per_sec = $status['current'] / (time() - ceil($status['start_time']));
      $status['rate'] = ceil($bytes_per_sec / 1024);
      $status['ETA'] = ceil((($status['total'] - $status['current']) / $bytes_per_sec) / 60);
    }

    return $status;
}

All’inizio dello script, da bravi ragazzi quali siamo, controlliamo che quello che ci serve sia a nostra disposizione. Superati i controlli prendiamo dalle variabili in GET un ipotetico parametro “apcid” (il nome l’ho scelto io, non è importante). Il valore di questo parametro dovrà essere esattamente quello che è stato generato dal form che abbiamo visto all’inizio, ovvero il valore della campo di form APC_UPLOAD_PROGRESS. Immaginiamo dunque di poter eseguire questo script tramite un URL del tipo:

http://localhost/~claudioc/progress.php?apcid=4ad4d0b7682f4

Non ci chiederemo, per ora, come fare a conoscere quel codice… le vie del JavaScript sono infinite, e l’argomento del prossimo articolo!

Per adesso preoccupiamoci solo di capire la tecnica: la parte importante è la riga successiva dello script, dove viene invocata (finalmente) la funzione apc_fetch();

Come ho accennato nella prima parte di questo articolo, APC può anche essere usato come sistema di storage key/value – un sistema, cioè, che permette di salvare in maniera semi permanente dei dati attraverso richieste HTTP successive; un po’ come fa il più famoso memcache. La funzione per reperire questi dati una volta salvati è – guarda caso – proprio apc_fetch(). Ecco dunque svelato il meccanismo: una volta attivato l’upload progressivo tramite il campo APC_UPLOAD_PROGRESS della form, APC inizierà internamente ad aggiornare un valore della sua cache corrispondente alla chiave che si chiamerà “upload_<ilcodiceunivocochehoscelto>”. Ecco dunque perché il valore di quel campo dovrà essere univoco: è una chiave all’interno di un “database” (la cache di APC).

E che dati saranno salvati in corrispondenza di quella chiave? Ovviamente tutti quelli che ci interessano per monitorare l’upload (sono predefiniti): il valore di “current” (quanti byte trasferiti), il “total” (il numero di byte da trasferire), lo “start_time” (il timestamp in cui è iniziato il trasferimento), più altre cosette che vedremo in un secondo momento.

Nello script oltre ai dati presenti ve ne inserisco altri “just for fun”. Visto che i dati ce li abbiamo, perché non calcolare anche il tempo stimato alla fine del caricamento e la velocità approssimativa dell’upload?

Prima di chiudere questo articolo, vi faccio conoscere un altro amichetto…

Il primo problema che vi troverete ad affrontare nel provare questa (o altre) soluzioni per il trasferimento di file, è che la vostra connessione in localhost è troppo veloce! I file passano dallo 0% al 100% in un attimo. Come fare a simulare una connessione casalinga? O anche qualcosa di ancora più infimo, un modem 56K?

Installatevi trickle.

$ apt-cache search trickle
trickle - user-space bandwidth shaper

I traffic shaper, solitamente, sono delle bestiacce: configurarli è un po’ una pena. Trickle invece è simpaticissimo: eseguite qualsiasi programma tramite di esso, e questo programma avrà la banda tagliata quanto decidete voi.

Un esempio vale più di cento parole: vogliamo simulare un Firefox di un utenza con 80KB/s di banda? Ecco:

trickle -s -d 80 -u 80 firefox

That’s it; usando quell’istanza di firefox per le prove la vostra banda sarà garantita essere non più di 80KB/s e vedrete con soddisfazione i vostri numerelli crescere piano piano.

A risentirci alla prossima puntata :)


Upload progressivo in PHP con APC e JavaScript – Parte 1

Prima parte (leggi la seconda parte, leggi la terza parte)

In questo articolo, suddiviso per motivi di lunghezza in più parti, cercherò di  spiegare il sistema tramite il quale sia possibile monitorare l’upload di un file in modo da visualizzare all’utente lo stato di avanzamento dell’operazione.

Questi articoli non sono vogliono essere un semplice how to, quando invece una dettagliata analisi delle problematiche inerenti a questo sistema diventato ormai di sempre più comune utilizzo sui siti web.

La complessità di tutto questo “giochino” è da imputare principalmente al linguaggio  server side che utilizzerò in questi articoli: il PHP.

Vediamo subito il perché.

A grandi linee, l’upload di un file dal browser al server funziona semplicemente così: l’utente seleziona il file dal proprio computer e poi invia la form. In quel momento il browser inizia una trasmissione dati con lo script che riceve i dati della form, inviandogli senza soluzione di continuità i byte che compongono il file in fase di trasmissione.

Lato server il PHP dal momento che inizia la ricezione del file, “appoggia” su un file temporaneo i dati che stanno arrivando finché la trasmissione non sarà terminata o cancellata. Nel secondo caso il PHP provvederà a cancellare il file temporaneo e tutto finisce lì. Nel caso di ricezione avvenuta, lo script riprenderà il controllo e sarà in grado di copiare il file temporaneo dove meglio crede (move_uploaded_file(), non dico certo niente di nuovo.)

Non esistono sistema “nativi” da parte del browser per capire quanti byte ha al momento trasmesso. È possibile utilizzare dei trucchetti lato client che prevedono l’utilizzo di un piccolo Flash movie che fa da “gateway” usando Flash stesso per aprire il socket di trasmissione dati verso il server. In tal caso sarà lo stesso Flash a fornire le indicazioni che ci interessano e la gestione è assai più semplice. Tuttavia l’approccio (usato in tantissime soluzioni di alto livello) non è scevro da problemi e in questa sede non ci interessa.

Senza usare Flash, dunque, quello che occorre fare è chiedere al server continuamente a che punto è arrivato leggendo il file temporaneo che sta crescendo man mano che la trasmissione procede.

Questa richiesta verrà fatta dunque via Ajax, chiedendo ad un altro script PHP lo stato di avanzamento del file xxxyyyzzz…

Sembra facile, ma non è.

Il problema nasce dal fatto, come dicevo poco fa, che stiamo usando PHP. Soltanto dalla versione 5.2 in poi è possibile infatti usare un sistema per chiedere al PHP a che punto è con l’upload. Prima, non esisteva modo… impossibile sapere il nome del file temporaneo da un altro processo, né tantomeno dal processo PHP che lo sta generando.

Dalla versione 5.2 è stato introdotto un “hook” che permette alle estensioni PECL del php di intercettare le fasi di upload di un file (no, non si può fare direttamente da PHP). Ovviamente non è che adesso ci mettiamo lì, con il nostro compilatore C a scrivere la nostra fantastica estensione che usa quell’hook. Questo lavoro, fortunatamente, è già stato fatto da qualcuno.

Credo che la maggior parte degli sviluppatori in PHP siano a conoscenza di APC. Il suo mestiere è duplice. In primis è un cosidetto sistema di “opcache”, ovvero permette al PHP di salvarsi i nostri script “precompilati” (passatemi il termine), in modo da velocizzarne le esecuzioni successive (stessa cosa di eAccelerator o xcache, se conoscete già queste alternative). In secundis, mette a disposizione agli script PHP un buffer di memoria ad accesso rapido, per salvare al suo interno delle informazioni magari lette da un sottostitema più lento (dbms, per esempio). È più o meno quello che fa memcache, solo che in questo caso non c’è bisogno di un servizio esterno che giri. APC infatti è un’estensione del PHP e come tale, una volta che questa sia stata compilata e caricata dal PHP, è disponibile da subito.

Fin qui – credo – nessuna grossa novità.

La cosa interessante è che, dalla versione PHP 5.2, APC implementa appunto la gestione di quel famoso “hook” per il monitoraggio dell’upload, offrendo un sistema ai normali script PHP per interrogare l’andamento della creazione del nostro file. Non solo: tramite il servizio di APC sarà così possibile controllare se il nostro file ha terminato il caricamento, se il caricamento è stato annullato dall’utente o da un problema di rete e fare dei semplici calcoli per sapere anche il “rate” di trasmissione in kbyte.

Nella seconda parte dell’articolo vedremo come fare ad implementare un sistema del genere, usando “solo” HTML, PHP e un po’ di sano JavaScript.

Nel frattempo, assicuratevi di avere a disposizione APC (perlomeno) sulla vostra macchina di sviluppo. In Ubuntu è questione di “sudo apt-get install php-apc” e far ripartire Apache. La configurazione, eventualmente, la troverete in
/etc/php5/apache2/conf.d/apc.ini


I contenuti di questo sito sono distribuiti con una licenza Creative Commons 2.5 eccetto dove diversamente specificato.

Tema WordPress Punto5 sviluppato da Claudio Cicali; icone del set famfamfam silk e komodomedia.

© 2005-2010
Claudio Cicali