E-Mail Express

Programma per invio di posta elettronica con interfaccia utente HTML

di Andrea Prosperi (prosperi@cosimo.ing.unifi.it) Elaborato per il corso di TELEMATICA Prof. F. Pirri Ing. M. Lunghi Anno Accademico 1995-96

INDICE

Introduzione Perché costruire interfacce basate sull'HTML? Cosa c'è bisogno di fare? Perchè non limitarsi a scrivere una GUI? Cosa occorre per un corretto funzionamento? L'uso del WWW per applicazioni interattive Introduzione Cenni sulle HTML Forms Descrizione delle HTML Form Osservazioni conclusive L'interazione tra WWW Browser, HTTP Server e programma CGI La Common Gateway Interface Invio del contenuto della form compilata dall'utente al Server WWW Come sono codificati i dati di una form. Come si ottengono i dati da una form. Metodo GET Metodo POST Comunicazione tra Server Web e Programma CGI Input dei CGI Script Variabili ambientali della CGI Output dei programmi CGI Convenzioni sul nome dei programmi CGI. Header prodotti dai programmi CGI. Esempi. Invio al client del risultato dell'esecuzione SOMMARIO DELLE OPERAZIONI DELLA CGI E-Mail Express: un servizio di posta in Internet. Scopo dell'elaborato Funzionamento del programma in linea di comando Vincoli cui sono sottoposti i programmi CGI Soluzione proposta Funzionamento del programma Problemi di implementazione incontrati Comandi disponibili 1. PUT: Invia un messaggio ad un utente. 2. SHOW: Mostra un messaggio ad un utente. 3. DEL: Cancella un messaggio dalla coda. 4. LIST: Mostra la lista dei messaggi in coda. 5. BYE: Chiude la connessione con il Mail-server. Comandi soppressi Conclusione Approfondimenti 1. Le URL URL relative a file (File URLs) URL relative a risorse Gopher (Gopher URLs) URL relative a risorse Usenet (News URLs) URL relative a risorse World Wide Web (HTTP URLs) URL parziali Altre URL 2. I sistemi con stato Definizione di STATO 3. La gestione dei file con il Perl Appendice A - Listati Implementazione in C di un programma CGI Input del programma CGI Variabili ambientali del programma CGI Output del programma CGI Modulo: HTML_UTILITY.c Modulo: MAIL_CLIENT.c Modulo: MAIL_CLIENTUTIL.c Modulo: JOLLY.c Modulo: JOLLY_PROC.c Modulo: PARAMETRI.c Documento: OPEN.html Appendice B - Come aprire un applicativo esterno Configurazione del server Come usare il browser Appendice C - Riferimenti Riferimenti ad ipertesti Altri riferimenti Appendice D - Librerie C per la programmazione CGI NCSA httpd LibCGI By EIT (Enterprise Integration Technologies Co.): libreria per ANSI C e C++ cgic: an ANSI C library for CGI Programming Cosa è cgic? Come ottenere cgic Conclusione

Introduzione

Questo documento descrive come costruire programmi con interfacce utente di tipo grafico basate sulle form, usando HTML (HyperText Markup Language) e CGI (Common Gateway Interface). L'HTML è usato solo per descrivere l'aspetto dell'interfaccia utente. Molti linguaggi di programmazione possono essere usati per scrivere il programma CGI; in questo caso si è usato il C. Per chi avesse poca familiarità con questi argomenti, è consigliabile consultare prima alcuni dei documenti riportati in Appendice C - RIFERIMENTI riguardanti la scrittura di form in HTML e di programmi CGI. Il caso di cui ci si occupa consiste nel dare una interfaccia utente di tipo HTML ad un programma già esistente scritto in linguaggio C avente interfaccia utente in linea di comando. Lo scopo di questa relazione è quello di mostrare come i vari problemi incontrati nel corso dell'aggiornamento del programma sono stati risolti e spiegare come scrivere programmi CGI con le loro form associate. Questa non vuole essere assolutamente una trattazione esaustiva sui programmi CGI; anzi deve essere considerato una base di partenza riguardo a questi argomenti, un vero e proprio "WORK IN PROGRESS", che necessita di aggiunte e precisazioni future.

Perché costruire interfacce basate sull'HTML?

Oggi al mondo esistono molte piattaforme e ciascuno vuole creare applicazioni indipendenti dalla piattaforma. Ciò può essere fatto con costosi cross-compilatori e costosi pacchetti software per la generazione di GUI (Graphycal User Interface). Per questo elaborato si è pensato invece di usare una interfaccia basata sull'HTML traendo vantaggio dal lavoro già fatto da altre persone, dal momento che non c'è motivo per cui ciascuno debba mettersi a scrivere lo &stesso& codice (più o meno) per generare finestre, bottoni, scatole, figure, ecc. L'HTML permette di rendersi le cose semplici, creando ad esempio un campo di input in modo estremamente facile scrivendo <INPUT NAME="inp">. In questo modo non si è costretti a scrivere un codice per maneggiare il testo selezionato nella finestra, o un codice per rendere l'input "clickable", in quanto è il browser web ad avere cura di tutto questo. Perciò, in ultima analisi, quando si usano le form dell'HTML si deve soltanto decidere il modo in cui l'interfaccia debba apparire, lasciando al browser web il compito di generarla; inoltre si genera una interfaccia utilizzabile potenzialmente da milioni di persone in tutto il mondo (tutti coloro che utilizzano il World Wide Web).

Cosa c'è bisogno di fare?

Le interfacce basate sull'HTML sono costituite da due parti principali: Nostro compito sarà quello di scrivere sia le form che i programmi. Fondamentalmente il lavoro principale consisterà nella stesura dei programmi CGI; essi saranno scritti in modo da prendere gli input necessari al programma dalle form e generare come output dei documenti HTML accessibili attraverso il browser web preferito.

Perchè non limitarsi a scrivere una GUI?

La principale ragione per usare l'HTML come interfaccia utente per un programma è che facendo in questo modo si trae vantaggio dal fatto che esistono Web browser praticamente per qualsiasi piattaforma sulla quale si può essere interessati a lavorare. Così è sufficiente possedere un compilatore che possa creare un codice eseguibile per la macchina di riferimento che possa funzionare da linea di comando ed un Web browser per la piattaforma e l'ambiente di presentazione desiderato. Le modifiche per fornire una interfaccia basata sull'HTML ad un programma già esistente ed avente interfaccia utente in linea di comando sono minime, limitandosi all'inserimento di alcune routine di libreria per estrarre gli input dalla forma codificata nella quale vengono passati al programma CGI. In realtà questo non è del tutto vero, in quanto l'esecuzione dei programmi CGI pone dei vincoli abbastanza forti, non tanto su programmi semplici, quanto su programmi tipo quello su cui si è operato (vedi il capitolo Vincoli cui sono sottoposti i programmi CGI per maggiori dettagli); questi vincoli tuttavia non sono tali da far rinunciare ai vantaggi offerti da questa soluzione.

Cosa occorre per un corretto funzionamento?


L'uso del WWW per applicazioni interattive

Introduzione

Gli utenti del Web si accostano in prima battuta al cosiddetto browsing - la navigazione passiva di informazioni statiche, quantunque in modo associativo e sensibile al contesto - col quale diventano familiari molto velocemente. L'uso del WWW per applicazioni interattive è invece meno immediato e richiede l'uso della Common Gateway Interface (CGI). L'interfaccia basata sulla CGI e le form dell'HTML permettono di sottoporre dei dati al server, che possono essere poi manipolati in un certo numero di modi. L'esempio di uso qui trattato e' la posta elettronica ma se ne possono pensare (e già ne esistono) tantissimi altri. Prima di passare al nostro problema specifico, vedremo come funziona a grandi linee l'architettura che permette l'interattività nelle applicazioni Web ed esploreremo gli sviluppi dei programmi CGI per permettere l'uso di queste applicazioni interattive.

Cenni sulle HTML Forms

I campi di inserimento dati (fill-out forms) sono usati nei documenti HTML per sottoporre delle informazioni ad un programma CGI attraverso un server HTTP e ricevere da questo i risultati richiesti. Esse consentono cioè di rendere interattivi i documenti HTML. Le form possono contenere un certo numero di elementi, alcuni essenziali, ed altri opzionali, a seconda della finalità della form. Ciascuna pagina HTML può essere una form, o avere una form come elemento all'interno della pagina. Le informazioni che seguono sono basate su "Mosaic for X version 2.0 Fill-out Form support" e "List of all Elements in the HTML 2.0 DTD". Per maggiori dettagli sulle form dell'HTML si veda in "The Internet Draft of the HTTP/1.0 Specification" ed in [Benvenuti]. Tutti questi documenti sono citati in Appendice C - RIFERIMENTI .

Descrizione delle HTML Form

L'elemento FORM viene usato per delimitare i moduli di inserimento dati all'interno di un documento HTML; quindi gli elementi HTML usati per richiedere i dati di ingresso devono essere contenuti entro i tag: <FORM> ... </FORM>.
I maggiori attributi del tag FORM sono ACTION e METHOD.
Gli altri elementi usati in una form sono:
  1. INPUT - Questo elemento rappresenta un campo i cui contenuti possono essere editati da un utente. Gli attributi dell'elemento INPUT sono:
    • TYPE - Il tipo di dato che questo campo accetta. Possono essere definiti diversi tipi:
      • TEXT - viene usato per campi di input costituiti da una singola linea di testo (è il tipo selezionato di default quando non viene specificato alcun attributo per l'elemento TYPE).
      • PASSWORD - viene usato per campi di input costituiti da una singola linea di testo nei quali i caratteri immessi vengono visualizzati con asterischi.
      • CHECKBOX - crea una serie di caselle ipertestuali tipo caselle da barrare con selezionamento non esclusivo; tutte le caselle sono identificate dallo stesso attributo NAME, ma ad ognuna è associato il proprio attributo VALUE. La selezione col mouse di una delle caselle CHECKBOX dà origine, nei dati trasmessi al programma CGI, ad una coppia nome/valore, che può anche risultare duplicata (in quanto la selezione da parte dell'utente non è esclusiva e sono ammesse più checkbox identiche in una stessa form).
      • HIDDEN - Il campo è nascosto all'utente, ed il contenuto viene inviato con i dati inseriti. Il valore imposto ad un campo di questo tipo costituisce una sorta di informazione di "stato" per il server web, cioè quella informazione che non deve essere modificata dal client e che dà al client stesso (cioè all'utente) l'illusione dello stato pur in presenza di un protocollo senza stato come l'HTTP. Vedi il capitolo APPROFONDIMENTI per la definizione di stato.
      • RADIO - crea una serie di caselle ipertestuali (tipo bottoni radio) con selezionamento esclusivo. Ciascuna casella ha lo stesso nome delle altre ma un valore diverso. Il bottone selezionato genera la coppia nome/valore nei dati trasmessi.
      • RESET - Crea un tasto speciale che, se selezionato dall'utente, riporta i campi della form ai loro valori iniziali di default cancellando i dati impostati dall'utente
      • SUBMIT - Crea una casella speciale che, quando selezionata dall'utente, dà l'istruzione di invio dei dati che sono stati impostati nei vari campi. Questo tasto deve essere sempre presente in una form; al limite è anche possibile creare una form costituita soltanto dal pulsante di invio. L'attributo VALUE fornisce una etichetta per il pulsante; se viene dato un attributo NAME, viene trasmessa una ulteriore coppia nome/valore assieme ai dati.
    • VALUE - Per campi testuali costituisce il valore inizialmente visualizzato nel campo stesso (default); per checkbox o radio buttons è invece il valore da ritornare se la casella viene selezionata.
    • CHECKED - Usato per indicare che un radio button o un checkbox è selezionato per default. Nota che i radio button e i checkbox non selezionati non ritornano alcuna coppia nome/valore quando la form viene sottoposta al programma CGI.
    • NAME - Un nome simbolico usato per sottoporre il contenuto della form. E' richiesto per molti tipi di input per fornire un unico identificatore per ciascuno dei vari tipi di campi di ingresso.
    • SIZE - Usato per specificare la grandezza della finestra per un campo di input (solitamente in caratteri). Tale finestra è scorribile in senso orizzontale, quindi l'utente può inserire quanti caratteri desidera.
    • MAXLENGTH - Usato per stabilire la massima lunghezza (solitamente in caratteri) di un campo. Questo attributo impone una limitazione superiore al numero di caratteri introducibili dall'utente.
  2. SELECT - Usato per rappresentare un menu con un insieme di alternative tra le quali l'utente può scegliere, alternative rappresentate dagli elementi OPTION. Gli attributi dell'elemento SELECT sono:
    • MULTIPLE - Necessario quando agli utenti è concesso di effettuare selezioni multiple.
    • NAME - stessa funzione dell'attributo NAME nei campi di tipo INPUT (vedi sopra).
    • SIZE - Specifica il numero di voci visibili. Se maggiore di uno, il browser mostrerà una lista (menu) con barre di scorrimento con le opzioni selezionabili.
  3. OPTION - può comparire solo all'interno di un elemento SELECT, e può assumere i seguenti valori:
    • SELECTED - indica che questa opzione è selezionata di default. In sua assenza viene assunto come default il primo valore della lista.
    • VALUE - indica il valore da ritornare se l'opzione viene selezionata. Il valore ritornato è quello scelto dall'utente o, in assenza di scelta, quello di default.
  4. TEXTAREA - Crea nel documento HTML una finestra scorribile in cui all'utente è consentito di inserire più di una linea di testo. Il testo tra i tag <TEXTAREA> e </TEXTAREA> è usato dal browser come il dato iniziale del campo. TEXTAREA ha i seguenti attributi:
    • NAME - stessa funzione di INPUT e SELECT (vedi sopra).
    • ROWS - indica l'altezza del campo in linee.
    • COLS - indica la larghezza del campo in colonne.

Osservazioni conclusive

Una HTML Form può trovarsi ovunque all'interno di un documento HTML, o anche essere creata da un programma CGI (come si vedrà meglio in seguito). La form vera e propria è composta dagli elementi compresi tra i due tag <FORM> ... </FORM>:

L'interazione tra WWW Browser, HTTP Server e programma CGI

I dati inseriti in una form dell'HTML da un utente sono assemblati dal browser in una serie di coppie nome/valore, un nome per ciascun elemento di ingresso, come un campo per l'inserimento di testo, un menu di opzioni, o una selezione a checkbox, ed un valore associato con il dato inserito o la scelta fatta dall'utente. Queste coppie nome/valore sono quindi inviate al server in modo che su di esse possa operare il programma CGI specificato dall'attributo ACTION dell'elemento FORM nella pagina HTML che contiene la form stessa. Il programma CGI può quindi agire sugli input, realizzando un certo numero di azioni, come inviare un messaggio di posta elettronica, interrogare o aggiornare un database, ordinare l'acquisto di beni per via elettronica, o qualunque altro task in grado di essere realizzato da un programma sul server web, e ritornare i risultati al client. Grazie alle form queste operazioni risultano notevolmente semplici per l'utente, il quale anziché ricorrere a programmi specifici spesso complicati, deve limitarsi a maneggiare documenti ipertestuali. E' importante sottolineare che il programma CGI è libero di scegliere il tipo di dati da restituire in risposta al client, nei limiti delle possibilità offerte dal protocollo HTTP. Egli può cioè generare sia un documento HTML (l'uscita più comune prodotta da programmi CGI), che qualunque altro tipo di dati previsto dallo standard MIME (Multipurpose Internet Mail Extensions), come immagini video o messaggi sonori, unendo alla interattività delle form dell'HTML la multimedialità offerta dal protocollo HTTP.
Nota: lo standard MIME specifica i tipi di dati che possono essere inviati con il protocollo HTTP.
La figura seguente evidenzia le modalità di passaggio al programma CGI dei dati inseriti dall'utente in una form.
... figura da inserire ...
Figura 1: Grafico che delinea le interazioni Browser/Server.
Lo scambio di informazioni tra browser e server WWW è gestito dal protocollo HTTP (HyperText Transport Protocol), le cui caratteristiche sono descritte in "The Internet Draft of the HTTP/1.0 Specification".
La comunicazione tra il server ed un programma esterno richiede invece una interfaccia apposita. La Common Gateway Interface, o CGI, è lo standard usato per interfacciare applicazioni esterne con un server di tipo HTTP o Web. Essa è descritta diffusamente nel capitolo seguente.

La Common Gateway Interface

La Common Gateway Interface, o CGI, è uno standard per programmi applicativi esterni che vogliano interfacciarsi con server di informazioni quali il World Wide Web. I Gateway sono programmi, o scripts che trattano le informazioni richieste e ritornano il documento appropriato o generano un documento in tempo reale ("on the fly"). Questi programmi possono essere scritti in linguaggi come il C, il Perl, il TCL; in linguaggi specifici per piattaforma come l'AppleScript o il Visual Basic o possono anche essere shell scripts. La NCSA ha diffuso informazioni dettagliate a proposito della "Common Gateway Interface". Le informazioni discusse sotto sono basate sulle specifiche per la CGI fornite dalla NCSA. Poiché in questa relazione ci limiteremo a quegli aspetti che sono direttamente coinvolti nella stesura dell'elaborato, lasciamo ai lettori particolarmente interessati nella creazione di programmi CGI l'invito a leggere a fondo il documento sopra citato.
La sorgente di informazioni più utile per i programmatori CGI è la "CGI Programmer's Reference", che contiene una lista di Frequently Asked Questions, risorse specifiche per i vari linguaggi rivolte ai programmatori CGI, puntatori alla documentazione CGI, peculiarità dei browser ed altro (vedi Appendice C - RIFERIMENTI). Si precisa subito che quelle che seguono sono le specifiche per CGI versione 1.1 (CGI/1.1). E' garantita comunque la compatibilità a ritroso per le future revisioni di questo protocollo.

Invio del contenuto della form compilata dall'utente al Server WWW

Come sono codificati i dati di una form.
Quando si scrive una form, ciascuna voce di ingresso ha un tag NAME. Quando l'utente pone i dati nei vari campi della form, tale informazione viene codificata nei dati della form. Il valore che viene dato dall'utente a ciascuna delle voci di input è chiamato VALUE. I dati di una form sono un flusso di coppie NAME=VALUE separate dal carattere &. Ciascuna coppia è codificata URL, ovvero gli spazi sono convertiti in segni "+" e alcuni caratteri sono sostituiti dalla loro codifica ASCII in esadecimale preceduta da un carattere "%" (%xx con xx espresso in esadecimale). Il contenuto della form (codificato URL) viene inviato al server come STREAM ininterrotto di dati (coppie NAME/VALUE) quando l'utente preme il tasto SUBMIT. Cosideriamo ad esempio la form generata dal documento HTML che lancia il programma CGI di posta che andiamo ad analizzare (comando OPEN).
.. aggiungere la figura ..
I nomi ed i valori dei campi della form vengono assemblati in un unico vettore ed inviati come segue:

hostname=lisa.dfc.unifi.it&login=Prosperi&pwd=Andrea

dove:
Vediamo adesso il caso di una form che presenta al suo interno più elementi, quale quella generata per l'immissione dei parametri relativi al comando PUT; essa è riportata nella figura sotto.
... inserire la figura ( 4 ).
I nomi ed i valori dei campi della form vengono assemblati in un unico vettore ed inviati come segue:

subject=prova+di+funzionamento+del+programma&text=questo+è+il+testo+di+un+breve+messaggio+dimostrativo¶m3=1¶m1=Prosperi&protocol=tcp
dove:

Come si ottengono i dati da una form.

Come noto, esistono fondamentalmente due metodi che possono essere usati per accedere alle form, il GET ed il POST. A seconda del metodo usato, i risultati codificati della form verranno ricevuti in modo diverso. La differenza principale tra i due metodi usati per inviare gli input dell'utente al programma CGI è che l'input dell'utente per GET è legato nella URL del programma CGI dopo il "?" (vedi esempio sotto), e sottoposto così al limite di 256 caratteri in un URL. Al contrario, POST invia gli input dell'utente al programma CGI tramite lo Standard Input, e così non è soggetto a nessun limite noto. D'altra parte il metodo POST non è abilitato di default su tutti i server, perciò può essere necessario riconfigurare il server su cui si vuol usare tale metodo. Questa operazione è largamente compensata dai vantaggi offerti dal metodo che consente al client di inviare dati non solo di qualunque lunghezza (come appena visto), ma anche di qualsiasi tipo compatibilmente con lo standard MIME.
Metodo GET
Si accenna brevemente al metodo GET (che, si noti, è il metodo di default), avendo usato in questo elaborato il metodo POST (fortemente raccomandato dagli sviluppatori del server NCSA). La URL generata con il metodo GET nell'esempio di figura 3 (dove ACTION="http://lisa.dfc.unifi.it/cgi-bin/mail_client") sarebbe stata la seguente:

http://lisa.dfc.unifi.it/cgi-bin/mail_client?hostname=lisa.dfc.unifi.it&login=Prosperi&pwd=Andrea
Si noti che la URL finale è formata dalla URL specificata dalla ACTION alla quale viene aggiunto, subito dopo il carattere di separazione"?", il vettore contenente nomi e valori dei campi (il contenuto della form compilata dal client). Per eseguire il programma CGI il server passa ad una shell del sistema operativo il seguente comando di linea:

>mail_client?hostname=lisa.dfc.unifi.it&login=Prosperi&pwd=Andrea
Il pacchetto HTTP completo inviato dal client al server con il metodo GET sarebbe stato quindi:

GET
/cgi-bin/mail_client?hostname=lisa.dfc.unifi.it&login=Prosperi&pwd=Andrea HTTP/1.0
Accept: www/source
Accept: text/html
...
Accept: image/gif
User-Agent: Lynx/2.2 libwww/2.14
From: netman@lisa.dfc.unifi.it
* una linea vuota (LF o CR/LF) *

In tale pacchetto, generato col metodo GET, si notano: Questo pacchetto è del tutto analogo a quello inviato dal client quando si richiede, anziché l'esecuzione di un programma CGI, la visione di un semplice documento HTML. Si noti infatti che il metodo GET, abilitato su tutti i server, costituisce il metodo di default anche per lo scambio dei normali documenti HTML. Le uniche differenze nel pacchetto nel caso di documenti HTML sono ovviamente:
Metodo POST
Nel nostro programma abbiamo usato comunque il metodo POST. In questo caso alla URL specificata da ACTION non viene aggiunto niente, per cui sarà del tipo:

http://lisa.dfc.unifi.it/cgi-bin/mail_client

e le corrisponderà un comando di linea nella shell del sistema operativo contenente solo il nome del programma CGI:

>mail_client

I dati della form sono inviati al programma CGI tramite lo Standard Input, e possono perciò essere lunghi a piacere senza pericolo di troncamento. Il programma CGI riceve i dati della form compilata dal client accedendo al dispositivo di standard input (vedi Appendice A - LISTATI per l'implementazione del programma CGI in linguaggio C). Vediamo, per concludere, il pacchetto HTTP completo inviato dal client al server con il metodo POST, per sottolineare le differenze sostanziali tra i due metodi:

POST /cgi-bin/mail_client HTTP/1.0
Accept: www/source
Accept: text/html
...
Accept: image/gif
User-Agent: Lynx/2.2 libwww/2.14
From: netman@lisa.dfc.unifi.it
Content-type: application/x-www-form-urlencoded
Content-lenght: 52
* una linea vuota (LF o CR/LF) *
hostname=lisa.dfc.unifi.it&login=Prosperi&pwd=Andrea

Rispetto al pacchetto inviato con il metodo GET si notano le seguenti differenze:

Comunicazione tra Server Web e Programma CGI

Il server ed il programma CGI comunicano principalmente in quattro modi, cioè attraverso:

  1. La linea di comando (usata solo in casi particolari, non per le form HTML)
  2. Lo Standard Input
  3. Le Variabili Ambientali
  4. Lo Standard Output
Per quel che ci riguarda quindi, il server passa i dati ricevuti da una form HTML ad un programma CGI tramite le variabili ambientali e/o lo Standard Input. Quindi viene eseguito il programma CGI, che normalmente ritorna tramite lo Standard Output alcune informazioni all'utente riguardanti il successo o il fallimento dei compiti a lui richiesti. Vediamo alcuni dettagli sulle variabili ambientali, lo standard input e lo standard output.
Input dei CGI Script
Per richieste che hanno le informazioni attaccate dopo l'header, come nel metodo POST, l'informazione sarà inviata al programma CGI tramite lo standard input. Il server invierà su questo descrittore di file (stdin) un numero di byte pari a CONTENT_LENGTH, ed allo stesso modo darà il CONTENT_TYPE dei dati. Il server non è in alcun modo obbligato ad inviare l'End-Of-File dopo che il programma ha letto un numero di byte pari a CONTENT_LENGTH.
Nell'esempio della form di figura 3 fin qui considerato, nel quale si ha che METHOD="POST", si ottengono come risultato della form 52 byte codificati come segue

hostname=lisa.dfc.unifi.it&login=Prosperi&pwd=Andrea

In questo caso il server pone:
CONTENT_LENGTH = 52;
CONTENT_TYPE = application/x-www-form-urlencoded.
Il primo byte sullo standard input del programma sarà "h", seguito dal resto della stringa codificata.

Poiché altri hanno già trattato questo tipo di problematiche (attorno alle quali c'è attualmente un forte interesse), esiste già un gran numero di programmi che svolgono questa decodifica e possono essere usati da chiunque. Esistono cioè degli archivi CGI, nei quali è possibile ritrovare i pacchetti software a cui occorre fare riferimento. Ad esempio, per il C esistono gli script di default per httpd della NCSA, una serie di routine di decodifica delle form e molti programmi di esempio; alcuni di essi sono stati utilizzati anche nel programma che andremo ad analizzare per tradurre la stringa di interrogazione in un gruppo di strutture (vedi APPENDICE A - LISTATI ed APPENDICE D - LIBRERIE C PER LA PROGRAMMAZIONE CGI per quel che riguarda il linguaggio C).
Altrettanto esiste per altri linguaggi, come il PERL o il TCL (vedi la "Yahoo's CGI list" per questi e gli altri linguaggi utilizzabili).
Osservazione> Il procedimento di base per la decodifica dei dati della form, indipendentemente dal linguaggio usato per l'implementazione delle routine, consiste nei seguenti passi
  1. dividere i dati dai caratteri "&";
  2. quindi, per ciascuna coppia nome=valore ottenuta, effettuare la decodifica URL prima del nome e poi del valore;
  3. infine utilizzare nomi e valori per gli scopi desiderati.
Variabili ambientali della CGI
Accanto alle coppie nome/valore assemblate con le form al server giungono dal client molte altre informazioni. I dati che costituiscono il contenuto della form non sono infatti le sole informazioni di cui il server ha bisogno per espletare correttamente le operazioni richiestegli dal client. Il server WWW deve infatti sapere anche tantissime altre cose, come il nome dell'host da cui ha ricevuto la form, il metodo usato dalla form, etc. Tutti questi dati, sia quelli inviati dalla form HTML che le informazioni di corredo, devono quindi essere passati all'applicativo esterno. Allo scopo di passare al programma CGI le informazioni da lui richieste, il server usa, oltre allo Standard Input (come si è appena visto), alcune variabili ambientali. Le variabili ambientali, che vengono settate quando il server esegue il programma applicativo, sono di tre tipi, come descritto dettagliatamente qui sotto, dove, per non appesantire la trattazione, sono riportate solo quelle che contengono le informazioni più importanti.
  1. Le seguenti variabili ambientali non sono specifiche per una richiesta e vengono settate per ogni richiesta:
    • SERVER_SOFTWARE
      Il nome e la versione del software del server di informazioni che inoltra la richiesta (e che lancia l'applicazione esterna).
      Formato: nome/versione.
    • SERVER_NAME
      Il nome dell'host del server, un alias DNS, o l'indirizzo IP come apparirebbe in un URLs auto-referenziato.
    • GATEWAY_INTERFACE
      La revisione delle specifiche CGI a cui questo server si conforma.
      Formato: CGI/revisione
  2. Le seguenti variabili ambientali sono specifiche per una richiesta essendo completate dal programma applicativo
    • SERVER_PROTOCOL
      Il nome e la versione del protocollo informativo con il quale questa richiesta arriva.
      Formato: protocollo/versione
    • SERVER_PORT
      Il numero della porta a cui la richiesta è stata inviata.
    • REQUEST_METHOD
      Il metodo con cui la richiesta è stata fatta. Per richieste HTTP, esso è normalmente "GET" or "POST". Sia GET che POST sono usati per inviare l'informazione della form al programma CGI. GET codifica tale informazione dentro la URL, mentre POST la codifica e la invia come standard input al programma CGI.
    • PATH_INFO
      L'interfaccia CGI permette al client di inserire nella URL che individua l'applicativo esterno informazioni aggiuntive specifiche al contesto del programma CGI stesso. Ciò significa che ai CGI-scripts si può accedere grazie ai path virtuali contenuti nella URL, seguiti da informazioni extra alla fine di questi path virtuali. Queste informazioni extra sono inviate come PATH_INFO e servono ad aggiungere ulteriore path alla fine del path già specificato per accedere ai programmi CGI. Questa informazione non viene codificata in alcun modo dal server, ma deve essere decodificata prima di essere passata al CGI script poiché arriva da un URL.
      L'esempio più esplicativo della funzione di PATH_INFO è la trasmissione delle locazioni dei file ai CGI script. Per illustrarlo, supponiamo di avere sul server un programma CGI chiamato "/cgi-bin/prova" che può operare sui file residenti sulla DocumentRoot del server stesso. Si deve essere in grado di dire a "prova" il file su cui operare. Includendo una informazione aggiuntiva sul path alla fine della URL, "prova" saprà la locazione del documento relativa alla DocumentRoot tramite la variabile ambientale PATH_INFO, o il vero path del documento tramite la variabile ambientale PATH_TRANSLATED che il server genera per lui.
    • QUERY_STRING
      Tutta quella informazione che segue il "?" nella URL e che referenzia il programma CGI. Questa è solitamente l'informazione di interrogazione, ad esempio quello che l'utente vuole cercare in un database archie, e nel metodo GET coincide con la serie di coppie nome/valore inviate dal browser quando usa una form. Questa informazione può essere aggiunta, oltre che da una FORM HTML (con METODO GET), anche da un documento o inserita a mano in una anchor HTML che fa riferimento ad un applicativo. Questa variabile è codificata secondo lo standard URL, e risulta necessario decodificarla se la si vuole usare. Essa dovrebbe sempre essere settata quando c'e una richiesta, indipendentemente dalla decodifica della linea di comando. Anche se il programma non sta decodificando i dati di una FORM, è ugualmente possibile ottenere sulla linea di comando la stringa di richiesta decodificata. Ciò significa che ciascuna parola della stringa di richiesta sarà in una diversa sezione di ARGV. Ad esempio, la stringa di richiesta "forms rule" sarà data al programma con argv[1]="forms" e argv[2]="rule". Se si sceglie di usare questo, non è necessaria alcuna operazione sui dati prima del loro uso.
    • REMOTE_HOST
      Il nome dell'host dove si trova il browser che effettua la richiesta di CGI. Se esso risulta non risolto (cioè se il server non possiede questa informazione), si ricorre all'indirizzo IP, contenuto in REMOTE_ADDR e questa variabile viene lasciata non settata.
    • REMOTE_ADDR
      L'indirizzo IP dell'host remoto che effettua la richiesta.
    • REMOTE_USER
      Se il server supporta la autenticazione dell'utente, e lo script è protetto,questo è il nome dell'utente così come è stato verificato.
    • CONTENT_TYPE
      Per richieste che hanno le informazioni attaccate alla richiesta di far partire un programma CGI, come nel metodo POST, questa variabile specifica il tipo di informazioni presenti (standard MIME).
    • CONTENT_LENGTH
      La lunghezza dei dati inviati così come viene data dal client.
  3. In aggiunta ad esse, le linee di header ricevute dal client, se esistono, sono poste dentro le variabili di ambiente con il prefisso HTTP_ seguito dal nome dell'header. Qualunque carattere "-" presente nel nome dell'header è convertito nel carattere "_". Il server può escludere qualunque header che ha già trattato, come Content-type e Content-length. Se necessario, il server può decidere di escludere alcuni o tutti questi header se includendoli viene superato uno dei limiti dell'ambiente del sistema. Esempi di questo tipo di variabili sono la HTTP_ACCEPT definita in CGI/1.0 e l'header User-Agent.
    • HTTP_ACCEPT
      I tipi di MIME che il client accetta, come dati dagli header HTTP. Altri protocolli hanno bisogno di ottenere questa informazioni da altre parti. Ciascuna voce in questa lista deve essere separata da virgole come per le specifiche HTTP.
      Formato: tipo/sottotipo, tipo/sottotipo
    • HTTP_USER_AGENT
      Il browser che il client sta usando per inviare la richesta.
      Formato generale: software/versione libreria/versione.
Output dei programmi CGI
Una volta richiamato, il programma CGI elaborerà i dati in ingresso e produrrà un output destinato al client in attesa di risposta. Il programma invia il suo output al server attraverso il dispositivo di Standard Output (stdout), facendo uso di una qualunque delle funzioni di uscita previste dal linguaggio con cui è implementato. Questo output può essere sia un documento generato dal programma, che delle istruzioni dirette al server per il ritrovamento dell'output desiderato.
Convenzioni sul nome dei programmi CGI.
Normalmente i programmi CGI producono una uscita che viene interpretata dal server prima di essere rimandata indietro al client. Un vantaggio di questo procedimento consiste nel fatto che il programma non ha bisogno di inviare un header HTTP/1.0 completo per ciascuna richiesta. Alcuni programmi potrebbero voler parlare direttamente al client evitando che il server analizzi la loro uscita; per distinguerli dagli altri, la CGI richiede che il loro nome inizi con nph-. In questo caso è responsabilità del programma CGI ritornare una risposta valida al client (conforme agli standard HTTP/1.0 o HTTP/0.9), se non vuole che il suo header sia analizzato dal server WWW.
Header prodotti dai programmi CGI.
L'uscita di un programma CGI consiste di un header opportuno, di una linea vuota, e del corpo della risposta da inviare al client in attesa. L'header consiste di alcune linee di testo, nello stesso formato di un header tipico del protocollo HTTP (vedi sotto), concluse da una linea bianca, composta cioè soltanto da un LF (Line Feed) o da un CR/LF (Carriage Return/Line Feed ). Ogni header che non è diretto al server viene inviato direttamente al client. Al momento attuale, le specifiche definiscono tre tipi di direttive al server WWW:

Esempi.

  1. Supponiamo che il programma CGI sia un convertitore di documenti da un qualche formato in HTML. Quando il convertitore ha esaurito il suo compito, invierà come output sullo stdout quanto segue (nota che le linee che iniziano e finiscono con --- sono solo per esempio e non sono prodotte in uscita):
    --- Inizio della uscita ---
    Content-type: text/html
    .....
    --- fine della uscita ---
    Notare la linea vuota dopo Content-type.

  2. Adesso supponiamo di avere uno script che, in certe circostanze, vuole che il server ritorni il documento /path/doc.txt proprio come se l'utente avesse realmente richiesto per cominciare un http://server:port/path/doc.txt. In questo caso, il programma produrrà in uscita quanto segue:
    --- Inizio della uscita ---
    Location: /path/doc.txt
    .....
    --- fine della uscita ---
    Il server realizzerà quindi la richiesta e la invierà al client.

  3. Adesso supponiamo di avere uno script che vuole far riferimento ad un server Gopher. In questo caso, se il programma vuole che l'utente faccia riferimento a gopher://gopher.ncsa.uiuc.edu/, produrrà in uscita quanto segue:
    --- Inizio della uscita ---
    Location: gopher://gopher.ncsa.uiuc.edu/
    .....
    --- fine della uscita ---

  4. Infine, supponiamo che lo script voglia parlare direttamente al client. In questo caso, se il programma è referenziato con SERVER_PROTOCOL pari a HTTP/1.0, produrrà in uscita la seguente risposta conforme all'HTTP/1.0:
    --- Inizio della uscita ---
    HTTP/1.0 200 OK
    Server: NCSA/1.0a6
    Content-type: text/html
    --- Questo è un documento di tipo html creato in tempo reale per il client ---
    .....
    --- fine della uscita ---

    Si veda l'Appendice A - LISTATI per l'implementazione di un programma CGI in linguaggio C.

Invio al client del risultato dell'esecuzione

Il server preleva i dati dallo Standard output e li invia al client secondo il protocollo HTTP. Vediamo un esempio di pacchetto HTTP inviato dal server al client:

HTTP/1.0 200 OK
Date: Wednesday, 23-Nov-1995 18:30:16 GMT
Server: NCSA/1.1
MIME-version: 1.0
Last-modified: Monday, 21-Nov-1995 11:10:56 GMT
Content-type: text/html
Content-lenght: 2542
* una linea vuota (LF o CR/LF) *
<HTML><HEAD><TITLE>E-mail express ...
* resto del documento HTML *

In esso è specificato:

SOMMARIO DELLE OPERAZIONI DELLA CGI

La Common Gateway Interface descrive i metodi per l'interazione tra server informativi e programmi applicativi esterni (o, in generale, qualunque applicazione esterna). Come si è visto, le problematiche coinvolte nei vari passi della comunicazione tra WWW browser, WWW server e programma CGI sono molteplici. Esse possono essere riassunte seguendo l'ordine in cui si susseguono i passi di tale comunicazione (vedi figura 2):
  1. Il client richiede al server l'esecuzione di un programma CGI con alcuni dati e parametri di ingresso. I problemi coinvolti in questo passaggio sono:
    • Il metodo di codifica del contenuto della form. Come è codificata l'informazione che il client immette durante la compilazione della form contenuta in un documento HTML interattivo visualizzato da un browser come Mosaic o Netscape.
    • Il modo in cui giunge al server la URL specificata dal comando ACTION, URL che contiene la richiesta di mandare in esecuzione un programma CGI.
  2. Il server chiama il programma passandogli i dati ed i parametri inviati dal client. I problemi coinvolti in questo passaggio sono:
    • Come il server richiama il programma CGI, come gli passa il contenuto della form, quali altri dati deve fornire il server al programma CGI per un corretto funzionamento di quest'ultimo.
  3. Il programma CGI esegue le operazioni richieste e rimanda al server (attraverso interfaccia CGI) i dati elaborati perchè li trasmetta al client, che è in attesa di risposta. Problemi:
    • Come il programma CGI passa al server i dati di output.
  4. Il server rimanda al client i dati elaborati dal programma CGI (tramite protocollo HTTP). Problemi:
    • Come il server preleva i dati inviati dal programma CGI e li passa al client.
Lo scambio di informazioni tra client e server è gestito dal protocollo HTTP (HyperText Transport Protocol); esso detta le regole standard per risolvere i problemi di cui ai punti 1 e 4. L' HyperText Transport Protocol è un protocollo senza stato (per la definizione di stato vedi il capitolo Approfondimenti) basato su un modello client-server. In base ad esso, i browser forniscono all'utente pagine "statiche", nelle quali cioè l'interazione con il server non dinamica (sebbene l'utente possa tirare giù le informazioni desiderate "clickando" sui link, non vi è effettiva interattività nel senso pieno del termine). I browser che impiegano questo protocollo possono raggiungere l'interattività ricorrendo alle form dell'HTML, con le quali si richiamano programmi applicativi esterni e si passano loro i dati di input.
La comunicazione tra il server ed un programma esterno richiede, come visto, una interfaccia apposita. La Common Gateway Interface, o CGI, è lo standard usato per interfacciare applicazioni esterne con un server di tipo HTTP o Web. Essa si occupa di gestire le questioni viste ai punti 2 e 3.
La figura seguente si propone di illustare e chiarire quanto appena detto:

... Figura 2 ...

Figura 2: Diagramma che illustra le interazioni tra WWW Browser, WWW Server e Programmi CGI.
Concludendo, i server HTTP che supportano la Common Gateway Interface possono sfruttare programmi esterni per svolgere le operazioni richieste dall'utente e calcolare i risultati che il server ritorna poi al browser. Questi server HTTP possono essere usati per sviluppare quella sorta di interattività richiesta all'interfaccia dai vecchi sistemi con stato. In questo modo mentre si forniscono l'indipendenza dalla piattaforma e una interfaccia attuale, si possono utilizzare le informazioni di sistemi già esistenti.

E-Mail Express: un servizio di posta in Internet.

Scopo dell'elaborato

L'applicazione che vedremo è l'implementazione di un programma CGI per l'invio di posta elettronica, usando il metodo POST. Nel nostro caso abbiamo un programma già esistente, scritto in linguaggio C, ed avente una interfaccia utente in linea di comando; ad esso vogliamo dare una interfaccia utente basata sull'HTML. Il programma in questione (che intendiamo modificare) è CLIENT, il programma che viene usato dagli utenti del servizio E-MAIL EXPRESS per collegarsi al MAIL-SERVER DAEMON.
Questo esempio illustra quella che è da molti riconosciuta come la classe di programmi con interfaccia HTML più complicati da realizzare, cioè quelli che richiedono gli input durante l'esecuzione. Nel nostro caso la situazione è aggravata dal fatto che il programma CGI (che nell'applicazione in questione agisce da client) interagisce con un server postale. Vedremo quali sono i vincoli cui sono sottoposti i programmi CGI, a quali problemi pratici di implementazione questi vincoli portano, quali sono state le soluzioni proposte per superare questi problemi. Il nostro esempio sarà utile soprattutto per mostrare le potenzialità delle interfacce basate sulle form, in particolare come un programma CGI possa costruire pagine HTML da ritornare al Web browser dell'utente, suggerendo che i programmi CGI possono essere usati per costruire una collezione di pagine HTML dinamicamente costruite.

Funzionamento del programma in linea di comando

Riassumiamo brevemente il funzionamento del programma CLIENT nella versione originaria, in cui gli input vengono dati dalla linea di comando.
Una volta effettuata la connessione al MAIL-SERVER DAEMON, il programma client presenta il prompt "Mail >", richiedendo un comando in linea del tipo:

Mail> COMANDO parametro#1 parametro#2 ... parametro#n

Il numero di parametri richiesti varia da comando a comando: alcuni comandi, come LIST e BYE, non ne richiedono alcuno, gli altri possono richiederne da uno, come nel caso di SHOW, fino ad un massimo di quattro per il comando PUTBINF. L'utente deve sapere esattamente non solo il numero di parametri richiesti da ciascun comando, ma anche l'ordine con cui devono essere inseriti, affinchè il programma sia in grado di interpretare ed eseguire correttamente l'operazione che l'utente vuole svolgere. Questo costringe l'utente a fare continuamente ricorso al comando HELP, con il quale viene visualizzato un file di testo in cui sono elencati tutti i comandi disponibili, i parametri relativi nel numero e nell'ordine in cui devono essere dati, corredati da un breve commento sulle operazioni svolte dai singoli comandi. Comunque sia , una volta inserito correttamente il comando e finita la fase di esecuzione dello stesso, il programma risponde con il risultato della operazione svolta e restituisce il prompt

Mail>

mettendosi in attesa del comando successivo. Analizziamo ad esempio il comando PUT, tramite il quale l'utente può mandare un messaggio di testo ad un altro utente con una certa priorità. Supponiamo che: la login dell'utente destinatario sia Alessandro il messaggio testuale che gli si vuole inviare sia memorizzato nel file prova.msg. si voglia dare al messaggio una priorità 3. Il comando da fornire sarà allora il seguente:

Mail> PUT prova.msg Alessandro 3

Se tutto procederà correttamente (esiste l'utente destinatario, esiste il file indicato, è nella directory corrente, è possibile aprirlo e leggerlo, è attiva la connessione con il Mail-Server, etc.) il client mostrerà un messaggio di conferma della buona riuscita dell'operazione. Tuttavia, da quanto appena visto, appare evidente che il programma risulta assai ostico da utilizzare per chi vi si avvicina per la prima volta, e non solo. L'utente, oltre a conoscere il nome del comando ed il numero dei parametri che richiede, deve anche inserire tali parametri nell'ordine giusto, affinchè tutto vada a buon fine. L'unico aiuto che viene fornito all'utente è il comando HELP, che permette la visualizzazione della lista dei comandi disponibili con tutti i parametri che richiedono e l'ordine in cui devono essere inseriti. Utilizzando la nuova versione di questo programma, l'utente sarà facilitato enormemente, in quanto dovrà soltanto inserire i dati nei campi di input delle form via via visualizzate dal WWW browser; sarà poi il programma CGI a preoccuparsi della interpretazione dei comandi inseriti, oltre alla gestione dei messaggi (l'invio, la ricezione, la cancellazione, etc.).

Vincoli cui sono sottoposti i programmi CGI

Osservando la versione originaria del processo CLIENT appena descritto (vedi per maggiori dettagli la relazione dell'elaborato E-Mail Express), si vede che esso era costituito da un ciclo while infinito che faceva nell'ordine le seguenti operazioni:
Trasformando il programma in un CGI script è assolutamente necessario stravolgere questa impostazione, che non può funzionare per i vincoli cui sono sottoposti i programmi CGI. Vediamoli per illustrare il motivo per cui non è possibile mettere un ciclo per chiedere ed eseguire i comandi come esiste invece in CLIENT.
  1. Per usare una interfaccia basata sull'HTML, i dati in input devono essere forniti al programma attraverso una form. Quando si sono descritte le form, si è visto che ciascun elemento <FORM> richiede tra l'altro, l'attributo ACTION, il quale specifica la URL del programma CGI al quale il server WWW passerà i dati di ingresso provenienti dalla form compilata dall'utente. Ad un INPUT fornito tramite una FORM deve sempre seguire una ACTION, ovvero sempre un eseguibile. Ciò comporta che l'input fornito non viene visto dal documento HTML o dal programma CGI da dove lo lancio, ma solo dal CGI script che è indicato dalla action e che riceve i dati. Non posso usare i campi di INPUT per inserire dei dati che servono direttamente al CGI che produce la form, ma solo per passarli ad un altro eseguibile. Già da questa considerazione si capisce che il programma CGI dovrà avere una struttura completamente diversa dal programma in linea di comando che realizzava le sue stesse operazioni. Esso dovrà lanciare un altro eseguibile (anche un processo analogo a sé) per ogni comando inserito: non potrà perciò eseguire un ciclo per ciascun comando, perchè non può sapere il comando (e gli eventuali parametri ad esso relativi) che l'utente ha fornito in input.
  2. Nel nostro caso specifico il problema è aggravato dal fatto che il programma CGI in questione svolge funzioni di client rispetto al server postale. Di conseguenza la sua terminazione causerà una caduta della connessione client-server, con conseguente messaggio di errore di tipo "Connection reset by peer" da parte del processo Mail-Server. Perchè ciò non accada è indispensabile che il processo client, una volta instaurata la connessione col server, non termini mai (a meno che non si voglia uscire dal programma), altrimenti la connessione instaurata cade. Ma come può non terminare se l'inserimento di ogni comando in ingresso tramite una form richiede il lancio di un eseguibile cui viene inviato il contenuto della form stessa? Si era pensato inizialmente di non far terminare il client, così da non far cadere la connessione col server ed evitare l'errore "Connection reset by peer", tenendolo attivo in qualche modo (es. con una specie di ciclo while infinito quale quello usato nel client in linea di comando descritto sopra). Si era organizzato il tutto nel modo seguente:
    • Iniziare con una form che richiedesse i parametri necessari all'apertura della connessione con il Mail-Server Daemon (nome dell'host su cui esso si trova, login e password dell'utente).
    • Questa form avrebbe dovuto lanciare un programma CGI che instaurasse la connessione e quindi mostrasse su un documento HTML contenente i comandi disponibili per l'utente. Ciascuno di questi comandi riportati nel documento sarebbe stato in pratica un link ad un altro documento contenente una form per l'inserimento dei parametri relativi a quel comando.
    • Da ciascuna di queste form (quindi per ogni comando) sarebbe stato lanciato un programma CGI (magari anche sempre lo stesso) per eseguire la procedura relativa al comando inserito, restituire, tramite un file di appoggio, i risultati al programma inizialmente lanciato e terminare.
    • Il programma inizialmente lanciato (che doveva nel frattempo, in attesa dei risultati, essere lasciato in vita per non provocare la caduta della connessione con il server) avrebbe letto i risultati, e li avrebbe mostrati su un documento HTML in appendice al quale avrebbe riproposto la lista dei comandi disponibili per l'utente, mettendosi in attesa del successivo.
    • Questa scaletta si sarebbe ripetuta finchè l'utente non avesse voluto terminare la sessione di posta.
  3. Il procedimento appena riportato appare adesso (alla luce di quello effettivamente adottato) alquanto artificioso, ma a parte le difficoltà che avrebbe creato ad esempio il tentativo di tenere "in vita" il processo client, questa strada non è comunque praticabile. Si è scoperto infatti un altro vincolo caratteristico di tutti i programmi CGI: il documento HTML prodotto in uscita da un CGI-SCRIPT non appare sullo Standard Output finchè non termina l'esecuzione del processo che lo genera. In altre parole, è necessario che il processo lanciato tramite una form termini per dar modo al documento HTML che ha prodotto in uscita di essere visualizzato sullo schermo. Magari il procedimento descritto avrebbe comunque rilevato altre difficoltà insormontabili, ma questa lo ha reso comunque impraticabile. Infatti l'utente, una volta lanciato il programma dalla form contenuta nel documento HTML mostrato all'inizio, non vede se si è effettivamente connesso, quali sono i comandi disponibili, etc, finchè non termina l'esecuzione del programma. D'altra parte se questo termina cade la connessione al server postale (con conseguente messaggio di errore "Connection reset by peer" da parte del server).

Soluzione proposta

A sostegno di quanto appena detto sulle difficoltà sollevate dal procedimento riportato nel paragrafo precedente vi sarebbero altre considerazioni da fare, ad esempio sul passaggio dei dati da un eseguibile all'altro, sulla gestione dei file da parte di un programma CGI, e non solo. Trascurando per un momento questo questioni, di cui riparleremo in seguito, vediamo come si sono superati i problemi posti dai vincoli appena descritti che esistono sui programmi CGI. La soluzione proposta in questo elaborato è a mio avviso l'unica praticabile.
Essa consiste nel far partire un eseguibile ogni volta che l'utente invia un comando. Questo eseguibile opera come segue: riceve dalla form che lo ha lanciato i dati relativi al comando che l'utente ha inserito, apre una connessione, esegue la procedura relativa al comando, produce un documento HTML con i risultati dell'esecuzione in appendice al quale inserisce la lista dei comandi disponibili per l'utente, chiude la connessione con il server e termina senza rimanere in attesa di ulteriori comandi. Si noti infatti che si può inviare al client qualunque tipo di documento HTML, anche uno che contiene a sua volta una form elaborata dal programma CGI in maniera specifica per il client. La form generata può addirittura richiamare lo stesso programma CGI passandogli gli appositi parametri. Se l'utente vuole che sia eseguito un altro comando, lo selezionerà nella form prodotta in output dalla ultima esecuzione del programma, provocando una ulteriore esecuzione con le stesse modalità appena descritte. Solo con questo escabotage si sblocca la situazione, che altrimenti sembra irrisolvibile. Le cose più importanti da notare sono le seguenti:
  1. Il programma termina dopo aver soddisfatto ciascuna singola richiesta, per essere poi rilanciato in caso venga inserito in input un ulteriore comando.
  2. Nonostante questo, all'utente sembra che il programma sia continuamente in esecuzione, in quanto tutto il procedimento è a lui trasparente grazie all'uso degli HIDDEN FIELDS (vedi sotto).
  3. Utilizzando la nuova versione del programma, l'utente dovrà soltanto inserire i dati nelle form via via visualizzate dal WWW browser, e non più inserire comandi e parametri in linea di comando.
Per capire di quale vantaggio si tratti non c'è niente di meglio che provare di persona !

Funzionamento del programma

Come risultato di tutte le considerazioni precedenti, si ha che l'intero processo client segue evidentemente un procedimento radicalmente modificato per evitare i vincoli detti sopra. La cosa più importante da notare subito, prima di entrare nei dettagli di funzionamento, è che sebbene all'utente possa sembrare di avere a che fare con un unico programma continuamente in esecuzione, in realtà egli interagisce con più eseguibili; il passaggio dall'uno altro di questi programmi è gestito automaticamente ed in maniera del tutto trasparente all'utente. Uno solo di essi sarà attivo ad un dato istante e terminerà dopo eseguito il suo compito, per essere poi rilanciato in caso venga inserito in input un ulteriore comando.
Per cominciare occorre aprire il file OPEN.HTML che si trova sull'host lisa.dfc.unifi.it nella directory /disk3/usr/telemat/WWW_PUB/open.html; ad esso si accede selezionando il link indicato con la dicitura "Programma di posta" presente nella HOME PAGE del server WWW presente sull'host lisa.dfc.unifi.it.
Il file OPEN.HTML (riportato in APPENDICE A - LISTATI) è un semplice documento HTML che contiene al suo interno una form. Il documento visualizzato da OPEN.HTML è riportato in Fig. 3. Nella form visualizzata dal documento OPEN.HTML devono essere inseriti i seguenti dati:
Compilata la FORM con tutti i dati, viene lanciato il programma MAIL_CLIENT (vedi Appendice A - Listati) specificato dall'attributo ACTION. Questo programma ha il compito di aprire una connessione con il MAIL-SERVER DAEMON (che ovviamente deve essere attivo, in attesa di richieste e libero, o quantomeno non saturo di richieste), allo scopo di verificare che i dati inseriti dall'utente siano corretti, e produrre un documento HTML in uscita con il risultato di tale operazione e la lista dei comandi disponibili.
Più precisamente MAIL_CLIENT effettua le seguenti operazioni:
  1. Estrae i dati inviati dalla FORM compilata dall'utente.
  2. Effettua la richiesta di connessione al Mail-Server Daemon.
  3. Se tale richiesta viene accettata, invia Login e Password dell'utente al server per la verifica della loro correttezza.
  4. Costruisce il documento HTML da restituire con le caratteristiche della connessione (Nome e indirizzo Internet del server, porta su cui è in attesa il server) e la lista comandi disponibili, ciascuno corredato con un breve commento.
  5. Si sconnette dal server.
  6. Termina per poter visualizzare sul monitor il documento prodotto, riportato in Fig. 5.
A questo punto il processo MAIL_CLIENT è terminato, ma dal punto di vista dell'utente sembra che vi sia un processo in attesa di quello che l'utente stesso vuol fare.
Il documento restituito da MAIL_CLIENT è un documento HTML contenente una form con la lista comandi disponibili (PUT, SHOW, DEL, LIST, BYE). Quando l'utente seleziona uno di questi comandi, viene lanciato PARAMETRI, il programma CGI selezionato dalla ACTION della FORM contenuta nel documento HTML costruito da MAIL_CLIENT. PARAMETRI (Vedi Appendice A - Listati) è un programma di passaggio che non effettua connessione al server ma è fondamentale per il funzionamento dell'intero sistema. Infatti esso effettua la seguenti operazioni:
  1. Costruisce un documento HTML con la FORM per l'inserimento dei parametri relativi al comando selezionato dall'utente.
  2. Fa da tramite per il passaggio delle informazioni necessarie al programma JOLLY che deve eseguire la procedura relativa al comando selezionato e che ha perciò bisogno di effettuare la connessione al server.
PARAMETRI infatti riceve dalla FORM costruita da MAIL_CLIENT il campo di INPUT contenente il comando selezionato dall'utente ed i campi di INPUT di tipo HIDDEN contenenti i parametri necessari per la connessione al Server postale, (Nome dell'host su cui è attivo il Mail_Server Daemon, Login e Password dell'utente). PARAMETRI usa soltanto il dato relativo al comando selezionato, mentre le altre informazioni di corredo, che non usa non dovendosi connettere al server, le passa all'eseguibile JOLLY che ne ha invece bisogno. Grazie a questo procedimento basato sull'uso dei campi di input di tipo HIDDEN si evita all'utente la seccatura di reinserire tutti i parametri necessari alla istaurazione della connessione ogni volta che seleziona un comando (cosa alquanto spiacevole !). In questo modo si crea nell'utente l'illusione di avere di fronte un programma continuamente in esecuzione, in quanto tutto il procedimento di trasmissione dei parametri è a lui trasparente. Una volta costruito il documento HTML con la form per l'inserimento dei parametri del comando selezionato, il programma PARAMETRI termina per poter visualizzare tale documento sul monitor. L'uscita del programma nel caso venga selezionato il comando PUT è mostrata in Fig.4. Quando l'utente ha compilato la FORM contenuta nel documento prodotto da PARAMETRI con tutti i dati richiesti, dà il segnale di OK affinchè venga eseguita la routine relativa al comando selezionato. Viene allora lanciato JOLLY (vedi Appendice A - Listati), l'eseguibile indicato dall'attributo ACTION della FORM, che nell'ordine:
  1. Estrae i dati inviati dalla FORM compilata dall'utente.
  2. Effettua la richiesta di connessione al Mail-Server Daemon.
  3. Se tale richiesta viene accettata, invia Login e Password dell'utente al server per la verifica della loro correttezza.
  4. Esegue la procedura relativa al comando selezionato, usando i parametri di input ottenuti dalla form (questa operazione richiede la connessione al server).
  5. Costruisce il documento HTML da restituire con i risultati dell'esecuzione della routine richiamata e ripropone in appendice la lista comandi disponibili con il solito, breve commento.
  6. Si sconnette dal server.
  7. Termina per poter visualizzare sul monitor il documento prodotto, riportato in Fig. 6.
Quando l'utente seleziona uno dei comandi della lista presente nel documento HTML generato, viene lanciato nuovamente PARAMETRI, che lancerà a sua volta nuovamente JOLLY, e così via. Se il comando scelto è BYE, il documento HTML costruito da JOLLY non ripropone la lista dei comandi disponibili, ma visualizza un messaggio di chiusura e inserisce un link alla HOME PAGE del server World Wide Web dell'host lisa.dfc.unifi.it, pagina dalla quale si era partiti (vedi Fig. 7).

Problemi di implementazione incontrati

L'adozione della struttura vista per il processo client, sebbene abbia risolto i problemi causati dai vincoli di cui si è ampiamente detto, ha comunque posto diverse difficoltà nella implementazione in linguaggio C, non tutte superate. Il primo problema che si è posto è stata la gestione dei dati in questo tipo di programma, in cui gli input vengono richiesti durante l'esecuzione (cosa che poi in realtà viene fatta dalla stragrande maggioranza dei casi). La soluzione che si è adottata nel nostro caso consiste, come si è visto, nel far uso nel documento dei cosiddetti campi nascosti (HIDDEN FIELDS), cioè di dati che non verranno visualizzati dal proprio web browser ma che interverranno nel processo di computazione. Essa è tornata utile anche per la questione della trasparenza all'utente di cui si è già detto. L'informazione da passare al programma successivo sarà tutta contenuta nel documento ma suddivisa in visibile ed "invisibile" (campi nascosti). Alternativamente, era possibile costruire file di ausilio al programma contenenti strutture di dati, oggetti come array, etc., in cui memorizzare i dati da passare al programma per le successive esecuzioni. Quest'ultima possibilità, valida in linea teorica, è stata scartata per diversi motivi: in primo luogo si è riscontrata l'impossibilità di gestire apertura scrittura e lettura di file da parte dei programmi CGI scritti in C (argomento di cui riparleremo tra poco). Inoltre si sarebbero dovuti considerare (ed evitare) i vari problemi di mutua esclusione che potevano presentarsi nel caso si fossero avuti più processi che volevano accedere allo stesso file (nel caso che più persone stiano usando il programma contemporaneamente). Infine non è consigliabile, in linea di principio, procedere con la creazione di file temporanei contenenti dati che la successiva replica userà nella sua esecuzione perchè molti utenti che, una volta esaurito il loro compito, non volessero continuare con la successiva replica del programma, lascerebbero i file temporanei (creati a loro insaputa) a occupare spazio su disco, richiedendo una loro successiva cancellazione. Il problema più grave che si è presentato e che non ha trovato soluzione è stato quello della gestione dei file dall'interno di un programma CGI. Inspiegabilmente infatti i programmi realizzati smettono di funzionare correttamente quando si cerca di operare con le istruzioni di gestione file tipiche del linguaggio C su un file qualunque (di cui si dà ad esempio il nome in input). Questo non accade invece quando si tratta con lo Standard Input o lo Standard Output. Allo scopo di trovare una soluzione al problema, sono state effettuate numerose ricerche di documentazione in rete relativa a queste problematiche. La conclusione più importante deducibile da questa ricerca è che le difficoltà sorte sono legate all'uso del linguaggio C per l'implementazione dei programmi CGI. E' per questa ragione che la stragrande maggioranza dei programmi CGI disponibili in rete sono scritti in Perl. La ragione per cui molti programmatori scelgono il Perl è da ricercarsi principalmente nella sua facilità di uso, che risulta particolarmente evidente nella gestione dei file, ma che non è limitata a questo. Per avere una idea di quanto appena detto, si legga una qualunque delle librerie per il Perl mostrate nella "Yahoo's CGI list" , ad esempio le lezioni di Brian Exelbierd relative alla stesura di programmi CGI in Perl per l'esecuzione di un gran numero di operazioni (tra cui l'invio di posta elettronica). Una breve rassegna sui vantaggi del Perl è anche riportata nel capitolo Approfondimenti.

Comandi disponibili

Data la nuova interfaccia utente predisposta e constatata l'impossibilità di gestire file, si capisce che il programma di posta risulti notevolmente modificato dal punto di vista dei comandi che sono adesso disponibili per l'utente. La soluzione proposta è stata infatti quella di togliere dal processo CLIENT qualunque operazione che prevedesse la gestione di file. Non potendo più salvare su file un messaggio ricevuto, è stato ad esempio soppresso il comando GET; non essendo più possibile inviare messaggi memorizzati su file, si è modificato il comando PUT, che prevede adesso l'invio al server soltanto di messaggi testuali editati con TEXTAREA. Di conseguenza i comandi disponibili sono notevolmente diminuiti in numero, passando dai 17 originari ai 5 attuali, dal momento che sono stati tolti tutti quelli che avevano a che fare con gestione file e directory, oltre a quelli resi obsoleti dalla nuova interfaccia realizzata. Vediamo una breve descrizione dei comandi rimasti.
1. PUT: Invia un messaggio ad un utente.
Constatata l'impossibilità di gestire file, è necessario che il programma accetti dallo stdin il messaggio che si vuole inviare. Viene usato perciò un campo di input di tipo TEXTAREA per editare il TESTO DEL MESSAGGIO da inviare, visto che non è possibile inviare qualcosa che sia già memorizzato su file. Questo porta una ulteriore limitazione: è possibile inviare solo messaggi di testo e non file binari come ad esempio immagini. Si noti comunque che, una volta risolti i problemi di gestione file, includere nel programma la possibilità di inviare immagini non dovrebbe risultare troppo difficile.
Oltre al testo del messaggio, l'utente deve inserire i seguenti dati di input (vedi figura 4):
La pagina che viene generata in risposta al comando PUT contiene semplicemente un messaggio che riporta la corretta esecuzione del comando o, in caso contrario, il tipo di errore che si è verificato. NOTA. L'uso del metodo POST per il passaggio dei parametri dalla form HTML al programma CGI consente di non avere limiti sulle dimensioni del messaggio da inviare come quello imposto invece dal metodo GET di 256 bytes della linea di comando (dato che il comando PUT prevede che il testo della lettera inviata all'utente desiderato venga editata con un campo di tipo TEXTAREA).
2. SHOW: Mostra un messaggio ad un utente.
Il comando SHOW permette la visione all'utente destinatario di un messaggio a lui diretto, senza però dargli la possibilità di salvarlo. Sono richiesti i seguenti dati di input:
3. DEL: Cancella un messaggio dalla coda.
Il comando DEL permette all'utente di cancellare dalla coda un messaggio a lui diretto. Richiede come unico ingresso un NUMERO intero, quello con il quale il messaggio che si vuole cancellare è identificato nella lista dei messaggi in coda mostrata dal comando LIST.
La pagina che viene generata in risposta al comando DEL contiene semplicemente un messaggio che riporta la corretta esecuzione del comando o, in caso contrario, il tipo di errore che si è verificato.
4. LIST: Mostra la lista dei messaggi in coda.
Il comando LIST visualizza sullo schermo la lista dei messaggi in coda inviati dall'utente o a lui diretti. Richiede come unico ingresso di scegliere se visualizzare TUTTI I MESSAGGI DELLA CODA o SOLO QUELLI NON ANCORA LETTI. La pagina che viene generata in risposta al comando LIST (se tutto avviene correttamente) mostra una lista che contiene le seguenti informazioni sui messaggi in coda:
5. BYE: Chiude la connessione con il Mail-server.
Il comando BYE chiude, più che la connessione con il Mail-Server, il meccanismo di passaggio da un eseguibile all'altro che sta alla base del funzionamento del programma di posta. La pagina che viene generata dal comando BYE contiene un messaggio di chiusura, un link ad una risorsa di tipo MAILTO (per l'invio di commenti, critiche suggerimenti, etc. sul programma), ed un secondo link alla HOME PAGE del server su cui è presente il programma CGI di posta elettronica (nel nostro caso al server lisa.dfc.unifi.it).

Comandi soppressi

Sono stati tolti i seguenti comandi:

Conclusione

I vincoli sui programmi CGI di cui si è parlato sopra sono stati superati con l'escabotage di aprire la connessione con il server ogni volta che viene richiesta dall'utente l'esecuzione di comando e chiuderla non appena tale esecuzione è terminata. Le informazioni richieste per instaurare inizialmente la connessione vengono inserite tramite la form HTML contenuta nel documento OPEN.HTML e passate all'applicativo esterno MAIL_CLIENT. Questi, una volta fatto uso di tali informazioni (che si possono considerare informazioni di stato in quanto necessarie per ristabilire la connessione ad ogni comando successivo), le rende disponibili per gli eseguibili che lo seguono. Esse vengono cioè passate avanti ed indietro tra i programmi PARAMETRI e JOLLY attraverso le coppie nome/valore delle form via via visualizzate dai documenti HTML prodotti in uscita dai due eseguibili e nascoste all'utente usando i campi HIDDEN delle form HTML. L'uso degli hidden fields rende tutta la struttura trasparente all'utente, che ha l'impressione di essere sempre collegato al server postale, e quindi di essere all'interno di una stessa sessione di posta. In realtà il collegamento con il server decadrà al termine dell'esecuzione di ogni singolo comando. La struttura del programma risulta radicalmente modificata. Come risulta dal semplice schema riportato, la funzione svolta in precedenza dal solo processo CLIENT, richiede adesso l'uso di tre eseguibili.

.. schema ..

Quindi anzichè instaurare una unica sessione di posta, si passa continuamente dal programma PARAMETRI al programma JOLLY. Infatti, mentre il primo predispone le form per l'inserimento dei dati richiesti dai vari comandi selezionati dall'utente, il secondo provvede a connettersi al server postale, ad eseguire le routine relative ai comandi stessi, ed a sconnettersi una volta terminata l'esecuzione.Con le modifiche introdotte il programma di posta risulta ovviamente molto più "friendly" oltre ad usare una interfaccia standard. Un altro aspetto positivo (molto secondario come importanza rispetto ai precedenti) legato al procedimento adottato è che adesso il client ha molte più probabilità di trovare il server libero quando effettua una richiesta di connessione. Infatti, mentre prima client e server restavano connessi per tutta la durata della sessione di posta, adesso dopo l'esecuzione di ciascun comando ci si sconnette (si ricordi che il Mail-Server è predisposto per gestire contemporaneamente al massimo 4 connessioni con altrettanti client).

Approfondimenti

1. Le URL

URL sta per Uniform Resource Locator. Si può pensare ad esso come all'estensione legata al campo delle reti per il concetto standard di filename: non solo è possibile puntare ad un file in una directory, ma quel file e quella directory possono esistere su qualsiasi macchine nella rete, possono essere raggiunti in vari modi, e potrebbero persino non essere qualcosa di così semplice come un file: gli URLs possono anche puntare a documenti memorizzati all'interno di database, ai risultati di un comando finger, o a tante altre cose. Il concetto di URL è infatti molto semplice ed affascinante ("SE ESISTE QUALCOSA IN RETE, E' POSSIBILE PUNTARE AD ESSO"). Questo paragrafo è solo una veloce rassegna attraverso alcuni dei tipi più comuni di URL e dovrebbe permettere di creare e capire velocemente le URL in una varietà di contesti.
URL relative a file (File URLs)
Supponiamo esista un documento chiamato "foobar.txt"; situato su un server FTP di tipo anonymous chiamato "ftp.yoyodyne.com" nella directory "/pub/files". L'URL per questo file è allora

file://ftp.yoyodyne.com/pub/files/foobar.txt

La directory al livello più alto di questo server FTP è semplicemente:

file://ftp.yoyodyne.com/

mentre la directory "pub" dello stesso FTP server è:

file://ftp.yoyodyne.com/pub

URL relative a risorse Gopher (Gopher URLs)
I Gopher URL sono un po' più complicati dei file URLs, dal momento che avere a che fare con un server Gopher è un po' più artificioso che trattare con un server FTP. Per visitare un particolare server Gopher (ad esempio chiamato gopher.yoyodyne.com), si usa il seguente URL:

gopher://gopher.yoyodyne.com/

Alcuni server Gopher potrebbero trovarsi sulle proprie macchine host in corrispondenza di porte di accesso dalla rete inusuali (nota che il numero di porta di default per il gopher è il 70). Se è noto che il server Gopher sulla macchina "gopher.banzai.edu" è sulla porta 1234 invece che sulla 70, allore la URL corrispondente dovrebbe essere:

gopher://gopher.banzai.edu:1234/

URL relative a risorse Usenet (News URLs)
Per puntare ad un newsgroup di Usenet (ad esempio, "rec.gardening"), l'URL è semplicemente:

news:rec.gardening

Attualmente, i client di rete come NCSA Mosaic non consentono di specificare un news server come ci si potrebbe normalmente aspettare (ad esempio, news://news.yoyodyne.com/rec.gardening); questo potrà forse accadere in futuro, ma nel frattempo occorre specificare il proprio news server locale attraverso qualche altro metodo. Il più comune consiste nel settare la variabile ambientale NNTPSERVER sul nome del proprio news server prima di lanciare Mosaic.
URL relative a risorse World Wide Web (HTTP URLs)
I server HTTP (HyperText Transport Protocol) sono comunemente usati per fornire documenti ipertestuali, essendo l'HTTP un protocollo che capitalizza sul fatto che la informazione per la navigazione può essere annidata direttamente in tali documenti, e la navigazione in rete risulta quindi semplificata rispetto a come viene fatta tramite i protocolli FTP e Gopher. Un file chiamato "open.html" sul server HTTP "lisa.dfc.unifi.it" nella directory "/disk3/usr/netman/mail.exp/prozio/html" corrisponde alla seguente URL:

http://lisa.dfc.unifi.it/disk3/usr/netman/mail.exp/prozio/html/open.html

La porta di rete di default per l'HTTP è la 80; se un HTTP server risiede su porte di rete diverse (ad esempio la porta 1234 su lisa.dfc.unifi.it), quindi la URL diventa:

http://lisa.dfc.unifi.it:1234/disk3/usr/netman/mail.exp/prozio/html/open.html

URL parziali
Una volta che un documento situato da qualche parte nella rete è stato raggiunto (ad esempio, il documento http://lisa.dfc.unifi.it/disk3/usr/netman/mail.exp/prozio/html/open.html), si può usare un URL parziale, o relativo, per puntare ad un altro file nella stessa directory, sulla stessa macchina, fornito dallo stesso server. Per esempio, se nella stessa directory esiste un altro documento chiamato "anotherfile.html", allora anotherfile.html è un URL parziale ma valido a quel punto. Questo fornisce un facile sistema per costruire insiemi di documenti ipertestuali. Se un insieme di documenti ipertestuali è situato in una directory comune, essi possono far riferimento uno con l'altro (ovvero costruire hyperlink) usando solo i loro filename. Una volta che un lettore accede ad uno di questi documenti, può essere effettuato un salto a qualunque altro documento della directory semplicemente usando a quel punto il filename dell'altro documento come URL parziale. Le informazioni aggiuntive (metodo di accesso, nome dell'host, numero di porta, directory, etc.) saranno assunte basandosi sull'URL usata per raggiungere il primo documento.
Altre URL
Molte altre URLs sono possibili, anche se quelle viste sono le più comuni. In cima alla finestra in cui è mostrato qualunque documento Mosaic vi è un campo di testo chiamato "Document URL"; guardandone il contenuto durante la navigazione in rete, si potrà osservare come sono strutturate le URL per i diversi tipi di informazioni cercate in rete.

2. I sistemi con stato

Per "sistemi con stato" si intendono quei sistemi la cui risposta ad una richiesta non è unica ma dipende dai comandi precedentemente ricevuti. I sistemi informativi più recenti sono basati sulla architettura client-server, in cui il client maneggia la presentazione e l'interazione con l'utente ed il server tratta i dati. Questi client possono avere interfaccie grafiche o testuali e sono indipendenti dal server. Questo è il modello che meglio si adatta alle comunicazioni su Internet dal momento che l'informazione può essere disponibile su molti server. Il reperimento dell'informazione per questo tipo di servizio deve essere veloce. A questo scopo sono stati perciò progettati protocolli senza stato, intendendo con ciò che la risposta del server dipende esclusivamente dal comando che è stato dato. Un esempio di protocollo senza stato è l'HTTP, di cui si è parlato in precedenza.
Definizione di STATO
"L'informazione che un server mantiene circa lo stato delle interazioni in corso con i client è detta informazione di stato. I server che non mantengono alcuna informazione di stato sono chiamati server senza stato (stateless), gli altri sono detti server con stato (stateful)" [Comer 94]. L'informazione di stato include tutte le variabili di stato memorizzate ed i loro valori. Un particolare insieme di valori definisce uno stato. Si dice che un server cambia il suo stato ogni volta che cambiano i valori delle sue variabili di stato.

3. La gestione dei file con il Perl

Innanzitutto occorre ricordare che il Perl è un linguaggio nato per l'elaborazione di testi, che si è poi sviluppato prendendo in prestito elementi di C, di Shell di Unix, etc. Senza scendere in eccessivi dettagli, si mostra come il Perl permetta una facile gestione dei file da parte di programmi CGI.
Vediamo ad esempio come salvare i dati inseriti da tastiera dall'utente in un file per riprenderli successivamente; nel nostro caso potremmo salvare il dato relativo al Nome dell'host su cui è attivo il Mail-Server Daemon, richiesto dalla prima form sottoposta (open.html), per poterlo usare per successive connessioni al server. La form inserisce il dato nella variabile hostname. Per salvare questo dato ad esempio in append ad un file contenente altri dati utili al programma, dobbiamo avere un file che sia scrivibile (w=writable) dall'http daemon. La creazione di un file di questo tipo varierà da sistema a sistema. Per aprire il file su cui fare l'operazione di append (aggiungere uno stuff alla fine), dobbiamo usare in Perl il comando open che ha la seguente form:

open(FILEHANDLE,">>filename");

FILEHANDLE è come chiameremo il file mentre è aperto; esso è cioè simile ad una variabile. E' consigliabile usare tutte lettere maiuscole per le variabili tipo FILEHANDLE per aiutare a distinguerle dalle variabili normali. "filename" è il file da aprire. I simboli ">>" sono significativi, e proprio come nella shell indicano l'append al file. Stampare su un file è semplicissimo: il primo argomento dell'istruzione print è la variabile con cui viene chiamato il file mentre è aperto, ed è separato da ciò che si vuol scrivere da uno spazio. Ad esempio per scrivere la linea "Prova di stampa " su FILEHANDLE, si usa la seguente istruzione:

print FILEHANDLE " Prova di stampa \n";

Per salvare su file le variabili si procede allo stesso modo: print FILEHANDLE " Variabile hostname:

",$in{'hostname'},"\n";

Finite le operazioni sul file, resta solo da chiuderlo, cosa possibile usando il comando close:

close FILEHANDLE;

Il Perl permette di fare anche altre cose interessanti. Si analizzi ad esempio il semplice CGI script per l'invio di posta elettronica scritto in Perl e disponibile in rete [HREF 7] che mette in evidenza molte altre ottime caratteristiche del Perl, che fanno capire i motivi per cui molti programmatori in CGI scelgono il Perl.

Appendice A - Listati

Osservazioni: Tutti i listati nel documento sono scritti in C. Vengono riportati solo i listati relativi al client in quanto il server è rimasto sostanzialmente lo stesso. Implementazione in C di un programma CGI Dopo aver descritto nel capitolo sulla COMMON GATEWAY INTERFACE i principi teorici sui quali è basato il funzionamento di un programma CGI, vediamo adesso in pratica come bisogna strutturare il codice per essere in grado di accedere ad un programma attraverso un web browser. Input del programma CGI Ogni volta che un client web richiede la URL corrispondente ad un programma CGI, il server lo esegue in tempo reale. L'output del programma ritornerà poi più o meno direttamente al client. Si potrebbe erroneamente pensare di poter inviare opzioni e argomenti al programma CGI attraverso la linea di comando, ad esempio nel modo seguente: Action:"http://lisa.dfc.unifi.it/disk3/usr/netman/mail.exp/prozio/html/mail_client SHOW 3" L'interfaccia CGI invece prevede l'uso della linea di comando per altri scopi e perciò questo non è direttamente possibile. Gli argomenti necessari giungono al programma CGI in modi diversi a seconda del METHOD scelto. In particolare si è visto che: se il metodo usato è GET, l'interfaccia CGI usa le variabili ambientali per inviare al programma i parametri. Le due principali variabili di ambiente usate a questo scopo sono QUERY_STRING e PATH_INFO. l'uso del metodo POST fa sì che i dati della form vengano inviati al programma CGI tramite lo Standard Input del server. L'uso dello standard input consente di inviare dati lunghi a piacimento, senza nessun pericolo che essi vengano troncati come nel metodo GET. Il programma CGI usa il metodo POST perciò riceverà nomi e valori dei campi della form compilata semplicemente accedendo al dispositivo di Standard Input. Implementando il programma CGI in linguaggio C si potrà usare una qualunque delle funzioni C di accesso al file. Ricordando che stdin è definito in , è possibile ad esempio aprire il file di stdin ed accedervi tramite l'istruzione fgetc(). #include main() { FILE *stdin; ... word[i]=fgetc(stdin); ... } Variabili ambientali del programma CGI Implementando un programma CGI in C, è possibile conoscere il valore delle variabili di ambiente con l'istruzione getenv(). Nel nostro caso essa è servita per conoscere alcuni dati importanti o per implementare alcuni controlli. Nel primo caso portato di seguito ad esempio ci si assicura che sia stato usato un METHOD di tipo POST ed, in caso contrario, si invia un messaggio di errore e si esce. Nel secondo caso si verifica invece che CONTENT_TYPE sia pari ad application/x-www-form-urlencoded. if(strcmp(getenv("REQUEST_METHOD"),"POST")) { printf("This script should be referenced with a METHOD of POST.\n"); ... exit(1); } if(strcmp(getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded")) { printf("This script can only be used to decode form results.\n"); exit(1); } Per richieste che hanno le informazioni attaccate dopo l'header, come nel metodo POST, l'informazione viene inviata al programma CGI tramite lo Standard Input. Il server invia su questo descrittore di file (stdin) un numero di byte pari a CONTENT_LENGTH, ed allo stesso modo darà il CONTENT_TYPE dei dati. Il server non è in alcun modo obbligato ad inviare l'End-Of-File dopo che il programma ha letto un numero di byte pari a CONTENT_LENGTH. Per conoscere la lunghezza della stringa dei dati passati al programma CGI tramite lo standard input si è fatto ancora uso dell'istruzione getenv(): cl = atoi(getenv("CONTENT_LENGTH")); Output del programma CGI Il programma CGI, terminata l'esecuzione, deve inviare l'output del programma al Web browser. Normalmente i programmi CGI producono una uscita che viene interpretata dal server e quindi rimandata indietro al client. Un vantaggio di questo procedimento consiste nel fatto che il programma non ha bisogno di inviare un header HTTP/1.0 completo per ciascuna richiesta. E' innanzitutto importante impostare l'uscita in modo che il server sia in grado di interpretarla correttamente. I programmi CGI possono ritornare al client una miriade di tipi di documenti: immagini, documenti HTML, a documenti di solo testo (plaintext), e perfino messaggi audio. Essi possono anche ritornare dei riferimenti ad altri documenti. Il client deve essere informato sul tipo di documenti che gli vengono inviati così da poterli presentare correttamente. Perchè ciò accada, il programma CGI deve dire al server che tipo di documento sta ritornando. L'interfaccia CGI prevede che il programma CGI ponga un piccolo header sulla propria uscita per dire il tipo di documento generato (se un documento vero e proprio o solo un riferimento ad un altro documento). Questo header è un testo ASCII che consiste di alcune linee separate da Line Feeds e/o Carriage Returns seguiti da una linea vuota. Quindi segue il corpo dell'uscita in un qualunque formato naturale. Vediamo in breve i due tipi di output che un programma CGI può generare. 1. Un documento intero con il corrispondente tipo di MIME In questo caso,occorre informare il server sul tipo di documento che si sta inviando in uscita tramite il tipo di MIME. I tipi comuni di MIME sono oggetti come text/html, usato per indicare i documenti HTML, e text/plain per i semplici testi ASCII. Vediamo ad esempio come rimandare indietro al client un documento HTML. Prima di tutto è necessario settare il programma in modo da indirizzare l'uscita in un documento HTML. Questo deve essere visto dal programma come suo device standard di uscita, cioè come standard output. Per far ciò sarà necessario aggiungere due istruzioni di stampa (printf in C) contenenti e rispettivamente ma soprattutto occorrerà identificare il tipo di documento al Web browser. Per far ciò il programma CGI dovrà inviare sullo Standard Output una intestazione in cui sia specificato il tipo di dati inviato. Tale intestazione consisterà nell'assegnazione "Content-type: MIME type", ("Content-type: text/html" nel nostro esempio) seguita da una o più linee vuote. Una volta specificate queste informazioni sarà il server a formare il pacchetto completo per il client. L'intestazione che deve perciò precedere l'invio di un pacchetto HTML da parte di un programma CGI è la seguente: Content-type: text/html * una o più linee vuote (LF o CRLF) * E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET"); * corpo del documento HTML * Si ricordi che per passare i dati al server i programmi CGI fanno uso dello Standard Output. In linguaggio C questo può essere realizzato usando una qualunque funzione di uscita, come printf() o fputc(x,stdout). Dopo aver incluso le modifiche viste, le prime righe che costituiranno il codice C finale pronto per l'HTML appariranno come segue: #include main() { printf("Content-type: text/html%c%c,LF,LF"); printf(""); printf("E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET"); .... printf(""); } Sebbene nella stragrande maggioranza dei casi l'uscita generata è un documento HTML, il server lascia libero il programma CGI di determinare il tipo di dati da inviare al client, ovviamente entro i limiti delle possibilità offerte dal protocollo HTTP; oltre a documenti HTML, possono essere generati anche tutti gli altri tipi di dati previsti dallo standard MIME [HREF 4], permettendo così di integrare l'interattività delle form con la multimedialità fornita dal protocollo HTTP. L'informazione deve essere di uno dei tipi che il client è in grado di accettare, ed inoltre, come visto anche nel caso di documenti HTML, il client deve essere informato sul tipo di informazione che riceve, per poterla correttamente interpretare. Altri tipi di MIME molto comuni sono "image/gif", "image/jpeg" e "audio/basic". 2. Come ritornare una pagina non generata on the "fly" (Un riferimento ad un altro documento) Passare come output una pagina HTML già esistente anzichè un documento generato in tempo reale è facile dal momento che l'HTTP fornisce la direttiva Location. E' sufficiente dire al browser dove reperire la nuova pagina o averla direttamente in uscita dal server. Per far ciò occorre semplicemente ritornare la linea: "Location: url" anziché: "Content-Type: text/html". Questo viene fatto banalmente con la seguente istruzione di stampa: printf("Location: http:// lisa.dfc.unifi.it/disk3/usr/netman/mail.exp/prozio/html/open.html %c%c",LF,LF); Anche la seconda linea vuota ha significato, in quanto i codici di ritorno di tipo MIME, uno dei quali è Location, richiedono due linee vuote a seguire per delineare la fine del codice di ritorno stesso. Trascurando la seconda linea vuota si provocherebbe un malfunzionamento incomprensibile. Vediamo adesso un altro esempio. Supponiamo di voler fare riferimento ad un file sul server Gopher identificato come "httprules.foobar.org". In questo caso, occorre conoscere la URL completa di ciò a cui ci si vuol riferire e inviare in output qualcosa del tipo: Content-type: text/html Location: gopher://httprules.foobar.org/0 * una o più linee vuote (LF o CRLF) * E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET"); ..... Adesso è disponibile un nuovo sito sul nostro server Gopher. ..... Se si vuol fare riferimento ad un altro file sul proprio server, è sufficiente inviare in output una URL parziale, del tipo seguente: Location: /dir1/dir2/myfile.html Il server agirà come se il client non avesse richiesto un CGI-script, bensì http://yourserver/dir1/dir2/myfile.html, ed avrà cura di tutto il resto, come il controllo sul tipo del file e l'invio dello header appropriato. Basta assicurarsi di inviare in uscita la seconda linea vuota. Osservazione> Tutto ciò è possibile se il server è compatibile con CGI/1.1. Modulo: HTML_UTILITY.c Questo modulo contiene le routine necessarie ad effettuare la decodifica dei dati inviati da una form che usa il metodo POST. Esso viene perciò utilizzato da tutti gli eseguibili che si sono realizzati. Una volta eseguite le routine, il programma che le usa ha a disposizione un vettore di strutture di tipo entry, in cui sono memorizzati i dati inviati al programma CGI dalla FORM HTML. Più precisamente si ha: in entries[n].name il nome della variabile (n+1)-esima passata dalla form in entries[n].val il valore assegnato dall'utente a tale variabile Questi dati sono adesso utilizzabili dal programma CGI per gli scopi per cui è stato implementato. Il listato allegato illustra come gli input sono presi e assegnati alle variabili nel caso specifico del linguaggio (il C) e delle librerie che sono stati utilizzati. /* HTML utilities */ /* Definizioni per forms HTML */ #define LF 10 #define CR 13 #define MAX_ENTRIES 10 /* Numero di campi di input */ typedef struct { char *name; char *val; } entry; /* Routine di decodifica delle forms HTML */ char *fmakeword(FILE *f, char stop, int *len); void plustospace(char *str); void unescape_url(char *url); char x2c(char *what); char *makeword(char *line, char stop); /*****************************************************************************/ /* FMAKEWORD: spezza il contenuto dello stream che gli viene passato tramite */ /* il file "f" (in questo caso i dati trasmessi dalla HTML form al programma */ /* CGI codificati secondo lo standard URL ed inviati con metodo POST tramite */ /* lo Standard Input), di lunghezza "cl" caratteri (dato ricavato dalla */ /* variabile ambientale CONTENT_LENGTH), nelle parole comprese tra */ /* due caratteri di stop successivi (in questo caso le "&") */ /*****************************************************************************/ char *fmakeword(FILE *f, char stop, int *cl) { int wsize; char *word; int ll; wsize = 102400; ll=0; word = (char *) malloc(sizeof(char) * (wsize + 1)); while(1) { word[ll] = (char)fgetc(f); if(ll==wsize) { word[ll+1] = '\0'; wsize+=102400; word = (char *)realloc(word,sizeof(char)*(wsize+1)); } --(*cl); if((word[ll] == stop) || (feof(f)) || (!(*cl))) { if(word[ll] != stop) ll++; word[ll] = '\0'; return word; } ++ll; } } /*****************************************************************************/ /* PLUSTOSPACE: converte in spazi bianchi i caratteri "+" presenti nella */ /* stringa che gli viene passata (inseriti dalla codifica secondo lo */ /* standard URL del contenuto della form HTML) */ /*****************************************************************************/ void plustospace(char *str) { register int x; for(x=0;str[x];x++) if(str[x]=='+') str[x]=' '; } /*****************************************************************************/ /* UNESCAPE_URL: decodifica alcuni caratteri speciali presenti nella stringa */ /* che gli viene passata inseriti dalla codifica secondo lo standard URL del */ /* contenuto della form HTML (alcuni caratteri vengono sostituiti nello */ /* standard URL dalla loro codifica ASCII in esadecimale preceduta da un */ /* carattere "%") */ /*****************************************************************************/ void unescape_url(char *url) { register int x,y; for(x=0,y=0;url[y];++x,++y) { if((url[x] = url[y]) == '%') { url[x]=x2c(&url[y+1]); y+=2; } } url[x]='\0'; } /*****************************************************************************/ /* X2C (X to char): converte nel carattere corrispondente della codifica */ /* ASCII il numero x (esadecimale) che gli viene passato. */ /*****************************************************************************/ char x2c(char *what) { register char digit; digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0')); digit *= 16; digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0')); return(digit); } /*****************************************************************************/ /* MAKEWORD: estrae dalla stringa che gli viene la parte compresa a sinistra */ /* del carattere "=" */ /*****************************************************************************/ char *makeword(char *line, char stop) { int x=0,y; char *word=(char *)malloc(sizeof(char)*(strlen(line)+1)); for(x=0;((line[x]) && (line[x] != stop));x++) word[x] = line[x]; word[x] = '\0'; if(line[x]) ++x; y=0; while(line[y++] = line[x++]); return word; } Modulo: MAIL_CLIENT.c #include #include #include #include #include #include #include #include #include #include #include #include #include "mail_clientutil.c" #include "html_utility.c" #define OK '0' #define NOK '1' #define SERVER_PORT 7501 #define LF 10 extern int errno; extern char *sys_errlist[]; struct protoent *getprotobyname(); struct hostent *gethostbyname(); struct protoent *pp; struct hostent *server; struct sockaddr_in client_sock,server_sock,bincl_sock,binary_sock; struct in_addr client_addr,server_addr; int client_id=0, bincl_id=0; int command; char *strdup(const char *); int main() { entry entries[MAX_ENTRIES]; /* HTML name-val pairs */ int x, cl, etnum, comnum, status; printf("Content-type: text/html%c%c",LF,LF); if(strcmp(getenv("REQUEST_METHOD"),"POST")) { printf("This script should be referenced with a METHOD of POST.\n"); printf("If you don't understand this, read "); printf("forms overview.%c",LF); exit(1); } if(strcmp(getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded")) { printf("This script can only be used to decode form results.\n"); exit(1); } cl = atoi(getenv("CONTENT_LENGTH")); etnum = 0; for(x=0;cl && (!feof(stdin));x++) { entries[x].val = fmakeword(stdin,'&',&cl); plustospace(entries[x].val); unescape_url(entries[x].val); entries[x].name = makeword(entries[x].val,'='); etnum++; } printf("E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET"); printf(""); printf("
");
	printf("
"); printf("%c",LF); printf("

SERVIZIO DI POSTA IN RETE INTERNET

"); printf("%c",LF); printf("%c",LF); printf("
"); printf("%c",LF); printf("

CONNESSIONE AL MAIL-SERVER DAEMON

"); printf("%c",LF); printf("
"); printf("%c",LF); BuildUpChannel(entries[0].val,1); ClientLogin(entries[1].val,entries[2].val); printf("%c",LF); ByeBye(0); printf("
"); printf("%c",LF); printf("

COMANDI DISPONIBILI:

"); printf("%c",LF); printf("
    "); printf("
    "); printf("
  1. PUT : invia un file all'utente destinatario"); printf("%c",LF); printf("
  2. SHOW : mostra un messaggio all'utente destinatario"); printf("%c",LF); printf("
  3. DEL : cancella un file presente nella coda dei messaggi"); printf("%c",LF); printf("
  4. LIST : mostra la lista dei messaggi nella coda"); printf("%c",LF); printf("
  5. BYE : chiude una connessione con il Mail- server"); printf("%c",LF); printf("", entries[0].val); printf("", entries[1].val); printf("", entries[2].val); printf("
"); printf("%c",LF); printf("
"); printf(" "); printf("
"); printf(""); /* out=fopen("/disk3/usr/netman/mail.exp/prozio/html/prova.c","wt"); fprintf(out,"%s\n",entries[0].val); fprintf(out,"%s\n",entries[1].val); fprintf(out,"%s\n",entries[2].val); fclose(out); */ ByeBye(0); printf(""); exit(1); } Modulo: MAIL_CLIENTUTIL.c /***************************************************************/ /* BuildUpChannel: costruisce il canale di comunicazione con */ /* il server per lo scambio di dati, comandi, file di testo */ /***************************************************************/ int BuildUpChannel(arg1,write) char *arg1; int write; { char hostname[40]; char command[BUFSIZ],data[BUFSIZ]; int length,nbytes,port,n_err; struct in_addr *host; if ((pp = getprotobyname("tcp")) == 0) { perror("\n-->Client: getprotobyname sconosciuto\n"); exit(1); } /* crea il socket */ if ((client_id = socket(AF_INET,SOCK_STREAM,pp->p_proto)) == -1) { perror("\n-->Client: socket\n"); exit(1); } strcpy(hostname,arg1); /* scopre indirizzo internet della macchina remota */ /* tramite il nome ricevuto come parametro */ if ((server=gethostbyname(hostname)) == 0) { perror("\n-->Client: gethostbyname - host sconosciuto\n"); close (client_id); exit(1); } host=(struct in_addr *)server->h_addr; if (write) { printf("%c",LF); printf ("----------- C L I E N T -------------"); printf("%c",LF); printf ("+ Nome Host: %30s +",server->h_name); printf("%c",LF); printf ("+ Indirizzo Host: %25s +",inet_ntoa(*host)); printf("%c",LF); printf ("+ Porta Mailserver: %23d +",SERVER_PORT); printf("%c",LF); printf ("-------------------------------------"); printf("%c",LF); } bcopy (server->h_addr, (char *)&server_addr,server->h_length); /* assegnazioni per collegamento al server */ server_sock.sin_family = AF_INET; server_sock.sin_addr = server_addr; server_sock.sin_port = htons(SERVER_PORT); /* richiesta di connessione */ if (connect(client_id, &server_sock,sizeof(server_sock)) == -1) { perror("\n-->Client: connect\n"); exit(1); } } /*************************************************************/ /* Checkdata: controlla la correttezza dei dati scambiati */ /*************************************************************/ int Checkdata(char *f) { char command[20],data[BUFSIZ]; int nbytes,n_err; strcpy(command,f); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } /* gestione errore dato inesistente */ if ((nbytes=recv(client_id,data,1,0))==-1) { perror("\n-->Client : recv dal server figlio \n"); return 0; } /* controlla OK dal server */ if (data[0]==NOK) { /* file inesistente */ sscanf(&data[1],"%d",n_err); printf("\n-->Client: %s\n",sys_errlist[n_err]); return 0; } return 1; } /*************************************************************/ /* CLIENTLOGIN: controlla login e password dell'utente */ /*************************************************************/ int ClientLogin(login,pwd) char login[20],pwd[20]; { if (Checkdata(login)==1) { if (Checkdata(pwd)==1) return 1; } return 0; } /****************************************************************/ /* RECEIVEACK: riceve dal server una stringa di acknowledgement */ /****************************************************************/ int ReceiveAck(int client_id) { int nbytes; char data[5]; do { if ((nbytes=recv(client_id,data,4,0))==-1) { perror("\n-->Client : receiving ack from server \n"); exit(1); } data[4]='\0'; } while (strcmp(data,"ACK")!=0); } /*****************************************************/ /* LISTMESSAGE: mostra la lista di tutti i messaggi */ /*****************************************************/ int ListMessage(int new) { char **lines=(char **)calloc(512,sizeof(char *)); char data[BUFSIZ]; int nbytes,nlines=1; int i=-1; if (new) { sprintf(data,"listnew"); printf("Listing unread messages"); lines[0]=strdup("\n"); } /* invia richiesta lista messaggi */ else { sprintf(data,"list"); printf("Listing all messages"); lines[0]=strdup("\n"); } if (send(client_id,data,strlen(data)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); while (((nbytes=recv(client_id,data,BUFSIZ,0))!= 0)) { if (nbytes < 0) { perror("\n--> Client: recv\n"); exit(1); } /*write (1,data,nbytes);*/ data[nbytes]='\0'; lines[nlines]=strdup(data); nlines++; /*printf("\n-++->Arrivati %d bytes su %d \n",nbytes,BUFSIZ-1);*/ lines[nlines-1][nbytes-4]='\0'; if (strcmp((data+nbytes-4),"ACK")==0) break; } lines[nlines]=NULL; printf("%c",LF); while (lines[++i]!=NULL) { printf("%s",lines[i]); printf("%c",LF); free(lines[i]); } free(lines); lines=NULL; return 1; } /**************************************************/ /* BYEBYE: chiude la connessione col server */ /**************************************************/ int ByeBye(int stampa) { char **lines=(char **)calloc(4,sizeof(char *)); char data[80]; int i=-1; sprintf(data,"bye"); if (send(client_id,data,strlen(data)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); if (stampa) { lines[0]="\n End of Mail Session"; lines[1]="\nFor bugs,problem,hints contact"; lines[2]="\n
netman@lisa.dfc.unifi.it
"; lines[3]="
"; lines[4]=" Torna alla home page del DFC"; lines[5]=NULL; } else lines[0]=NULL; printf("%c",LF); while (lines[++i]!=NULL) { printf("%s",lines[i]); printf("%c",LF); free(lines[i]); } free(lines); lines=NULL; return 1; } Modulo: JOLLY.c #include #include #include #include #include #include #include #include #include #include #include #include #include "mail_clientutil.c" #include "html_utility.c" #include "jolly_proc.c" #define OK '0' #define NOK '1' #define SERVER_PORT 7501 #define LF 10 extern int errno; extern char *sys_errlist[]; struct protoent *getprotobyname(); struct hostent *gethostbyname(); struct protoent *pp; struct hostent *server; struct sockaddr_in client_sock,server_sock,bincl_sock,binary_sock; struct in_addr client_addr,server_addr; int client_id=0, bincl_id=0; int command; char *strdup(const char *); int main() { entry entries[MAX_ENTRIES]; /* HTML name-val pairs */ int x, cl, etnum, comnum, status; printf("Content-type: text/html%c%c",LF,LF); if(strcmp(getenv("REQUEST_METHOD"),"POST")) { printf("This script should be referenced with a METHOD of POST.\n"); printf("If you don't understand this, read "); printf("forms overview.%c",LF); exit(1); } if(strcmp(getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded")) { printf("This script can only be used to decode form results.\n"); exit(1); } cl = atoi(getenv("CONTENT_LENGTH")); etnum = 0; for(x=0;cl && (!feof(stdin));x++) { entries[x].val = fmakeword(stdin,'&',&cl); plustospace(entries[x].val); unescape_url(entries[x].val); entries[x].name = makeword(entries[x].val,'='); etnum++; } printf("E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET"); printf(""); printf("
");
	printf("
"); printf("%c",LF); printf("

SERVIZIO DI POSTA IN RETE INTERNET

"); printf("%c",LF); printf("%c",LF); printf("
"); printf("%c",LF); printf("

COMANDO %s - ESECUZIONE :

",entries[4].val); printf("%c",LF); BuildUpChannel(entries[0].val,0); ClientLogin(entries[1].val,entries[2].val); printf("
"); printf("%c",LF); switch (atoi(entries[3].val)) { case 1 :PutMsg(entries[5].val,entries[6].val,atoi(entries[7].val), entries[8].val,entries[9].val); break; case 2 :ShowMsg(atoi(entries[5].val),entries[6].val); break; case 3 :DelMessage(atoi(entries[5].val)); break; case 4 :if (strcmp(entries[5].val,"TUTTI I MESSAGGI NELLA CODA")==0) { ListMessage(0); break; } else { ListMessage(1); break; } case 5 :ByeBye(1);printf("");exit(1);break; default: printf("Unknown command"); break; } printf("%c",LF); printf("
"); printf("%c",LF); printf("

COMANDI DISPONIBILI:

"); printf("%c",LF); printf("
    "); printf("
    "); printf("
  1. PUT : invia un file all'utente destinatario"); printf("%c",LF); printf("
  2. SHOW : mostra un messaggio all'utente destinatario"); printf("%c",LF); printf("
  3. DEL : cancella un file presente nella coda dei messaggi"); printf("%c",LF); printf("
  4. LIST : mostra la lista dei messaggi nella coda"); printf("%c",LF); printf("
  5. BYE : chiude una connessione con il Mail- server"); printf("%c",LF); printf("", entries[0].val); printf("", entries[1].val); printf("", entries[2].val); printf("
"); printf("%c",LF); printf("
"); printf(" "); printf("
"); printf(""); ByeBye(0); printf(""); exit(1); } Modulo: JOLLY_PROC.c /*******************************************************************/ /* PUTMSG: invia un messaggio ad un utente con una certa priorita' */ /*******************************************************************/ int PutMsg(char *subject,char *message,int prior,char *dest,char *protocol) { char **lines=(char **)calloc(512,sizeof(char *)); char command[BUFSIZ],data[BUFSIZ],p[80]; int n=0,nbytes; char line[BUFSIZ]; int i=-1; /* invia comando */ strcpy(command,"putmsg\0"); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* invio PROTOCOLLO da usare */ strcpy(command,protocol); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* ricevo PORT NUMBER da usare */ if ((nbytes=recv(client_id,data,BUFSIZ,0)) < 0) { perror("\n--> Client: recv\n"); exit(1); } data[nbytes]='\0'; /* Crea il socket per il trasferimento di file binari */ if (strcmp(protocol,"udp")==0) { /* se il protocollo e' udp */ Socket_UDP(atoi(data)); if (sendto(bincl_id,message,strlen(message)+1,0,&binary_sock, sizeof(binary_sock))<0) { perror("\n--> Client: sendto\n"); exit(1); } } else { /* se il protocollo e' tcp */ /* crea il socket TCP */ Socket_TCP(atoi(data)); /* invia la richiesta di connessione */ if (connect(bincl_id, &binary_sock,sizeof(binary_sock)) == -1) { perror("\n-->Client: connect tcp\n"); exit(1); } if (send(bincl_id,message,strlen(message)+1,0)<0) { perror("\n--> Client: send\n"); close(bincl_id); exit(1); } } ReceiveAck(client_id); close(bincl_id); /* invia destinatario del messaggio */ sprintf(command,"%s",dest); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* invia soggetto del messaggio */ sprintf(command,"%s",subject); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* invia priorita' del messaggio */ sprintf(command,"%d",prior); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); sprintf(p,"Message sent to %s with priority %d using protocol %s", dest,prior,protocol); lines[0]=strdup(p); lines[1]=NULL; printf("\n"); while (lines[++i]!=NULL) { printf(" %s",lines[i]); free(lines[i]); } printf("\n"); free(lines); lines=NULL; return 1; } /*****************************************************************/ /* SHOWMSG: riceve un messaggio dal server usando il protocollo */ /* TCP o UDP e lo visualizza sullo schermo (senza salvarlo) */ /*****************************************************************/ int ShowMsg(int number,char *protocol) { char **lines=(char **)calloc(2,sizeof(char *)); char p[80],m[80],command[BUFSIZ],data[BUFSIZ]; int length,nbytes,nlines=1,i=-1; length=sizeof(struct sockaddr_in); /* invia richiesta lettura messaggio */ strcpy(command,"getbinf\0"); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* invia protocollo */ sprintf(command,"%s",protocol); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* invia numero del messaggio */ sprintf(command,"%d",number); if (send(client_id,command,strlen(command)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* Riceve dati dal server */ /* 1. Riceve PORT NUMBER da usare */ if ((nbytes=recv(client_id,data,BUFSIZ,0)) < 0) { perror("\n--> Client: recv\n"); exit(1); } data[nbytes]='\0'; /* 2. Riceve il messaggio richiesto sul socket creato */ /* per il trasferimento di file binari */ if (strcmp(protocol,"udp")==0) { /* se il protocollo e' udp */ Socket_UDP(atoi(data)); sprintf(data,"go"); if (sendto(bincl_id,data,strlen(data)+1,0,&binary_sock,length)<0) { perror("\n--> Client: sendto\n"); exit(1); } if ((nbytes=recvfrom(bincl_id,data,BUFSIZ,0,&server_sock,&length)) <0) { perror("\n--> Client: recvfrom\n"); exit(1); } data[nbytes]='\0'; sprintf(p,"Get message at position %d\n",number); lines[nlines]=strdup(data); nlines++; lines[nlines-1][nbytes-4]='\0'; lines[0]=strdup(p); lines[nlines]=NULL; close(bincl_id); } else { /* se il protocollo e' tcp */ Socket_TCP(atoi(data)); if (connect(bincl_id, &binary_sock,sizeof(binary_sock)) == -1) { perror("\n-->Client: connect tcp\n"); exit(1); } /* riceve dati dal server */ while (((nbytes=recv(bincl_id,data,BUFSIZ,0))!= 0)) { if (nbytes < 0) { perror("\n--> Client: recv\n"); close(bincl_id); exit(1); } data[nbytes]='\0'; lines[nlines]=strdup(data); nlines++; lines[nlines-1][nbytes-4]='\0'; if (strcmp((data+nbytes-4),"ACK")==0) break; } sprintf(p,"Get message at position %d\n",number); lines[0]=strdup(p); lines[nlines]=NULL; close(bincl_id); } printf("\n"); while (lines[++i]!=NULL) { printf(" %s",lines[i]); free(lines[i]); } printf("\n"); free(lines); lines=NULL; return 1; } /*****************************************/ /* DELMESSAGE: cancella un messaggio */ /*****************************************/ int DelMessage(int number) { char **lines=(char **)calloc(512,sizeof(char *)); char p[80],data[BUFSIZ]; int i=-1; /* invia richiesta cancellazione messaggio */ sprintf(data,"del"); if (send(client_id,data,strlen(data)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* invia numero del messaggio da cancellare */ sprintf(data,"%d",number); if (send(client_id,data,strlen(data)+1,0)<0) { perror("\n--> Client: send\n"); exit(1); } ReceiveAck(client_id); /* Riceve ACK dal server a cancellazione avvenuta */ ReceiveAck(client_id); sprintf(p,"Del message at position %d",number); lines[0]=strdup(p); lines[1]=NULL; while (lines[++i]!=NULL) { printf(" %s",lines[i]); free(lines[i]); } printf("\n"); free(lines); lines=NULL; return 1; } /***********************************************************************/ /* Socket_TCP: crea un socket con protocollo TCP per il trasferimento */ /* dei messaggi */ /***********************************************************************/ int Socket_TCP(int porta) { int length; if ((pp = getprotobyname("tcp")) == 0) { perror("\n-->Client: getprotobyname tcp\n"); exit(1); } /* crea il socket TCP */ if ((bincl_id = socket(AF_INET,SOCK_STREAM,pp->p_proto)) == -1) { perror("\n-->Server: socket tcp\n"); exit(1); } printf("%c",LF); printf ("--------------- C L I E N T -----------------"); printf("%c",LF); printf ("+ Nome Host: %30s +",server->h_name); printf("%c",LF); printf ("+ Indirizzo Host: %25s +",inet_ntoa(server_addr)); printf("%c",LF); printf ("+ Porta per trasf. TCP: %19d +",ntohs(porta)); printf("%c",LF); printf ("---------------------------------------------"); printf("%c",LF); printf("
"); binary_sock.sin_family = AF_INET; binary_sock.sin_addr = server_addr; binary_sock.sin_port = porta; } /***********************************************************************/ /* Socket_UDP: crea un socket con protocollo UDP per il trasferimento */ /* dei messaggi */ /***********************************************************************/ int Socket_UDP(int porta) { int length; if ((pp = getprotobyname("udp")) == 0) { perror("\n-->Client: getprotobyname udp\n"); exit(1); } /* crea il socket UDP */ if ((bincl_id = socket(AF_INET,SOCK_DGRAM,pp->p_proto)) == -1) { perror("\n-->Server: socket udp\n"); exit(1); } /* faccio la bind con: indirizzo = client_addr port = 0 */ bincl_sock.sin_family = AF_INET; bincl_sock.sin_addr = client_addr; bincl_sock.sin_port = 0; length = sizeof(bincl_sock); if (bind(bincl_id, &bincl_sock,sizeof(bincl_sock)) == -1) { perror("\n-->Client: bind udp\n"); close(bincl_id); exit(1); } /* leggo il nome socket ( dominio,indirizzo,porta ) */ if (getsockname(bincl_id, &bincl_sock,&length)== -1) { perror("\n-->Client: getsockname udp\n"); exit(1); } printf("%c",LF); printf ("--------------- C L I E N T -----------------"); printf("%c",LF); printf ("+ Nome Host: %30s +",server->h_name); printf("%c",LF); printf ("+ Indirizzo Host: %25s +",inet_ntoa(server_addr)); printf("%c",LF); printf ("+ Porta Server per trasf. UDP: %12d +",ntohs(porta)); printf("%c",LF); printf ("+ Porta Client per trasf. UDP: %12d +", ntohs(bincl_sock.sin_port)); printf("%c",LF); printf ("---------------------------------------------"); printf("%c",LF); printf("
"); binary_sock.sin_family = AF_INET; binary_sock.sin_addr = server_addr; binary_sock.sin_port = porta; } Modulo: PARAMETRI.c /* richiede i parametri per i vari comandi implementati */ #include #include #include #include #include #include #include #include #include #include #include #include #define LF 10 extern int errno; extern char *sys_errlist[]; #include "html_utility.c" int command; char *strdup(const char *); int main() { entry entries[MAX_ENTRIES]; /* HTML name-val pairs */ int x, cl, etnum, comnum, status; printf("Content-type: text/html%c%c",LF,LF); if(strcmp(getenv("REQUEST_METHOD"),"POST")) { printf("This script should be referenced with a METHOD of POST.\n"); printf("If you don't understand this, read "); printf("forms overview.%c",LF); exit(1); } if(strcmp(getenv("CONTENT_TYPE"),"application/x-www-form-urlencoded")) { printf("This script can only be used to decode form results.\n"); exit(1); } cl = atoi(getenv("CONTENT_LENGTH")); etnum = 0; for(x=0;cl && (!feof(stdin));x++) { entries[x].val = fmakeword(stdin,'&',&cl); plustospace(entries[x].val); unescape_url(entries[x].val); entries[x].name = makeword(entries[x].val,'='); etnum++; } printf("E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET"); printf(""); printf("
");
	printf("
"); printf("%c",LF); printf("

SERVIZIO DI POSTA IN RETE INTERNET

"); printf("%c",LF); printf("
"); printf("", entries[1].val); printf("", entries[2].val); printf("", entries[3].val); printf("%c",LF); printf("
"); printf("%c",LF); switch (atoi(entries[0].val)) { case 1 : ParPut(); break; case 2 : ParShow(); break; case 3 : ParDel(); break; case 4 : ParList(); break; case 5 : NoPar(); break; default: printf("Unknown command"); break; } printf("%c",LF); printf("
"); printf(" "); printf("
"); printf("
"); printf(""); exit(1); } int ParPut() { printf("

COMANDO PUT - INSERIMENTO PARAMETRI :

"); printf("%c",LF); printf("
"); printf("%c",LF); printf(""); printf(""); printf("%c",LF); printf("
");
	printf("Inserire nell'area sottostante il messaggio da 		inviare");
	printf("%c",LF);
	printf("%c",LF);
	printf("Soggetto: ");
	printf("%c",LF);
	printf("%c",LF);
	printf("Testo del messaggio:");
	printf("%c",LF);
	printf("");
	printf("%c",LF);
	printf("%c",LF);
	printf("Priorita' del messaggio (valore numerico): 			");
	printf("");
	printf("%c",LF);
	printf("%c",LF);
	printf("
"); printf("%c",LF); printf("Login dell'utente destinatario del messaggio: "); printf("%c",LF); printf(""); printf("%c",LF); printf("%c",LF); printf("
"); printf("%c",LF); printf("%c",LF); printf("Tipo di protocollo da adottare per il trasferimento del "); printf("%c",LF); printf("messaggio: "); printf("%c",LF); } int ParShow() { printf("

COMANDO SHOW - INSERIMENTO PARAMETRI :

"); printf("%c",LF); printf("
"); printf("%c",LF); printf(""); printf(""); printf("%c",LF); printf("Numero del messaggio che voglio leggere: "); printf(""); printf("%c",LF); printf("%c",LF); printf("
"); printf("%c",LF); printf("Tipo di protocollo da adottare per il trasferimento del "); printf("%c",LF); printf("messaggio: "); printf("%c",LF); } int ParDel() { printf("

COMANDO DEL - INSERIMENTO PARAMETRI :

"); printf("%c",LF); printf("
"); printf("%c",LF); printf(""); printf(""); printf("Numero del messaggio che voglio cancellare: "); printf(""); printf("%c",LF); } int ParList() { printf("

COMANDO LIST - INSERIMENTO PARAMETRI :

"); printf("%c",LF); printf("
"); printf("%c",LF); printf(""); printf(""); printf("Tipo di messaggi da mostrare: "); printf("%c",LF); } int NoPar() { printf("

COMANDO BYE :

"); printf("%c",LF); printf("
"); printf("%c",LF); printf(""); printf(""); printf("Il comando bye non richiede alcun parametro "); printf("%c",LF); } Documento: OPEN.html E_MAIL EXPRESS: SERVIZIO DI POSTA IN INTERNET

SERVIZIO DI POSTA IN RETE INTERNET


Per i dettagli sul funzionamento del programma leggi E-MAIL EXPRESS

APERTURA CONNESSIONE CON IL MAIL-SERVER DAEMON:


Nome Host su cui e' attivo il Mail-Server Daemon: N.B. E' possibile inserire:
  • l'indirizzo IP numerico
  • l'indirizzo mnemonico
  • un nome presente nel file etc/hosts

Login utente :
Password utente :

Note riguardo al codice: I caratteri " che occorrono all'interno dei printf devono essere duplicati così da non poterli interpretare come i " che concludono la stringa. Non sono inclusi controlli per campi di input lasciati vuoti o parametri di ingresso sbagliati (es. Login dell'utente destinatario errata nel comando PUT). Qualunque dato, anche errato, verrà inviato al server senza alcun controllo preventivo sulla sua validità, provocando così un errore e, talvolta, il blocco del sistema. Questi controlli sono comunque abbastanza facili da implementare (vedi APPENDICE D - LIBRERIE C PER LA PROGRAMMAZIONE CGI) e se ne possono inserire quanti se ne desidera. Dall'analisi dei listati si vede che MAIL_CLIENT e JOLLY sono molto simili. Ciò suggerisce l'idea che essi potrebbero essere ridotti ad un solo eseguibile che comprenda le routine sia dell'uno che dell'altro. Sarebbe poi sufficiente inserire un semplice controllo per consentire al programma di discriminare tra il caso che si debbano lanciare le routine proprie del primo o del secondo, a seconda dello stato in cui ci si trova. Un'altra modifica possibile potrebbe essere la seguente. Alcuni programmatori CGI pensano che non sia necessario separare la form dallo script che elabora l'output dalla form, cioè che non vi siano motivi per cui si debbano avere due parti, ma che sia meglio combinare sia la form che il programma interprete della form in un solo modulo. In tal caso occorre scrivere un programma che sarà eseguito due volte; la prima per generare la form e la seconda per eseguire lo script con gli input immessi e generare l'uscita (nella strgrande maggioranza dei casi un documento HTML). Il problema è far sapere al programma quale modo deve essere attivato (la generazione della form o la processazione dei risultati della form). Si noti che il primo passo (la generazione della form) non transmette alcun dato al programma mentre il secondo (richiesta di attivazione di uno script) comporta l'invio di dati. La determinazione del fatto che vi sia un input destinato alla form può essere lasciato ad opportune funzioni di libreria CGI che sono disponibili in rete che possono essere facilmente incluse nel programma; basterà comprendere una sezione contenente una semplice condizione decida se ci sono o meno input per il programma. Se non ci sono input il programma genererà la form HTML, mentre nel caso contario il programma effettuerà le operazioni richieste e ritornerà il risultato in un documento HTML. if (è presente un input) /* è possibile generare l'output HTML */ { inserisce gli input dentro le variabili printf(""); ... printf(""); return 1; } else /* non ci sono input => occorre generare la form */ { printf(""); printf("
"); ... printf(""); printf(""); printf("
"); printf(""); return 1;} }

Appendice B - Come aprire un applicativo esterno

Configurazione del server Una volta compilato il programma CGI, è necessario assicurarsi del fatto che l'eseguibile sia nella locazione corretta, la directory cgi-bin del proprio HTTP server. Inoltre è necessario assicurarsi di dove e come il server WWW si aspetti di trovare i programmi CGI, e verificare che esso abbia i corretti permessi per eseguirli. In particolare occorre tener conto dei seguenti aspetti. Quando il client invia la URL relativa al programma CGI da eseguire, come nel nostro esempio: http://lisa.dfc.unifi.it/cgi-bin/mail_client il server deve accorgersi che quello richiesto non è un semplice documento HTML ma un programma CGI che deve essere eseguito da una shell del sistema operativo. Per rendere possibile questo occorre innanzitutto che i programmi CGI siano tutti compresi in una apposita directory, ma soprattutto che nel file di configurazione del server siano specificate tutte le informazioni necessarie per la corretta apertura dell'applicativo esterno: il path in cui trovare i programmi CGI; il server va a cercare il programma CGI specificato nel path che viene indicato nella configurazione. l'identificatore che indichi che è richiesta l'esecuzione di una applicazione; l'identificatore solitamente scelto è "/cgi-bin/" ma è consentito anche scegliere "/Htbin/" che, nelle vecchie versioni di alcuni server, costituiva l'unico identificatore possibile. Tutto ciò che segue l'identificatore viene interpretato dal server come nome del programma da eseguire. Sulla macchina lisa, su cui si trovano i programmi CGI realizzati, è presente il server implementato dal CERN. Esso lavora in ambiente UNIX. La sistemazione di default del server CERN prevede che il file di configurazione si chiami httpd.conf e che si trovi nella directory /etc. Per i dettagli sulla configurazione del server si veda in [Benvenuti]. Gli aspetti da sottolineare relativamente al file di configurazione di default /etc/httpd.conf sono i seguenti: La riga contenente il comando Exec: Exec /* identificatore */ /* path programmi CGI */ che specifica l'identificatore dei programmi CGI ed il path sotto il quale tali programmi si trovano. Nel nostro caso, per fare in modo che il server tratti i file contenuti in /disk3/usr/netman/mail.exp/prozio/cgi-bin/ come programmi eseguibili e non come documenti HTML o di altro tipo, essa appare come segue: Exec /cgi-bin/ /disk3/usr/netman/mail.exp/prozio/cgi-bin/ Quando viene inviata una URL del tipo http://lisa.dfc.unifi.it/cgi-bin/mail_client il server provvede ad eseguire il seguente comando di linea: > /disk3/usr/netman/mail.exp/prozio/cgi-bin/mail_client La riga contente il comando Enable: Enable POST Se si intende usare il metodo POST è necessario accertarsi che il server sia abilitato a ricevere tale metodo, ovvero sia presente nel file di configurazione la riga sopra indicata. Infatti il metodo POST non è abilitato di default, a differenza del GET, che nel protocollo HTTP è il metodo con cui si scambiano i normali documenti HTML. Come usare il browser Informazioni specifiche su come lanciare dal browser il programma di posta qui illustrato: Netscape - dopo aver lanciato Netscape, aprire il menu File e selezionare l'opzione Open location. Quindi digitare in "http://" il nome del server (lisa.dfc.unifi.it) seguito dal simbolo "/": http://lisa.dfc.unifi.it/. Così facendo si visualizza la Home Page del server lisa.dfc.unifi.it; da essa si può accedere poi al file OPEN.HTML che contiene la form da cui ha inizio il programma selezionando il link indicato con la dicitura "Programma di posta" Mosaic - dopo aver lanciato mosaic, aprire il menu File e selezionare l'opzione Open URL. Quindi seguire le stesse istruzioni valide per il browser Netscape.

Appendice C - Riferimenti

Per chi non sia molto familiare nella scrittura di form all'interno documenti HTML, il documento "Mosaic for X version 2.0 Fill-out Form support" della NCSA è un ottimo punto di partenza per scoprire gli aspetti basilari. Sempre la NCSA mette a disposizione la documentazione di base sullo standard CGI: il documento " The Common Gateway Interface " è un buon punto di partenza per apprendere la CGI. La "Yahoo's CGI list" è invece la migliore risorsa di informazioni sui siti presso i quali sono disponibili librerie, documentazioni e quantaltro a proposito della CGI. Particolarmente interessanti sono risultati i documenti "Building HTML-Based Interface" e "Beyond Hypertext: Using the WWW for Interactive Applications" . Riferimenti ad ipertesti HREF 1 Mosaic for X version 2.0 Fill-out Form support URL di questo documento: HREF 2 The Internet Draft of the HTTP/1.0 Specification. URL di questo documento: http://www.w3.org/hypertext/WWW/Protocols/HTTP1.0/draft-ietf-http-spec.html HREF 3 The Common Gateway Interface URL di questo documento: http://hoohoo.ncsa.uiuc.edu/cgi/overview.html HREF 4 Standard MIME URL di questo documento: http://www.oac.uci.edu/indiv/ehood/MIME/MIME.html HREF 5 CGI Programmer's Reference URL di questo documento: http://www.best.com/~hedlund/cgi-faq/ HREF 6 List of all Elements in the HTML 2.0 DTD URL di questo documento: http://www.oac.uci.edu/indiv/ehood/html2.0/ALL-ELEM.html HREF 7 CGI for non programmer (contiene le lezioni di Brian Exelbierd implementate in Perl) URL di questo documento: http://www.catt.ncsu.edu/~bex/tutor/index.html HREF 8 Yahoo's CGI list URL di questo documento: http://www.yahoo.com/Computer_and_Internet/Internet/World_Wide_Web/CGI___Common_Gateway_Interface/ HREF 9 Building HTML-Based Interface URL di questo documento: http://blackcat.brynmawr.edu/~nswoboda/prog-html.html HREF 10 Beyond Hypertext: Using the WWW for Interactive Applications URL di questo documento: http://www.its.unimelb.edu.au:801/papers/AW04-04/ Altri riferimenti utili [Benvenuti] Benvenuti F. Interattività nei documenti HTML: le "form".Relazione per l'elaborato del Corso di Telematica [Comer et al. 94] Comer, Douglas E., Stevens, David L. Internetworking with TCP/IP Volume III. Client-server programming and applications: Prentice-Hall. 1994. ISBN 0-13-474230-3

Appendice D - Librerie C per la programmazione CGI

NCSA httpd LibCGI By EIT (Enterprise Integration Technologies Co.): libreria per ANSI C e C++ cgic: an ANSI C library for CGI Programming cgic è una libreria in linguaggio ANSI C per la creazione di applicazioni World Wide Web basate sul CGI. Nel documento HTML di presentazione della libreria sono contenute tutte le informazioni ed avvertenze necessarie per ottenere le routine di utilità dai due siti in cui esse sono messe a disposizione:
  1. cgic: an ANSI C library for CGI Programming
  2. cgic: an ANSI C library for CGI Programming