Il leggendario CGIDEV2

Transcript

Il leggendario CGIDEV2
Il leggendario CGIDEV2
Secondo articolo
di Giovanni Battista Perotti
Il sussidiario di CGIDEV2
Per fare in modo che i lettori possano più comodamente
riprodurre - ed eventualmente estendere – gli esempi riportati in
questi articoli, abbiamo creato un Sussidiario, composto dalla
libreria SUSSIDEV2 e dalla cartella IFS /sussidev2 contenenti il
materiale esemplificativo.
Il tutto si scarica dalla pagina
http://easy400.coraltree.co.uk/sussidev2.zip .
Un-zippando il file ricevuto si ottiene un file di salvataggio ed un
testo Readme che spiega come installare il tutto e farlo funzionare
su AS/400.
E’ richiesto almeno il rilascio V5R2.
Questo sussidiario viene aggiornato ad ogni nuovo articolo.
Nel primo articolo di questa serie abbiamo parlato del servente http
Apache, abbiamo spiegato che cosa è il protocollo web CGI e in che
cosa consista la non-persistenza di un programma CGI. Abbiamo poi
fatto una panoramica su CGIDEV2, fornendone un cenno storico e
spiegando come uno dei suoi punti di forza sia la separazione tra il
programma CGI e la sua presentazione per il tramite di uno script
HTML esterno. Abbiamo illustrato come scaricare, installare e far
funzionare CGIDEV2 per il tramite di una particolare istanza http di
esempio, denominata CGIDEV2APA. Abbiamo poi invitato i lettori, in
attesa di questo secondo articolo, a studiare un poco di HTML e di
CSS, indicando alcuni siti attrezzati per ciò.
ESEMPIO 1
In questo secondo articolo incominciamo a spiegare, con un esempio, come si
programma in RPG-ILE con CGIDEV2. Proponiamo il caso dell’inserimento di
nuovi nominativi in una anagrafica clienti. La pagina che vogliamo ottenere è
esposta in Figura 1.
Per prima cosa vanno definiti i file database essenziali al programma. Nel
nostro caso si è deciso che siano due, un file fisico ANACLI (Anagrafica
Clienti, Figura 2) ed un logico ANACLI01 (che serve unicamente ad
controllare che uno stesso cliente non compaia più di una volta, Figura 3)
R CLIRCD
CLICOD
9P 0
CLIRAGSOC
50
CLIINDIR
50
CLICAP
5
CLILOCAL
50
CLIPROV
2
UNIQUE
TEXT('record cliente')
TEXT('codice cliente')
COLHDG('codice cliente')
TEXT('ragione sociale')
COLHDG('ragione sociale')
TEXT('indirizzo')
COLHDG('indirizzo')
TEXT('CAP')
COLHDG('CAP')
TEXT('localita''')
COLHDG('localita''')
TEXT('provincia')
COLHDG('provincia')
TEXT('contatto')
COLHDG('contatto')
TEXT('indirizzo e-mail')
COLHDG('indirizzo' 'e-mail')
Figura 1
R
K
K
K
K
K
CLIRCD
CLIRAGSOC
CLIINDIR
CLICAP
CLILOCAL
CLIPROV
PFILE(SUSSIDEV2/ANACLI)
Figura 3
In secondo luogo, secondo la prassi, occorrerebbe definire la
presentazione. Nella tradizione si tratterebbe di definire il file display.
CLICONTAT
30
Nel nostro caso si tratta di definire il suo omologo, uno script esterno di
HTML.
CLIEMAIL
50
Questo HTML esterno si può scrivere in un membro di file origine,
oppure in un file di flusso (stream file) IFS. In genere una persona
K CLICOD
incomincia con un membro di file origine. Le limitazioni del SEU sono
Figura 2
però tali che alla fine si decide sempre di passare ad uno stream file
IFS, che si può molto più agevolmente scrivere con un semplice NotePad (Blocco Note). Si aprirebbe qui la discussione se valga o meno la pena di
utilizzare, per scrivere l’HTML, uno scrittore di HTML (HTML Authoring Tool), per es. Dreamweaver. La mia opinione è che da tali strumenti
bisogna starsene alla larga, in quanto generano codice che difficilmente si riesce a controllare manualmente, e di controllo manuale ne occorre
alquanto, come vedremo tra poco. Quindi: Stream file e NotePad, questa è la regola.
Ecco dunque lo stream file di HTML esterno /sussidev2/html/pgm1.htm che sarà utilizzato dal nostro programma CGI:
<as400>inizio
Content-type: text/html
*** SEZIONE INIZIO HTML
<html>
<head>
<title>Nuovo cliente</title>
<link rel="SHORTCUT ICON" href="/sussidev2/graphics/favicon.ico">
<link rel="stylesheet" type="text/css" href="/sussidev2/css/css.css">
</head>
<body>
<table>
<tr><td><img src="/sussidev2/graphics/redBaron.jpg"></td>
<td width="20">&nbsp;</td>
<td class="title">Nuovo cliente</td></tr>
</table>
<as400>OK
*** SEZIONE CONFERMA IMMISSIONE DATI
<table>
<tr><td class="okmsg">Assegnato codice <b>/%clicod%/</b> al cliente <b>/%cliragsoc%/</b></td></tr>
</table>
<as400>ERR
<table>
<tr><td class="errmsg">/%errmsg%/</td></tr>
</table>
*** SEZIONE ERRORE IMMISSIONE DATI
<as400>inputForm
*** SEZIONE IMMISSIONE DATI NUOVO CLIENTE
<p class="grosso">Immettere i dati e premere il bottone <i>invio</i>:</p>
<p>
<form name="nuovocli" method="post" action="/sussidev2p/pgm1.pgm">
<input type="hidden" name="xrequest" value="immissione">
<table>
<tr><td>Ragione sociale:</td>
<td><input class="inpg" name="xragsoc" size="50" maxlength="50" value="/%xragsoc%/"></td></tr>
<tr><td>Via e numero civico:</td>
<td><input class="inpg" name="xindir" size="50" maxlength="50" value="/%xindir%/"></td></tr>
<tr><td>CAP:</td>
<td><input class="inpg" name="xcap" size="5" maxlength="5" value="/%xcap%/"></td></tr>
<tr><td>Località:</td>
<td><input class="inpg" name="xlocal" size="50" maxlength="50" value="/%xlocal%/"></td></tr>
<tr><td>Provincia:</td>
<td><input class="inpg" name="xprov" size="3" maxlength="2" value="/%xprov%/"></td></tr>
<tr><td>Nome per contatto:</td>
<td><input class="inpg" name="xcontat" size="30" maxlength="30" value="/%xcontat%/"></td></tr>
<tr><td>E-mail per contatto:</td>
<td><input class="inpg" name="xemail" size="50" maxlength="50" value="/%xemail%/"></td></tr>
<tr><td colspan=2 align=center>
<input type=submit class="bott" value="invio"></td></tr>
</table>
</form>
<as400>fine
</body>
</html>
*** SEZIONE FINE HTML
Figura 4
Le Sezioni (i tracciati record)
CGIDEV2 prevede che il programma CGI carichi in memoria lo script esterno di HTML (lo stream file di cui sopra) con una sua particolare
procedura (vedremo più avanti). La emissione dell’HTML nel buffer di output diretto al browser client, viene effettuata dal programma CGI tramite la
write di sezioni dell’HTML esterno. La sezione corrisponde grosso modo al tracciato record di un file display.
Le sezioni HTML vengono identificate da una riga (colorata in giallo nell’esempio) in cui compare
• un prefisso identificativo dell’inizio sezione(nel nostro caso <as400> )
• il nome della sezione (ad esempio inizio ), lungo al massimo 20 caratteri
• ed un eventuale commento
Il contenuto di una sezione va dalla riga successiva alla riga identificativa (gialla), sino alla riga precedente alla riga identificativa (gialla) della
sezione successiva o alla fine dello stream file, se non esistono sezioni successive.
Nell’esempio, l’ultima sezione ha nome “fine” e contiene la riga </body> e la riga</html>.
Nell’esempio abbiamo 5 sezioni, di nome “inizio”, “OK”, “ERR”, “inputForm” e “fine”.
Le Variabili di Output
Si tratta in pratica dei campi di output, ai quali il programma CGI assegnerà i valori calcolati in base alla sua logica di processo. Queste variabili di
output hanno un nome con prefisso /% e suffisso %/ (in verde nell’esempio). Ad esempio, /%clicod%/ definisce una variabile (un campo) di output
di nome clicod .
La Header “Content-type”
Il buffer HTML spedito dal programma CGI al browser client deve sempre iniziare con una Header (Testata) Content-type (tipo di contenuto). Essa
è indispensabile al browser per sapere come debba rappresentare (mimare) lo script che segue. E’ un html, un xml, un testo semplice, …,
insomma che cosa è? In assenza di tale header, il browser (salvo vecchie versioni di I.E.) non sa che cosa fare e va in ERRORE 500. Le header
Content-type più comuni sono:
• Content-type: text/html
• Content-type: text/plain
• Content-type: text/xml
Non esiste solo la header Content-type. Se ne possono aggiungere altre, ma per il momento non ne parliamo.
L’importante è sapere che la fine delle header si segnala con due sequenze CariageReturnLineFeed (CRLF), cioè AcapoNuovaRiga. Questa
dizione rappresenta storicamente il comportamento della macchine da scrivere meccaniche (dalla vecchia Remington Rand alla Olivetti Lettera 24),
quando agendo sulla leva alla destra del carrello si terminava una riga e si posizionava il foglio per iniziarne un’altra.
In pratica, per segnalare la fine delle header, bisogna andare a capo due volte.
Nel nostro caso, si tratta di lasciare una riga vuota (priva persino di blank) subito dopo il Content-type: text/html .
Si veda infatti come inizia la sezione denominata “inizio” .
Alcune precisazioni sull’HTML utilizzato
Qui si intende che il lettore abbia, se non dimestichezza, almeno qualche rudimento di HTML e di CSS. Per questo motivo ci limiteremo a
commentare solo qualche aspetto dell’HTML utilizzato (Figura 4).
1. <title> - Questa tag viene utilizzata per definire il testo che deve comparire come titolo della pagina prodotto dal browser (Vedi Figura 1, il
titolo “Nuovo cliente” sulla tab relativa alla pagina)
2. <link rel="SHORTCUT ICON" ..> - Questa tag viene utilizzata per associare una icona al bookmark (favorite) che si può
prendere per memorizzare l’URL della pagina. Nel nostro caso, l’icona citata /sussidev2/graphics/favicon.ico ha questo aspetto:
(Vedi Figura 1).
3. < link rel="stylesheet" type="text/css"…> - Questa tag viene utilizzata per specificare gli stili CSS da utilizzare in questo
HTML. Il file CSS specificato contiene quanto segue:
body
a
a:hover
p
th
td,li
.title
.inpg
.grosso
.okmsg
{background-color: #ffffff; color: #000000; margin-left: 10px; margin-top: 2px;}
{font-family: arial,verdana,sans-serif; font-size: 12px; color: #6666ff; text-decoration: underline;}
{color: #ff6666}
{font-family: arial,helv,sans-serif; font-size: 12px; margin-top: 7px; margin-bottom: 0px;}
{font-family: arial,helv,sans-serif; font-size: 12px; font-weight: bold; text-align: center;}
{font-family: arial,helv,sans-serif; font-size: 12px;}
{color: Blue; font-weight: Bold; font-size: 24pt; font-family: souvenir lt bt, verdana, serif;}
{color: blue; background-color: #feef4c; font-family: Georgia, Verdana, Arial; font-size: 13px;}
{font-size: 16px;}
{font-family: verdana,arial,helv,sans-serif; font-size: 14px; background-color: #99ff99;
padding-left: 15px; padding-right: 15px; border: #000000 1px solid;}
.errmsg {font-family: verdana,arial,helv,sans-serif; font-size: 14px; background-color: #ff9999;
padding-left: 15px; padding-right: 15px; border: #000000 1px solid;}
.border1 {padding-left: 15px; padding-right: 15px; padding-top: 15px; padding-bottom: 15px;
border-left: #000000 1px solid; border-top: #000000 1px solid; border-right: #000000 1px solid;
border-bottom: #000000 1px solid;}
.bott
{font-family: verdana,arial,helv,sans-serif; font-size: 14px; color: #ffffff; background-color: #0000ff;
padding-left: 5px; padding-right: 5px; border: #000000 1px solid;}
Figura 5
Per esempio, la classe .bott è stata utilizzata per definire il bottone blu di invio nella pagina in Figura 1.
4. La figura Snoopy-BaroneRosso ed il titolo “Nuovo Cliente”- Per disporre appropriatamente questi
elementi, si è creata una tabella (<table>) di una riga (<tr>) e tre celle (<td>). La prima cella contiene l’immagine di Snoopy-BaroneRosso, la
seconda cella è larga 20 pixel è contiene dei blank (&nbsp;), la terza cella contiene la dizione “Nuovo Cliente” con la classe CSS “title” (Vedi Figura
5).
5. La sezione OK – Viene utilizzata per emettere un messaggio di conferma dell’avvenuta registrazione di un nuovo cliente. Il
messaggio utilizzato è una tabella di una sola riga ed una sola cella, che si presenta a sfondo verde ed in cornicetta a bordo nero, grazie alla
classe CSS okmsg .
6. La sezione ERR – Viene utilizzata per emettere un messaggio di errore quando si presentano manchevolezze nella immissione di
un nuovo cliente. Il messaggio utilizzato è una tabella di una sola riga ed una sola cella, che si presenta a sfondo rosa ed in cornicetta a bordo
nero, grazie alla classe CSS errmsg .
7. La sezione InputForm – Questa sezione è fondamentale per il lancio della richiesta di immissione dati. Vediamone la
composizione.
• Prima di tutto il contenitore <form …> </form>. Questo contenitore contiene tutti i campi di input che devono essere trasmessi al
programma CGI.
•
•
Quale programma CGI? Quello specificato nel parametro action della <form …>
In quale modo deve essere trasmesso l’input? Con il metodo (POST) specificato nel parametro method della <form …>.
Sono possibili due metodi di trasmissione: GET e POST.
o Il metodo GET è un metodo primitivo, che trasmette tutte le variabili in una stringa visibile nella
URL che si presenta come ?input1=valore1&input2=valore2&input3=valore3 Gli
svantaggi sono che l’utente può tentare di ripetere l’URL variandone la stringa dei valori, caratteri
non alfanumerici hanno una rappresentazione particolare, la lunghezza della stringa di valori ha un
massimo di circa 3000 caratteri che variano da browser a browser. Il vantaggio è che il neofita si
sente più sicuro perché “vede” che cosa viene trasmesso al CGI.
o Il metodo POST è quello che effettivamente si usa nelle applicazioni produttive, in quanto non ha
praticamente limiti e tiene nascosti i valori di input (come vedremo esistono anche i campi di input
nascosti, gli hidden, come nei file display) .
Con CGIDEV2 non fa alcuna differenza che si usi il metodo GET od il metodo POST. Resta il fatto che, a livello di HTML,
nel form va specificato quale metodo va usato.
• Il contenitore <form …> </form> può contenere campi di input nascosti (type=”hidden”). I campi hidden sono campi che l’utente finale
non vede e sui quali non ha controllo. Questi campi contengono in genere o valori che servono per qualificare la richiesta al
programma CGI o valori che servono per ricordare al CGI (che sappiamo non-persistente, cioè privo di memoria) di che cosa si stia
occupando (quale cliente, quale ordine, e così via). Per esempio, il nostro HTML in Figura 4 presenta nel form un campo hidden, di
nome “xrequest” che serve per dire al CGI che i dati in arrivo da questo form sono attinenti alla immissione di un nuovo cliente.
• Il contenitore <form …> </form> può altresì contenere campi di input testuale (type=”text”), ai quali va data una lunghezza visibile
(size) ed una lunghezza massima (maxlength). Questi campi possono contenere un valore iniziale (value) modificabile dall’utente.
• Nel nostro esempio il contenitore <form …> </form> contiene anche come campo di input un bottone di immissione (type=”submit”) la
cui pressione scatena la chiamata al CGI con il trasferimento (in modalità POST) dei vari campi di input.
Passiamo ora al sorgente del programma RPG-ILE sussidev2/pgm1.
*=============================================================================================
* Immissione nuovi clienti
*
* CRTBNDRPG PGM(SUSSIDEV2/PGM1) SRCFILE(SUSSIDEV2/QRPGLESRC) DFTACTGRP(*NO)
*
ACTGRP(PGM1) DBGVIEW(*SOURCE)
*
*=============================================================================================
/copy SUSSIDEV2/qrpglesrc,hspecs
/copy SUSSIDEV2/qrpglesrc,hspecsbnd
FANACLI
if a e
k disk
usropn
FANACLI01 if
e
k disk
usropn rename(clircd:clircd01)
/copy SUSSIDEV2/qrpglesrc,prototypeb
/copy SUSSIDEV2/qrpglesrc,usec
* Numero delle variabili di input
D nbrVars
s
10i 0
* Saved query string
Dsavedquerystring...
D
s
32767
varying
*
D extHtml
s
2000
* Indicatori per la procedura GetHtmlIfsMult (caricamento HTML esterno)
D IfsMultIndicators...
d
ds
D NoErrors
n
D NameTooLong
n
D NotAccessible
n
D NoFilesUsable
n
D DupSections
n
D FileIsEmpty
n
D
* Variabili di input
xrequest
s
10
D
D
D
D
D
D
D
D
xcod
xragsoc
xindir
xlocal
xcap
xprov
xcontat
xemail
s
s
s
s
s
s
s
s
* Altre variabili
D rc
s
D InpErrMsg
s
C
C
C
C
C
C
like(clicod)
like(cliragsoc)
like(cliindir)
like(clilocal)
like(clicap)
like(cliprov)
like(clicontat)
like(cliemail)
10i 0
1000
Key01
klist
kfld
xragsoc
kfld
xindir
kfld
xcap
kfld
xlocal
kfld
xprov
*=============================================================================================
* Logica principale
*=============================================================================================
/free
// Preliminari
exsr LoadHTML;
exsr OpnF;
exsr RcvInput;
//carica l'HTML esterno
//apri i file
//ricevi le variabili di input
// Incomincia a scrivere l'inizio dell'HTML di risposta
wrtsection('inizio');
// Analizza il tipo di richiesta e prendi il percorso opportuno
select;
when xrequest=' ';
exsr PrimaVolta;
when xrequest='IMMISSIONE';
exsr Immissione;
endsl;
// Scrivi la fine dell'HTML di risposta
wrtsection('fine');
// Lancia il buffer della risposta HTML e termina il programma
exsr Exit;
/end-free
*=============================================================================================
* Carica l'HTML esterno
*=============================================================================================
/free
Begsr LoadHTML;
extHTML='/sussidev2/html/pgm1.htm';
IfsMultIndicators=getHtmlIfsMult(%trim(exthtml):'<as400>');
//Se il caricamento dell'HTML esterno fallisce, ritorna (causando ERRORE 500)
if NoErrors=*off;
return;
endif;
Endsr;
/end-free
*=============================================================================================
* Ricevi le variabili di input
*=============================================================================================
/free
Begsr RcvInput;
nbrVars=zhbgetinput(savedquerystring:qusec);
//acquisisci il buffer di input
xrequest =zhbGetVarUpper('xrequest');
xragsoc =zhbGetVarUpper('xragsoc');
xindir =zhbGetVarUpper('xindir');
xcap
=zhbGetVarUpper('xcap');
xlocal =zhbGetVarUpper('xlocal');
xprov =zhbGetVarUpper('xprov');
xcontat =zhbGetVarUpper('xcontat');
xemail =zhbGetVar('xemail');
Endsr;
/end-free
*=============================================================================================
* Se è la prima chiamata al programma, emetti un form con i campi di input vuoti
*=============================================================================================
/free
Begsr PrimaVolta;
exsr ClearForm;
wrtsection('inputForm');
//abblenca i campi di input del form
//emetti il form di input
Endsr;
/end-free
*=============================================================================================
* Se sono stati ricevuti dati dal form di immissione, ...
*=============================================================================================
/free
Begsr Immissione;
// Se l'input non è completo, chiedi la correzione dell'errore e termina
exsr ChkInput;
// Memorizza i dati sull'anagrafica cliente
exsr FileData;
// Emetti il messaggio "registrazione effettuata"
updhtmlvar('clicod':%editc(clicod:'1'));
updhtmlvar('cliragsoc':cliragsoc);
wrtsection('ok');
// Abblenca ed emetti il form per registrare un eventuale nuovo cliente
exsr ClearForm;
wrtsection('inputForm');
Endsr;
/end-free
*=============================================================================================
* Se l'input non è completo:
* -invia un messaggio di errore
* -riemetti il form con il suo input
* -lancia il buffer di output e termina
*=============================================================================================
/free
Begsr ChkInput;
inpErrMsg=' ';
// assumi che non ci siano errori
select;
when xragsoc=' ';
inpErrMsg='manca la ragione sociale';
inpErrMsg='manca l''indirizzo';
when xcap =' ';
inpErrMsg='manca il CAP';
when xlocal =' ';
inpErrMsg='manca la localita''';
when xprov =' ';
inpErrMsg='manca la provincia';
when xcontat=' ';
inpErrMsg='manca il nome di contatto';
when xemail =' ';
inpErrMsg='manca l''e-mail del contatto';
endsl;
// Se i dati sono completi, controlla che il nuovo cliente non sia un duplicato
if inpErrMsg=' ';
chain key01 clircd01;
if %found;
inpErrMsg='questo cliente esiste già';
endif;
// Se si sono riscontrati errori
if inpErrMsg<>' ';
updhtmlvar('errmsg':inpErrMsg);
wrtsection('err');
//emetti il messaggio di errore
exsr Replica;
//replica i campi di input
wrtsection('inputForm');
//emetti il form di input
wrtsection('fine');
//scrivi la fine dell'HTML di risposta
exsr Exit;
//lancia il buffer di output e termina
endif;
Endsr;
/end-free
*=============================================================================================
* Abblenca i campi di input del form
*=============================================================================================
/free
Begsr ClearForm;
updhtmlvar('xragsoc':' ');
updhtmlvar('xindir' :' ');
updhtmlvar('xlocal' :' ');
updhtmlvar('xcap' :' ');
updhtmlvar('xprov' :' ');
updhtmlvar('xcontat':' ');
updhtmlvar('xemail' :' ');
Endsr;
/end-free
*=============================================================================================
* Replica i campi di input del form
*=============================================================================================
/free
Begsr Replica;
updhtmlvar('xragsoc':xragsoc);
updhtmlvar('xindir' :xindir);
updhtmlvar('xcap' :xcap);
updhtmlvar('xlocal' :xlocal);
updhtmlvar('xprov' :xprov);
updhtmlvar('xcontat':xcontat);
updhtmlvar('xemail' :xemail);
Endsr;
/end-free
*=============================================================================================
* Memorizza i dati sull'anagrafica cliente
*=============================================================================================
/free
Begsr FileData;
// Assegna il codice cliente
clicod=*hival;
setgt clicod clircd;
readp clircd;
if %eof;
clicod=0;
endif;
clicod=clicod+1;
// Memorizza il nuovo record cliente
cliragsoc=xragsoc;
cliindir =xindir;
clicap =xcap;
clilocal =xlocal;
clilocal =xlocal;
cliprov =xprov;
clicontat=xcontat;
cliemail =xemail;
write clircd
Endsr;
/end-free
*=============================================================================================
* Apertura file
*=============================================================================================
/free
Begsr OpnF;
if not %open(ANACLI);
rc=docmd('ovrdbf ANACLI SUSSIDEV2/ANACLI secure(*yes)');
open ANACLI;
endif;
if not %open(ANACLI01);
rc=docmd('ovrdbf ANACLI01 SUSSIDEV2/ANACLI01 secure(*yes)');
open ANACLI01;
endif;
Endsr;
/end-free
*=============================================================================================
* Chiusura file
*=============================================================================================
/free
Begsr CloF;
if %open(ANACLI);
close ANACLI;
rc=docmd('dltovr ANACLI');
endif;
if %open(ANACLI01);
close ANACLI01;
rc=docmd('dltovr ANACLI01');
endif;
Endsr;
/end-free
*=============================================================================================
* Fine programma
*=============================================================================================
/free
Begsr Exit;
exsr CLOF;
wrtsection('*fini');
return;
//chiudi i file
//spedisci il buffer di output al browser
//ritorna con *LR in *off
Endsr;
/end-free
Come creare il programma
Solitamente il programma CGI va creato (vedi i commenti all’inizio del simbolico) con ACTGRP(nome) DBGVIEW(*SOURCE).
DBGVIEW(*SOURCE) è essenziale per fare il debugging del programma quando non si comporta come dovrebbe (ci si ricordi di utilizzare il
comando CGIDEV2/EDBG per snellire le operazioni di attivazione del debug, vedi articolo precedente).
ACTGRP(nome) – dove nome è il nome assegnato al gruppo di attivazione con cui il programma deve essere eseguito ed è in genere lo stesso
nome del programma - è importantissimo. Se il programma esegue in un dato gruppo di attivazione ed evita di terminare con lindicatore *LR
in *on, la successiva volta che sarà richiamato (dallo stesso client o da un altro client) sarà trovato attivo con tutte la sue zone di memoria così
come sono state lasciate. Ora, a dire il vero, a noi non interessano i valori delle normali variabili di programma, in quanto non è dato sapere a quale
client il programma avesse ultimamente risposto. A noi interessa la zona di memoria in cui è stato caricato l’HTML esterno. Se questa zona di
memoria è stata preservata e se l’HTML esterno (lo stream file) non è stato modificato, il CGI evita di ricaricare l’HTML esterno. Il ricaricamento
dell’HTML esterno è una attività con notevole carico di CPU, non tanto per la lettura dello stream file, che è velocissima, quando per la attività che
la procedura di CGIDEV2 addetta a tale operazione svolge per registrare le sezioni dell’HTML e le variabili di output. Risparmiare questo lavoro
significa ridurre significativamente il tempo di risposta del programma. In questo modo si riescono ad ottenere prestazioni da 5 a 10 volte migliori.
Quindi: gruppo di attivazione con nome e *LR in *off.
Utilizzo delle /copy
All’inizio del programma si notano delle /copy di membri di CGIDEV2/QRPGLESRC.
Il membro HSPECS contiene delle specifiche H. Il membro HSPECSBND contiene una specifica H essenziale per risolvere i nomi delle procedure
CGIDEV2 sul programma di servizio CGIDEV2/CGISRVPGM2. Il membro PROTOTYPEB contiene i prototipi di tutte le procedure disponibili nel
programma di servizio CGIDEV2/CGISRVPGM2. Il membro USEC definisce la struttura dell’area di errore richiesta dalle API di sistema.
Il caricamento dell’HTML esterno
Il programma inizia con il caricamento in memoria dell’HTML esterno (Subroutine LoadHtml). Questa funzione viene espletata dalla procedura
getHtmlIfsMult. Questa procedura richiede di norma due parametri: il nome dello stream file contenente l’HTML da caricare, ed il delimitatore
sinistro del nome delle sezioni di HTML (che, nel nostro esempio, è <as400> ). La procedura restituisce degli indicatori che dicono se l’operazione
è andata a buon fine ed - in caso negativo - quale sia stata la causa dell’insuccesso.
Ricordiamo ancora una volta che, prima di effettuare la costosa operazione di caricamento, la procedura controlla se il caricamento era stato
precedentemente già effettuato e se lo stream file non è stato variato dall’ultima volta in cui fu caricato dallo stesso programma. Se i controlli sono
entrambi favorevoli, l’operazione di ricaricamento viene ritenuta superflua e questo consente un buon risparmio di tempo e di risorse.
Esecuzione di comandi CL
Una delle procedure più usate di CGIDEV2 è quella che consente la esecuzione di un comando CL. La procedura si chiama DoCmd, ha come
unico parametro il comando CL da eseguire e restituisce un intero con valore 0 se il comando ha avuto successo.
Apertura dei file e “library list”
In genere a questo punto un programma CGI procede alla apertura dei file. Per garantirsi di aprire il file nella libreria appropriata, ci sono due modi:
utilizzare un comando di override oppure far ricorso all’elenco delle librerie del lavoro batch in cui il programma CGI esegue.
Per impostare l’elenco delle librerie si può utilizzare una direttiva Apache dell’istanza http. Questa direttiva ha il formato
SetEnv QIBM_CGI_LIBRARY_LIST "libreria1;libreria2;…;libreriaN"
e va inserita nella definizione del gruppo al quale vuole essere applicata.
Per esempio, per applicarla a tutti i programmi della libreria SUSSIDEV2:
<Directory /QSYS.LIB/SUSSIDEV2.LIB>
SetEnv QIBM_CGI_LIBRARY_LIST "libreria1;libreria2;…;libreriaN"
</Directory>
Ricezione delle variabili di input
Il programma CGI, per sapere che cosa gli viene richiesto, deve ricevere il buffer di input speditogli dal browser client ed analizzare i valori delle
singole variabili di input (Vedi subroutine RcvInput). Il buffer di input viene ricevuto con la procedura ZhbGetInput. Questa procedura richiede
come parametro una variabile carattere in cui restituire la eventuale stringa di parametri (query string) che viene trasmessa quando i usa il metodo
GET, e restituisce in una variabile numerica intera il numero delle variabili ricevute. Dato che il <form> nel nostro HTML, usa il metodo POST, la
stringa dei parametri ricevuti (query string) è vuota. Questo non ha comunque nessuna importanza, in quanto, una volta che il buffer di input sia
stato ricevuto, la ricezione delle singole variabili di input (operazione che si chiama “parsing”) può essere fatta con le procedure ZhbGetVar o
ZhbGetVarUpper.
La procedura ZhbGetVar vuole come parametro il nome della variabile da ricevere (il nome della variabile è il valore del parametro name della tag
<input …>) e restituisce il suo valore. Per esempio, se nel form HTML un campo di input è definito come
<input type=”text” name=”xragsoc” value=”Martelli & Rossi”>
con la istruzione
miaragsoc=ZhbGetVar(‘xragsoc’)
il CGI riceve nella variabile “miaragsoc” il valore ‘Martelli & Rossi’.
La procedura ZhbGetVarUpper funziona nello stesso modo, ma restituisce il valore in lettere maiuscole.
L’emissione di una sezione di HTML
Come vedremo tra poco, il programma sceglierà tra due strade. In entrambi i casi tuttavia, dovrà emettere una stessa parte iniziale di HTML, la
sezione di nome inizio, che non contiene alcuna variabile di output.
Per emettere la sezione, il programma utilizza la procedura WrtSection che ha come unico parametro il nome della sezione da emettere. Con
questa procedura si possono anche emettere più sezioni in una volta sola, esempio:
wrtsection(‘sezione1 sezione2 … sezioneN’) . Prima di emettere una sezione è fondamentale che siano valorizzate tutte le variabili di
output in essa contenute (vedi poco più avanti la procedura di valorizzazione delle variabili di output). La emissione della sezione consiste nel fatto
di scriverla, con le variabili valorizzate, nel buffer di output che sarà trasferito al browser client alla fine del programma.
L’albero delle decisioni
Giunto a questo punto, il programma CGI ha tutte le informazioni per decidere che cosa fare. Ora, non è detto che un programma CGI debba fare
solo una specifica operazione. Naturalmente può farne più di una, per esempio inserire il nominativo di un nuovo cliente, aggiornare un cliente
esistente, fornire una elenco di clienti, e così via. Che si abbia un programma CGI per ciascuna operazione, oppure un solo programma CGI per
tutto un gruppo di operazioni, è una scelta del programmatore e la scelta viene generalmente fatta in base alle capacità ed alle strategie
organizzative dello stesso. Dopo aver scritto innumerevoli programmi CGI, io sono personalmente giunto alla conclusione che sia meglio ridurre il
numero di programmi in circolazione e concentrare in un solo programma più funzioni tra loro congrue.
In questo caso il programma CGI si trova, a seconda della richiesta fatta, a dover prendere una particolare strada.
Per questo motivo la mia strategia è quella di includere nel <form> una variabile di input hidden alla quale viene assegnato un valore diverso a
seconda del tipo di richiesta fatta (nel caso in esempio, la variabile di input hidden si chiama “xrequest”). Il programma CGI esaminerà il valore
ricevuto per tale variabile e determinerà quale percorso compiere.
Venendo ora all’esempio in questione, si possono presentare due situazioni. La prima situazione è quando il programma viene evocato la prima
volta tramite la ULR http://.../sussidev2p/pgm1.pgm . In questo caso il programma deve semplicemente emettere un form vuoto in cui l’utente finale
possa immettere i dati relativi al cliente da registrare. La seconda situazione è quando il programma viene chiamato unitamente ai dati del cliente
da registrare. In questo secondo caso il programma dovrà validare la richiesta (emettendo messaggi di errore se è il caso), registrare il cliente,
rispondere che il cliente è stato registrato e fornire il form per la registrazione di un altro cliente.
Nella prima situazione il programma CGI riceverà un valore blank nella variabile di input “xrequest” (infatti L’URL di cui sopra non trasmette alcun
valore per tale variabile). Nella seconda situazione, il programma CGI troverà nella variabile di input “xrequest” il valore “immissione” trasmessogli
tramite la variabile hidden di nome “xrequest” presente nel form.
L’albero decisionale del programma è dunque una semplice select sul valore della variabile di input “xrequest”.
Nella prima situazione il programma eseguirà la subroutine PrimaVolta. Nella seconda situazione eseguirà la subroutine Immissione.
La valorizzazione delle variabili di output
Prima di emettere una sezione (con la procedura WrtSection) occorre valorizzare tutte le variabili (esempio: /%xragsoc%/ ) di output in essa
contenute. Se la procedura WrtSection scopre che una variabile non è stata valorizzata, le assegna il valore ***missing data*** . La procedura
per la valorizzazione delle variabili di output si chiama UpdHtmlVar e se ne hanno esempi nella subroutine ClearForm, ChkInput e Replica del
nostro programma. La procedura UpdHtmlVar ha due parametri obbligatori:
updhtmlvar(nome_variabile:valore)
• nome_variabile - una costante o una variabile contenente il nome della variabile di output, senza il prefisso /% ed il suffisso /%.
Esempio: ‘xragsoc’ .
• valore – una costante o una variabile contenente il valore (in carattere ) da assegnare alla variabile. Per trasformare variabili
numeriche (es. quantita ) in carattere, si usano solitamente le %BIF (Built-In-Function) %editc , %editw oppure %char. Esempio:
updhtmlvar (‘xquantita’:%editc(quantita:’1’))
La spedizione del buffer di output
Per spedire al browser-client il buffer di output occorre emettere la pseudo-sezione ‘*fini’ (una sezione che di fatto non esiste):
wrtsection(‘*fini’)
Questo va fatto quando tutte le sezioni HTML sono state emesse. Se il buffer di output non viene spedito, non appena il programma termina, il
browser riceve una risposta nulla ed in conseguenza emette un errore di codice 500.
Per la precisione, occorre dire che la tempificazione della spedizione del buffer è nel momento in cui il programma termina. In altre parole, se, dopo
aver emesso la sezione *fini, il programma continua svolgendo altre attività, il browser-client resta in attesa di ricevere la risposta, che sarà
trasmessa solo quando il programma finisce.
La fine del programma
Per avere prestazioni ottimali (ricaricamento dell’HTML esterno solo se necessario), è importante lasciare in *off l’indicatore *LR quando il
programma termina. La questione di chiudere o meno i file prima di terminare il programma, è materia di opinione. Non chiudere i file avvantaggia,
sia pure in modo poco sensibile, il tempo di risposta, ma comporta problemi se si devono fare salvataggi, perché in questo caso i file, per poter
essere salvati, vanno chiusi ed è quindi necessario far terminare la istanza http.
Codici di errore riportati dal browser
Il maggior stress iniziale del programmatore CGI è dovuto alla comparsa, da parte del browser, di codici di errore a lui sconosciuti. Ecco i principali:
• 500 – Il programma CGI ha ricevuto il controllo, ma è terminato senza spedire il buffer di output. I possibili motivi sono (in ordine di
maggior frequenza):
ƒ
Il programma ha incontrato una eccezione ed è andato a fine anormale. Occorre esaminare il joblog del lavoro ed
eventualmente iniziarne il debug.
ƒ
Il programma non è riuscito a caricare l’HTML esterno, pertanto tutte le WrtSection sono fallite ed il buffer è rimasto
vuoto. In questo caso, vedi più avanti Un altro aiuto per il debug.
ƒ
Il programma è andato a fine senza emettere la sezione *fini ed il buffer di output non è stato spedito.
• 404 – La pagina richiesta non è stata trovata. In genere si tratta di un errore di battitura dell’utente nello specificare l’URL richiesta,
oppure la istanza http manca di una direttiva per poter accedere alla pagina.
• 403 – Pagina proibita. La pagina non è accessibile al profilo utente QTMHHTTP.
• 401 – Non autorizzato alla pagina. Era stata richiesta una pagina protetta, all’utente era stato richiesto un nome-utente ed una
password, ma i dati forniti non sono stati ritenuti validi.
Debug di un programma CGI
Un programma CGI è un programma batch e come tale deve essere debug-ato tramite i comandi STRSRVJOB e STRDBG. Per superare alcune
difficoltà ed aver maggior efficienza in questa attività, si veda l’articolo precedente di questa serie.
Un altro aiuto per il debug
Le procedure di CGIDEV2 (per esempio, le procedure GetHtmlIfsMult, WrtSection, UpdHtmlVar), quando incontrano qualche anomalia scrivono dei
messaggi di log nel file fisico *LIBL/CGIDEBUG (dove *libl è la lista delle librerie del lavoro CGI batch). Prima di dar inizio al debug di un
programma CGI può essere conveniente fare un DSPPFM del file CGIDEBUG e controllare i suoi ultimi record.
Esiste anche un comando CGIDEV2/CGIDEBUG con il quale si può registrare sul file CGIDEBUG il buffer di output dei programmi CGI.
Per ulteriori informazioni su questo argomento, si vedano le pagine
http://www.easy400.net/cgidev2o/exhibit8.htm
http://www.easy400.net/cgidev2o/exhibiu5.htm
Nei prossimo articolo illustreremo come creare e leggere “subfile” con i programmi CGI .
Inoltre incomineremo ad immettere un poco di intelligenza Javascript nei nostri HTML esterni.
A questo proposito si consiglia di incominciare a leggere il “tutorial” di Javascript alla pagina
http://www.w3schools.com/js/default.asp .