A Perl programozási nyelv

Alkalmazások

Modulok, CPAN

A Perlhez tartozó standard modulokon kívül rengeteg kiegészítés készült a nyelvhez, archiválástól, bonyolult matematikai számításokon át, mp3 fájlok kezeléséig mindenféle feladatra. A CPAN (Comprehensive Perl Archive Network), a kiegészítő modulok központi gyűjteménye, a http://www.cpan.org címen érhető el, de a világon számos helyen tükrözve van.
Hazánkban két helyen a található meg:
http://cpan.artifact.hu/
ftp://cpan.artifact.hu/CPAN/
http://ftp.kfki.hu/packages/perl/CPAN/
ftp://ftp.kfki.hu/pub/packages/perl/CPAN/
Ez a CPAN központi oldala, a modulok közötti keresgélésre a http://search.cpan.org/ szolgál. Itt témakörönként és szerzőnként csoportosítva böngészhetünk a modulok között, de külön megnézhetjük az utolsó néhány napban módosult modulokat is. Fontos, hogy nem minden modul van kész melyekről itt informálódhatunk, de természetesen az egyes moduloknál fel van tüntetve, hogy a fejlesztés milyen fázisban van.
A modulok tar.gz, vagy ritkábban zip formátumban tölthetők le, kitömörítés után fordítani, és installálni kell őket. Ennek menete platformonként kicsit különbözik, a pontos leírás megtalálható a fent említett oldalakon, illetve a modulokhoz tartozó README, és INSTALL fájlokban.

A CPAN modul

Nem mindig szükséges azonban mindezt kézzel elvégezni, ennek automatizálására szolgál a CPAN modul, ami eleve rendelkezésre áll. Csak beírjuk, hogy
perl -MCPAN -e 'install "Games::Crosswords"'
és máris használhatjuk a Games::Crosswords modult, amivel keresztrejtvényeket lehet készíteni. Ez a parancs elvégzi a letöltést, fordítást és installálást is. A modul az első használatakor feltesz jó néhány kérdést, például, hogy melyik CPAN szervert szeretnénk használni. Ezek címei után természetesen nem nekünk kell keresgélnünk, az országunk megadása után felajánlja, hogy az országban jelenleg működő CPAN-tükrök közül válasszunk.

Lássunk néhány érdekesebb, vagy fontosabb modult. A lista távolról sem teljes, csak ízelítőt akar adni a 2003 februári állapotról.

Archiválás, tömörítés

Név Állapot Leírás
Archive::Tar alfa Tar állományok létrehozására, és manipulálására szolgál. Lehet egy archív fájlhoz hozzáadni új fájlokat, törölni belőle régieket, listázni vagy kibontani külön-külön a fájlokat, vagy egyben az egészet. 32 bites Windows környezetben nem működik, de a függvényeit automatikusan helyettesítik az Archive::Tar::Win32 modul függvényei, így ilyen környezetben is használható.
Archive::Zip Kibocsátott Minden amit egy zip fájllal megtehető. Önkicsomagoló állományokat is létre tud hozni, de nem használható gzip fájlokhoz, azokhoz külön modul van. Sok példaprogramot adnak hozzá.
Compress::Bzip2 Kibocsátott BZip2 tömörítés

Matematika

Név Állapot Leírás
Math::BigIntFast Kibocsátott Gyors műveletek tetszőlegesen nagy egész számokon. Hibrid modul, azaz C kódot is tartalmaz a sebesség miatt. (A tisztán Perl változat, azaz a Math::BigInt standard modul)
Math::BigRat Kibocsátott Tetszőlegesen nagy racionális számok, és műveleteik
Math::Matrix alfa Mátrixok, és műveleteik. Alapműveletek, determináns-számítás, lineáris egyenletrendszer megoldása.
Math::Logic Kibocsátott 2, 3 és többértékű logikai függvények
Math::Polynomial Kibocsátott Polinomok, öszeadás, kivonás, szorzás, maradékos osztás
Math::Polynomial::Solve Kibocsátott Legfeljebb negyedfokú polinomok gyökeinek meghatározása
Math::Spline Kibocsátott Négyzetes spline interpoláció
Math::Bezier Kibocsátott Bezier görbék megoldása Robert D. Miller algoritmusával.
Math::FFT alfa Gyors Fourier Transzformáció
Ezek mellett vannak még különféle gyakran használt adatszerkezetek, pl. halmaz, zsák, kupac, fa, gráf.

Grafika

Név Állapot Leírás
GD alfa Interfész a Gd grafikus könyvtárhoz. Jpg, png, xbm, bmp és más formátumú fájlokat tud írni és olvasni. Lehet vele primitíveket rajzolni (pont, vonal, poligon, ellipszis-ív stb), feliratokat tenni a képre, képeket összehasonlítani, egymásra másolni. C kódot is tartalmaz.
GD::Graph Kibocsátott Két dimenziós grafikonokat rajzol a GD modul segítségével. Vonal-, oszlop-, terület-, tortadiagramokat készíthetünk vele. Utóbbiból 3d változatot is tud.
Chart::Graph Kibocsátott Szintén grafikonok készítésére alkalmas, de a kimenete nem egy kép, hanem egy gnuplot, XRT, vagy Xmgrace által értelmezhető fájl. Így a modul használatához ezek valamelyike is szükséges, viszont kihasználhatjuk azok minden funkcióját.
GIMP Kibocsátott Interfész az egyik(?) legnépszerűbb nyílt forrású rajzolóprogramhoz.
Image::Thumbnail Kibocsátott A Gd modult használva előnézeti képek generálásra alkalmas.
Image::WorldMap Kibocsátott Világtérképet rajzol, bejelöli rajta a megadott koordinátájú pontokat, és feliratozza őket, úgy, hogy a címkék lehetőleg ne fedjék egymást.
Természetesen ebben a kategóriában is vannak még más érdekes és/vagy hasznos modulok, pl vonalkód rajzoló, teknőcgrafika, UML szekvenciadiagramok, animált gif manipuláló modulok stb.

Egyéb

Név Állapot Leírás
Convert::Translit 8-bites karakterkonverziók az RFC 1345-nek megfelelően: ASCII, EBCDIC, latin1-2, IBM karakterkódolások, cirill kódolások stb, de csak egyet-egyre átalakításokat végez.
Date::* Naptár átváltások Gregorián, kínai, egyiptomi, etióp, hindu, japán, hobbit stb. naptárak között.
Date::Calc Kibocsátott Gregorián naptárral végezhető rengeteg művelet
AI::* Kibocsátott Mesterséges intelligencia: modulok Fuzzy logikához, genetikus algoritmusokhoz, mesterséges neuronhálókhoz.
Astro::* Asztronómiai számításokat végző modulok. Napkelte, napnyugta, holdfázisok, koordináta transzformációk
GSM::SMS Kibocsátott Sms küldés, és fogadás
LEGO::RCX LEGO-robot vezérlés infraporton keresztül
MP3::* MP3 készítés, MP3-tag-ek lekérdezése, megváltoztatása, playlist generálás
Hook::PrePostCall Ezzel a modullal egy Perl program normális futásába avatkozhatunk bele a kód módosítása nélkül. A modul lehetőséget ad arra, hogy megadjunk kódrészleteket, amik egy bizonyos szubrutin meghívása előtt, illetve lefutása után közvetlenül végrehajtódnak.
Hook::Scope Az előzőhöz hasonló célú modul, ez arra ad lehetőséget, hogy egy névtelen szubrutin akkor fusson le, amikor a vezérlés elhagyja az a blokkot, amiben a szubrutin definiálva lett. A megadott kód akkor is lefut, ha a blokkot egy die() hatására hagyjuk el.

World Wide Web

Talán ez az a terület, ahol a legnagyobb bőségben állnak rendelkezésünkre a modulok, csak az Apache-hoz kapcsolódó modulok száma 200, ezért nem is látom értelmét egyet-egyet kiragadni, inkább csak részterületenként megemlítem a főbb lehetőségeket.
Apache
Ezek a modulok interfészt adnak az Apache API-hoz. Van modul ami a hirdetéseket, bannereket kezeli, egy másik épp ezeket tiltja le, van spam-szűrő, rengeteg féle autentikáció (pl. LDAP, SSL, NIS, IMAP), de vannak egészen egyszerű modulok is, például az Apache::Clickable modul, ami csak annyit csinál, hogy a html dokumentumokban lévő URL-eket, és e-mail címeket automatikusan hiperlinkekké alakítja. Emellett természetesen megtalálhatók az adatbázis kapcsolattal foglalkozó, az üzemeltetést, konfigurálást segítő, és a naplózó modulok is.
HTML
Html sablonok, dinamikusan generált oldalak, formok kitöltése és feldolgozása, menük és keresések eredeményének formázott megjelenítése, html kód ellenőrzése és hibakeresés, html parser... és még sok minden más.
HTTP
HTTP szerver, proxy, eszközök cookie-k kezeléséhez.
WWW
Találhatunk itt robotot, ami egy megadott oldalt letölt, majd a rajta talált linkeken elindulva rekurzívan elkezdi letölteni a netet, de HotMail interfészt, és a népszerűbb keresők használatát lehetővé tevő modulokat is (Google, Yahoo, Lycos, HotBot, Magellan). Kereshetünk az eBay árverési között, sőt még a Google cache-éhez is hozzáférhetünk.

Adatbáziskezelés

Unix alatt, a rendszer által támogatott adatbázis eszköz a DBM, amely alkalmas kulcs-érték párokba szervezett információ tárolására két háttérállomány felhasználásával. Ehhez hasheken keresztül lehet hozzáférni, majd módosítani. Vagyis a hash egy fajta közvetítő az adatbázis rendszer és a programozó között.

DBM megnyitása:
dbmopen(%hash, "dbm", $mode); ahol
%hash: DBM-el összekötendő hash neve
"dbm": az adatbázis neve
$mode: a megnyitandó állomány hozzáférési módja (írás/olvasás/futtatás)

DBM bezárása:
dbmclose(%hash) #elég, ha csak a "közvetítőt" töröljük

DBM módosítása:
1.törlés: delete $emp{id};
2.módosítás: $emp{id} = "Robert"; #simán felülírjuk a régi bejegyzést

Fontos megjegyezni, hogy a DBM-et csak kulcs-érték párok tárolása esetén tudjuk használni. Komplexebb problémáknál (ha relációs adatbázis használata nem lehetséges) közvetlen hozzáférésű állományokat kell használnunk.

Közvetlen hozzáférésű állományok

Fix hosszúságú, közvetlen hozzáférésű állományokat tudunk használni összetettebb adatbázis kezelésére. Adatokat egyforma hosszúságú rekordokban tároljuk. A "seek" paranccsal közvetlenül hozzá tudunk férni a rekordokhoz (nem vagyunk rákényszerítve a szekvenciális olvasásra).

Függvény --------> Szerepe
open ------------->Állomány megnyitása
binmode --------->Állománymutatót bináris módra állítja
seek-------------->A rekordmutató pozicionálása
pack ------------->Adatok méretre szabása íráshoz és olvasáshoz
unpack----------->Adatok feldarabolása alkotó részeire
print-------------->Adatok írása az állományba
read-------------->Adatok olvasása az állományból

Adatbázis-programozás Windows környezetben

Microsoft ActiveX Data Objects (ADO) és az ODBC (Open Database Connectivity) felületeken keresztül létre tudunk hozni a Perlből relációs adatbázist. Az ADO külső adatforrásokhoz való kapcsolódásnál nyújt hozzáférési lehetőséget bizonyos objektumokhoz. Ezek a külső adatforrások lehetnek Access vagy SQL szerverek. (ODBC adatelérési technika a Win32::ABDC modulban van).

ActiveX Data Objects (ADO)

Az ADO egy könnyen használható adatbázis hozzáférési réteget biztosít a felhasználó számára, amelyet a Perl használhat. Az ADO lényegében az ODBC API egy leegyszerűsített változata, amely kevés beállítható tulajdonsággal és az objektumokat vezérlő metódusokkal rendelkezik.

ADO modell

A modell egy hierachikusan felépített rendszer. Ennek külső objektumai a következők:

A második szinten vannak:

Ez alatt már csak egy réteg van, ahol a felette levő rétegben összegyűjtött egyéni objektumok jelennek meg.

Mivel objektumoknak elég sok metódusakkal illetve tulajdonságokkal rendelkeznek, ezért most csak a legfontosabbakat sorolom fel, amelyekre általában szükség van az adatbázis programozásához.

Tulajdonság/Metódus Funkció

Open ------------> Létrehozza a kapcsolatot az adatforrással
Close ------------>Megszűnteti a kapcsolatot az adatforrással
Execute ----------> Egy SQL utasítás végrehajtása
EOF() ----------->Állomány vége jel
MoveNext() ----->Rekordhalmaz következő rekordjára ugrik
Value ------------>Egy mező objektumértékét tárolja

ADO használatához szükséges rendszer beállítások

Első dolgunk az OLE-DB SDK installálása a rendszerünkön. Ezt a https://www.microsoft.com/ado weblapról lehet letölteni. Ennek telepítése után, az ODBC Data Source Administrator-al létrehozunk egy adatforrást, amelyre csatlakozhatunk a későbbiekben. A további lépéseket nem részletezem, mivel ezek adatbázis típusától függnek. Általánosságban a következőket kell tenni:

Adathozzáférés ADO-n keresztül

1.Kapcsolat létrehozása:
Az OLE modul betöltése után a "CreateObject"-el tudunk Automation objetkumot létrehozni.
use OLE;
$cnBiblio = CreateObject "ADODB.Connection";


Ahol "cn" prefix a connection objektumot jelzi és a Biblio a könyvtárra utal. Egy fontos megjegyzés, hogy a CreateObject második argumentuma mindig az ADODB.Connection, ha nem a Perl hibaüzenetet generál.

2.Adatforrás megnyitása:
$cnBiblio -> Open("OLE_BIBLIO");

Ahol az egyetlen paraméterre a korábban létrehozott DSN

3.SQL utasítás megfogalmazása:

$sql = "helyes SQL utasítás ill. sorozat"#módosítás esetén: insert into vagy delete

4.Létrehozunk egy rekordhalmaz objektumot, amiben az SQL lekérdezés eredményét tároljuk

$rsTitles = $cnBiblio ->Execute($sql); #végrehajtjuk $sql-t és eredménye $rsTitlesben lesz

5. Eredményünket a Fields objektum Value tulajdonságán érhetjük el.

$booktitle = $rsTitles -> Fields("cím") ->Value;
$rsTitles -> MoveNext(); #rekordhalmaz következő rekordjára hivatkozunk

6. Csatlakozás bontása:

$rsTitles -> Close();
$cnBiblio -> Close();

Adatbázis alkalmazások

A DBI/DBD keretrendszer – az ODBC- és JDBC- technológiákhoz hasonlóan – adatbázis-kezelő és platform-független módon, egységes programozási felületet biztosít relációs adatbázis-kezelő rendszerekhez.

Az architektúra kétrétegű: a DBI- modulból, és az ún. meghajtó (driver) DBD- modulból épül fel. A DBI- modul definiálja a programozói felületet (API-t), és továbbítja a metódushívásokat az adatbázis-specifikus meghajtónak.

A DBD- modulok (meghajtók) felelősek a DBI- specifikációnak egy adott adatbázisrendszerre való implementálásáért: az adatbázis-kezelő és az alkalmazás (DBI- modul) közötti kommunikációt valósítják meg. A CPAN-ból ingyen letölthető a megfelelő DBD- meghajtó bármelyik, széles körben elterjedt adatbázisrendszerhez (lásd DBD::Oracle, DBD::MySQL stb.).

Kezelőobjektumok

Az adatbázis és a program közötti kommunikáció ún. kezelőobjektumokon keresztül történik, amelyeket típusaik szerint három kategóriába sorolhatunk:

A meghajtó-kezelő objektumok a DBD- modulok betöltésekor jönnek létre. Az adatbáziskapcsolat-kezelő a connect osztálymetódus meghívására keletkezik. Az utasításkezelők az adatbáziskapcsolat-kezelőkből származnak, és az SQL-parancsok végrehajtásáért felelősek.

Műveletek

Az adatbázis-programozás alapvető lépései a következők:

  1. Kapcsolódás az adatbázishoz,

  2. Lekérdezés vagy egyéb SQL-művelet előkészítése,

  3. A művelet végrehajtása,

  4. A lekérdezés esetén a kiválasztott rekordok további feldolgozása,

  5. A kapcsolat bontása, lezárás.

>Kapcsolódás, előkészítés, végrehajtás

>1. Példa

my $dbh = DBI - >connect ('dbi::SQLite:alkalmazott.db', undef, undef) or die DBI - >errstr; $dbh - >do ('CREATE TABLE Alkalmazott ( azonosito INTEGER, nev VARCHAR(75), fizetes INTEGER)') or die $dbh - >errstr; my $sth = $dbh - >prepare ('INSERT INTO Alkalmazott VALUES (?, ?, ?)'); open (F, "data.cvs"); while (<F>) { my @data = split(','); $sth - >execute(@data) or die $sth - >errstr; } close (F);

A példaprogram első lépése: kapcsolódás az „alkalmazott.db” SQLite típusú adatbázishoz. Amennyiben a kapcsolat létrehozása sikertelen volt, a program hibaüzenettel leáll. A hibaüzenetet a DBI--> errstr paranccsal kérdezhetjük le. A kapcsolat létrehozásához szükséges információk a következők:

· az adatforrás „elérési útvonala”, azonosítója,

· felhasználói név,

· >jelszó,

· >egyéb beállítások (lásd később).

A továbbiakban létrehozzuk az „Alkalmazott” táblát a do metódus meghívásával, amelynek egyetlen paramétere maga a táblakészítő SQL- utasítás. A tábla feltöltéséhez a prepar metódussal „előkészítjük” a végrehajtandó SQL-utasítást, amely jelen esetben helyőrzőket (pozicionálás paramétereket, placeholders) tartalmaz. A helyőrzők a végrehajtás során kapnak értéket.

A prepare utasítás lehetővé teszi, hogy az adatbázis kielemezze, majd lefordítsa az SQL utasítást, aminek következtében futási időt lehet megspórolni az utasítás ismételt végrehajtása esetén.

A továbbiakban a „data.csv” állományból beolvassuk az adatokat, a split fügvény segítségével felbontjuk az egyes sorokat a vesszők mentén, az értékeket pedig eltároljuk a @data tömbben.

Az előkészített utasítást az execute metódussal hajtjuk végre, paraméterként átadva a @data tömböt. A helyőrzők fölveszik a @data tömbben tárolt értékeket, majd megtörténik az utasítás tényleges végrehajtása az adatbázis-kezelőben.

2. példa

eval { my $dbh = DBI - >connect ( 'dbi:SQLite:alkalmazott.db', undef, undef, {raiseError => 1}); my $sth = $dbh - >prepare (SELECT azonosito, nev FROM Alkalmazott WHERE fizetes = 50000'); Print "Nincs minimálbérrel rendelkező alkalmazott" If sth - >rows == 0; My @data; While (@dat = $sth - >fetchrow_array() ) { Print $data[0], " ", $data[1], "\n"; } $sth - >finish; $dbh - >disconnect; } if ($@) { print "Hiba történt a művelet végrehajtásakor: ",$@, "\n" };

A második példaprogramban a connect metódust a RaiseError 1-re állításával hívtuk meg, és a kódot standard kivétel-kezelési (eval{} if ($@) {}) blokkokban helyeztük el. A RaiseError bekapcsolásával, bármilyen hiba bekövetkeztekor automatikusan meghívódik a die függvény a megfelelő hibaüzenettel. Ezt követően már nincs szükség a lépésenkénti hibaellenőrzésre, így a kód jelentősen leegyszerűsödik, és áttekinthetővé válik.

A prepare metódus meghívásával előkészítjük a lekérdező utasítást, majd végrehajtjuk az execute meghívásával. Az execute visszatérési értéke – skaláris kontextusban – megegyezik az eredménytábla sorainak, vagyis a kiválasztott sorok számával.

Az eredménytáblát egy while-ciklussal dolgozzuk fel. A sorokat egyenként kérjük le a fetchrow_array metódussal, amelynek visszatérési értéke egy olyan tömb, amelyben az utasításnak megfelelő sorrendben és számban találhatók a feltételnek megfelelő rekordok adatai. Az adatfeldolgozás befejeztével lezárjuk az utasításkezelőt, és bontjuk az adatbázis-kapcsolatot.

Eredménytábla feldolgozása

Az ismertetett fetchrow_array mellett a következő metódusok használhatók adatok beolvasására egy eredménytáblából:

Fetchrow_arrayref:

az eredménytábla egy sorát adja vissza tömbreferencia formájában (ami megegyezik egy fetchrow_array hívás tömbjével),

Fetchrow_hashref:

az aktuális rekordot egy hash-referenciában adja át, amelynek kulcsai megegyeznek az utasításban szereplő oszlopnevekkel,

Fetchall_arrayref:

a teljes eredménytáblát mátrix (tömbreferencia, melynek elemei tömbreferenciák) formában adja vissza.

A bind_col és bind_columns metódusok segítségével előírhatjuk, hogy a lekérdezés eredményei automatikusan egy változóba vagy változókba kerüljenek:

$sth - >bind_columns (\$nev, \$fizetes); #$$sth - >bind_col(1, \$nev); #$$sth - >bind_col(2, \$fizetes); while ($$sth - >fetch) { # fetchrow_arrayref print "$nev $fizetes\n"; }

A $nev és $fizetes változók a cikluson belül fölveszik az eredménytábla első és második oszlopához tartozó soronkénti értékeit.

Tranzakció-kezelés

A tranzakció-kezelés a commit és a rollback metódusokkal valósítható meg:

Eval { # műveletek $dbh - >commit; }; if ($@){ # hiba lépett föl $dbh - >rollback; }

A kapcsolat létrehozásakor az Autocommit paraméter értékének 1-re állításával bekapcsolható az automatikus tranzakció-kezelés: minden sikeres utasítás végrehajtását követően automatikusan lefut a commit parancs.

Metaadatok

A DBI- specifikáció lehetőséget biztosít az adatbázisra, valamint a táblákra vonatkozó metaadatok lekérdezésére. A tables metódus meghívásával megkaphatjuk egy adatbázisban létrehozotttáblák neveit:

@tables = $dbh-->tables;

Az utasításkezelő (azonosító) objektum NAME, TYPE és NUM_OFF_FIELDS attributumai az utasításban szereplő oszlopok neveiről, típusáról és számáról tárolnak információkat:

Eval { My $dbh = DBI - >connect ( "dbh:SQLite:alkalmazott.db"); My @tables = $dbh - >tables; Foreach my $table )@tables) { My $sql ="SELECT * FROM $table"; My $sth = $dbh - >prepare ($sql); For my $i (0 .. $sth - >{NUM_OF_Fields}) { "oszlop neve: " , $sth - >{'NAME'} [$i], " "; "típusa: ", $sth - >{'TYPE'} [$i], "\n"; } } }; if ($@) { print $@, "\n"; }
Adatbázistáblák szerkezetének feltérképezése

Az adatbázisok szerkezetének ismerete fontos szerepet játszhat grafikus kezelőfelületek automatikus kialakításában, vagy minden olyan esetben, amikor szükség van az egyes táblákat alkotó oszlopok pontos nevének, típusának pontos ismeretére.

A DBI-modul legújabb változatai – a fentebb ismertetett metódusokon túl – több metódust is biztosítanak az adatbázisok metaadatainak lekérdezésére, de ezek implementálását az adatbázis-meghajtókra bízzák, így nincs garancia működésükre.

A teljes hordozhatóság érdekében a DBI API-ját megkerülve oldható meg az adatbázistáblák felderítése.

Implementáció

Az adatbázistábla szerkezetének térképezését a DBI::Introspector::Table és DBI::Introspector::Field osztályok végzik, a metaadatokat a DBI::Meta::Table és DBI::Introspector::Table objectumok tárolják.

A DBI::Introspctor::Table table metódusa az alábi algoritmussal dolgozik: végighalad a tábla oszlopain, és a DBI::Introspector::Field közreműködésével begyűjti a metaadatokat:mező neve, típusa, értéktartománya, numerikus típusok esetén a minimum és maximum érték, diszkrét típusoknál az értékek listája (lásd az SQL-kifejezéseket a DBI::Introspector::Field osztály _prep_sql metódusában).

A DBI::Introspector::Table metódusaival megállapítható, hogy létezik-e egy adott nevű tábla az adatbázisban (has_table), hogy a tábla üres vagy sem (is_table_epty), hogy van-e a táblában adott nevű mező/oszlop (has_field).

Az SQL-típusok helyett a mezőknek négy fajtáját különböztetjük meg.

·Egész (’Int’),

·Valós (’REAL’),

·Karakterlánc, max. 40 karakterhosszig (’STRING’)

·Szövegek (40 karakter fölött, ’TEXT’).

Ez a négy típus – a dátum, idő és bináris típusok kivételével lefedi az SQL szabvány típusait. Korlátai és „egyszerűsége” ellenére is elegendő lehet több alkalmazás számára.

DBI::Introspector::Table
package DBI::Introspector::Table; use DBI::Meta::Table; use DBI::Meta::Field; use DBI::Introspector::Field; use Carp; use DBI; use Class::MethodMaker get_set => [qw(dbh)], new_hash_with_init => 'new'; sub init { my ($self, %a) = @_; # check dbh if is-a real dbh, ref(dbh)... #$self->_db_connection(%a); } sub table { my ($self, $table) = @_; my $tbl = new DBI::Meta::Table('name' => $table); my $sth = $self->_select_all($table); for (0 .. $sth->{NUM_OF_FIELDS} - 1) { my $fieldname = $sth->{NAME}[$_]; my $finsp = new DBI::Introspector::Field( 'name' => $fieldname, 'table' => $table, 'dbh' => $self->dbh, 'level' => 'all', ); my $fieldobj = $finsp->field; $tbl->field($fieldobj->name , $fieldobj); } $sth->finish; return $tbl; } sub _select_all { my ($self, $table) = @_; my $sql = "SELECT * FROM $table WHERE 1 = 0"; my $sth = $self->dbh->prepare($sql); $sth->execute; return $sth; } sub has_table { my ($self, $table) = @_; my ($t) = grep { $_ eq $table } $self->dbh->tables; (defined $t) ? 1 : 0; } sub is_table_empty { my ($self, $table) = @_; my $sth = $self->_select_all($table); my ($cnt) = $sth->fetchrow_array; $sth->finish; (defined $cnt && $cnt >= 1) ? 0 : 1; } sub has_field { my ($self, $table, $field) = @_; my $sth = $self->_select_all($table); for (0 .. $sth->{NUM_OF_FIELDS} - 1) { return 1 if $field eq $sth->{NAME}[$_]; } return 0; } sub fields { my ($self, $table) = @_; my $sth = $self->_select_all($table); return @{$sth->{NAME}}; } 1;
DBI::Introspector::Field
package DBI::Introspector::Field; use Tie::IxHash; use Regexp::Common 'number'; use DBI::Meta::Field; use Data::Dumper; use Class::MethodMaker get_set => [qw(dbh name field table count level)], hash => 'sql', new_hash_with_init => 'new'; sub init { my ($self, %a) = @_; $self->_prep_sql; $self->field($self->_find_meta_info); } sub _prep_sql { my ($self) = @_; my ($name, $table) = ($self->name, $self->table); my %sql = ( 'type' => "SELECT $name FROM $table", 'min' => "SELECT MIN($name) FROM $table", 'max' => "SELECT MAX($name) FROM $table", 'distinct' => "SELECT $name FROM (SELECT DISTINCT $name FROM $table)", 'count' => "SELECT COUNT($name) FROM $table", ); $self->sql(%sql); } sub _get_val { my ($self, $cmd) = @_; my $sth = $self->_exec_sql($cmd); my ($val) = $sth->fetchrow_array; $sth->finish; return $val; } sub _get_array { my ($self, $cmd) = @_; my $sth = $self->_exec_sql($cmd); my $arr = $sth->fetchall_arrayref; $sth->finish; my @arr = map {$_->[0]} @$arr; return @arr; } sub _exec_sql { my ($self, $cmd) = @_; my $sql = $self->sql($cmd); my $sth = $self->dbh->prepare($sql); $sth->execute; return $sth; } sub _find_meta_info { my ($self) = @_; $self->count($self->_get_val('count')); my $type = $self->_find_type; my $obj = DBI::Meta::Field->new('name' => $self->name, 'type' => $type); $self->_find_extra_info($type, $obj) if $self->level eq 'all'; return $obj; } sub _find_extra_info { my ($self, $type, $obj) = @_; if ($type eq 'REAL' || $type eq 'INT') { my $min = $self->_get_val('min'); my $max = $self->_get_val('max'); $obj->min($min); $obj->max($max); $obj->range($min, $max); } else { my @distinct = $self->_get_array('distinct'); $obj->range(@distinct); } } sub _find_type { my ($self) = @_; my ($int, $real, $str); my ($cnt, $total) = (0, $self->_set_num_rex); my $sth = $self->_exec_sql('type'); my $strsize; while ($cnt++ < $total ) { my ($c) = $sth->fetchrow_array; if ($c =~ /^$RE{num}{real}$/) { $real++; $int++ if $c =~ /^$RE{num}{int}$/; } else { $strsize += length($c); $str++; } } $sth->finish; $strsize /= $total; $self->_type_rule($real, $int, $str, $strsize); } sub _type_rule { my ($self, $real, $int, $str, $strsize) = @_; my $type; $type = 'REAL' if (($str == 0) && ($real > $int)); $type = 'INT' if (($str == 0) && ($real == $int)); $type = 'STRING' if (($str > 0) && ($strsize < 40)); $type = 'TEXT' if (($str > 0) && ($strsize > 40)); return $type; } sub _set_num_rex { my ($self) = @_; my $total = $self->count; my $num = $total * 0.2; ($num < 1) ? $total : $num; } 1;
DBI::Meta::Table
package DBI::Meta::Table; use Tie::IxHash; use Data::Dumper; use Class::MethodMaker get_set => [qw(name owner qualifier type remarks)], tie_hash => [ '_field' => { tie => 'Tie::IxHash' } ], new_hash_with_init => 'new'; sub init { my ($self, %a) = @_; } sub num_fields { scalar $_[0]->_field_keys; } sub field_names { $_[0]->_field_keys; } sub field { $_[0]->_field($_[1] , $_[2]) if defined $_[2]; $_[0]->_field($_[1]); } sub fields { $_[0]->_field_values; } sub fields_of_type { grep { $_->type eq $_[1] } $_[0]->_field_values; } sub numeric_fields { my @int = $_[0]->fields_of_type('INT'); my @real = $_[0]->fields_of_type('REAL'); my @numeric = (@int, @real); map { $_->name } @numeric; } sub string_fields { my @string = $_[0]->fields_of_type('STRING'); map { $_->name } @string; } sub to_xml { my ($self) = @_; my $cont; my $name = $self->name; my $head = "<table name=\"$name\">"; $cont .= $_->to_xml for ($self->_field_values); my $tail = "\n</table>"; return $head.$cont.$tail; } 1;
DBI::Meta::Field
package DBI::Meta::Field; use Class::MethodMaker get_set => [qw(name type min max)], list => ['range'], new_hash_with_init => 'new'; sub init { my ($self, %a) = @_; } sub to_string { my ($self) = @_; my @range = $self->range; return 'name: '.$self->name."\n". 'type: '.$self->type."\n". 'min: '.$self->min."\n". 'max: '.$self->max."\n". 'range: '."@range\n"; } sub to_xml { my ($self) = @_; my ($xml, $rng, $cont); my @range = $self->range; my $name = $self->name; my $type = $self->type; my $head = "\n\t<field name=\"$name\" type=\"$type\">"; if (defined $self->min) { my ($min, $max) = ($self->min, $self->max); $rng = "\n\t\t<range>\n\t\t\t<min> $min </min>\n\t\t\t<max> $max </max>\n\t\t</range>\n"; } elsif (@range > 0) { $cont .= "\n\t\t\t<item> $_ </item>" for (@range); $rng = "\n\t\t<range>$cont\n\t\t</range>"; } my $tail = "\n\t</field>"; return $head.$rng.$tail; } 1;
A DBI::OpcodeBook modul

A modul segítségével szétválaszthatjuk az SQL-utasításokat és a Perl-kódot. Mivel minden egyes SQL-utasítás megfeleltethető egy eljárásnak/függvénynek, az egyes SQL-lekérdezéseket elnevezzük, és letároljuk egy hash-változóban, a modul pedig szimbólumtábla módosításával a befogadó osztályban szubrutinokat (metódusokat) készít belőlük.

Implementáció

A modul bemenő adatként egy utasításból álló „könyvet” vár. Az egyes utasítások névből és műveletekből (SQL-lekérdezés, törlés, frissítés stb.) állnak. A műveletek típussal rendelkeznek. A típusok a következők:

·Plain:

„egyszerű” SQL, egy SQL-utasítás (például „SELECT * from Documents;”) tárolására szolgál, nem hajtódik végre.

·Sql:

preparált SQL-utasítás, végrehajtása elött paraméter-behelyettesítés történik.

(„SELECT FROM Document WHERE Title = ?”)

·Mxsql:

preparált SQL-utasítás, amelynek a végső formája változók behelyettesítése után áll elő:

SELECT * FROM {$table} WHERE {attrib} = ?”.

Mivel a DBIx::OpcodeBook osztály közvetlenül nem példányosítható, az utasításkönyvet regisztráltatni kell a befogadó osztállyal: a modult elhelyezzük a „befogadó” osztály öröklési hierarchiájában, és az osztály konstruktorában meghívjuk a register_opcodebook metódust.

Az implementáció részletezése elött, egy példán keresztül bemutatjuk a modul használatát.

Package Bank;

# a DBIx::OpcodeBook-ot beillesztjük a hierarchiába use base qw(DBIx::OpcodeBook); # egy osztályattributumban tároljuk az utasításkönyvet my $book = { # utasítás neve: 'ugyfeltabla keszit' # típusa: 'sql' # művelet kódja: 'CREATE….' 'ugyfeltabla_keszit' => { 'sql' => 'CREATE TABLE ugyfel (id NUMBER, nev CHAR(50), cím CHAR(100) )' }, 'szamlatabla_keszit' => { 'sql' => 'CREATE TABLE szamla (id NUMBER, egyenleg FLOAT, tipus NUMBER )' }, 'ugyfelszamlatabla_keszit' => { 'sql' => 'CREATE TABLE Ugyfel_szamla (ugyfel_id NUMBER, szamla_id NUMBER )' }, 'uj_ugyfel' => { 'sql' => 'INSERT INTO Ugyfel (id, nev, cím) values (?, ?, ?)' }, 'aktualis_egyenleg' => { 'sql' => 'SELECT egyenleg FROM Szamla WHERE id= ?' } # további utasítások # elkészítjuk az osztály konstruktorát sub new { my ($class, %p) = @_; my $self ={}; bless $self, $class; $self - >init (%p); return $self; } # az inicializáció során regisztráljuk az utasításkönyvet # az adatbáziskapcsolatot leíró objektumot külső # paraméterként kapjuk sub init { my ($self, %p) = @_; $self - >register_opcodebook( 'book' - > $book, 'dbh' - > $p{'dbh'}, 'createsubs' - > 1, ); } 1; #Példa a bank osztály használatára Use Bank; Use DBI; # adatbázis-kapcsolat létrehozása my $dbh = DBI - >connect('…', '…', '…'); # példányosítás my $bank = new Bank ('dbh' => $dbh); # műveletek kötegelt végrehajtásával elkészítjük az # adatbázis-táblákat, a műveletekből álló mátrix felfogható # egyfajta miniprogramként…. $bank - >exe_ops{ [ ['ugyfeltabla_keszit'], ['szamlatabla_keszit'], ['ugyfel_szamlatabla_keszit'] ] }; # létrehozunk egy új ügyfelet $bank - >uj_ugyfel ( 'dat' =>[11299235, 'Kiss Pista', 'Csökmö, Petőfi u. 12']); # további utasítások # bármelyik utasítás végrehajtható az exec_op metóduson # keresztül is $bank - >exec_op( 'uj_ugyfel', 'dat' => [98922788, 'Nagy Jancsi', 'Ujiraz, Ady u. 18']); my ($egyenleg) = $bank - >exec_op('aktuális egyenleg', 'dat' => [$id]);

A DBIx::OpcodeBook modul, az implementáció szempontjából a következő egységekre tagolható:

  1. modulok betöltése,
  2. a műveletek szétosztásáért felelős diszpécsertábla elkészítése,
  3. lekérdező/beállító metódusok (automatikus) generálása,
  4. inicializáció – utasítások regisztrálásáért felelős metódusok elkészítése,
  5. adatbázis-műveletekért felelős kód,
  6. segédszubrutinok,
  7. az utasításkönyv szerializálásáért felelős metódus.

Az osztály, a modulok betöltésével és verziószám beállításával kezdődik:

Package DBIx::OpcodeBook

use DBI; use Carp qw(croak cluck); use strict; use Data::Dumper; use YAML qw(:all); use Error qw(:try); use Text::Template 'fill_in_string'; our $VERSION = '0.07'

A Text::Template (CPAN) modult, a változók sztring interpolációjának kiváltására használjuk az ’mxsql’ típusú utasítások feldolgozásánál. A modul szubrutinjai közül csak a fill_in_string-et importáljuk saját névterünkbe. A fill_in_string két paraméterű függvény, az első paramétere a sablonszöveg, a második a behelyettesítendő értékeket tároló adatszerkezet:

My tmp1 = 'SELECT * FROM {$TABLE} WHERE {$ATTRIB1}=? AND {$ATTRIB2}=?'; MY $VALUES = { 'TABLE' =>'Documents', 'attrib1' => 'title', 'attrib2' =>'year' };

Az utasítástípusokhoz egy diszpécsertáblát rendelünk, amely nem más, mint egy hash-referencia, amelynek kulcsai a típusnevek, értékei pedig a DBIx::OpcodeBook metódusaira mutató szubrutin-referenciák:

my $DPTBL = { 'sql' => '_exec_op_sql', 'plain' => '_exec_op_plain', 'mxsql' => '_exec_op_mxsql', };

A lekérdező/beállító metódusokat a CLASS::MethodMaker segítségével állítjuk elő:

use Class::MethodMaker get_set => [qw(book mydbh)], boolean => 'checksql', hash => [qw(stype ssql smxsql)];

Az utasításkönyv regisztrálásáért az alábbi kód felelős:

sub register_opcodebook { my ($self, %p) = @_; (defined $p{'dbh'}) ? $self->mydbh($p{'dbh'}) : cluck "No database handle!"; (defined $p{'book'}) ? $self->_set_opbook($p{'book'}) : cluck "No opbook!"; $self->_create_subs if defined $p{'createsubs'}; } sub _create_subs { my ($self) = @_; no strict 'refs'; my $pkg = ref $self; foreach my $cmd (keys %{$self->book}) { my $type = $self->stype($cmd); my $meth = $DPTBL->{$type}; my $code = sub {shift->$meth($cmd, @_)}; *{"$pkg\::$cmd"} = $code; } } sub _set_opbook { my ($self, $b) = @_; $self->_set_book($b); $self->_set_ops; } sub _set_book { my ($self, $b) = @_; my $book; if (ref $b eq 'HASH') { $book = $b; } else { if (defined $b && -f $b) { $book = LoadFile($b); } } $self->book($book); } sub _set_ops { my ($self) = @_; my $book = $self->book; my $dbh = $self->mydbh; while (my ($cmd, $v) = each %{$book}) { # watch out for paranthesis... newbie boy :) my ($type) = keys %$v; # lc($type...) my ($sql) = values %$v; my $op = ($type eq 'sql') ? $dbh->prepare_cached($sql) : $sql; $self->stype($cmd, $type); $self->ssql($cmd, $op); } }

A _set_book metódusnak egyetlen paramétere van, amely kétféle típusú lehet:

Hash-referencia esetén a memóriában levő, az utasításkönyvet tartalmazó adatszerkezet, vagy egy YAML-dokumentumot tároló állomány neve.

Ennek köszönhetően az SQL utasításainkat elhelyezhetjük egy vagy több külső állományban, az egyes SQL-dialektusoknak megfeleleően, és futáskor az adatbázisrendszernek megfelelőt „töltjük be”.

A _set_ops metódus sorban feldolgozza az utasításokat: az ’sql’ típusúakból preparált és tárolt SQL-utasítást készít, a ’mxsql’ és ’plain’ típusúakat változatlan formában hagyva eltárolja egy hash-ben, az utasítás nevét használva kulcsként. Az utasítások típusát és nevét egy másik hash-ben szintén eltároljuk.

A _create_subs az utasítás nevével megegyező metódusokat hoz létre a szimbólumtábla módosításával. A típusnak megfelelő metódushívásokat beburkoljuk egy névtelen szubrutinba, majd bejegyezzük a szimbólumtáblába az utasítás nevének megfelelő kulccsal.

Az SQL-utasítások végrehajtásában a következő metódusok vesznek részt:

sub exec_op { my ($self, $cmd) = @_; my $type = $self->stype($cmd); my $opcmd = $self->can($DPTBL->{$type}); $opcmd->($self, $cmd, @_); } sub exec_ops { my ($self, $ops) = @_; $self->exec_op(@$_) for(@$ops); } sub _exec_op_plain { $_[0]->ssql($_[1]); } sub _exec_op_sql { my ($self, $cmd, %p) = @_; my $op = $self->ssql($cmd); $self->_exec_sql($op, %p); } sub _exec_op_mxsql { my ($self, $cmd, %p) = @_; unless (defined $p{'fill'}) { my $op = $self->smxsql($cmd); return $self->_exec_sql($op, %p); }else { my $tmpl = $self->ssql($cmd); my $sql = fill_in_string($tmpl, HASH => $p{'fill'}); my $op = $self->mydbh->prepare_cached($sql); $self->smxsql($cmd, $op); } } sub _exec_sql { my ($self, $sth, %p) = @_; my %out; # bind columns - very fast ... my $np = $sth->{NUM_OF_PARAMS}; my @param = (defined $p{'dat'}) ? @{$p{'dat'}} : () ; croak "It is 'dat' not '-dat'... :(" if defined $p{'-dat'}; #print $np, "\t", scalar @param, " (NUM_PARAMS)\n"; if (@param == $np) { (@param > 0) ? $sth->execute(@param) : $sth->execute; my $nf = $sth->{NUM_OF_FIELDS}; return if $nf == 0; if (defined $p{'bind'}) { $sth->bind_columns(\(@out{@{$sth->{NAME}}})); return (\%out, sub { $sth->fetch() }); # 'Is DBI OK?' } return $sth if defined $p{'sth'}; return _flat($sth->fetchall_arrayref) if $nf == 1; return $sth->fetchall_hashref($p{'col'}) if $nf>1 && defined $p{'col'}; return $sth->fetchall_arrayref($p{'slice'}) if $nf > 1; } else { croak "Number of parameters don't match"; } }

Az _exec_sql metódusba koncentrálódik az adatbázis-műveletekkel kapcsolatos logika 99%-a. A metódus első lépésben lekérdezi a behelyettesítendő paramétereket (placeholderek) számát, és összehasonlítja a ’dat’ paraméterben átadott tömb hosszával. (Ebben a tömbben adjuk át a behelyettesítendő paramétereket a metódusnak.) Ha a paraméterek száma és a tömbméret eltér, a metódus kivételdobással megszakítja futását. Ily módon megakadályozza, hogy az adatbáziskezelő-rendszer szintjéig terjedjen a hiba, és az olyan utasítást hajtson végre, ami biztosan hibás. Amennyiben az átadott paraméterek száma megfelelő, a korábban előkészített SQL-utasítás végrehajtására kerül sor (execute). Ha a végrehajtott SQL-utasítás egy DSL-művelet, a (NUM_OF_FIELDS) attributum értéke megegyezik az eredménytábla oszlopainak számával, minden más esetben (create, insert) pedig 0 lesz.

Az _exec_sql által visszaadott eredménytábla méretét és a tárolásához használt adatszerkezet típusát a metódusnak átadott paraméterekkel befolyásolhatjuk. A szabályok a következők:

Ha definiálva van egy ’bind’ paraméter, akkor a visszatérési érték egy hash-referencia és egy iterátor lesz. Az iterátorral előre lépve, a hash-referencia felveszi az eredménytábla aktuális sorának értékeit. (A hash-referencia kulcsai az oszlopneveknek felelnek meg.) Ez a leggyorsabb és legalacsonyabb memóriafogyasztással járó módja az eredménytábla bejárásának.

Ha definiálva van egy ’sth’ paraméter (’sth’ => 1), akkor a metódus visszatérési értéke egy lekérdezéskezelő objektum lesz.

Ha a paraméterlista tartalmaz egy ’slice’ nevű paramétert, akkor az eredménytábla bizonyos oszlopait tartalmazó mátrix lesz a visszatérési érték. Ha a ’slice’ paraméter típusa hash-referencia, akkor a hash kulcsaivalmegegyező nevű oszlopok kerülnek bele a mátrixba, tömbreferencia esetén, a tömbben található sorszámok jelölik ki az oszlopokat.

Ha sem az 1. sem a 2. szabály nem teljesül, akkor a teljes eredménytábla lesz a metódus visszatérési értéke.

A segédszubrutinok a következők:

# „SQL-injekciós” támadásokban használt SQL-kifejezések

# detektálására szolgáló szubrutin

sub smells_sql_injection { my ($sql) = @_; # regexps taken from: # Detection of SQL Injection and Cross-site Scripting Attacks # by K. K. Mookhey and Nilesh Burghate, March 17, 2004 # http://www.securityfocus.com/infocus/1768 if ( $sql =~ /\w*((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix or $sql =~ /((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(;))/i or $sql =~ /((\%27)|(\'))union/ix or $sql =~ /exec(\s|\+)+(s|x)p\w+/ix) { return 1; } return 0; } sub _flat { my ($arr) = @_; my @narr = map {$_->[0]} @$arr; return \@narr; } sub serialize_opcodebook { my ($self, $type) = @_; my $book = $self->book; return Dumper $book if ($type eq 'datdump'); return Dump $book if ($type eq 'yaml'); }

CGI programozás Perlben

A Perl egyik leggyakoribb alkalmazási területe a világhálóra szánt CGI­programok írása. A CGI-programok on-line tranzakciók feldolgozását vég­zik, animációkat végeznek, és más dinamikus elemeket visznek az amúgy statikus weboldalakba, egyszerűsítik a rendszeradminisztrációs feladato­kat, és még lehetne sorolni. A CGI-programoknak van legalább egy közös vonása: a módszer, amellyel a kliens webböngészőkkel információt cserél­nek. Ismerve, hogy a CGI-adatcsere nem áll másból, mint szögeges bemenet feldolgozásából és formattált szöveg kimenetre küldéséből (HTML-kód), nem okozhat túl nagy meglepetést, hogy a Perl vált az első számú nyelvvé ehhez a feladathoz.

A Common Gateway Interface (CGI) főbb tulajdonságainak rövid át­tekintésével kezdjük a fejezetet. Elsősorban azt vizsgáljuk meg, hogyan kerülnek átadásra az adatok a webböngészőből a CGI-programba, és milyen elvárásoknak kell megfelelnie a program kimenetének. Ezután meg­nézünk néhány CGI-program példát, amelyek kiválóan demonstrálják a Perl hatékony alkalmazhatóságát a CGI-adatok kezelése terén. Majd be­mutatjuk a Perl CGI-modulját, amely most már része a standard Perl ter­jesztésnek. Használatával elegánsabbá és egyszerűbbé válik a CGI-programok írása. A fejezet végén a CGI-programozás biztonságossági vonatkozá­sáról ejtünk néhány szót.

A következő részek feltételezik a hálózati dokumentumok készítésére használt HTML-nyelv alkalmazásszintű ismeretét. Ha az olvasó eljutott arra a szintre, hogy a CGI-programozást szeretné tanulmányozni, akkor már bizonyára volt dolga a HTML-lel. Amennyiben soha nem volt alkalma HTML-kód készítésére vagy olvasására, akkor javaslom, hogy vegyen a ke­zébe egy ezzel a témával foglalkozó könyvet, mielőtt belevetné magát a CGI­programozás rejtelmeibe.

A CGI rövid áttekintése

Még itt az elején felhívjuk a figyelmet, hogy ennek a fejezetrésznek nem célja a CGI teljes körű tárgyalása, csak arra vállalkozik, hogy a CGI főbb tulajdon­ságait megvilágítsa, a súlypontot a fejezetben alkalmazásra kerülő lehetősé­gekre helyezve. A fejezet feldolgozásával képesek leszünk egyedül átgondolni azokat a megoldásokat, amelyekkel a különböző formátumú bemeneti ada­tokat kezelni tudjuk a Perl használatával. Éppen ezért az ismertetést szándé­kosan fogtuk ilyen rövidre. A következő alfejezetben láthatunk majd példákat.

A Common Gateway Interface vagy CGI, egy módszert definiál, amellyel adatokat cserélhetünk a webkliensek (böngészők) és a webszerveren futó programok, az ún. CGI-programok között. A CGI-programok szerepe a sta­tikus HTML-oldalakban rejlő lehetőségek kiterjesztése. Ez történhet kicsi­ben - például animációk hozzáadásával vagy dokumentumainkat intelli­genssé tevő dinamikus oldalgenerálással - valamint lehet olyan jelentős mértékű, mint egy felület létrehozása más nem webalapú, a host gépen működő szolgáltatás számára. Az tény, hogy a CGI-programok a szerveren futnak és nem a kliensen, megkülönbözteti őket más webprogramoktól (mint pl. Java appletek) és hatékonyabb eszközzé teszi őket olyan felada­tokra, amelyek megkövetelik a kölcsönhatást a szerverrel.

Adatok átadása a CGI-programnak

A CGI működésének alapja az, hogy a webböngésző bemeneti adatokat fogad el egy HTML dokumentumtól (általában egy munkalaptól), vagy egy URL-hez rendelt speciális szövegtől. A böngésző az adatokat egy sztringbe kódolja és elküldi ezt az adatsztringet ("query string") a webszervernek, amely ennek hatására lefuttatja a CGI-programot. A szerver a query strin­get elérhetővé teszi a CGI-program számára. Az adatok átadása a szerveren kétféleképpen történhet attól függően, hogy a böngésző hogyan hívja meg a CGI-programot. A CGI-program élvégzi a feladatát és kimenetként HTML­kódot szolgáltat, amit a szerver visszajuttat a böngészőhöz.

A további tárgyalás érdekében egy egyszerű HTML-form meglétét fel­tételezzük, amely két adatot kér be: a felhasználó bejelentkezési nevét és a tel­jes nevét, majd ezeket az add name.cgi nevű programnak adja át. A HTML­kód számunkra lényeges része valami ilyesmi lehet:

Bejelentkezési név: Teljes nev:

Miután a böngésző előtt ülő személy begépeli a kívánt adatokat az űr­lapon és rákattintott a Submit gombra, a böngésző becsomagolja az adato­kat egy query stringbe és elküldi a szervernek. Általában az adatsztring kulcs=érték séma szerinti párok sorozatából van elkészítve. Az egymás utáni adatpórok & jellel vannak elválasztva egymástól. A mi konkrét pél­dánkban két névvel ellátott adatelem szerepel a "login" és a "name". Mi­előtt a böngésző elküldené az adatokat a szervernek, el kell végeznie bizo­nyos karakterek kódolását, nehogy a webszerver félreértelmezze őket.

HTML kódolás

Annak ellenére, hogy a CGI-bemenet mindig szöveges, bizonyos karakterek­hez speciális jelentéssel bírhat attól függően, hogy a kliens milyen módon adja át az információt a szervernek. Ezek közé a karakterek közé tartoznak azok is, amelyeknek a HTML-kódolásban van sajátos jelentésük, úgymint a szó­közök, perjelek (/) és sokan mások. Ezek mind veszélyt jelenthetnek, mert nem tudni, hogy milyen jelentést tulajdonít nekik a szerver parancsértelme­zője. Megakadályozandó az esetleges félreértelmezését a szerveroldalon mind a parancs-shell, mind pedig a CGI-program által, a böngésző az alábbi módon kódolja a karaktereket: minden problematikus karaktert helyettesít egy száza­lékjel prefixszel és egy hexadecimális számmal, amely a karakter helyét jelöli ki az ASCII karakterhalmazban. Például a kérdőjel (?) ASCII kódja Ox3f (deci­mális 63). Így HTML kódolt formában a kérdőjelnek a %3F felel meg. Egy kivétel az általános kódolási szabály alól: a szóközök pluszjelként is írhatók.

Ha példa formunkban a felhasználó "tomo"-t ad meg bejelentkezési név­ként, és "Tom O'Brien"-t teljes névként, a böngésző a következő adatsztringet generálja:

Login=tomo&name=Tom+O%27Brian

Vegyük észre, hogy a szóköz és az aposztróf is kódolva szerepel. A CGI­program feladata ezen karakterek kibogozása; a webszerver ezt nem csi­nálja meg helyettünk. A kódolandó karakterek listáját bármilyen HTML referenciából megtudhatjuk. Fontos azonban tudnunk, hogy bármilyen ASCII-karakter kódolható ily módon, még a normális alfabetikus karak­terek is. Ennek megfelelően alapkövetelmény, hogy a CGI-programunk képes legyen bármilyen kódolt adatot felismerni.

Az, hogy a kódolt adatsztring a két lehetséges módszer közül melyikkel kerül átadásra, a HTML-form kulcsszavából derül ki. A két lehetőség a get és a post. A webszerver automatikusan beállítja a request method rendszer­változót ezen értékek egyikére, amelyből a CGI-program megtudhatja a meghívás módját.

A get-tel átadott adatsztring

A Get az alapértelmezett átadási mód, amellyel form adatokat küldhetünk a CGI-programoknak. Ennél a módszernél az adatsztring CGI URL hivat­kozásához fűződik hozzá, egy kérdőjel közbeiktatásával. Az előző példá­ban, mivel nem jelöltünk meg semmilyen módszert a form kulcsszóban, a get a feltételezett rnód. A szervernek átadott URL így fog kinézni:

http: //server.company.com/cgiin/add_name.pl?login=tomo&name=Tom+O % 27Brien .

A webszerver az input adatokat a query_string rendszerváltozón keresz­tül teszi elérhetővé a CGI-program számára. Ha a CGI-program a FREF kulcsszóból lett meghíva (és nem a HTML FORM-ból), vagy a felhasználó maga adja meg a program URL-jét a böngészőben, az adatsztringet explicit módon "hozzá kell ragasztanunk" az URL végéhez. Ebben az esetben a fel­használónak vagy a HTML programozónak kell gondoskodnia az adat­sztring helyes kódolásáról.

A POST-tal átadott adatsztring

Ha a CGI-program a post módszerrel lett meghíva a formból, akkor a query string a program szabványos bemenetén fog megjelenni. Továbbá a beme­

neti (kódolt) adatsztring hossza (karaktereinek száma) a content length r rendszerváltozóba másolódik. A post módszer használatához a példa for­

munk FORM kulcsszavát a következőképpen kell módosítanunk:

="/cgi-bin/add name.pl" METHOD=POST>

Extra elérési út információk használata CGI-programokkal

A CGI-programoknak történő adatátadás egy másik alkalmazható mód­szere az, amikor kiegészítő elérési út információkat fűzünk az CGI URL­jéhez. Ez a módszer a webszerver azon tulajdonságára támaszkodik, hogy képes felismerni a tényleges elérési út végét, így biztosítva a CGI-program végrehajtását. A többletinformáció a path_data környezeti változón keresz­tül válik elérhetővé a CGI-program számára. Például az add name.cgi szkriptet meghívhatjuk a következő URL-lel:

http://server.company.com/ cgi-bin/add_name.cgi/update?login=tomo&name=Tom+O%27Brien.

Ebben az esetben a webszerver lefuttatja az add name.cgi-t, miközben a path_data környezeti változó a "/update" értéket tartalmazza, a queri_string változó pedig a kódolt adatsztringet, mint korábban.

CGI környezeti változók

Mint azt tapasztalhattuk, a webszerver bizonyos információkat környezeti változókon keresztül juttat el a CGI-programhoz. Láthattuk, ahogyan a szer­ver az átadási móddal, a request sztringgel és sztring hosszával bánik. Valójában a szerver ennél jóval több információt raktároz a környezeti változókban: a kliens böngészővel kapcsolatos adatokat, a kliens gép címét és néhány részletet magáról a szerverről. A következő fejezetrészben egy olyan CGI-programot mutatunk be, amelyen keresztül a környezeti válto­zók teljes készletével megismerkedhetünk.

HTML-adatok generálása a kimeneten

A végső mozzanatot egy CGI-programban a kliens böngészőn megjelení­tendő HTML-kód generálása jelenti. Ami magát a CGI-programot illeti, nincs nehéz dolga: csupán szöveget kell kiküldenie a szabványos kimenetre; a webszerver feladata a HTML-kód eljuttatása a böngészőhöz. Idézzük fel, hogy egy HTML-dokumentum egy fejrészből (header), és egy törzsből (body) áll, a kettőt egy üres sor választja el egymástól. Amikor statikus HTML-kódot készítünk, csak a törzsrésszel kell foglalkoznunk, a fejinfor­mációt a szerver küldi meg, amikor a dokumentumot elküldi a böngésző­nek. Egy CGI-programnak viszont kötelessége fejinformációval is ellátni a kódot, amely általában legalább egy "Content-type" sor hozzáadását jelenti. A szerver ezt szükség esetén saját információkkal egészíti ki.

Fejlécek kezelése

A HTTP fejlécek kezelése két esetben érdekes. Az egyik a HTTP kérés fejlécei, míg a másik a HTTP válasz fejlécei. A HTTP kérés fejléceihez a CGI program direkt módon nem fér hozzá, azokat a Webszerver nem adja át. Ez talán érthető is, ha arra gondolunk, hogy a HTTP fejlécben szerepelhetnek olyan információk, mint a felhasználó jelszava, amelyet a Webszerver esetleg maga értelmez, és ellenőriz, és semmi dolga vele magának az alkalmazásnak. Azokat az információkat, amelyeket a böngésző a http fejlécben küld el a webszervernek, és amelyekre a CGI programnak szüksége lehet a webszerver a CGI processz környezeti változóiban helyezi el. Alaphelyzetben a CGI programnak nem kell semmilyen fejlécet előállítania. A minimálisan szükséges fejléceket a html szöveg elé odateszi a webszerver, elegendő, ha a CGI program magát a HTML szöveget visszaküldi. Megtehetjük, hogy a teljes általunk küldendő fejlécet a program a HTML kód előtt kiírja, pontosan úgy, ahogy annak a http válaszban szerepelnie kell. Létezik a fejlécekkel kapcsolatban az NPH fogalma is, amely a No Parsed Header angol kifejezés rövidítése, amely azt jelenti, hogy a webszerver a CGI alkalmazás által visszaküldött http választ teljesnek tekinti, nem vizsgálja, hogy van-e benne Status vagy bármilyen más olyan fejléc, amellyel kezdeni tud valamit, nem tesz a CGI program által generált fejlácek elé, vagy mögé semmit, hanem leküldi egy az egyben a böngészőknek. Az nph scriptek írására a CGI szabvány azt mondja, hogy ezek a futtatandó fájlok nph- karakterekkel kell, hogy kezdődjenek. Térjünk tehát rá arra, hogy milyen fejléceket érdemes a programnak a HTML szöveg előtt küldenie. Nem fogunk minden fejlécet áttekinteni, csak a legfontosabbakat.

Content-type

A legfontosabb, és a Status után az első amit minden programnak küldenie kell, az a Content-type fejlécsor, amelyben a böngészővel közöljük, hogy a küldendő információ, amelyik a fejlécsorok után jön, milyen MIME típusú. Ez általában text/html de ha nem HTML szöveget küldünk vissza, csak sima szöveget akkor text/plain, de előfordulhat, hogy egy bináris állományt küldünk vissza, ami mondjuk egy GIF kép, akkor image/gif az érték, így a programsor, amelyik ezt az információs mint fejlécsort kinyomtatja:

print "Content-type: text/html\n";

vagy

print "Content-type: text/plain\n";

vagy

print "Content-type: image/gif\n";

A következő példaprogram CGI scripten keresztül küld a felhasználónak egy GIF képet:

#!/usr/bin/perl require 'state.pl'; print_http_state('200 OK'); print <<END; Content-type: image/gif END open(F,"girl.gif"); binmode F; binmode STDOUT; while( read(F,$buffer,1024) ){ print $buffer; } close F;

Location

Ez egy olyan fejléc, amellyel azt mondhatjuk aböngészőnek, hogy az információ, amit ő kér nem is ott van, ahol ő keresi, hanem valahol máshol. Ilyenkor általában a Status sorban sem a 200 OK üzenetet adja vissza a program, hanem valamilyen más hibakódot, általában a 301 Moved Permanently értéket. Ez azt jelenti a böngészőnek, hogy a kért információ nem ott található, ahol ő keresi, hanem valahol máshol, nevezetesen a Location fejlécsorban megadott helyen. Egy egyszerű példaként álljon itt egy kis script, amelyik a felhasználót az AltaVizsla keresőhöz küldi:

#!/usr/bin/perl require 'state.pl'; print_http_state('301 Permanently Moved'); print <<END; Location: http://altavizsla.matav.hu END

Mire jó ez? Nos a leggyakrabban a hirdetési bannereket megjelenítő programok használják ezt a technikát. Amikor megjelenik egy hirdetési kép, és valaki rákattint, akkor nem lehet azonnal a hirdető Web oldalaira küldeni, mert előtte a hirdetést megjelenítő szolgáltató még rögzíteni akarja egy naplófájlba, hogy egy rákattintás történt, és csak ezután küldi el a felhasználót a keresett oldalra. Például az Internetto is ezt a technikát használja. Ha valaki Apache webszervert használ, és valamilyen oknál fogva egy weboldalba a képeket CGI scripten keresztül akarja küldeni, például azért, mert ellenőrizni akarja, hogy aki le akarja tölteni az oldalt az kifzette-e a havi díjat és így jogosult a képek megnézésére, akkor is használhatja ezt a technikát. Sokkal hatékonyabb lesz, mint beolvasni az egész fájlt, majd a CGI processzen keresztül elküldeni a webszervernek, amelyik azt továbbküldi. Az Apache van annyira intelligens, hogy észreveszi, hogy a cgi script az elérendő információ helyeként egy olyan URL-t adott meg, amelyet szintén ő kezel, és ahelyett, hogy a hibakódot leküldené a böngészőnek, rögtön az új URL-en levő információt küldi le. Ami persze lehet kép, szöveg, hang vagy akár egy újabb CGI script, vagy valamilyen más információ, amelyet például egy Apache modul kezel. Ha pedig nem fizetett, akkor a visszatérési státusz kód lehet 402.

Content-Length

Ebben a fejléc mezőben adhatjuk meg, hogy milyen hosszú a visszaküldött HTML oldal, vagy egyéb információ. Ha ezt nem teszzük meg, akkor ezt vagy megteszi helyettünk a webszerver vagy nem. Mindenesetre ha megtesszük, akkor sokkal nagyobb biztonságban érezhetjük magunkat afelől, hogy a böngésző rendben megjeleníti a leküldött információt. Ha nincs megadva a tartalom hossza, akkor a böngésző nem lehet egészen biztos abban, hogy az egész tartalmat megkapta-e. Ha viszont megadjuk, akkor a böngésző azt figyelembe veszi, és például az előző példát módosítva:
#!/usr/bin/perl require 'state.pl'; print_http_state('200 OK'); print <<END; Content-type: image/gif Content-Length: 1200 END open(F,"girl.gif"); binmode F; binmode STDOUT; while( read(F,$buffer,1024) ){ print $buffer; } close F;

Állapotkezelés (session)

Az egyes munkamenetek elkülönítése, az állapotok tárolása (session sate management) Session objektumokkal. A session objektumot a szerver tárolja, az adott felhasználóhoz kötődik, egyedi azonosító szükséges a URL-hez illesztve, így az a programozó adatokat tárolhat benne.

use CGI::Session; # CGI/Session.pm #Új munkamenet kezdés: $ses=new CGI::Session("driver:file",undef, {Directory=>"/tmp"}); #Létező munkamenet inicializálás: $ses=new CGI::Session(undef,$cgi, {Directory=>"/tmp"}); #Munkamenet zárás #Nem feltétlenül kell explicit zárni #Ha mégis: undef($ses); #Adat mentése: $ses->param("10230", "Kutya"); $ses->param("Béla",1); #Adat kiolvasása: $x=$ses->param("Kutya"); $y=$ses->param("10230") || ""; #Adat törlése a munkamenetből: $ses->clear(["kutya", "Béle"]; #Egész munkamenet törlés, /tmp-ből $ses->delete(); #Érvényességi idő: $ses->expire(1800); #1800 másodperc $ses->expire(‘+5m'); # +5 perc, stb

A CGI-programozás alapjai Perlben

Elérkeztünk a fejezet központi témájához - nevezetesen, annak bemuta­tásához, hogy mitől is olyan alkalmas a Perl a CGI-programozásra. Itt most nem arra helyezzük a hangsúlyt, hogy demonstráljuk, mire képes a CGI, hanem inkább arra, hogy rámutassunk, hogyan lehet a Perl segítségünkre ezen a téren. Már tapasztalhattuk, hogy érdemes a Perlt választani álta­lános rendszerprogramozási feladatokhoz, ebben a fejezetrészben a Periben rejtőző újabb lehetőségekre derül fény, amely megmagyarázza, hogy a Perl miért olyan népszerű a CGI-programozásban.

Néhány alapvető példa

Kezdjük vizsgálódásainkat egy nagyon egyszerű Periben írt CGI-szkripttel. Ez a példaprogram nem fogad el bemenetet a böngészőtől. Meghíváskor nem csinál mást, mint meghívja a szerver who parancsát és a kimenetet elküldi a böngészőnek HTML formátumú dokumentumként. (A who parancs a bejelentkezett felhasználókat listázza.)

# #$who.cgi: felsorolja, ki van bejelenkezve a szerverre # print "Content-type: text/html\n\n"; my $who_cmd = '/bin/who'; my $who = ~$who cmd'; print < EOM; <HTML> <HEAD> <TITLE>Ki van a szerveren</TITLE> </HEAD> <BODY> <H1>Ki van a szerveren?</H1> <PRE> <TT> $who </TT> </PRE> </BODY> </HTML> EOM exit;

A program egy Content-type" sor generálásával kezdődik, amelyet egy üres sor választ el a dokumentum törzsrészétől. Ezek együtt egy részleges fejet alkotnak, amelyet a szerver kiegészít, még mielőtt a dokumentumot elküldené a böngészőnek. A kód nagy részét a HTML-kód generálása teszi ki. Erre a célra a helyben tárolt dokumentumos (here) megoldást használjuk. Egy kis időt nyertünk azáltal, hogy a who parancs kimenete előre van formattálva (a <PRE> </PRE> kulcsszavakkal), és a backtick operátor kimenetét egy skalár változóban tároljuk. A program által generált minta­kimenetet egy másik példa kapcsán közöljük a 8.3. ábrán.

Íme egy másik programocska, amely a CGI-program környezet változóit jeleníti meg:

# #showenv.cgi: Az összes környezeti változó megjelenítése # print "Content-type: text/html\n\n"; print < EOM; <HTML> <HEAD> <TITLE>A CGI környezet</TITLE> </HEAD> <BODY> <H1>A CGI környezet</H1> <TABLE> EOM for $key (sort keys %ENV) { print "<TR> <TH ALIGN=RIGHT > $key <TD > $ENV{$key} </TR > \n"; } print < EOM; </TABLE> </BODY> </HTML> EOM exit;

Tanulságos lehet a programot több különböző módon meghívni, hogy lássuk, hogyan kerülnek az adatok átadásra a CGI-programnak a kör­nyezeti változókon keresztül. Például próbáljuk meg a programot a "-showenv.cgi?up&down=down" hivatkozással elindítani vagy írjunk egy egyszerűbb formot, amely a POST-tal küldi el az adatokat. A CGI-program meghívása az alábbi módon történik:

/cgi-bin/showenv.cgi/extra/path/info?name=this+tune

Vegyük sorra a fontosabb kulcs-értél párokat:

SERVER_SOFTWARE A szerver-program neve, és verziója.
GATEWAY_INTERFACE A gateway (a webszerver és a program közötti felület) neve és verziója, általában "CGI/1.1".
DOCUMENT_ROOT Az a könyvtár, amit a kliens a gyökérkönyvtárnak lát.
REMOTE_ADDR A kliens, vagy az általa használt proxy szerver IP címe.
REMOTE_HOST A kliens host neve, általában nem áll rendelkezésre (a szerver nem határozza meg, vagy egyáltalán nincs).
SERVER_PROTOCOL A használt protokoll, és annak verziója.
REQUEST_METHOD A HTTP kérés típusa (ld. később).
QUERY_STRING GET típusú hívás esetén ebből nyerhetők ki az adatok.
CONTENT_LENGTH POST típusú hívás esetén az elküldött bájtok száma (ld. később).
HTTP_USER_AGENT A kliens böngészőprogramjának a neve és verziója, általában az operációs rendszerrel kiegészítve.
HTTP_ACCEPT A kliens által elfogadott mime típusok listája, általában szerepel benne a */*, mivel így a szerver minden típust elküld, és a böngésző dönti el, hogy mit kezd vele.
HTTP_ACCEPT_LANGUAGE Azokat a nyelveket találjuk itt, amelyeket a böngésző tulajdonosa beállított, hogy elfogad, el tud olvasni.
SCRIPT_NAME Programunk virtuális helye (azért virtuális, mert itt az a könyvtár számít a gyökérkönyvtárnak, amelyet a kliens is annak lát).
SCRIPT_FILENAME Programunk helye a szerveren (a valódi gyökérkönyvtárból számolva)
SERVER_NAME A szerver neve, vagy IP címe.
REQUEST_URI Ezt az URI-t kérte a kliens.
SERVER_PORT A port száma, ahol a kérés érkezett.
HTTP_HOST A szerver host neve.
PATH_INFO A CGI program virtuális könyvtárként is használható, ebben a változóban a programnév utáni, további alkönyvtárak találhatók.
PATH_TRANSLATED Az előző teljes változata.
CONTENT_TYPE A kéréshez csatolt információ mime típusa (ld. később)

A SCRIPT_NAME, és a SCRIPT_FILENAME alapján megkülönböztetünk kétféle elérési utat, egy valódit, és egy virtuálisat. Tudni kell, hogy HTTP-n keresztül nem látható a szerver háttértárának a teljes tartalma. Amit a kliens gyökérkönyvtárnak lát, az nem a szerver gyökérkönyvtára, csak egy, a szerver program konfigurálásánál megadott mappa. Az alábbi program a környezeti változókat felhasználva meghatározza valódi, és virtuális helyét a szerveren (utóbbi elé a szerver host-ját is kiírja, így kapjuk meg a teljes url-t):

Bemeneti adatok értelmezése

Most, hogy az olvasó túl van a CGI áttekintésén és fel van vértezve progra­mozói szintű Perl ismeretekkel, bizonyára megfogalmazódott a fejében egy Perl program, amely egy CGI-bemenetet dolgoz fel. Mivel a bemeneti CGI­adatok értelmezése a legtöbb CGI-programban felmerül, érdemes kifejlesz­tenünk néhány újra hasznosítható függvényt ehhez a feladathoz. Alább két rutint közlünk, amelyekkel CGI paraméterek nyerhetők ki egy alkalmas Perl szerkezetbe, függetlenül a meghívás módjától.

sub getQuery { my $query = undef; if ($ENV{'REQUEST METHOD'} eq 'GET'){ $query = ENV{'QUERY_STRING'}; elsif ($ENV{'REQUEST METHOD'} eq 'POST'){ read(STDIN, $query, $ENV{'CONTENT_LENGTH;}); } $query; } sub parseQuery { my $query = shift; my (%input, @elements, $element, $key, $value); @elements = split /&/, $query; for $element (@elements){ $element = ~tr/+/ /; #A + átalakítása szóközzé ($key, $value) = split /_/, $element; $key = - s/~([\dA-Fa-f]{2})/pack("C", hex($1))/ge; #kulcs dekódolása $value = - s/%([\dA-Fa-f]{2})/pack("C", hex($1))/ge; #érték dekódolása if (defined $input{$key}){ $input{$key} .="\0$value"; } else { $input{$key} = $value; } \%input; } }

A split függvény oldja meg a különböző adatpór elemek kinyerését a query sztringből. A kulcsokat és az értékeket egy ötletes reguláris ki­fejezés dekódolja, amely százalékjel után álló kétjegyű hexadecimális szá­mokat keres. Ha ilyet talál, akkor a hex függvény segítségével átalakítja decimálissá, a konvertálás rögtön a pack argumentumában történik. A pack függvény kimenete végül helyettesíti a sztring kódolt karakterét. Az eszkö­zök, amelyekkel a Perl a kódolt adatsztringet kezeli, azok közül is leg­főképpen a bármely HTML-kódolt karakter visszaállítását végző, elegáns reguláris kifejezés, egy a sok ok közül, amely a Perl népszerűségét magya­rázza CGI-programozás terén.

A parseQuery függvény egy hash-ra mutató referenciát ad vissza. A hash tartalmazza a query sztring kulcs/érték párjait. Figyelem, a rutin egy kulcshoz rendelt több értéket is kezel, az értékeket a nullkarakter (\0) választja el egymástól. Az alább közölt egyszerű program a fenti ruti­nokat használja. A példa nem csinál mást, mint egy szépen formatált listába szedi a query stringbe csomagolt adatokat. Ez hasznos lehet HTML-formok teszteléséhez, később pedig egy másik példában vesszük hasznát:

# testparse.CGI: Megjelenti az átadott CGI adatokat my $query = getQuery(); my $input = parseQuery($query); # a $input egy referencia egy hash-ra print "Content-type: text/html\n\n"; print < EOF; <HTML> <TITLE>Query Példa</TITLE> </HEAD> <BODY> <CENTER> <H3>A CGI programnak átadott kulcs/érték párok:</H3> <TABLE> <TR><TH ALIGN=RIGHT>Kulcs<TD ALIGN = LEFT ><Érték(ek)<\TR> EOF for $key (sort keys %{$input}){ print "<TR><TD ALIGN=RIGHT > $key<TD ALIGN=LEFT >"; print join ", ", split /\0/, $input->{$key}; print "<\TR>"; } print < EOF </TABLE> </CENTER> </BODY> </HTML> EOF exit;

HTML-kimenet generálása

A HTML-kód generálása a CGI-programból viszonylag egyszerű, mecha­nikus eljárás, ahogy azt az olvasó is minden kétséget kizáróan felfedezte az előző példákban. A helyben tárolt dokumentumos technika alkalmazásával lehetővé válik a programozó számára, hogy közvetlenül a CGI-program forráskódjába ágyazza a HTML-kódot, míg a print utasítások rövidebb kódok kiíratására használhatók.

A HTML-fejek készítése további figyelmet érdemel. Az idáig bemutatott példákban a CGI-program által készített fejek kiterjedése egyetlen sor volt, amely a tartalomtípust tüntette fel (content-type), ami a legtöbb esetben elegendő is. Amellett, hogy a webszerver HTML-fej iránti igényeit kielé­

gítjük, függetleníteni tudjuk programjainkat a szerver alapértelmezett tar­talomtípus választásától. Léteznek azonban más elemek is, amelyeket adott esetben fel szeretnénk tüntetni a CGI-fejben. Ilyen lehet a "Status" sor. Például a "Status: 204 No Response" sorral közölhetjük a böngészővel, hogy a program futás alatt áll, de nem fog kimenetet generálni. Hasonló módon értelmes "Status" kód generálás beépítésével a CGI-szkriptet külön­böző hibakezelőkkel is felszerelhetjük a futtatás során jelentkező hibák esetére (pl. "Status: 500 Internal Server Error"). Az olvasó biztosan meg­találja a HTML-állapotkódok (status codes) teljes listáját a kedvenc HTML-dokumentációjában.

Két másik említésre méltó, a fejrészben használható sor az "Expires" és a "Location". Az előbbi a böngészőnek jelzi, hogy az oldallal kiküldött adatok érvényessége lejár a megadott idő után és kéri az újbóli letöltését. Az utóbbi utasítja a szervert egy másik dokumentum átadására. Például a következő rendkívül egyszerű CGI-program a szervert az "under-con­struction.html" dokumentumhoz irányítja át:

print "Location: under-construction.html\n\n";

exit;

A "Location" és a "Content-type" sorok nem szerepelhetnek ugyanabban a fejrészben.

Más tartalomtípusok küldése a kimenetre

Az itt közölt néhány példából hibás lenne azt a következtetést levonni, hogy a CGI-programok csak szöveges adatokat generálhatnak kimenetként. Például, teljesen helyes az alábbi kód is, amelyben a CGI-program bináris képi információt küldhet (feltéve, hogy a kliens böngésző fogadja):

my $file="picture.jpg" my $length = (stat($file))[6]; open(FILE, "<$file") or die "A $file nem nyitható meg: $!" @data = <FILE>; print "Content-type: image/jpeg\n" print "Content-length: $length\n\n"; #a fejrészt záró extra sor print @data;

Egy keretes példa

Az alap HTML-nyelv egy népszerű kiterjesztésében a keretek (frame) is. A CGI-programok alkalmasak kereteket tartalmazó dokumentumok generá­lására. A fődokumentum keretekre bontása két lépésből áll. Először a keret­halmazt definiáljuk, majd a kereteket megtöltjük dokumentumokkal vagy CGI-kimenetekkel a kerethalmaz definíciójától függően. A kiegészítő el­érési utas technika kényelmes megoldást kínál, amellyel egyetlen CGI-prog­ramban megvalósítható a kerethalmaz generálása és a keretek benépesí­tése. Tekintsük az alábbi CGI-kódot, amely bemenetként (a get-en keresz­tül) egy hivatkozást kap egy másik CGI-programra (cél), amelynek ki­menetét megjeleníti egy keretben, a forráskódját pedig egy másikban listázza ki. A program nevét a cél CGI query sztringje kell hogy kövesse. Továbbá feltétel, hogy a cél ugyanabban a könyvtárban legyen, mint az eredeti szkript. Meghívására példa:

/cgi-bin/showcgi.cgi?testparse.cgi/extra/path/info?up=up

És a tényleges kód:

my $query = getQuery(); my($cgi, $args); if ($ENV{'PATH_INFO'}) { $cgi = $query; showCGI(); } else $query = -/([^~?~/]*)(.*)/; #A saját név leválasztása az adatstringről $cgi = $1; $args = $2; buildFrames(); } exit; sub showCGI { print "Content-type: text/plain\n\n"; open(CGI, "<$cgi"); while (<CGI>) { print "$_", } close CGI; exit; } sub buildFrames { print "Content-type: text/html\n\n"; print <EOF>; <HTML> <HEAD> <TITLE>ShowCGI: $cgi</TITLE> </HEAD> <FRAMESET rows="50,50"> <FRAME SRC = "$cgi$args" NANE="cgi"> <FRAME SRC = "$ENV{'SCRIPT NAME'}/display?$cgi" NAME="source"> </FRAMESET> </HTML> EOF exit; }

A program valójá­ban kétszer kerül meghívásra, először böngésző kérésére, amikor a build­Frames függvény végrehajtódik, másodszor saját kérésre a showCGI hívás alkalmával, amikor az alsó keret a célszkript szövegével töltődik fel. Az extra elérési út információk az URL után fűződnek, amikor a program meg­hívja önmagát, ez különbözteti meg a program példányait.

Néhány megjegyzés a példával kapcsolatban: figyeljük meg, hogyan választja szét a főprogram egy elegáns reguláris kifejezése a cél CGI-szkript nevét és query sztringjét (és az extra elérési út információkat, ha vannak). Ismét megcsillantak a Perl képességei. A showCGI-függvény kiír egy "Content-type" sort, amelyben a tartalomtípusnak text/plain-t jelöl meg.

Ezt látva a böngésző nem fogja értelmezni a HTML-kódokat, amelyek jelen lehetnek a cél CGI-forrásában.

Egy érdekes kérdés: Mit gondol az olvasó, mi fog történni, ha a show­cgi.cgi-t önmagával hívjuk meg a következőképpen: "/cgi-bin/showcgi.cgi? showcgi.cgi"?

print "Content-type: text/html\n\n";

my $who_cmd = '/bin/who';

my $who = '$who_cmd';

print <EOM;

<HTML>

<HEAD>

<TILE>Ki van a szerveren</TITLE>

</HEAD>

<BODY>

<H1>Ki van a szerveren?<H2>

<PRE>

A Perl CGI-modul

Ismerve, hogy a CGI-programozók nagy része Perl párti, nem okozhat meg­lepetést, hogy kimondottan CGI-kódokhoz kifejlesztettek egy Perl modult. A modult - amely egyszerűen a CGLpm nevet kapta - Lincoln Stein készítette és az 5.004-es Perl változattól kezdve része a standard Perl könyvtárnak. (Külön is elérhető a CPAN-ról.) A CGI-modul sok olyan szol­gáltatást kínál, amely nagyban leegyszerűsíti a CGI-kódok megírását. Ezek közé tartozik a query adatok automatikus tagolása, HTML-kódsorozatok előállítását végző függvények és hibakezelő rutinok, amelyek a böngésző számára generálnak hasznos információkat.

Egy egyszerű példa

Ismerkedésünket a CGLpm modullal kezdjük egy rövid CGI-programmal. Ez bizonyos tekintetben hasonlít a korábban látott showwho.cgi szkriptre, de ez a szerver traceroute parancsát futtatja. Célállomásként vagy a meg­adott IP-címet használja, vagy ha nem jelölünk meg semmit, akkor a kliens gép IP-címét.

>Megjegyzés: A traceroute egy olyan program, amely ügyesen fel­használva az ICMP-csomagok "time to live" mezőjét, felfedi azon útválasztók sorozatát, amelyeket a hálózati csomagok érintenek

a megadott hosthoz vezető útjuk során. A program elérhető sok Unix ver­zió, valamint Windows NT alatt, ahol a tracert nevet viseli.

Mielőtt a CGI traceroute programot közölnénk, nézzük meg a HTML­munkalapot, amelyről elindíthatjuk:

<HTML> <HEAD> <TITLE>Tracertoute demo</TITLE> <HEAD> <BODY> <FORM ACTION="nph-trace.cgi" METHOD=GET> <H1>Traceroute demo</H1> <CENTER> Útkövetés a szervertől a(z) <INPUT TYPE=TEXT NAME=HOST SIZE=30> -ig (alapértelmezés: kliens kost) <P> <INPUT TYPE=SUBMIT VALUE="Mehet!"> </CENTER> </FORM> </BODY> </HTML>

Lássuk most a CGI-programot:

# nph-trace.cgi

use CGI;

use CGI::Carp qw(fatalsToBrowser);

$I = 1; #Pufferolás nélküli input

my $query = new CGI;

my $host addr = $query->server_name;

my $dest addr = $query->param('host') ? $query->param('host') : $query->remote_addr;

die "Rossz karakter ($1) a hostnévben"

if $dest_addr =~­/({\\;*><'Il)/;

die "Cél cím hiányzik" unless $dest addr; print $query->header(-type=>'text/html', -status=>'200 OK';

-nph=>1, );

print $query->start html('Traceroute demo');

print $query->h1('Traceroute demo');

print $query->h2($host addr,'-tól a(z)', $dest addr,'-ig');

print $query-.>hr;

print "<PRE><TT>\n";

open (TRACE, "/usr/sbin/traceroute $dest addr I");

while (<TRACE>){

print;

}

print "</PRE></TT>\n";

print $query->hr;

print $query->end_html; exit;

A kód elején a $query-t egy új CGI-objektumként deklaráljuk, ez egyben megoldja a bemeneti adatsztring feldolgozását is, függetlenül a kérelem módjától. A bemeneti adatok ezek után a $query objektum param metódusa által lesznek elérhetők a programozók számára. Általában a `name' nevű adatelem értéke a következőképpen kérdezhető le:

$value = $query->param('name');

A paraméterek neveihez így férhetünk hozzá:

@names = $query->param;

A szerver környezeti, változói közvetlenül elérhetők a CGI-objektum metódusai segítségével. Erre két példát láthatunk is a kódban: remote_addr és server name metódusok alkalmazását. A hasznos metódusok közé soro­landó még a path_info, a query_string, a request method, accept és a user_agent. A showenv.cgi szkript kimenetéből a többi kitalálható.

A kód print utasításait megvizsgálva látható, hogyan kímélhetjük meg magunkat a fáradságos gépeléstől, amikor a HTML-kód generálására kerül sor. Az argumentumok nélküli header függvény elkészíti a "Content-type: text/html" és a fejrészt a dokumentumtörzstől elválasztó üres sort. További argumentumok megjelölésével létrehozhatunk "Status" sort is vagy - mint a példa kódban - "Expire" sort, vagy akármilyen számunkra szükséges fejinformációt. A start html függvény a <HTML>, a <HEAD> valamint a <TITLE> </TITLE> és a nyitó <BODY> kulcsszavakat adja vissza. A hl függvény az argumentumait szóközökkel tölti ki és a kapott sztringet a <H1> és a </H1> kulcsszavak közé helyezi. Ennek alapján várható, hogy hasonló

módon más címsorokat (h2-h6) is lehet készíteni. Jó néhány más argumen­tum nélküli függvény is van, ilyen a példabeli "hr" is (amely a <HR> HTML­kulcsszót készíti el).

Az előző példának van még egy szokatlan tulajdonsága: az, hogy ez egy "no-parse-header" vagy NPH-program. Rendes körülmények között a web­szerver megvárja, amíg a CGI-program befejezi futását, mielőtt a kimenetet a böngészőhöz továbbítaná. Egy NPH-program esetében azonban a CGI­program által generált kimenet késedelem nélkül továbbítódik a kliensnek. A traceroute példában ez a kívánatos megoldás, mert elképzelhető, hogy a traceroute parancs végrehajtása akár egy percet is igénybe vesz. Ha a ki­menet azonnal a böngészőbe jut, akkor van lehetősége a felhasználónak, hogy félbeszakítsa a szkriptet, ha valami miatt úgy kívánja. Az auto­matikus puffer ürítés változót ($I) igazra állítottuk, amely azt biztosítja, hogy a parancs futása közben - állapotától függően - készüljön a kimenet. A módszer, amely által egy CGI-programot NPH-programmá tehetünk, szerverfüggő. Néhány, mint pl. az NCSA és az Apache megköveteli, hogy a program neve "nph-"-val kezdődjön. Mások a fejrészben keresnek vala­milyen kérelmet, amit az előző példában az nph igazra állításával tettünk meg a header függvényben.

Hibakezelés és nyomkövetés a CGL.pm-mel

Észrevehető, hogy fenti CGLpm-re támaszkodó programban egy másik modult is felhasználtunk, nevezetesen a CGI::Carp-ot. Ez a modul az alap­vető hibakezelési függvényeknek - úgymint a warn és a die, továbbá a croak, confess és a carp - megfelelő modulbarát hibarutinokat definiálja. Ezek a helyettesítő függvények további információkat fűznek az általuk meg­jelentetett hibaüzenetekhez, úgymint a hiba időpontját, amely a webszerver hibanapló állományában követhetőbb bejegyzéseket eredményez. A qw(fatals­ToBrowser) használata arra kéri a modult, hogy a fatális hibákat (amelye­ket a die, a croak vagy a confess generál) küldje el mind a szerver naplóba, mind a böngészőnek.

Egy másik hibakeresési technika, amely a CGLpm-et használja, a kód hálózat nélküli futtatása. A CGI-modul ugyanis elfogadja a kulcs=érték formában megadott bemenetet a parancssorból (a program meghívásakor) és interaktív módon a szabványos bemenetről is. A bemeneti adatot a mo­dul először megfelelő query sztringgé alakít, majd azt a megszokott módon

(a param metódus segítségével) elérhetővé teszi a program számára. Ez a kód hibamentesítésének egy tiszta és kényelmes módja, valamint sokkal hatékonyabb, mint a szerver naplóállományok átfésülése a hibák felderí­téséhez.

Egy munkalap (form) példa

Az nph-trace.cgi kódban használt függvények csak egy töredéke, annak a készletnek, amelyet a CGLpm nyújt. A HTML-oldalak létrehozása az, amelyben a CGI-modul haszna leginkább megnyilvánul. Tekintsük az alábbi példát, amely a 8.6. ábrán látható formot hozza létre:

use CGI qw(:standard); my $query = new CGI; print header, start html('CGI.pm példaform'); print h1('CGI.pm példaform'); print start_form(-action=>'testparse_cgi', -method=>'post'); print hidden(-name=>hidden, -value=>'nincs sehol'); print "\n"; print ""; print "", my @radioValues = ('Lofi', 'HiFi'); my óradioLabels = ('Lofi' _> 'AM', 'HiFi' _> 'FM'); print "", my @popupList = ('macska', 'kutya', 'csavar'); print ""; my @scrollList = map "Tétel $_", (1..10); print "", print ""; my $default = "A szövegmező alap-\n;értelmezett szövege"; print "": print "
Szöveg bemenet:", textfield(-name=>'input'),"
Jelszó bemenet:",password field(-name=>'password'), "
Rádiógombok:", radio-group(-name=>'radio', -value=>/@radioValues, -labels=>\~radioLabels), "
Lenyíló lista:", popup_menu(-name=>'popup', -value=>\@popupList),"
Gördülő lista:", scrolling_list(-name=>'scroll', -value=>\@scrollList, -multiple=>'true', -size=>3),"
Szövegmező:", textarea(-name=>'textl', -rows=>2, -cols=>20),"
Szövegmező (szöveggel):",textarea(-name=>'text2', -rows=>2, -cols=>20, -value=>$default),"
\n"; print submit('Mehet!'); print end_html; exit;

Egy kicsit elidőzünk a példaszkript elemzésével, mert jó néhányat illusztrál a CGI-modul lehetőségei közül. Mindjárt az első sorban a CGI-modul use­zal történő importálásakor a atandard címkét használjuk, amely a leg­hasznosabb szimbólumokat helyezi a főprogram névterébe. Ez azt jelenti, hogy írhatunk olyat is, mint "print start html" ahelyett, hogy fel kellene

tüntetnünk a minősítőt is a függvénynévvel, mint a korábbi példákban: "print $query->start html". Egy ilyen rövid programban nem áll fenn a ve­szélye, hogy beszennyezzük a névteret. A CGI-modul számos hasonló címkét definiál, ilyenek például a :form, :htm12, :html3 és a :netscape. A CGLpm forrásában a teljes ilyen címkelistát megtaláljuk a hozzájuk tar­tozó szimbólumokkal együtt.

Sok más említésre méltó momentum található az előző példában. Először is vegyük észre, hogy szabadon kevertük a CGLpm függvényhívásokat ­amelyek HTML-kimenetet produkálnak - az explicit HTML-kóddal. A pél­dában egy táblázatot hoztunk létre a formátum kezeléséhez, és a CGLpm függvényhívások köré HTML-táblázatsor és táblázatadat kulcsszavakat helyeztünk a print utasításokba. Másodszor figyeljük meg a radio_group, a popup_fist és a scrolling list függvényeket: ezek argumentuma lehet tömbre mutató referencia - a listaelemek értékei számára -, vagy hash-re mutató referencia - a listaelem címkék számára, mint pl. a radio-buttons hívásban). Sőt még az is megengedett, hogy kifejtett tömb adatokat adjunk át, miként a következő kódban:

print popup_list(-name=>'hal', -value=>['egy', 'kettő', 'piros', 'kék']);

Ezek a függvények jól mutatják, hogyan rejtheti el a CGI-modul a prog­ramozó elől a HTML bőbeszédűségét. Például egyetlen popup list hívás a fenti sorban az alábbi HTML-kódot generálja:

<SELECT NAME="hal">

<OPTION VALUE="one">egy

<OPTION VALUE="two">kettő

<OPTION VALUE="red">piros

<OPTION VALUE="blue">kék

</SELECT>

Megjegyzés: Nem árt tudnunk, hogy az előző példában is használt jelszó típusú inputmező nem teljesen biztonságos. Igaz ugyan, hogy a beütött karakterek nem jelennek meg a képernyőn, de a há­

lózaton titkosítás nélkül utaznak. Ha az adatokat a GET-módszerrel küld­jük át, akkor a jelszó megjelenik az URL-ben. A lényeg az, hogy ne hasz­náljunk jelszó típusú inputmezőt, ha biztonságosságra törekszünk.

Amennyiben hálózaton keresztül történő biztonságos adatcserét kívánunk megvalósítani CGI-programokkal, akkor egy biztonságos szerver konfigu­rációra van szükségünk. Ez a témakör azonban jócskán túlmutat a könyv keretein.

Egy végső megjegyzés a CGI biytonságával kapcsolatban

A CGLpm teljes függvénykészletének és az azok nyújtotta lehetőségek megismeréséhez, futtassuk a perldocCGI-t a rendszerünkön, vagy tanulmá­nyozzuk át a forráskódban található dokumentációt.

Egy végső megjegyzés a CGI biztonságosságával kapcsolatban

A CGI-programozás tárgyalásából nem maradhat el - ha csak egy-két szó erejéig is - a biztonságosság kérdése. Csakugyan a CGI-programokkal kapcsolatosan nem lehet eleget hangsúlyozni a biztonságosság fontosságát. Elegendő arra gondolnunk, hogy a CGI egy olyan módszer, amely majdnem mindenki számára lehetővé teszi, hogy egy programot hajtson végre a gépün­kön. Ha a CGI-programok nem elég elővigyázatosan vannak elkészítve, rosszindulatú felhasználók igyekezni fognak saját céljuk elérésére felhasz­nálni azt és nem arra, amire eredetileg szántuk. Ez nem azt jelenti, hogy nem szabad CGI-programokat írni. Ha tudatában vagyunk a felmerülő veszélyeknek, akkor azokat el is tudjuk kerülni.

A biztonságosságra vonatkozó első szabály így szól: kerüljük az olyan CGI-kódot, amely rendszerparancsok hívásakor a query sztringből kinyert adatokat adja át argumentumként. Ha semmiképpen sem tudunk más mód­szert alkalmazni, akkor előzetesen végezzünk a sztringen vizsgálatot, hogy kiszűrjük a potenciális veszélyt jelentő karaktereket, amelyek speciális jelentéstartalommal rendelkezhetnek a parancsértelmező számára. Az nph-trace.cgi példában láthattuk, hogy lehet egy ilyen szűrést elvégezni. Igénybe vehetjük a Perl egy hasznos szolgáltatását is - mindössze a -T kap­csolóval kell meghívni -, amely abban áll, hogy a fordító ellenőrzi néhány alapvető biztonságossági feltétel meglétét a program környezetébe. Például a fordító fatális hibát jelez, ha egy rendszer parancsot a teljes elérési út feltüntetése nélkül próbálunk meghívni, kivéve, ha beállítottuk az elérési utat a %ENV hash-ben. A -T kapcsoló használatára a dokumentációkban "taint checking" néven van utalás. A -T kapcsoló a szkript első sorában is megadható:

#!/usr/local/bin/perl -T

Célszerű engedélyezni a biztonsági ellenőrzést, ha olyan CGI-programot fejlesztünk, amely rendszer parancsokat futtat vagy lokális állományokba ír.

Egy másik fontos dolog, amelyet jó, ha szem előtt tartunk, az, hogy a CGI-szkript a webszervert futtató effektív felhasználó hozzáférési jogo­sultságaival rendelkezik. Ezek az engedélyek jellemzően nagyon korláto­zottak, ami gyakran meghiúsítja a program elkészítésével kapcsolatos elképzeléseinket. Például akadályba ütközhetünk, ha el akarjuk érni, hogy CGI-programunk egy korlátozott elérést engedélyező adatbázishoz férjen hozzá. Ilyen esetekben meggondolatlan lépésekre is elszánhatjuk magunkat: pl. a CGI-programunkból setuid-t próbálunk alkalmazni, vagy a web­szerver konfigurálásával igyekszünk jogosultsági körét kibővíteni. Az ilyen próbálkozások nem nevezhetők józannak. Általában mindig található valamilyen biztonságosabb út terveink kivitelezésére. Szerencsére sok web­szerver nem engedélyezi olyan CGI-programok futását, amelyeknek be van kapcsolva a setuid bitjük, legalábbis nem speciális konfiguráció nélkül. Csak abban az esetben futassunk beállított setuiddel rendelkező CGI-prog­ramokat, ha teljesen biztosak vagyunk a dolgunkban.

Végezetül még valami, amire fel kell hívnunk a figyelmet. Lehetőség van arra, hogy CGI-programjainkba magunk is beépítsünk bizonyos fokú hozzá­férési kontrollt. Ez különböző információkra támaszkodhat, mint pl. a kliens gép IP-címe (amely a remote_addr környezeti változóból olvasható ki), vagy a távoli felhasználó azonosítója (a remote_user környezeti változóban, ha elérhető), a CGI-program eldöntheti, hogy bizonyos adatokhoz és funkcio­nalitáshoz engedélyezzen-e hozzáférést. Azonban ne nagyon bízzunk a kliens gépről nyert információkban, hacsak nem egy biztonságos webszervert futtatunk, mert vannak módszerek, amelyekkel ezek hamisíthatók. Általá­nosan elmondható a számítógépek biztonságosságáról, hogy minél többet tudunk működéséről, annál biztonságosabbá tudjuk tenni környezetünket. Az ellenkezője még inkább igaz: Amennyiben nem teljesen vagyunk tisz­tában a dolgok természetével, jobb, ha a legnagyobb óvatossággal járunk el.

PerlScript

Semmi kétség, hogy a Perl a szerveroldali webprogramozásra használt leg­elterjedtebb szkript nyelv. Körülbelül az összes dinamikus weboldalt Perl szkriptek generálják. De mi a helyzet a kliensoldali szkriptekkel, amelyek dinamikus tartalmat adhatnak olyan weboldalak számára, ahol nincsen szükség szerveroldali feldolgozásra: azaz állományok írására és olvasására, illetve valamely adatbázis adatainak felhasználására?

A kliensoldali webprogramozáshoz a Perl egy szkript változatát kínálja az alapnyelvnek, ezt nevezik PerlScriptnek. A PerlScript egy ActivX szkript motor, amely lehetővé teszi a felhasználó számára, hogy kódot írhasson webszerverek és webböngészők számára egyaránt. A PerlScript az Active­State terméke, ugyanazé a cégé, amely a közkedvelt Win32-es Perl vál­tozatot is kifejlesztette. Az aktuális PerlScript változatot Windows NT-re vagy Windows 95-re az ActiveState honlapjáról tölthetjük le, vagyis a http://www.activestate.com címről.

Miért használjunk PerlScriptet?

Egy olyan nyelv használatával, mint például a PerlScript, lehetővé válik a weboldalaink tartalmának programszintű vezérlése. A PerlScript segítsé­gével magához a webböngészőhöz is hozzáférhetünk. Az alábbi fejezet­részekben bemutatjuk, hogyan aknázhatók ki a PerlScript lehetőségei web­oldalaink és tartalmuk "feldobásához". Előrebocsátjuk, hogy a könyv el­készültekor a PerlScript kizárólag a Microsoft Internet Explorer böngésző­családdal működik együtt, így a fejezet példái valószínűleg nem működnek a Netscape-ben és más böngészőkben.

HTML-objektumok kezelése

HTML-objektumoknak a tipikus vizuális weboldal komponenseket (úgy­mint bemeneti ablak) nevezzük. A HTML-objektumok egy gyakori alkal­mazási területe a bemeneti formok. A bemeneti formok legtöbbet látható megjelenési formája a vendégkönyv. Ezek a formfajták tisztán HTML­kulcsszavakkal is elkészíthetők, de ha arra vágyunk, hogy a HTML-alapú (ormon információk is megjelenjenek, akkor valamilyen kód beépítése is szükséges a munkalapba.

A PerlScript valós idejű adatok megjelenítésére is alkalmas a HTML­objektumokban. Néha találkozhatunk olyan weboldallal, amely képes megjeleníti IP-címünket. A PerlScript használható a felhasználó Environment Address változójának lekérdezésére, amelyben az IP-cím van tárolva. Természetesen egy ilyen alkalmazás a szerveroldalon is megvaló­sítható, de a feldolgozás egy részének a kliensre terhelése segíthet a terhek kiegyenlített elosztásában és így a lestrapált webszerver egy kis "levegő­höz juthat".

A Browser objektum kezelése

A PerlScript segítségével a Browser (böngésző) objektum és nagyszámú tulajdonsága elérhetővé válik. Ez azt jelenti, hogy a böngésző jellemzőinek nagy része, úgymint a címsor és az előzmények listája vezérelhetővé válik a PerlScript használatával. Megváltoztathatjuk a böngésző ablakának meg­jelenését az ablak jellemzőinek módosításával, az eljárás hasonlít a keretek kezeléséhez.

A MSIE- szkript objektum hierarchia

A böngésző, mint objektum, maga is számos objektumból tevődik össze. Ezt fontos szem előtt tartanunk, amikor saját PerlScript szkriptjeinket készít­jük. A következő fejezetrészekben áttekintjük a PerlScriptben található jelentősebb objektumokat.

A Window objektum

A Window objektum a webböngésző fő komponense. A Window-ból szár­mazik az összes többi objektum. Az objektum egy eseménnyel is rendel­kezik, az OnLoad-dal, amely lehetővé teszi a szkriptek futtatását a szkrip­tet tartalmazó weboldal böngészőbe töltése után.

A Window

objektum három hívható metódussal rendelkezik: . Alert: Egy üzenetablakot jelenít meg egy OK gombbal

.

Status: Szöveges sztringet jelenít meg a böngésző aljában található állapotsorban

. Open: Egy új Window objektumot nyit meg

A következő példában a Window objektumot a $window nevű változó hordozza. A példa az alert metódus használatát mutatja be:

<html>

<head>

<script language = "PerlScript">

$window->alert("PerlScript veszély!");

</script>

</head>

</html>

A Frame objektum

A Frame

objektum lehetővé teszi, hogy keretekkel dolgozhassunk az abla­kon belül. Mivel egyszerre egy ablak több keretet is tartalmazhat, a Frame objektum tulajdonképpen egy objektumtömb. Szkriptjeinkben az egyes keretekre a keret nevével vagy a Frame objektum tömb referenciáján keresz­tül hivatkozhatunk. Például két keretet feltételezve, a topFrame-et és a bot­I~ rame-et, akkor hivatkozhatunk rájuk egyszerűen a nevükkel, vagy úgy is, mint Frames(0) illetve Frames(1) a létrehozás sorrendjének megfelelően.

A Document objektum

A Document objektumon keresztül az aktuális weboldalt (amelyikkel éppen dolgozunk) érhetjük el. Amikor szkriptet használva valamilyen szöveget jelenítünk meg a weboldalon, a Document objektumra kell hivat­koznunk. Például egy olyan PerlScript sor, amely egy üdvözlő üzenetet jele­nít meg, a weboldalunkon így néz ki (később ezt a példát tüzetesebben meg­vizsgáljuk):

$window->document->write("Helló Világ");

A Document objektum segítségével egy létező alapoldalba beágyazott weboldalakat hozhatunk létre. _

A Form objektum
A Form

objektum szintén egy objektumtömb, mivel egy oldalon több form (más néven munkalap) is elképzelhető. Ahogyan a Frames objektumnál is, a Form objektum tömbjének az indexelése is 0-val kezdődik. Ezt az objek­tumot használjuk felhasználói bemeneti formok és olyan szkriptek készíté­séhez, amelyek automatikus ellenőrzést végeznek a formon, mielőtt az el­küldésre kerülne a szervernek.

Az element objektum

Az element objektum tulajdonképpen nem más, mint a munkalapokban alkalmazható különböző vezérlő elemek, vagy vizuális komponensek gyűj­teménye, amelybe többek között a szövegmezők, parancsgombok, lenyíló listaablakok tartoznak. Az Element objektum is egy tömb. Minden kompo­nensre hivatkozhatunk egyszerűen az elem tömbbeli helyének megfelelő index megadásával. Minden komponens - mint ahogy később látni fogjuk ­rondelkezik egy névvel is, és így a sokkal könnyebb névszerinti hivatkozás is használható.

Több dokumentum használata

A PerlScripttel lehetséges, hogy egy betöltött dokumentumból egy másik be­töltött dokumentum értékeire hivatkozzunk. Ilyen szituációval gyakran szem­be kell néznünk például kétkeretes weboldalak esetén, ahol is az egyik keretbe az A dokumentumot töltöttük be, a másikba pedig a B-t. A PerlScriptre akkor van szükség, amikor az A dokumentum adatait a B dokumentumban kívánjuk felhasználni, vagy ha olyan komponenseket szeretnénk az A dokumentumba helyezni, amelyekkel a B dokumentum objektumai vezérelhetők.

Új weboldalak létrehozása futási időben

A PerlScript egyik legnagyobb erénye, hogy segítségével új weboldalakat állíthatunk elő futási időben, miközben a felhasználó kölcsönhatásban áll a weboldallal. Például ha a felhasználó a szükséges adatokat megadta, az adatok alapján testre szabott formot hozhatunk létre kizárólag a tartalmat előállító PerlScript szkript segítségével. _

A felhasználói bemenet feldolgozása

Csakúgy mint bármely hagyományos programozási nyelv, a PerlScript is lehetővé teszi változók definiálását és azokban a felhasználótól begyűjtött adatok tárolását. Ennek a jellemzőnek egy gyakori alkalmazása a fel­használói bement ellenőrzése, mely által biztosíthatjuk, hogy a dokumen­tum megfeleljen bizonyos kritériumoknak. A felhasználói bemenet jóvá­hagyása mellett a PerlScriptben matematikai függvények és műveletek teljes skáláját is igénybe vehetjük a felhasználói adatokon végzendő számí­tásokhoz. Ez tisztán a HTML-lel nem tehető meg.

ActiveX komponensek használata

Mivel a PerlScript egy ActiveX szkriptmotor, használható ActiveX kompo­nensek elkészítésére weboldalakon, illetve az oldalakra helyezett más ActivcX komponensekkel való kölcsönhatásra. Ennek a tulajdonságának köszi)nhe­tően a PerlSripttel "Windowsos" megjelenésű weboldalakat hozhatunk létre.

Rövid kliensoldali szkript esettanulmány

Mielőtt beleásnánk magunkat a PerlScript példákba, érdemes felelevení­teni azt a folyamatot, amelynek hatására kliensoldali szkript beágyazódik a weboldalba. Emlékezzünk, hogy mikor szerveroldali szkripteket készí­tettünk Perlben, a weboldalon egy kulcsszó gondoskodott a szerveren talál­ható Perl szkript meghívásáról; amikor a dinamikus webtartalmat a kliens­oldalon akarjuk létrehozni, a szkriptet be kell ágyaznunk a weboldalak HTML-kódjába.

Az alábbi példa egy olyan weboldalt készít el, amely a klasszikus "Helló Világ!" üzenetet jeleníti meg. A példa a fent leírtak működését is illuszt­rálja. Íme a kód, amelyet majd működésének leírása követi:

<html>

<head>

<title> PerlScript "He11Ó Világ!" példa</title>

<script language = "PerlScript">

sub DisplayMessage {

$window->document->write("Helló Világ!");

}

</script>

</head>

<body>

<script>

DisplayMessage();

</script>

<p>Az oldalt PerlScript generálta. <p>

</body>

</html>

A szkript első három sora szabványos HTML-kód. A negyedik sor jelzi a böngészőnek, hogy a PerlScript az alkalmazott szkriptnyelv. A következő három sorban egy PerlScript függvénydefiníció található, a Display­Message függvényé. A függvény középső sora végzi a munkát a Window­Document objektum write metódusának meghívásával, amely a "Helló

Világ!" üzenetet jeleníti meg. A függvénydefiníciót követő két sor lezárj<z a SCRIPT és a HEAD utasításokat.

A HEAD lezárása után a BODY kulcsszó nyitja meg a törzset. Az öss-r_es PerlScript függvény a weboldal fejrészében kerül deklarálásra, és a doku­mentumtörzsből hívjuk meg őket. A <SCRIPT> kulcsszó jelzi a böngésző­nek, hogy feldolgozandó kód következik. Majd a függvényhívás következik, amely nem különbözik egy szokásos Perl függvény meghívásától. Mivel több dolgunk nincs, az egyetlen függvény meghívása után következhet a </SCRIPT>, amely lezárja a szkriptet. Végül egy egyszerű HTML-sort írunk ki ("Az oldalt PerlScript generálta") és végül a weblapot a </BODY> és </HTML> utasítások zárják.

Összetettebb alkalmazások

Szöveg generálása nem nevezhető éppen tudományos bravúrnak mí~~ akkor sem, ha kliensoldali PerlScript függvény által végeztük. Tcrmu­szetesen nem vethetjük bele magunkat csak úgy, mindenféle elv>tanal­mányok nélkül a hálózati megrendelő formok készítésébe. A kövotkurí~ példából megtudhatjuk, hogyan adhatunk gombokat és szövegmezi>I<ut weboldalainkhoz.

Szövegmező és parancsgomb hozzáadása a weboldalakhoz

Képernyő elemek (widgets) - úgymint szövegmezők, beviteli gombok - el­helyezése egy weboldalon standard HTML használatával történik, és nem igényel semmilyen szkriptnyelv alkalmazást. Azok számára, akik nem jár­tasak a HTML-nyelvben, itt megtudhatják a komponensek hozzáadásának módját HTML segítségével.

A komponenseket és más weboldal objektumokat nem csak úgy rápakol­juk a weboldalra. Ki kell számukra jelölni egy helyet, ahová formként beilleszthetők a weblapra. Ezt a FORM HTML kulcsszóval lehet kivitelezni. A kulcsszó által definiált blokkmeghatározott blokk tartalmazza az összes megjelenítendő komponenst. Használata:

<form action = " " name = "Widgets">

Az acton tulajdonságnak általában üres sztringet adunk meg, mert a form nem végez valódi tevékenységet. A name tulajdonságnak ad unk érté­ket, mert a szkriptjeinknek képesnek kell lennie hivatkozni a forrnra, hogy hozzáférhessenek a tulajdonságaihoz.

A komponensek, függetlenül attól, hogy szövegmezőkről vagy parancs­gombról van-e szó, "input" objektumoknak tekintendők a HTML-ben. Ez azért van így, mert általában ezeket az objektumokat használj uk a fel­használói bemenet begyűjtésére. Egy komponens megjelenítéséhez a doku­mentumon meg kell adnunk a típusát, nevét, és más, az elem típusától függő jellemzőket. Az alábbi kód egy szövegablak komponenst definiál:

<input type = "text" size = "70" name = "InputString">

Ezt a HTML-sort input tagnak (címkének) nevezzük. Az első beállított tulajdonság a type, amely a "text" értéket kapta, ez jelöli a szőve-Bablakot. A második jellemzőnek, a size-nak "70"-et adtunk értékül, amivel a szöveg­mező hosszát határoztuk meg.

Az utolsó tulajdonság a name. Ezzel adhatunk nevet a bemeneti ablak­nak, amellyel a szkriptben majd a komponensre hivatkozhatunk. Ha ezt a sort beillesztjük egy minimális HTML-kódba, amely oldal létrehozásához feltétlenül szükséges, az alábbi sorokhoz jutunk. Ezek eredményeként az üres weboldalunkon egy szövegmező jelenik meg:

<html>

<head>

<title>szövegmező és bemeneti gomb készítése</tile>

<body>

<form action = " " name = "Widgets">

<input type = "text" size = "70" name = "InputString">

<p>Szövegmező felhasználói adatok bekérésére<p>

</form>

</body>

</html>

A következő lépés egy parancsgomb készítése. A gomb esetében is az előző típusú HTML kulcsszót kell használnunk, az objektum típusának most azon­ban "button"-t kell megadnunk a "text" helyett és meg kell jelölnünk a gomb szövegét is. Ennek megfelelően a gomb készítését végző HTML-sor így néz ki:

<input type = "button" name = "DisplayText" value = "Szöveg megjelenítése lejjebb">

Ez hozzáadható a fenti kódhoz úgy, hogy a gomb a szövegmező alá kerülji)n:

<html>

<head>

<title>Szövegmezők és parancsgombok létrehozása</title>

<body>

<form action = " " name = "Widgets">

<input type = "text" size = "70" name = "InputString">

<p>

<input type = "button" name = "DisplayText" value = . "Szöveg megjelenítése lejjebb">

</form>

</body>

</html>

Komponensek összekapcsolása szkript alkalmazásával

Most, hogy elsajátítottuk, hogyan helyezhetünk komponenseket a web­oldalra felhasználói munkalapok készítéséhez, bizonyára szeretnénk, ha formjaink valamit csinálnának. Bevezetőként kezdjük egy egyszerű fel­adattal. Azt szeretnénk megvalósítani, hogy a gomb lenyomásával a szöveg­mezőbe beírt szöveg még egy példányban megjelenne.

A weboldal komponensek képesek a felhasználó által kiváltott esemé­nyekre reagálni. Például, ha egy parancsgombbal csináltatni akarunk vala­mit, egy olyan szkriptet kell írnunk, amely akkor kerül végrehajtásra, amikor a gomb onClick esemény bekövetkezik. Minden komponenshez tartoznak bizonyos események, amelyekhez kódot tudunk rendelni. A fejezet utolsó részében felsoroljuk a különböző PerlScríptben lekezelhető eseményeket.

Ahhoz, hogy az egyszerű példánk parancsgombja kiírja a szövegmező tartalmát, egy olyan függvényt kell definiálnunk, amely ezt a feladatot ,elvégzi a gomb lenyomására. A függvény kiolvassa a felhasználó által be­gépelt szöveget a mezőből, és egy másik szövegmezőben kiírja a gomb alá. Az alábbi PerlScript szubrutin ezt valósítja meg:

sub Widgets::DisplayText_onClick() {

$text = $Widgets->InputString->('Value');

$Widgets->OutputString->{'Value'}= $text;

A függvénydefiníció első sorában a nevét adjuk meg, amely a gomb és a függvényt meghívó esemény nevének kombinációjából kell hogy álljon.

A következő sor olvassa ki az InputString nevű szövegmező tartalmát és a kapott sztringet a $text skalárba másolja be. Az utolsó sor fogja ezt a ska­lárt, és értékül adja a másik szövegmező value tulajdonságának, aminek eredményeként a $text tartalma megjelenik benne.

Az előző HTML-kódot a függvénnyel kiegészítve a következőt kapjuk:

<html> <head> <title>Szövegmezők és parancsgombok létrehozása</title> <script language = "PerlScript"> sub Widgets::DisplayText onClick() { $text = $Widgets->InputString->('Value'); $Widgets->OutputString->{'Value'}= $text; } </script> <body> <form action = " " name = "Widgets"> <input type = "text" size= "70" name = "InputString"> <p> <input type = "button" name = "DisplayText" value = "Szöveg megjelenítése lejjebb"> <p> <input type = "text" size= "70" name = "OutputString"> </form> </body> </html>

Más komponensek és további trükkök

Természetesen a Perl majdnem minden funkcionalitása használható a Perl­Scriptben is. Az alábbi példaszkript a korábbi példára támaszkodva, a fel­használó által az első mezőbe beírt szöveget átalakítja úgy, hogy az a máso­dik szövegmezőben csupa nagybetűs formában jelenjen meg.

<html> <head> <title>Szövegmezők és parancsgombok létrehozása</title> ><script language = "PerlScript"> sub Widgets::DisplayText onClick() { $text = $Widgets->InputString->('Value'); $text --- tr/[a-z]/[A-Z]/; $Widgets->OutputString->{'Value'}= $text; } </script> <body> <form action = " " name = "Widgets"> <input type = "text" size= "70" name = "InputString"> <p> <input type = "button" name = "DisplayText" value = "Szöveg megjelenítése lejjebb"> <p> <input type = "text" size= "70" name = "OutputString"> </form> </body> </html>

Egy másik input formokon gyakran előforduló komponens a lenyíló lista­ablak vagy más néven kombóablak.

Lenyíló listaablakokat a HTML-ben is készíthetünk a select kulcsszó segít­ségével, amellyel magát a kombót hozhatjuk létre, és a választható elemek listáját is megadhatjuk. A HTML SELECT kulcsszó használatát alább lát­hatjuk:

<select name = "select option"> <option selected> Téte11 <option> Téte12 <option> Téte13 <option> Téte14 </select> <html> <head> <script language = "PerlScript"> sub Mailer::Convert onClick() { $email = $Mailer->ceo name->options(0)->{'Tetxt'}; $email --- tr/ //d; $Mailer->Address->{'Value'} _ $email; </script> </head> <body> <h1>Email küldése egy híres informatikai személyiségnek</h1> <form action = " " name = "Mailer"> <p> <strong>Melyik személynek kíván emailt küldeni?</strong> <select name = "ceo name"> <option select> Bill Gates @ microsoft.com <option> Larry Ellison @ oracle.com <option> Scott McNealy @ sun.com <option> Andrew Grove @ intel.com </select> <p> <input type = "button" name = "Convert" value = "A névhe.~ tartozó emailcím"> <p> <input type = "text" size = "70" name = "Address"> </form> </html>

A munka oroszlánrészét a Mailer::Convert_onClick függvény végzi, amely egyszerűen a kombóablak kiválasztott elemének értékét olvassa ki, eltávolítja belőle a szóközöket, majd az eredményt egy szövegmezőben joleníti meg. Az egész eljárás kulcsa az, ahogyan a kombóablak választott. elemét kinyerjük. Ilyen programozási szituációkban, mivel PerlScript dokumentáció gyakorlatilag nem létezik, más referenciákhoz kell fordul­nunk, úgymint a Microsoft VBScript dokumentációja. A VBScript által használt objektumok és metódusok közel állnak a PerlScript objektu­maihoz, illetve metódusaihoz; a lényegi különbséget a szintaxisbeli eltéré­sek adják a két nyelv között.

Dinamikus weboldalak készítése

Most, hogy az olvasó elsajátította a PerlScript használatának alapjait web­oldalak kezeléséhez, bizonyára kíváncsi, hogyan használható ez a nyelv új weboldalak dinamikus előállítására. Ez anélkül is megtehető, hogy a szer­verre bármiféle feladatot hárítanánk. Mielőtt megismerhetjük, hogyan építhetünk weboldalakat magából a böngészőből, szükséges megértenünk, hogyan változtathatjuk meg a weboldal tartalmát, amikor az letöltc~dik a böngészőbe.

Tartalom hozzáadása letöltési időben

A PerlScript segítségével megváltoztathatjuk a weboldalak tartalmát a bön­gészőbe történő letöltődésük közben. Egy gyakori példája ennek a dinami­kus dokumentum lábrész (footer). A lábrészben különböző információkat tárolhatunk, mint pl. dátum és idő, a lapot letöltő böngészőt azonosító adatok és más értékes információk.

A következő szkript azt illusztrálja, hogyan lehet minden weboldalra egy lábrészt helyezni. A lábrész számos PerlScript változót használ olyan infor­mációk közléséhez, mint a weboldal megtekintésének dátuma és a használt webböngésző típusa. Íme a szkript:

<html> <head> </head> <body> <center> <h2>Változó Web tartalom készítése</h2> </center> <p> <font size = 2> <blockquote> <b> </b> </blockquote> <hr> <center> <font size = 1> <script language = "PerlScript"> ($sec, $min, $hour, $mday, $mon, $wday, $yday, $isday) = localtime(time); $mon += 1; $window->document->write("Az oladal létrejöttének időpontja: $year/$mday/$mon."); $window->document->write("<br>"); $user = $window->Navigator->userAgent; $window->document->write("Az oldal megnézéséhez használt eszköz: $user."); </script> </form> </html>

A fenti kód szkript részében két változó játszik fontos szerepet az aktuális dátum és a felhasználó által használt böngésző típusának tárolá­sában. A dátumhoz a szkript a PerlScript localtime függvényének meg­hívásával jut hozzá, amely hónapra, napra és évre van tagolva. A fel­használó webböngészőjének típusát a Navigator objektumot használva tudhatjuk meg, amelynek userAgent tulajdonsága a felhasználói ágens sztringet tartalmazza, mint pl. "Mozilla/4.0 (compatible; MSIE 4.0; Windows 95)".

Webdokumentumok létrehozása valós idöben
Az előző részt csak egy kis ízelítőnek szántuk a dinamikus webtartalmak elő­állítására letöltési idő alatt. A PerlScripttel a látottaknál jóval érdekesebb dolgot is művelhetünk, nevezetesen teljesen új dokumentumokat hozhatunk létre anélkül, hogy azokat valamilyen formában a szerveren tárolnánk.

Webdokumentumok valós idejű készítéséhez a kulcsot a Document objektum open metódus jelenti. Ez a metódus létrehoz egy új dokumen­tumot és lehetővé teszi számunkra, hogy azt tartalommal töltsük fel. Az open használata rendkívül egyszerű:

$document->open;

Nyilvánvalóan szeretnénk az oldalt szöveggel is ellátni. Ehhez a write metódust használjuk a következő formában:

$document->write("Ez egy új weboldal");

Ezzel a két metódussal a böngészőből készíthetünk új webdokumentu­mokat, esetleg válaszként valamilyen felhasználói bemenetre. Fontos azon­ban szem előtt tartanunk azt a tényt, hogy az open metódus meghívásának eredményeként a jelenleg látható dokumentumot az új felül fogja írni.

<html> <html> ></html> <body> <center> <h2>Új Web oldalak létrehozása</h2> </center> <p> <form action = " " name = "Dynamo"> Kérem a nevét: <input type = "text" size = "70" name = "User"> <input type = "button" name = "NewPage" value = "Új oldal készítése"> <script language = "PerlScript"> sub Dynamo::NewPage onClick() { $newpage->document->open; $userName = $top->newpage->document->User->{'Value'}; $newpage->document->write("<html><head><title = 'Úi Web oldal'>"); $newpage->document>write("</title></head><body><hl> $newpage->document->write("$userName új Web oldala"); $newpage->document->write("</h1>"); ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isday) = localtime(time); $mon += 1; $window->document->write("Az oladal létrejöttének idő pontja: $year/$mday/$mon."); $window->document->write("<br>"); $user = $window->Navigator->userAgent; $window->document->write("Az oldal megnézéséhez használt eszköz: fuser."); } </script> </form> </html>

Összefoglalás

A PerlSctipt megoldást kínál olyan helyzetekben, amikor valamilyen kliens­oldali tevékenységre van szükségünk. Két olyan szituáció, amikor célszerű ezt a technikát alkalmazni: a felhasználói bemenet ellenőrzése és jóvá­hagyása, valamint új weboldalak valós idejű létrehozása. Más szkript­nyelvek - úgymint a JavaScript és VBScript - szintén használhatók kliens­oldali szkriptként. A PerlScript a megfelelő választás, ha a Perl nyelv képességeit szeretnénk felhasználni a szkriptekben. A PerlScript letölthető az ActiveSate cég weboldaláról (http://www.activestate.com). Ott mindig megtalálhatjuk a PerlScript legfrissebb változatát és mellette néhány Perl­Script példát. Látogatásunkat összeköthetjük a Perl for Win32 legújabb változatának letöltésével.