momente şi schiţe de informatică şi matematică
anti point—and—click

Extinderea funcţionalităţii serverului, prin CGI

CGI | env | factorial | funcții | grafice | telnet
2007 sep

Procedeul mecanic Download—>Setup.exe—>Gata s-a vulgarizat; uneori click-urile nu sunt suficiente… În mesajul următor ar fi vorba de server şi de browser, de protocol de comunicare între server şi browser şi de CGI (mai puţin e vorba, de perl):

Am instalat perl de la http://programmerworld.net/articles/tips/install_apache.php. Deci am instalate Apache, PHP, MySQL şi phpMyADmin din pachetul AppServ; fişierele de tip .php le execut din browser intrând pe http://localhost şi acestea sunt în C:/AppServ/www/file.php. Dar nu ştiu cum să execut fişierele de tip .pl; am încercat să execut din cmd şi a mers, dar nu ştiu cum să execut din browser:

   1    #!/usr/bin/perl
   2    print "content-type: text/html \n\n";
   3    print "Hello, PERL!"; 

Cum fac să văd rezultatul execuţiei acestui cod în browser? (se încheie mesajul)

Cerere şi Răspuns; scriere = comunicare altui program

Serverul aşteaptă cereri (asupra unor resurse disponibile) de la browsere (clienţi), le rezolvă într-un fel sau altul după cum este construit şi returnează un răspuns; cererile şi răspunsurile trebuie să respecte anumite reguli de formulare (un protocol prestabilit). Este evident că programul perl de mai sus are scopul de a afişa "Hello..." şi dacă intenţia ar fi aceea de a-l executa "din cmd", atunci linia 2 se poate elimina (ea ţine de un protocol inutil lui "cmd").

Instrucţiunea 2 nu este destinată afişării propriu-zise, ci serveşte comunicării: procesul care va prelua output-ul programului (urmând eventual să-l transmită drept răspuns la cererea iniţială) este informat asupra faptului că mesajul produs de linia 3 va trebui transmis sub formă de secvenţă de caractere (text/html) şi nicidecum ca IMG (de exemplu); iar destinatarul va putea sesiza şi el natura răspunsului, în scopul de a-l putea interpreta corect. Altfel spus, scriere nu înseamnă neapărat "scriere pe ecran", ci comunicare; informaţiile transmise la STDOUT pot fi preluate de un alt proces sau program de transformare.

Dacă cererea este pentru un fişier, atunci serverul o rezolvă direct: caută într-un anumit director (determinat pe baza instrucţiei pe care o are din fişierele de configurare) acel fişier şi îl transmite browserului, drept răspuns.

În contextul mesajului de mai sus, nu poate fi vorba de cerere pentru un fişier, ci pentru datele returnate (mesajul "Hello, PERL!") de execuţia programului; serverul va căuta (într-un anumit director) programul a cărui execuţie s-a cerut, va lansa programul respectiv în execuţie internă şi va redirecţiona cumva ieşirea programului către browserul care ceruse datele furnizate de program.

Unul dintre mecanismele care permit ca serverul (în loc să răspundă static, cu fişierul cerut) să lanseze în execuţie un program şi să "redirecţioneze" către client datele rezultate, are denumirea CGI (Common Gateway Interface); este vorba de o "interfaţă de programare" (API, Application Programming Interface) prin care se poate extinde funcţionalitatea clasică (cerere şi răspuns pentru fişier) a serverului.

Nu este necesar ca programul să fie în perl, poate să fie cam în orice limbaj (fie ca script, fie ca "executabil"); dar trebuie ca programul să se afle într-un anumit director (despre care serverul are instrucţie prealabilă) şi este necesar ca programul respectiv să-şi formuleze "outputul" respectând protocolul de comunicare asumat între server şi browser (răspunsul propriu-zis trebuie prefixat cu anumite antete informative, precum linia 2 în programul perl citat mai sus). Desigur, în cererea sa browserul trebuie să indice serverului numele programului pe care acesta să-l lanseze şi modalitatea de transmitere a eventualilor parametri.

Exemplu de aplicaţie CGI - mimetex

Uneori avem de redat (într-un document HTML) expresii matematice. mimeTeX este un program CGI care analizează o expresie matematică primită ca parametru şi returnează imaginea GIF corespunzătoare. După ce se descarcă mimetex.zip şi se dezarhivează, se obţine un director mimetex care conţine nişte programe C (excelent documentate!); se compilează aceste programe, de exemplu:

    gcc -DGIF mimetex.c gifsave.c -lm -o mimetex.cgi

obţinând fişierul executabil mimetex.cgi (care leagă şi codul bibliotecii matematice libm, datorită opţiunii "-lm"); README (din directorul mimetex) face precizări suplimentare pentru cazul compilării pe un sistem Windows (caz în care se poate obţine o bibliotecă mimetex Win32 DLL în loc de aplicaţie CGI).

Din linia de comandă, se poate verifica funcţionarea programului - de exemplu:

vb@debian:~/mimetex$  ./mimetex.cgi  a^2+b^2
Ascii dump of bitmap image...
..........................................***...
.........................................*...*..
...........***...........................*....*.
..........*...*..........................*....*.
..........*....*..................**..........*.
..........*....*...................*.........*..
...............*.........*.........*.........*..
..............*..........*........*.........*...
..............*..........*........*........*..*.
..***.*......*...........*........****....*...*.
.*...**.....*..*.........*........*...*..******.
.*...*.....*...*.........*.......**...*.........
*....*....******...************..*....*.........
*....*...................*.......*....*.........
*...**.*.................*.......*...*..........
*...*.*..................*.......*...*..........
.***.**..................*........***...........
.........................*......................
.........................*......................

Funcţia main() este de forma standard:

int main( int argc, char* argv[], char* envp[] ) {  }

Pentru apelul din linia de comandă redat mai sus, envp este NULL (şirul variabilelor de mediu); argc este 2 (numărul de argumente), argv[0] pointează la şirul "mimetex.cgi" (numele programului), iar argv[1] referă şirul "a^2+b^2".
Programul analizează argv[1] şi scrie la ieşirea standard ceea ce se vede (parţial) mai sus.

Dar întrebarea era: Cum fac să văd rezultatul execuţiei acestui cod în browser?. Pentru aceasta, fişierul mimetex.cgi trebuie transferat într-un director cgi-bin, dintre cele pe care serverul le poate recunoaşte ca "director care conţine fişiere executabile". Pentru un server Apache (aici, în versiunea 1.3), în fişierul de configurare httpd.conf (aflat de obicei în /etc/apache/) găsim secvenţa:

# ScriptAlias: This controls which directories contain server scripts.
# ScriptAliases are essentially the same as Aliases, except that
# documents in the realname directory are treated as applications and
# run by the server when requested rather than as documents sent to the client. #...
#
<IfModule mod_alias.c>
    ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/

# "/usr/lib/cgi-bin" could be changed to whatever your ScriptAliased
# CGI directory exists, if you have that configured.

    <Directory /usr/lib/cgi-bin/>
        AllowOverride None
        Options ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all
    </Directory>
</IfModule>

Deci mimetex.cgi trebuie copiat în directorul /usr/lib/cgi-bin/ (dar se poate înlocui în secvenţa de mai sus /usr/lib/cgi-bin/ cu altă cale, eventual din /home), unde el trebuie să fie atributat cu permisiunile necesare (folosind comanda chmod, pentru a seta drepturile de execuţie pentru "owner", "group", "others"). După aceste operaţii, "rezultatul execuţiei acestui cod în browser" se poate obţine folosind bara de adresă, de exemplu:

În acest caz, browserul transmite parametrii prin metoda GET (adăugând la URL şirul ?nume-parametru1=valoare1&nume-param2=valoare2 etc.). Serverul preia parametrii adăugaţi după ?, în variabila de mediu QUERY_STRING şi lansează programul indicat (verificând însă concordanţa cu setările pentru CGI din fişierele de configurare); main() din mimetex.cgi constată acum că nu există parametri argv[1] (din linia de comandă), dar variabila QUERY_STRING este nenulă, astfel că preia de aici parametrii (folosind getenv("QUERY_STRING")).

Programul CGI se poate accesa şi fără a implica un browser; folosind telnet, de pe linia de comandă a shell-ului, avem posibilitatea să vedem şi antetele informative HTTP transmise de către server:

vb@debian:~$ telnet  localhost  80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /cgi-bin/mimetex.cgi?a^2+b^2 HTTP/1.0  
    /* formularea cererii, de pe linia de comandă */

HTTP/1.1 200 OK	 /* header-ul răspunsului de la server */
                 /* '200 OK': documentul cerut s-a găsit şi va fi transmis */
Date: Sat, 03 Nov 2007 00:04:19 GMT	

Server: Apache/1.3.34 (Debian) mod_perl/1.29
Cache-Control: max-age=7200
Content-Length: 248				
Connection: close
Content-Type: image/gif	  /* după antet urmează un rând gol 
                             şi apoi corpul documentului cerut */

GIF87a0���������������nnnbbbFFF!�,0�������( (޷ /* etc. ("image/gif" în modul text...) */
;Connection closed by foreign host.

Apelând astfel mimetex.cgi (cu telnet, din shell), nu obţinem o imagine (cum specifică header-ul Content-type:image/gif), precum în cazul apelării din bara de adresă a browserului. Serverul nu face altceva decât să returneze documentul cerut; browserului îi revine sarcina de a interpreta răspunsul, în scopul formatării HTML şi al prezentării imaginilor (iar shell-ul nu are în mod nativ, aceste posibilităţi de interpretare - încât imaginea primită este afişată ca o secvenţă de caractere "indescifrabile").

Exemplu clasic de program CGI - variabilele env[]

Sistemul (Linux, Windows, etc.) menţine un număr de variabile globale ("environment variables"). Sub Linux, putem apela env pentru a lista variabilele mediului (sau pentru a le modifica, sau pentru a executa un program într-un mediu modificat):

vb@debian:~/lar$  env	
TERM=xterm	  # programul folosit pentru X Window System
SHELL=/bin/bash	  # shell-ul utilizat
OLDPWD=/home/vb	  # vechiul director de lucru
PWD=/home/vb/lar	  # directorul curent
USER=vb	           # HOME=/home/vb etc.
PATH=/home/vb/bin:   # căile pentru fişierele binare (executabile)

Şi shell-ul şi serverul şi programele care se execută pot adăuga alte astfel de variabile; serverul (Apache, de exemplu) comunică cu un program CGI prin intermediul variabilelor de mediu.

Când lansează un program, sistemul depune pe stivă un pointer la numele programului, valoarea argc (cum este denumită de obicei) a numărului de argumente şi apoi un tablou argv[] de pointeri la argumentele cu care este lansat programul, un tablou env[] de pointeri la variabilele de mediu şi desigur, adresa de revenire după încheierea execuţiei programului lansat; sfârşitul unuia sau altuia dintre tablourile de pointeri menţionate este marcat prin anexarea unui pointer NULL. La adresele indicate în argv[] sau în env[], se află şiruri de caractere (cu sfârşitul marcat printr-un octet NULL); în cazul env[], şirul referit are forma "nume=valoare" (incluzând caracterul separator "=").

perl menţine un hash (perechi cheie => valoare) %ENV, pentru variabilele de mediu; programul următor, "test-env.cgi" afişează %ENV:

#!/usr/bin/perl

print "Content-type:text/html\n\n";  # antet HTTP, urmat de două linii albe

print "<table border='1'><tr><th>variabila</th><th>valoare curentă</th></tr>";
foreach $key (keys %ENV) {
   print "<tr><td>$key</td><td>$ENV{$key}</td></tr>";
}
print "</table>";

Bineînţeles, programul trebuie copiat în cgi-bin şi trebuie făcut executabil:

vb@debian:~$ sudo  cp  lar/root/test-env.cgi  /usr/lib/cgi-bin
vb@debian:~$ sudo  chmod  0755  /usr/lib/cgi-bin/test-env.cgi

Apoi se poate cere tabelul respectiv direct din browser: http://localhost/cgi-bin/test-env.cgi.

La fel se poate proceda cu următorul program C, analog celui de mai sus:

/* env.c */
#include <stdio.h>
int main(int argc, char *argv[], char *env[]) { int i = 0;
   fprintf(stdout, "%s", "Content-type:text/html\n\n");  
                         /* antet HTTP, urmat de două linii albe */
   while(env[i]) 
      fprintf(stdout,"%s\n", env[i++]);	  
                            /* scrie şirul de adresă env[i] (apoi i++) */
}

Se compilează "env.c", se verifică din shell şi se copiază executabilul "env" în cgi-bin:

vb@debian:~$  gcc -O2  -o  env  env.c
vb@debian:~$ ./env  /* verifică execuţia */
  Content-type:text/html

  TERM=xterm
#  etc. (celelalte variabile de mediu)
vb@debian:~$ sudo  cp  env  /usr/lib/cgi-bin

Apoi, "env" poate fi apelat din browser: http://localhost/cgi-bin/env (Eventual, în funcţie de configuraţie, "env" trebuie copiat folosind extensia standard ".cgi": sudo cp env /usr/lib/cgi-bin/env.cgi).

Un CGI în limbaj de asamblare (factoriale)

Am intenţionat de la bun început să dăm şi un exemplu de program CGI în limbaj de asamblare; şi anume, vizam iniţial tot variabilele env[]. Între timp însă (cum se întâmplă adesea), am găsit webvars "that prints the environment the same way the Unix env command does", în limbaj de asamblare. Ca urmare, am abandonat ideea; în schimb…

În [1] am realizat un program amifac.s în limbaj de asamblare, pentru calculul factorialului. Ce ar trebui făcut pentru a-l transforma într-un program CGI? Mai întâi (cum am văzut mai sus), trebuie prefixat "output"-ul (afişarea factorialului) cu o secvenţă de emitere la STDOUT a header-ului HTTP "Content-type:text/html\n\n"; în principiu, această modificare este chiar suficientă - rămâne de amplasat executabilul (obţinut după make) în directorul cgi-bin de pe server. Factorialul 10000! se va putea obţine din browser: http://.../cgi-bin/amifac?10000.

Dar publicând astfel programul respectiv, trebuie să avem în vedere situaţia accesării simultane (mai mulţi clienţi pot să lanseze programul din browserele lor, eventual cam în acelaşi timp) şi trebuie să ţinem seama de ceea ce am constatat în [1]: durata obţinerii factorialului în forma binară este mult mai scurtă decât durata convertirii rezultatului binar la forma zecimală. Prin urmare, trebuie adăugată o secvenţă de afişare hexazecimală a rezultatului (care să preceadă secvenţa de conversie la baza 10^9 din amifac.s); eventual, programul ar trebui reformulat puţin, pentru a permite utilizatorului să opteze fie pentru afişarea în hexazecimal, fie pentru afişarea în forma zecimală…

Nu mai avem de-a face cu "cmd", dar atunci şi grijile cresc; odată publicat, programul devine accesibil oricui şi oricând - încât trebuie să avem în vedere (pe lângă situaţia accesării simultane, vizate mai sus) diverse alte aspecte, ţinând de comportamentul specific utilizatorilor.

De exemplu, trebuie prevenite abuzurile (mulţi "utilizatori" de Internet au obiceiul de a tasta cu nepăsare date, fără a manifesta vreun interes de înţelegere pentru programul accesat; ei vor cere de exemplu, factorial de 1, sau de 1111111111111111111111111111, sau poate de -22, sau desigur, factorial de "asdf"). Prin urmare, ar apărea şi necesitatea testării parametrului N, returnând un mesaj de eroare dacă N-ul furnizat în QUERY_STRING nu este un număr natural acceptabil.

Desigur că - în loc de a include direct în amifac.s asemenea testări - este preferabilă o interfaţă HTML care să furnizeze un formular pentru introducerea lui N şi pentru a opta asupra formei de afişare (programul CGI urmând să fie accesat după "submit", numai dacă N este acceptabil); doar că această soluţie nu exclude de la sine posibilitatea abuzului (utilizatorul ar putea ocoli formularul pus la dispoziţie, folosind bara de adresă pentru a accesa "cum îl taie capul" programul respectiv).

Vom adăuga pe aici - cândva - un asemenea formular, prin intermediul căruia se accesează versiunea CGI a programului amifac.s

…iată: factoriale, respectiv se poate accesa direct http://docere.ro/cgi-bin/amifac.cgi?1234

Bibliografie

Hypertext Transfer Protocol (HTTP)The Common Gateway InterfaceCGI Programming FAQApache HTTP Server Version 1.3
 [1] Subrutină performantă pentru calculul factorialului" (oct 2007)

docerpro | Prev | Next