X. CGI programok írása NetREXX-ben

Bevezetés

Ebben a leckében arra fogjuk használni a NetREXX-et, hogy CGI (Common Gateway Interface) programokat írjunk benne. A CGI program tulajdonképpen egy olyan szkript, amely a web kiszolgálón fut és a kliens által küldött paraméterek felhasználásával web oldalakat generál. CGI programokat nagyon sok nyelven írni, s a legtöbb kiszolgáló támogatja a Java programokat is, amelyeket servleteknek is neveznek.

CGI alapok

Nagyon sok esetben van arra szükség, hogy olyan információt tegyünk elérhetôvé a hálózaton, amely eredeti formátuma nem alkalmas arra, hogy a böngészôk közvetlenül értelmezni tudják. Sokszor arra is szükség van, hogy ne az egész információhalmazt, hanem annak csak azt a részét jelenítsük meg, amelyre a felhasználónak szüksége van. Vegyünk például egy sok száz bejegyzést tartalmazó DB2 adatbázist! Ezt nem tudják a böngészôk olvasni, de még ha tudnák is, akkor se érnénk vele sokat, mert a felhasználóknak sok idôbe telne kiválasztani a számunkra hasznos részeket. Az ilyesfajta feladatok megoldására kiválóan alkalmasak a CGI programok, mivel azokkal könnyedén megoldható a felhasználó számára érdekes adatok kiválasztása és annak a böngészô számára értelmezhetô formátumúra alakítása.

A felhasználó böngészôje paramétereket juttathat el a CGI programhoz, hogy ezzel például behatárolja azt, hogy mely adatokat kívánja lekérni az adatbázisból. A paramétereket a böngészô az URL részeként tárolja el, s ezekbôl a szerver oldalon környezeti változók keletkeznek, amelyeket aztán a CGI program le tud kérdezni a futás során. Mivel a Java programok nem férnek hozzá közvetlenül a környezeti változókhoz, ezért a kiszolgálókat úgy módosították, hogy azok a környezeti változókat automatikusan átkonvertálják rendszertulajdonságokká (system properties), amelyek nevei megegyeznek a környezeti változók neveivel és így lekérdezhetôk a System.getProperty metódussal. A négy leggyakrabban használt környezeti változót és azok rendeltetését összefoglaltuk az alábbi táblázatban:

Környezeti változó:Rendeltetés:
SCRIPT_NAMEA CGI program neve
REMOTE_ADDRA kliens TCP/IP címe
QUERY_STRINGA paramétereket tartalmazó sztring a Get HTML metódus használata esetén
CONTENT_LENGTHA bemenetre küldött bájtok száma a Post HTML metódus esetén

A négy környezeti változó közül a QUERY_STRING a legfontosabb, mivel ez tartalmazza az átadandó paraméterek nevét és azok értékét. A paramétereket a & jel választja el egymástól. A QUERY_STRING tartalmának általános alakja a következô:

változó1=érték1&változó2=érték2&változó3=érték3...

Fontos még azt is tudnunk, hogy a speciális karaktereket a % jel és a speciális karakter kódja írja le. A % karaktert például %25-tel helyettesíti a böngészô, míg a szóközöket + jelekre cseréli.

A paraméterek átadásának másik módja a CGI szkript standard bemenetére küldött adatok feldolgozása. Ilyenkor a HTML nyelv Post metódusát használjuk. A bemenetre küldött adatokat a következô sorral lehet kiolvasni:

query = BufferedReader(InputStreamReader(System.in)).readLine()

A paraméterek feldolgozása és a CGI programban definiált egyéb mûveletek elvégzése után a kapott eredményeket a standard kimenetre írjuk ki, amelyet aztán a web kiszolgáló továbbít a klienshez. Ez azt jelenti, hogy egy NetREXX programban a Say utasítás segítségével írathatjuk ki a visszaadandó HTML oldal sorait. Mielôtt azonban az elsô sort kiíratnánk, küldenünk kell egy, a fájl adattípusát leíró sort, amelyet mindig egy üres sornak kell követnie:

Content-Type: text/html

<html>   <=== a HTML oldal kezdete
...      <=== a HTML oldal törzse
</html>  <=== a HTML oldal vége

Példaprogramok

Reméljük, hogy még mindenki emlékszik a 8. leckében használt DB2 adatbázisra. Ebbôl az adatbázisból fognak ugyanis CGI programjaink adatokat kikeresni és a web kiszolgáló közremûködésével elküldeni a kliensnek. Az elsô program a minta adatbázis EMPLOYEE táblázatából keresi ki a felhasználó által megadott nevû munkavállalókat. A második program kilistázza egy adott munkavállaló összes adatát. Az elsô példaprogramhoz mellékeltünk egy HTML oldalt is (lecke10a.htm), amely a következôképpen jelenik meg egy böngészôben:

[ntrx1001]

A keresett személy nevének begépelése és a Submit Query gombra bökés után a böngészô elküldi a kiszolgálónak a keresett nevet, amelyet az aztán továbbpasszol az alábbi CGI programnak:

/* lecke10a.nrx */

import java.sql.
Class lecke10a

properties static
prefix = Rexx "USERID" -- a keresett oszlop azonosítója
con = Connection       -- DB2 kapcsolat
driver = String 'COM.ibm.db2.jdbc.net.DB2Driver'
/* driver = String 'COM.ibm.db2.jdbc.app.DB2Driver' */
url = String 'jdbc:db2:/loopback:8888/sample'
/* url = String 'jdbc:db2:sample' */
partialname = String

/* fômetódus */
method main(args=String[]) static
 	args = args

 	say "Content-Type: text/html" -- a kötelezô sor
 	say ""                        -- ez is kötelezô
 	say "<html>"                  -- HTML fájl kezdete
 	say "<head><title>Információ az alkalmazottakról</title></head>"
 	say "<body>"
 	say "<H2>Találati lista</H2>"
 	say "<br> Program: "System.getProperty("SCRIPT_NAME")
 	say "<br> Client : "System.getProperty("REMOTE_ADDR")
 	list = Rexx System.getProperty("QUERY_STRING") -- paraméterek
 	list = queryTranslate(list)
 	list = list.translate("%", "*")
 	say "<br> Query :" list
 	parse list "name=" partialnamex " " -- a név kiolvasása
 	partialname = partialnamex.upper'%'

 	jdbcConnect()        -- JDBC kapcsolódás az adatbázishoz
 	performRetrieve()    -- DB2 SQL
 	say "</body></html>" -- a HTML vége
return

/* a kapott paramétersztring átalakítása */
method queryTranslate(qry=Rexx) private static returns Rexx
 	qryt = qry.translate(" ", "+") -- + = szóköz!
 	ist = qryt.pos("%")
 	loop while ist > 0
 		c = qryt.substr(ist+1,2).x2c
 		qryt = qryt.substr(1,ist-1)""c""qryt.substr(ist+3)
 		ist = qryt.pos("%", ist+1)
 	end
return qryt

/* kapcsolódás az adatbázishoz */
method jdbcConnect() private static
 	do
 		say "<br>Kapcsolat :" url
 		Class.forName(driver)
 		con = Connection DriverManager.getConnection(url, 'userid', 'password')
 		if con.getWarnings() \= null then do
 			say "<p> Error "con.getWarnings().getMessage()
			return
 		end
 		dma = DatabaseMetaData con.getMetaData()
 		say '<br>Driver : 'dma.getDriverName() dma.getDriverVersion()
 		catch ex=SQLException
 		say '<p> *** SQLException történt ***'
 		say "<br> "ex.getMessage()
 		loop while (ex \= null)
 			say '<br>SQLState: 'ex.getSQLState()
	 		say '<br>Message:  'ex.getMessage()
	 		say '<br>Vendor:   'ex.getErrorCode()
 			ex = ex.getNextException()
			say '<br>'
 		end
 		catch ex2=java.lang.Exception
 		say "<p> Error: "ex2.getMessage()
 		ex2.printStackTrace()
 	end
return

/* az adatok letöltése */
method performRetrieve() private static
 	say "<p> Az alkalmazottak letöltése folyik: "partialname
 	do 
 		query = 'SELECT empno, lastname, firstnme' -
 		'FROM' prefix'.employee' -
 		'WHERE lastname LIKE "'partialname'"'
		say "<p>"
 		say "<table border=2 cellpadding=0>"
 		say "<tr>"
		say "<th>Sorszám</th> <th>Vezetéknév</th> <th>Keresztnév</th>"
 		say "<tr>"
 		stmt = Statement con.createStatement()
 		rs = ResultSet stmt.executeQuery(query)
 		more = boolean rs.next()
 		loop row=0 by 1 while (more)
 			num = Rexx rs.getString("empno")
 			say "<td>" "<a href='lecke10b.class?number="num"'><b>"num"</b></a></td>"
 			say "<td>" rs.getString("lastname")"</td>"
 			say "<td>" rs.getString("firstnme")"</td>"
 			say "<tr>"
 			more = rs.next()
 		end
 		say "</table>"
 		rs.close() 
 		stmt.close() 
 		say "<p> Találtunk "row" alkalmazottat."
 		catch ex=SQLException
 		say "<p> Error: "ex.getMessage()
 	end
return

A példaprogram a kötelezô típusleíró sor és az azt követô üres sor kiíratásával kezdôdik. Ezután lekérdezzük a környezeti változókat a már említett system properties metódus segítségével. A paramétereket tartalmazó környezeti változó tartalmát átalakítjuk a queryTranslate metódussal, amely visszanyeri a speciális karaktereket és a szóközöket. Az adatok birtokában kapcsolódunk az adatbázishoz a jdbcConnect metódussal, és a performRetrieve metódusban egy SQL parancs segítségével lekérdezzük azon alkalmazottak listáját, amelyek neve megegyezik a felhasználó által megadott névvel. Az így kapott adatokat HTML táblázatban íratjuk ki a standard kimenetre, amelyet aztán a web kiszolgáló juttat el a felhasználó böngészôjéhez. Valami ehhez hasonló oldalnak kell megjelennie:

[ntrx1002]

Amint látjuk, a CGI programot a P* paraméterrel hívtuk meg, ami válaszul az összes P-vel kezdôdô nevû alkalmazottat megjelenítette. A program ugyanis képes a * karakter gyorsítókarakterként történô értelmezésére is. A megtalált alkalmazottak sorszáma egyben egy link is, amelyre klikkantva elindul a második CGI példaprogram, amely megjeleníti az adott sorszámú alkalmazott adatait:

/* lecke10b.nrx */

import java.sql.

Class lecke10b

properties static
prefix = Rexx "USERID" -- oszlopazonosító
con = Connection -- DB2 kapcsolat
driver = String 'COM.ibm.db2.jdbc.net.DB2Driver'
--driver = String 'COM.ibm.db2.jdbc.app.DB2Driver'
url = String 'jdbc:db2:/loopback:8888/sample'
--url = String 'jdbc:db2:sample'
empno = String

/* fômetódus */
method main(args=String[]) static
	args = args
	say "Content-Type: text/html" -- a kötelezô sorok
	say ""
	say "<html>"                  -- kezdôdik a HTML fájl
	say "<head><title>Információ az alkalmazottakról</title></head>"
	say "<body>"

	say "<H2>Az alkalmazott adatai</H2>"
	say "<br> Program: "System.getProperty("SCRIPT_NAME")
	list = System.getProperty("QUERY_STRING") -- paraméterek
	parse list "number="empno " "
	jdbcConnect() -- JDBC kapcsolódás
 	performRetrieve() -- DB2 SQL
 	say "</body>" -- a HTML vége
 	say "</html>"
return

/* kapcsolódás az adatbázishoz */
method jdbcConnect() private static
 	do
 		Class.forName(driver)
 		con = Connection DriverManager.getConnection(url, 'userid', 'password')
 		if con.getWarnings() \= null then do
 			say "<p> JDBC driver: "driver
 			say "<br>Kapcsolat  : "url
 			say "<p> Error "con.getWarnings().getMessage()
 			return
 		end
 		catch ex=SQLException
 		say '<p> *** SQLException történt ***'
 		say "<br>" ex.getMessage()
 		loop while (ex \= null)
 			say '<br>SQLState: 'ex.getSQLState()
 			say '<br>Message:  'ex.getMessage()
 			say '<br>Vendor:   'ex.getErrorCode()
 			ex = ex.getNextException()
 			say '<br>'
 		end
 		catch ex2=java.lang.Exception
 		say "<p> Error : "ex2.getMessage()
 		ex2.printStackTrace()
 	end
return

/* az adatok lekérése */
method performRetrieve() private static
 	say "<br> Az adatok letöltése folyik: "empno
 	do 
 		query = 'SELECT empno, firstnme, midinit, lastname,' -
 		'phoneno, sex, birthdate, hiredate, job, edlevel,' -
 		'salary, bonus, comm, workdept' -
 		'FROM' prefix' .employee' -
 		'WHERE empno = " 'empno' " '
 		stmt = Statement con.createStatement()
 		rs = ResultSet stmt.executeQuery(query)
 		more = boolean rs.next()
 		say "<pre>"
 		loop row=0 by 1 while (more)
 			say "<p>"
 			say "Sorszám  : "rs.getString("empno")
 			say "Név      : "rs.getString("firstnme") rs.getString("midinit") rs.getString("lastname")
 			say "Telefon  : "rs.getString("phoneno")
 			say "Nem      : "rs.getString("sex")
 			say "Született: "rs.getString("birthdate")
 			say "Felvétel : "rs.getString("hiredate")
 			say "Funkció  : "rs.getString("job")
 			say "Képzetts.: "rs.getString("edlevel")
 			say "Fizetés  : "rs.getString("salary")
 			say "Bonus    : "rs.getString("bonus")
 			say "Extra    : "rs.getString("comm")
 			if rs.getString("workdept") \= null then
 			say "Részleg  : "rs.getString("workdept")
 			more = rs.next()
 		end
 		say "</pre>"
 		rs.close() 
 		stmt.close() 
 		if row=0 then say "<p> A(z) "empno". sorszámú alkalmazott nem létezik."
 		catch ex=SQLException
 		say "<p> Error: "ex.getMessage()
 	end
return

A második program nagyon hasonlít az elsôhöz. Most is a kötelezôen elküldendô sorokat íratjuk ki elôször, majd pedig lekérdezzük a környezeti változókban rejlô bemeneti információkat. Az adatok birtokában kapcsolódunk az adatbázishoz és lekérdezzük, majd pedig megjelenítjük a megadott sorszámú alkalmazott adatait.

[ntrx1003]

Leckénk elején említettük, hogy ha a HTML oldalon Post metódust használnak, akkor a paraméterek a CGI program standard bemenetére kerülnek. Ebben az esetben természetesen nem mûködnek a fenti példaprogramok, mivel a QUERY_STRING környezeti változóból nem lehet kinyerni a szükséges információt. Viszonylag kis munkával át lehet azonban alakítani a programokat, hogy alkalmasak legyenek a Post metódus támogatására. Az alábbi programrészlet azt mutatja be, hogy hogyan lehet átalakítani a második példaprogramot:

method main(args=String[]) static
 	args = args
 	say "Content-Type: text/html"
 	say " "
 	say "<html>"
 	say "<head><title>Információ az alkalmazottakról</title></head>"
 	say "<body>"
 	say "<H2>Az alkalmazott adatai</H2>"
 	say "<br> Program: "System.getProperty("SCRIPT_NAME") -
			"query length" System.getProperty("CONTENT_LENGTH")

	-- itt történik a standard input kiolvasása
 	list = BufferedReader(InputStreamReader(System.in)).readLine()
 	parse list "number=" empno " "

 	jdbcConnect()
 	performRetrieve()
 	say "</body>"
 	say "</html>"
return


REXX GYÍK:

K1. Mely web kiszolgálók alkalmasak servletek futtatására?
V1. Szinte mindegyik korszerû web kiszolgáló alkalmas a Java vagy NetREXX nyelven írt CGI programok futtatására. Néhány példa a sok közül: Apache, MS IIS, Lotus Domino, Netscape.

K2. Igaz-e, hogy a CGI programok használata nem biztonságos?
V2. Mivel a CGI programok a kiszolgálón futnak és az átadott paramétereket a böngészô által megadott URL tartalmazza, ezért elképzelhetô, hogy egy furfangosan megszerkesztett URL-lel olyan mûvelet hajtható végre, amellyel a kiszolgáló tönkretehetô. A fenti példaprogramok esetében pl. megadható olyan URL, amellyel az adtabázis törlését lehet kierôszakolni, amennyiben a CGI program rendelkezik a megfelelô jogosultságokkal. A problémák elkerülése érdekében végrehajtás elôtt tesztelni kell a megadott paramétereket és csak annyi jogosultságot kapjon a CGI program futtatója, amennyi a helyes mûködéshez feltétlenül szükséges.


Gyakorlat:

1. Írja át az elsô példaprogramot oly módon, hogy az mind a Get, mind pedig a Post metódust támogassa!

Kádár Zsolt
2000. 07. 28.
[ Elôzô lecke | Következô lecke | Tartalom ]