Home / Indice sezione | www.icosaedro.it | ![]() |
Ultimo aggiornamento: 2008-01-21
In questo articolo descriviamo una metodologia di sviluppo per siti WEB attivi incentrata sulla strutturazione per funzioni e orientata al programmatore invece che al grafico. Questo approccio è particolarmente utile per creare articolate interazioni con l'utente che possono richiedere molte pagine. Per dare concretezza alla trattazione descriveremo gli esempi usando il popolare linguaggio PHP, sebbene gli stessi concetti si possano adattare ad altri linguaggi e sistemi di sviluppo.
Pagine o funzioni?
Strutturare il sito WEB per funzioni
L'esempio in pratica
Estendere il sito
Sicurezza
Limitazioni
Modularità
Conclusioni
L'uso più comune del PHP prevede prima di creare un sito WEB statico, e successivamente di inframmezzare al codice HTML le istruzioni PHP necessarie per riempire le parti dinamiche della pagina. Il risultato tipico di questo modo di operare è che il sorgente della pagina risulta un confuso guazzabuglio difficile da manutenere. Se questo metodo di lavoro può essere adeguato nei casi più semplici, per siti WEB articolati e complessi rapidamente la situazione sfugge di mano.
Struttura tradizionale di un sito WEB attivo. Ogni pagina che costituisce il sito corrisponde a un URL distinto e a un mini-programma che la genera. L'attenzione degli sviluppatori si concentra sul layout grafico delle pagine e il codice sorgente risulta ripetitivo e difficile da manutenere. |
Per dare ordine al tutto esistono diverse tecniche. Ad esempio si può uscire dalla logica "un file per ogni pagina WEB" e passare alla logica "una funzione per ogni pagina WEB". Così tutto il sito si riduce a un solo file di programma dove ogni funzione si occupa di generare una pagina. In questo articolo descriviamo questa soluzione.
Il trucco è abbastanza semplice: ogni pagina WEB è
individuata da un parametro di nome f
che riporta il nome
della funzione che la deve generare. Ad esempio, per la pagina "home"
del sito scriveremo una funzione Home()
che sarà
individuata dal parametro f=Home
. Le altre pagine del sito
dovranno essere battezzate con un nome e una rispettiva funzione che le
genera. Potremo così avere la pagina Listino()
,
la pagina Carrello()
, ecc. che hanno il significato
facilmente intuibile.
WEB-application capace di generare le diverse pagine di cui è composto il sito in base al valore di un parametro contenuto nella richiesta proveniente dal client. Nell'esempio della figura il parametro f=Home determina la chiamata della funzione Home() della WEB-application. Lo sviluppo del sito WEB si concentra sull'aspetto di programmazione. |
Il nostro programma, oltre alle varie funzioni/pagine, avrà anche
una parte di codice che interpreta il valore del parametro f
e che richiama la funzione prevista. A questo scopo basta una semplice
istruzione switch
.
Il programma sarà allora costituito da qualche funzione di utilità
(Anchor()
, Testa()
, Piede()
),
dalle funzioni che generano le varie pagine (Home()
,
Pagina1()
, Pagina2()
, ...) e da uno switch
che invoca la funzione in base al valore di f
.
Schematicamente:
$f = $_REQUEST['f']; switch( $f ){ case 'Home': Home(); break; case 'Pagina1': Pagina1(); break; case 'Pagina2': Pagina2(); break; default: Home(); }
Ogni ancora dovrà contenere un parametro di nome f
che
è il nome della funzione da chiamare. A questo provvede la funzione
Anchor()
. Questa funzione ha due argomenti fissi: il testo
dell'ancora in formato HTML e il nome della funzione da richiamare se
quell'ancora viene cliccata. La funzione provvede poi a generare il
tag HTML corretto. La funzione Anchor()
può anche avere
coppie nome/valore di parametri da aggiungere all'URL generato: la
funzione provvede a codificarli opportunamente e ad aggiungerli nell'URL.
Ad esempio, per produrre un'ancora che richiama la funzione/pagina
Tabella()
con parametri utente=Mario Rossi
e
sessione=abcdefgh
(il cui significato, qualunque esso sia,
per il momento non ci interessa) basterà scrivere:
Anchor("Tabella dati", "Tabella", "utente", "Mario Rossi", "sessione", "abcdefgh");
che invierà nella pagina WEB il seguente codice (su di una unica riga):
<a href="/esempio1.cgi?f=Tabella&utente=Mario%20Rossi &sessione=abcdefgh>Tabella dati</a>
Per dimostrare il funzionamento di tutto l'ambaradam, ho preparato anche un esempio funzionante: basta cliccare qui sotto per vedere il programma in funzione e il sorgente completo:
#!/bin/php -c.. <?php /** * Example of WEB application structured by functions. * This source program is commented in the article {@link * http://www.icosaedro.it/php/web-per-funzioni.html}. It illustrates how a * WEB application might be structured by functions, each one implementing a * "page". The HTML FORMs are not handled, because this is the subject of * another article (see <i>Secure Call-Back Handling in WEB Pages</i>). * @package WebByFunctions * @copyright 2004 by icosaedro.it di Umberto Salsi */ /*. require_module 'standard'; .*/ /** * Generate a HTML anchor. The HTML anchor generated, if clicked by the * user, will cause the invocation of the given function with the given * arguments. * @param string $text Text of the anchor that the user will see. * @param string $func Name of the function to call if this anchor is clicked * by the user (also referred as the "call-forward" in the article).<p> * The optional arguments are the HTTP parameters that $func will get * through $_REQUEST[] or $_GET[]. These parameters may be numbers * or strings. Complex types must be serialized with serialize() and * unserialized with unserialize() by $func. * @return void */ function Anchor($text, $func /*., args.*/ ) { echo "<a href=\"" . $_SERVER["PHP_SELF"] . "?f=$func"; for( $i = 2; $i < func_num_args(); ){ echo "&", (string) func_get_arg($i), "=", urlencode((string) func_get_arg($i+1)); $i+=2; } echo "\">", $text, "</a>"; } /** * Send the HTTP header and the HTML header. This function may be customized * to include backgrounds, page borders, styles and other layout elements. * @return void */ function PageHead() { header("Content-Type: text/html; charset=ISO-8859-15"); echo "<html><head><title>Esempio 1</title></head><body>"; } /** * Close the HTML code. The logical complement of PageHead(). * @return void */ function PageFoot() { echo "</body></html>"; } /** * The home page of this WEB application. This is the page the application * will call by default if no other page is specified or an error * occurred. Three anchors are set: the first one simply call the function * Page1() without arguments; the second one call Page2() with the argument * 'x'; the last one shows the source of this program. * @return void */ function Home() { PageHead(); echo "<h1>Wellcome in our beautiful WEB site!</h1>"; echo "Please, select a page:<p>"; Anchor("Page 1", 'Page1'); echo "<p>"; Anchor("Page 2", 'Page2', 'x', 'the_value_of_x'); echo "<p>"; Anchor("See the source", 'SeeSource'); echo "<p>"; PageFoot(); } /** * Show the source of this program. * @return void */ function SeeSource() { header("Content-Type: text/plain"); readfile( basename( $_ENV['SCRIPT_NAME'] ) ); } /** * Simple, little WEB page. * @return void */ function Page1() { PageHead(); echo "<h1>Page 1</h1>A simple, little WEB page."; PageFoot(); } /** * Acquire the parameter 'x'. This "page" acquires the parameter 'x' * we set inside Home(). * @return void */ function Page2() { PageHead(); echo "<h1>Page 2</h1>"; echo "x=", htmlspecialchars( (string) $_REQUEST['x'] ); PageFoot(); } /* Determinate the page to call. */ $f = isset($_REQUEST['f'])? (string) $_REQUEST['f'] : "Home"; switch( $f ){ case 'Home': Home(); break; case 'SeeSource': SeeSource(); break; case 'Page1': Page1(); break; case 'Page2': Page2(); break; default: Home(); }
La procedura da seguire per aggiungere nuove pagine al sito è la seguente:
NuovaPagina
.
NuovaPagina()
. E' utile sfruttare
le funzioni di utilità Testa()
e Piede()
per dare al sito un aspetto uniforme, ma questo non è strettamente
necessario. Ricordare di generare le ancore sempre usando la funzione
Anchor()
.
switch
nel corpo
principale del programma:
case 'NuovaPagina': NuovaPagina(); break;
Home()
:
Anchor('Clicca qui', 'NuovaPagina');
Tutti i parametri provenienti dal client possono essere artefatti,
avere lunghezza arbitraria o mancare del tutto. Non esiste alcuna garanzia
che i parametri impostati con la funzione Anchor()
verranno
ritornati al server intatti. L'utente può comporre nella barra degli
indirizzi del suo browser un URL arbitrario con parametri arbitrari e poi
richiamare la nostra WEB-application con questi parametri.
Pertanto in ogni funzione/pagina che richiede dei parametri DOBBIAMO controllare che questi parametri ritornati siano validi e coerenti, le stringhe siano codificate nel charset previsto e siano di lunghezza ragionevole, i numeri siano positivi o nulli e non superino un certo massimo, ecc. Inoltre, anche se i dati sono validi presi singolarmente, dovremo verificare che siano validi nel loro insieme: il mese "febbraio" e il giorno "31" sono valori validi presi singolarmente, ma nel complesso individuano un giorno che non esiste. Se il nostro programma accetta ciecamente questi parametri, ecco che avremo creato una falla di sicurezza con conseguenze imprevedibili.
Essere costretti ad affrontare il problema della affidabilità dei parametri della richiesta dentro a ogni pagina/funzione vuol dire anche che la sicurezza del sito tende a calare man mano che il numero di pagine (e quindi di funzioni) aumenta. Inoltre dover verificare la correttezza di parametri che erano già stati a suo tempo determinati dal nostro stesso programma sembra un po' stupido, aumenta la complessità e riduce l'efficienza al crescere del numero dei parametri. In una parola, il nostro metodo di sviluppo scala male al crescere della WEB-application.
Lo stesso problema incombe anche sui siti WEB strutturati nel modo tradizionale, ma da un sistema di programmazione WEB serio è lecito aspettarsi qualcosa di più. Nel prossimo articolo metteremo a punto un meccanismo che risolve completamente questo problema.
Esistono delle limitazioni alla lunghezze delle richieste GET, sia lato client, sia lato server. Ad esempio, il server Apache dispone della direttiva LimitRequestLine il cui valore default è di 8190 bytes. Questo pone un limite al numero e alla lunghezza degli argomenti delle richieste GET. Anche questa limitazione verrà risolta man mano che perfezioneremo il nostro framework.
Qui ci siamo concentrati sulle richieste di tipo GET coinvolte nelle ancore HTML. Per supportare i FORM potremmo estendere facilmente il nostro mini-framework aggiungendo un campo nascosto "f" con il nome della funzione che acquisisce il FORM; altri campi nascosti potrebbero contenere gli eventuali parametri aggiuntivi. Nel prossimo articolo affronteremo il problema dei FORM in modo più versatile e vedremo come sia possibile produrre FORM con due o più bottoni e come sia possibile associare ad ogni bottone una distinta funzione della WEB-application. Questo ci permetterà di sviluppare maschere di inserimento più ricche e articolate.
WEB-application più articolate richiedono di suddividere il programma in
più moduli. Una tipica applicazione gestionale potrebbe richiedere i moduli
Anagrafica, Fatture, Magazzino, ecc. Il nostro esempio didattico non tiene
conto di questa necessità. Ad esempio, come fare per individuare in modo
univoco la funzione "Inserimento()" del modulo "Fatture"? Un modo semplice
di risolvere questo problema è introdurre una sintassi articolata per il
nome della funzione che comprende anche il nome del modulo dove si trova
la funzione. Ad esempio, la richiesta proveniente dal client conterrà
il parametro f=Inserimento@Fatture
dove il modulo da caricare
viene indicato dopo la chiocciola.
Per implementare questo meccanismo dovremo cambiare un po' la struttura
della WEB-application, in modo che tutte le richieste provenienti dal client
vadano al request handler rh.php
. Il request handler
agisce come centro di smistamento delle richieste: analizza la richiesta
proveniente dal client, individua il modulo e la funzione da richiamare,
carica dinamicamente il modulo necessario ("Fatture") e infine invoca la
funzione di questo modulo ("Inserimento").
Al request handler si possono delegare anche altre utili funzionalità accessorie, come riconoscere e rinnovare la sessione, mantenere un log degli accessi della WEB-application, filtrare le richieste malevole o artefatte, ecc. Vedremo come implementare tutto questo gradatamente nel corso dei prossimi articoli e arriveremo a delineare un sistema completo che ho denominato "bt_" (bee-tee-underscore).
Abbiamo descritto una implementazione delle ancore HTML. Non abbiamo invece affrontato il problema quando sono coinvolti i FORM. Questo richiederà una ulteriore evoluzione della nostra tecnologia che vedremo nel prossimo articolo (Organizzare siti WEB per funzioni in PHP - Soluzione generale, www.icosaedro.it/php/web-per-funzioni-2.html) e che permetterà di associare ad ogni bottone del FORM una diversa funzione della nostra WEB-application.
Il difetto di questa strategia, come del resto di tutte le soluzioni comunemente utilizzate (ASP e .NET inclusi), è che i dati viaggiano inutilmente avanti e indietro tra client e server. Anche questo si può risolvere, ma occorre fare una evoluzione ulteriore che coinvolge il concetto di sessione e che porta ad una interazione di tipo modale.
La sezione dedicata al PHP (www.icosaedro.it/php) prosegue con altre soluzioni più sofisticate di WEB-application stateful e ad interazione modale che coprono anche i FORM, rimuovono il problema dei parametri di stato che viaggiano avanti e indietro, e incrementano la sicurezza in modo trasparente al programmatore.
Umberto Salsi | Commenti | Contatto | Mappa | Home / Indice sezione |
Still no comments to this page. Use the Comments link above to add your contribute.