CGI scripty


Co je to CGI script?

Z počátku WWW server posílal klientům pouze statické dokumenty, které byly předem připraveny a vystaveny na serveru.

Brzy se však ukázalo, že by bylo výhodné, kdyby server mohl ovlivnit data dokumentu dynamicky v době požadavku. Klasickým případem jsou databázové aplikace. Není vhodné mít všechny možné výstupy předem staticky připraveny vzhledem k objemu a hlavně proměnlivosti dat. Je výhodné sestavovat odpověď přesně podle požadavku až v době požadavku. Úkoly tohoto typu řeší zavedení tzv. dynamických (virtuálních) dokumentů. Pokud klient žádá virtuální dokument, představuje URL v dotazu program, který má server spustit. Výsledek programu pak má server předat klientovi. Programy tohoto typu se nazývají CGI programy nebo CGI scripty.

CGI script je externí program, který je na požadavek od uživatele spuštěný WWW serverem jako samostatný proces.

CGI script je libovolný script nebo program. Jedinou podmínkou je schopnost programu komunikovat v WWW serverem. Program musí umět data přebírat od WWW serveru a musí umět předat serveru zpět výsledek. WWW server musí umět tento program nebo script spustit. Jako CGI může pracovat program tj. spustitelný soubor vzniklý překladem zdrojového textu napsaného např. v jazyce C. Nebo script (procedura) tj. sekvence příkazů interpretované např. programem Perl, Shellem v Unixu apod. Dále budu v textu používat termín CGI script.

CGI scripty jsou často vytvářeny ve scriptovém jazyce Perl nebo v některém shellu OS Unix. Z programovacích jazyků se používá např. jazyk C nebo C++, Visual Basic. Výběr jazyka závisí pouze na autorovi CGI scriptu, a také na tom, jaký jazyk má autor k dispozici.

Pravidla komunikace (rozhraní) mezi CGI scriptem a WWW serverem definuje norma CGI - Common Gateway Interface dostupná na http://hoohoo.ncsa.uiuc.edu/cgi. Existuje i český překlad této normy. Díky existenci normy pro CGI rozhraní je možné používat CGI scripty ve spojení s většinou existujících serverů a způsob použití je ve všech případech stejný.

CGI scripty přebírají data zadaná uživatelem, zpracovávají je a jako výsledek vytvářejí většinou html stránky. Tyto dynamicky vytvořené stránky pak WWW server posílá zpět klientovi. Pomocí CGI může server provádět výpočty, vyhledávat data v databázi nebo např. odesílat objednávky. Výsledkem scriptu může být nejen html stránka vytvořená podle požadavků uživatele ale i obrázek v gif formátu, nebo jiná data, která je možno zobrazit prohlížečem.

Komunikaci mezi klientem, serverem a CGI scriptem uvádí obrázek.

Kroky zpracování CGI požadavku

V případě, že klient požaduje od serveru statický dokument, server tento dokument vyhledá o odešle zpět klientovi.

V případě, že klient pošle CGI požadavek, pracuje server pouze jako prostředník mezi klientem a CGI scriptem.

Kroky komunikace při zpracování CGI požadavku:

  1. Klient pošle požadavek serveru.

  2. Pokud server rozhodne, že jde o CGI požadavek, založí CGI proces. Jediným účelem tohoto procesu je nastavit komunikaci mezi CGI procesem a serverem. Tento proces je jedním z procesů, které má server alokované, je proto třeba zajistit dostatečné množství alokovaných procesů. Pokud CGI script běží příliš dlouho má server o jeden proces méně k obsloužení dalších příchozích požadavků. Jelikož je CGI proces kopií procesu serveru má přístup k informacím z CGI dotazu. Např.:

  • CGI porces uloží informace z požadavku do poměnných prostředí. CGI proces také vytvoří spojení mezi serverem a externím (CGI) programem. Tj. server může poslat pragramu data od klienta a CGI script může poslat odpověď zpět klientovi prostřednictvím serveru. CGI proces může poslat data CGI programu na standardní vstup.

  • Po zpracování dotazu externím programem, odešle CGI proces data serveru, který je vrátí klientovi. Obsah odpovědi posílá CGI script přes svůj standardní výstup. Server převezme odpověď od scriptu, přidá k ní výsledkový kód a nezbytné hlavičky a pošle ji klientovi.
    Je také možné poslat odpověď vytvořenou CGI scriptem přímo klientovi jako tzv. nonparsed header dokument. V takovém případě však musí odpověď obsahovat všechny potřebné náležitosti, aby byla platnou odpovědí v HTTP protokolu a klient uměl tuto odpověď správně zobrazit uživateli.

    Bezpečnost a CGI scripty

    CGI scripty jsou vhodným nástrojem pro interakci s uživateli, mohou však představovat určité bezpečnostní riziko. Při vytváření CGI scriptů je třeba dodržovat tato pravidla:

    Přístup k CGI přes URL

    Server při obdržení dotazu od klienta musí určit, zda jde o dotaz na statický dokument nebo o dotaz na CGI script a má tedy CGI script spustit. K rozpoznání CGI dotazu používá server dvě metody. Žádná v těchto metod není používána serverem implicitně, její volba se provádí v konfiguraci serveru.
    1. Je možné specifikovat MIME typ pro CGI script a uložit script do adresáře spolu s html dokumenty. Při použití této metody je každý soubor s příponou cgi spuštěn jako CGI script. Tato možnost je pružnější, ale vyžaduje opatrnost, o které jsme mluvili v odstavci o bezpečnosti.

    2. Je možné specifikovat adresář, ve kterém budou CGI scripty uložené. Tato metoda poskytuje větší kontrolu nad tím, kdo scripty vytváří a kdo k nim má přístup. V konfiguraci serveru se namapuje adresář s cgi scripty na virtuální cestu. Každé URL, které ukazuje na soubor v tomto adresáři je serverem interpretováno jako požadavek na spuštění CGI scriptu. Často se používá virtuální adresář cgi-bin, který se mapuje na adresář cgi-bin pod root adresářem WWW serveru.
    V konfiguraci serveru je také potřeba povolit spouštění CGI scriptů. Pokud se při pokusu o spuštění scriptu objeví chybová hláška v errorlogu nebo se text programu zobrazí v klientově okně, pak nejsou CGI aktivovány v konfiguraci.

    Rozšiřující informace v URL

    URL, které spouští CGI script je možné rozdělit do tří částí:

    [virtuální cesta][doplňkové informace]?[vstupní řetězec]

    Volání CGI scriptu je možné pomocí linky v html stránce. Tabulka uvádí příklady URL, které ukazují na CGI. Ve všech příkladech je jméno CGI programu /misc/search.cgi a root serveru je fyzický adresář /netscape/dosc. Způsob předání dat do CGI scritpu bude vysvětlen později.

    Na disku předpokládáme následující adresářovou strukturu:

    URLPopis
    http://www.server.cz/misc/search.cgiJednoduché URL na CGI script uložený v adresáři /netscape/docs/misc/search.cgi bez extra informací a vstupního řetězce
    http://www.server.cz/misc/search.cgi/type=minimalToto URL používá doplňkové informace type=minimal. Efekt je podobný jako poslání stejných informací pomocí vstupního řetězce
    http://www.server.cz/misc/search.cgi/misc/movies.mdbJe předávána doplňková informace /data/movies.ndb např. pro vyhledání čehosi v databázi /netscape/dosc/data/movies.mdb
    http://www.server.cz/misc/search.cgi?netscapeURL pro automatické vyhledání slova netscape bez zadávání uživatelem
    http://www.server.cz/misc/search.cgi/data/movies.mdb?netscape URL pro automatické vyhledání slova netscape v databázi /netscape/dosc/data/movies.mdb
    (Tabulka předpokládá metodu GET).

    Vstup od uživatele

    Existují tři způsoby, jak může klient předat data CGI scriptu:

    HTML formulář

    Pokud jsou zadána data do formuláře jsou před přenosem na server kódována metodou aplication/x-www-form-urlencoded. Toto kódování má dvě pravidla:
    1. Mezery jsou nahrazeny znakem +.
    2. Některé znaky jsou nahrazeny sekvencí %xx, kde xx je hexadecimální hodnota původního znaku. Tabulka uvádí některé z často používaných znaků:

      ZnakKódování
       : (dvojtečka) %3A
       \ (obrácené lomítko) %5C
       / (lomítko) %2F
       % (procento) %25
       " (uvozovky) %22
       , (čárka) %2C
       & (amprsand) %26
       ( (levá závorka) %28
       ) (pravá závorka) %29

      Nekódují se znaky anglické abecedy, číslice, tečka, pomlčka a podtržítko.

    Příklad: Takto vyplněný formulář

    odešle klient při použití metody POST dotaz ve tvaru:

    POST /cgi-bin/objed.pl HTTP/1.0
    Accept: text/html
    Accept: image/gif
    Accept: image/jpeg
    Accept: video/mpeg
    User-Agent: Mozilla/3.1N
    From: novak@poc.firma.cz
    Content-Type: application/x-www-form-urlencoded
    Content-length: 104
       * prazdny radek *
    reply=novak@firma.cz
    &nazev=Tvorba+WWW
    &jmeno=Jan+Novak
    &ulice=Pod+lesem+25%2F113
    &mesto=Veseli+nad+Luznici
    &stravovani=ano
    &ubytovani=ano
    

    odešle klient při použití metody GET dotaz ve tvaru:

    GET /cgi-bin/objed.pl?reply=novak@firma.cz&nazev=Tvorba+WWW&jmeno=Jan+Novak
    &ulice=Pod+lesem+25%2F113&mesto=Veseli+nad+Luznici&stravovani=ano
    &ubytovani=ano HTTP/1.0
    Accept: text/html
    Accept: image/gif
    Accept: image/jpeg
    Accept: video/mpeg
    User-Agent: Mozilla/3.1N
    From: novak@poc.firma.cz
       * prazdny radek *
    
    

    Pro úplnost uvádím i zdrojový text formuláře:

    <H1>Objednávka</H1> <HR SIZE=7><P> <FORM METHOD="POST" ACTION="/cgi-bin/objed.pl"> <INPUT TYPE=HIDDEN NAME=subject VALUE="Objednavka zbozi"> <TABLE> <TR><TD><B>Odesilatel (e-mail):</B></TD> <TD><INPUT NAME=reply SIZE=40 MAXLENGTH=40></TD> <TR><TD><STRONG>Název semináře:</STRONG></TD> <TD><INPUT TYPE=TEXT NAME=nazev SIZE=40></TD> <TR><TD><STRONG>Jméno a příjmení</STRONG></TD> <TD><INPUT TYPE=TEXT NAME=jmeno SIZE=40></TD> <TR><TD><STRONG>Adresa:</STRONG></TD> <TD><INPUT TYPE=TEXT NAME=ulice SIZE=40></TD> <TR><TD><STRONG></STRONG></TD> <TD><INPUT TYPE=TEXT NAME=mesto SIZE=40></TD> <TR><TD><B>Objednavame:</B></TD> <TD><INPUT TYPE=CHECKBOX NAME=ubytovani VALUE=ano>ubytovani</TD> <TR><TD></TD><TD><INPUT TYPE=CHECKBOX NAME=stravovani VALUE=ano>stravovani<BR> </TABLE> <HR> <TABLE> <TR><TD><INPUT TYPE=SUBMIT VALUE="Odeslani objednavky"></TD> <TD><INPUT TYPE=RESET VALUE="Vymazani zadanych hodnot"</TD> </TABLE> </FORM>

    Data jsou předávána CGI scriptu v závislosti na použité metodě, která je specifikována v atributu METHOD ve formuláři.

    Použití metody GET je limitováno množstvím informací odesílaných v dotazu. Ve formulářích je výhodné používat metodu POST.

    Vždy mají data tento tvar: jmeno1=hodnota1&jmeno1=hodnota1...&jmenon=hodnotan
    kde & je oddělovačem jednotlivých položek a jmenoi je označení položky ve formuláři.
    Pokud se v hodnotách vyskytne znak = nebo & je kódován zmíněných kódováním.

    CGI script tedy musí data dekódovat. Musí nejprve rozdělit jednotlivé části vstupu tj. odstranit znaky & a poté nahradit sekvence %xx původním znakem. Pořadí položek na vstupu je totožné s pořadím položek ve formuláři.

    Jednotlivé typy vstupních polí formuláře mají svá pravidla při předávání hodnot:

    Vstupní pole ISINDEX

    Při použití pole ISINDEX jsou data předána jako parametr příkazového řádku. Dekódování provádí CGI proces. Váš CGI script tedy obdrží data jako argument příkazového řádku již dekódovaná.

    Dnes již se tato možnost příliš nepoužívá, nebot pomocí formuláře je možné řešit stejný problém konfortněji.

    Citlivá mapa

    V případě citlivé mapy jsou data předávána pomocí proměnné prostředí QUERY_STRING a mají tvar xx,yy kde xx a yy jsou souřadnice bodu na mapě. Script, který má tato data zpracovat je určený přímo v konfiguraci serveru.

    Použití proměnných prostředí

    Proměnné prostředí jsou často používané. Do proměnných prostředí server ukládá nejen data předaná v URL, ale i informace o spojení s klientem a informace o serveru samotném.

    Cgi scripty mohou převzít informace z proměnných prostředí různým způsobem. Způsob se liší podle použitého programovacího nebo scriptového jazyka. Proměnné prostředí jsou označeny jménem a obsahují hodnotu ve tvaru textového řetězce. Uveďme příklady použití proměnných prostředí pro některé jazyky:

    V C a C++
    se použije knihovna getenv, tedy:
    #include <STDLIB.h>
    char *rhost = getenv("REMOTE_HOST");
    
    V Perlu
    jsou proměnné prostředí přístupné pomocí pole @ENV, tedy:
    $rhost = $ENV{'REMOTE_HOST'};
    
    V Bourne shellu (/bin/sh)
    jsou proměnné prostředí přístupné jako všechny proměnné shellu, tedy:
    RHOST=$REMOTE_HOST
    

    Proměnné prostředí

    Následující proměnné prostředí nejsou závislé na požadavku klienta a jsou nastavovány vždy.
    SERVER_SOFTWARE
    Jméno a verze softwaru serveru, s nímž bude CGI script komunikovat.
    Formát: jméno/verze
    Příklad: Netscape-Comunication/1.1
    SERVER_NAME
    Internetovská adresa nebo jméno serveru.
    Formát: Plné doménové jméno nebo IP adresa
    Příklad: 194.149.119.37 nebo info.pvt.net
    GATEWAY_INTERFACE
    Verze CGI, kterou server používá.
    Formát: CGI/verze
    Příklad: CGI/1.1
    Následující proměnné prostředí jsou závislé na požadavku klienta a naplňuje je server.
    SERVER_PROTOCOL
    Jméno a verze protokolu, kterým žádal klient o dokument.
    Formát: protokol/verze
    Příklad: HTTP/1.0
    SERVER_PORT
    Číslo portu, na který byl poslán požadavek od klienta. V podstatě číslo portu, na kterém čeká server, který spustil náš CGI program.
    Formát: číslo od 1 do 65535
    Příklad: 80
    REQUEST_METHOD
    Metoda, kterou klient použil při odesílání požadavku. Pro HTTP to může být GET, POST, HEAD. Při aktivaci linky v html dokumentu se použije metoda GET.
    Formát: metoda
    Příklad: GET
    PATH_INFO
    Zde jsou předávány extra informace např. jméno adresáře. Tyto informace jsou uvedeny v URL za cestou ke scriptu.
    Formát: /adr1/adr2...
    Příklad: /html/obr/puntik.gif
    PATH_TRANSLATED
    Obsahuje překlad proměnné PATH_INFO na fyzickou cestu, tj. vztaženou ke kořenovému adresáři filesystému.
    Formát: /adr1/adr2...
    Příklad: /netscape/html/obr/puntik.gif
    SCRIPT_NAME
    Zde je uvedeno jméno spuštěného scriptu i s virtuální cestou.
    Formát: /adr1/adr2/jméno_skriptu
    Příklad: /cgi-bin/prg1.pl
    QUERY_STRING
    Informace předávané scriptu pomocí metody GET,ISINDEX. Část URL následující za ?.
    Formát: různý
    Příklad:tlac1=on&tlac2=off nebo Jan+Novak
    REMOTE_HOST
    Počítač, který vyvolal požadavek. V případě, že server tuto informaci nezná, musí nastavit REMOTE_ADDR a nechat tuto poloľku prázdnou.
    Formát: host.subdomena.domena
    Příklad: info.pvt.net
    REMOTE_ADDR
    IP adresa počítače, který vyvolal požadavek.
    Formát: n.n.n.n
    Příklad: 194.149.199.45
    AUTH_TYPE
    V případě, že server podporuje ověření autentičnosti a CGI program je chráněn, pak je zde uvedena autentifikační metoda, užívaná pro ověření uživatele.
    Příklad: basic
    REMOTE_USER
    V případě, že server podporuje ověření autentičnosti a CGI program je chráněn, pak je zde uvedeno jméno pod jakým je uživatel identifikován.
    Příklad:novak
    CONTENT_TYPE
    Pro požadavky, které mají přídavné informace, jako jsou metody HTTP protokolu POST a PUT, je zde uveden typ dat. Informace jsou stejné jako v hlavičče HTTP protokolu Content-type. Pokud klient pošle application/x-www-form-urlencoded, je proměnná prázdná.
    Formát: typ/podtyp
    CONTENT_LENGTH
    Délka dat v bytech předávaných klientem. V případě, že žádná data předávaná nejsou, pak není tato proměnná nastavena.
    Příklad: Content-Length: 65
    K těmto proměnným mohou přibývat další. Další hodnoty jsou položky z HTTP hlavičky požadavku a bývají uloženy v proměnných prostředí se jménem začínajícím HTTP_. Písmena názvu položky jsou převedena vždy na velká písmena a znak - na _. Server nemusí v tomto tvaru uložit položky, které jsou dostupné v jiných proměnných, např: CONTENT-TYPE, CONTENT-LENGTH. Data z položek jsou pouze přepsána do proměnné. Když je v HTTP hlacičce více položek stejného jména, pak data z těchto položek, jsou spojena do jedné proměnné prostředí.

    Jako příklad uvedu několik proměnných:

    HTTP_ACCEPT
    MIME typy, které může klient přijmout. Toto se v HTTP protokolu zadává v hlavičce Accept. Typy budou uloženy v této proměnné odděleny čárkami, stejně jako v HTTP.
    Formát: typ/podtyp, typ/podtyp
    Příklad: image/gif, image/jpeg
    HTTP_USER_AGENT
    HTML prohlížeč, krerý vyvolal požadavek.
    Formát: různý
    Příklad: Mozilla/3.1N (Windows)

    Použití standardního vstupu

    Pokud použije klient při odeslání požadavku metodu POST, jsou data předávána na standardní vstup CGI scriptu. Pomocí proměnné CONTENT_LENGTH je možné určit počet bytů, které je potřeba přečíst ze standardního vstupu. Není zaručeno, že na konci dat bude uveden znak pro konec souboru (EOF).

    Použití parametru příkazového řádku

    Předávání dat do CGI scriptu jako parametr příkazové řádky se používá pouze při použití vstupního pole ISINDEX na původní html stránce. Tento způsob je možné použít pouse v metodě GET a HEAD, nelze použít ve formulářích. Požadavek chápeme jako ISINDEX pokud URL obsahuje za ? řetězec bez znaku = (nekódovaného). Jednotlivé části řetězce za otazníkem jsou odděleny znakem +. Kódované znaky jsou CGI procesem dekódovány a předány CGI scriptu.

    Výstup z CGI scriptu

    Obvykle CGI program posílá odpověď klientovi prostřednictvím serveru. Proto se CGI program nemusí starat o hlavičky týkající se protokolu. CGI script tedy může být jednodušší a nezávislý na změně protokolu. CGI script musí generovat pouze hlavičky vztahující k obsahu odpovědi. Pokud, ale máte dobré znalosti HTTP protokolu, můžete poslat odpověď klientovi přímo použitím nonparsed header vlastností.

    Jako odpověď na CGI dotaz dostane klient odpověď ve tvaru:

    stavový řádek
    http hlavička
    http hlavička
    ..
    http hlavička
    
    data
    
    - doplní server
    - některé doplní server
    .
    - některé generuje CGI script
    .
    - CRLF generuje CGI script
    - generuje CGI script

    Příklad jednoduché odpovědi generované CGI scriptem:

    Content-type: text/html
    
    <HTML><HEAD>
    <TITLE>Vystup z CGI scriptu</TITLE>
    </HEAD><BODY>
    <H1>Jednoduchy vystup</H1>
    Co si o <STRONG>tom</STRONG> myslite?
    </BODY></HTML>
    

    Nonparsed headers - vynechání serveru při odpovědi

    Nonparsed headers umožňují vynechat při posílání server a poslat odpověď přímo klientovi.

    Tato metoda se např. u serverů NCSA, Netscape, Apache aktivuje pokud jméno CGI scriptu začíná prefixem nph-. Jakmile aktivujete tuto metodu, je CGI script odpovědný za všechny hlavičky a výsledkový kód vztahující se k protokolu jak je patrné z příkladu:

    HTTP/1.0 200 OK
    Date: Monday, 13-JAN-97 12:31:05 GMT
    Server: Netscape-Comunication/1.1
    MIME-version: 1.0
    
    <HTML><HEAD>
    <TITLE>Vystup z CGI scriptu</TITLE>
    </HEAD><BODY>
    <H1>NPH vystup</H1>
    Telo dokumentu
    </BODY></HTML>
    

    CGI script musí odpovědět přesně podle HTTP protokolu,kterým komunikuje klient. Pokud to není nezbytně nutné je lépe tuto metodu nepoužívat.

    Hlavičky nejčastěji generované CGI scriptem

    Odpověď generovaná CGI scriptem posílaná serveru musí začínat obecnými hlavičkami. Hlavičky jsou textové řádky ve tvaru: jmeno: hodnota, jde o hlavičky HTTP protokolu. Konec záhlaví odpovědi je indikován prázdnou řádkou. Po obdržení prázdné řádky končí server s analýzou hlaviček a posílá zbytek dat klientovi nezměněný. CGI script může generovat libovolnou hlavičku a server ji odešle klientovi. Některé z běžně používaných hlaviček uvedeme. Další jsou součástí popisu HTTP protokolu. Pokud některou z uvedených hlaviček vygeneruje CGI script, pak tuto hlavičku server již nemodifikuje.

    Výsledkový kód

    Pokud by chtěl CGI script nastavit jiný než implicitní výsledkový kód (200 OK nebo 302 Found v případě přesměrování) může odeslat na standardní výstup řetězec:

    Status: XXX doprovodný text

    kde XXX je požadovaný návratový kód.

    Techniky CGI scriptu

    V prostředí Unixu, jsou CGI scripty často psány v Bourne shellu, C-shellu a Perlu.

    Je-li CGI script napsán v Unixu, pak se operačnímu systému musí sdělit, jaký interpret má spustit, na první řádce cgi scriptu se uvede cesta k interpretu použitého jazyka. Tato první řádka je např.:

    Je-li CGI script napsán jako spustitelný (zaveditelný) program např. v jazyce C, pak tuto skutečnost operační systém sám pozná. Příklad jednoduchého Scriptu, který klientovi zašle výpis systémových proměnných. Script je napsaný v C shellu:

    #!/bin/sh
    echo "Content-Type:text/html"
    echo ""
    echo "<TITLE>Systemove promenne </TITLE>"
    echo "<H1>Obsah systémových proměnných při provádění CGI </H1>"
    echo "<PRE>"
    set
    echo "<-PRE>"
    

    Soubor obsahující CGI script musí mít nastaveno právo pro spuštění. V OS Unix nastavení zajistí příkaz:
    chmod 755 jmeno_souboru
    .