Jak na CGI
(aneb Daliho už ne tak úplně malinký průvodce s ručením omezeným)

Autor: Ing. Dalibor Šrámek, email: dali@kumbal.vse.cz


Obsah

  1. Úvodem
  2. Co je to CGI
  3. Kam umístit CGI
  4. Výstup z CGI skriptu (příklad v shellu)
  5. Hlavička CGI skriptu
  6. Jakým jazykem psát
  7. Prostředí CGI skriptu (příklad v Perlu)
  8. Definované proměnné prostředí
  9. Co je to ten QUERY_STRING
  10. Vstup dat do CGI skriptu
  11. Vstup metodou POST (příklad v C)
  12. Použití vstupních polí
  13. Nad dopisy čtenářů
  14. Užitečné dodatky (SSI, ...)

Úvodem

Tento text vznikl, jelikož mě několik přátel požádalo o radu při tvorbě CGI skriptů a nechtělo se mi to každému zvlášť opakovat. Pro jeho uspěšné strávení se předpokládají obecné znalosti operačního systému a programování. Konkrétní příklady jsou z prostředí Unixu. Obecné informace lze většinou využít i na jiných platformách.

Ručení omezené, zmíněné v podtitulu, se vztahuje zejména na tři skutečnosti:

Co je to CGI

CGI (Common Gateway Interface) je jednou z možností, jak zavést dynamiku do WWW stránek. V zásadě je to předpis pro program spouštěný na serveru, jak má číst data od WWW serveru a jak mu má data posílat. Výsledkem je, že když se odkazujete na link s CGI programem (obvykle nazývaným CGI skriptem), nenatáhne se do WWW klienta tento skript, ale výstup získaný jeho spuštění.

Klasickým příkladem jsou počítadla přístupu na WWW stránky, gatewaye k různým slovníkům a databázím nebo třeba obměňující se reklamy na WWW. Následující odkazy vedou k ukázkám CGI skriptů na serveru Kumbál, které dokumentují některé ze způsobů využití:

Kam umístit CGI

CGI skript nelze z důvodu bezpečnosti spouštět z jakéhokoliv místa na serveru. Obvykle je třeba dohodnout se se správcem serveru o podmínkách provozování CGI skriptů. Mimo jiné byste se měli dozvědět, kam skript umístit. Umístění souvisí také s problémem rozpoznání skriptu WWW serverem, který musí rozlišit, zda má soubor poslat klientovi nebo jej spustit. Rozlišení se provádí buď dle adresáře, kde soubor je (skripty jsou jen v definovanych adresářích), nebo dle koncovky souboru (skripty mívají .cgi,.pl).

Je také třeba zajistit, aby CGI skript byl spustitelný pro WWW server. V Unixovém prostředí běží WWW server obvykle s minimálnimi právy (např. uživatel nobody), proto musí mít skript nastavená práva pro spouštění jakýmkoliv uživatelem.

Abyste se vyhnuli zbytečnému hledání chyby ve skriptu, když ve skutečnosti je špatně nakonfigurovaný server, doporučuji nejdříve zkusit zcela jednoduché skripty - ideálně například níže uvedený výpis proměnných prostředí.

Určitou možností, jak spouštět skripty na serveru je také SSI.

Výstup z CGI skriptu

Jak již bylo uvedeno, je CGI skript v podstatě normální program. V unixovém prostředí to může být třeba běžný skript shellu, program v jazyce Perl nebo program zkompilovaný v nějakém z klasických jazyků (C, Pascal...).

Chcete-li, aby CGI skript měl nějaký výstup, musí jej zapisovat na zařízení STDOUT (standardní výstup - čili obvykle obrazovka). Následující příklad ukazuje velmi jednoduchý skript, který vypisuje datum (příklady jsou psány pro Bourne shell a jemu podobné).

#!/bin/sh
echo Content-type: text/plain
echo
date
První řádek určuje, jak se bude CGI skript interpretovat - konkrétně říká, že skript se má spustit pomocí shellu /bin/sh. Pokud je CGI skript napsaný v interpretovaném jazyce (shell, Perl), je nutné tento řádek uvádět. Pro kompilované programy pochopitelně nemá smysl.
Druhý a třetí řádek vypisuje povinnou hlavičku pro WWW server, která určuje typ přenášených dat. Hlavička je ukončena prázdným řádkem (třetí řádek skriptu) - tak server pozná, že následující data jsou již pro klienta. O formátu hlavičky bude ještě psáno dále.
Poslední řádek skriptu je příkaz vypisující datum.

Nazvete-li tento skript např. date.cgi a budete se na něj odkazovat linkem http://mujserver/mojecesta/date.cgi, server skript vykoná a výsledek v podobě aktuálního data zašle klintovi.

Drobnou změnou hlavičky docílíte možnosti formátovat výstup pomocí HTML.

#!/bin/sh
echo Content-type: text/html
echo
echo "<HTML>"
echo "<BODY>"
echo "<H3>Aktuální datum a cas</H3>"
echo "<B>"
date
echo "</B>"
echo "</BODY>"
echo "</HTML>"
HTML tagy se vypisují také na standardní výstup (povšimnete si, že v příkladu jsou uzavřeny do uvozovek, aby se zabránilo interpretaci speciálních znaků shellem).

Ještě poznámka o standardních I/O zařízeních pro nepříliš zkušené programátory. Se zařízením STDIN a STDOUT se obvykle pracuje těmi nejjednodušími rutinami vstupu a výstupu. V jazyve C jsou to například printf a puts, v Pascalu pak writeln.

Hlavička CGI skriptu

Jak jsme viděli, musí CGI skript vypisovat na STDOUT hlavičku pro klienta, která sestává minimálně z jednoho řádku a je ukončena prázdným řádkem. Podívejme se, co více se dá ještě s hlavičkou dosáhnout.
#!/bin/sh
echo Content-type: text/html
echo Pragma: no-cache
echo
Tato hlavička (přesněji pridaný druhý řádek) říká klientovi, že dokument nemá uchovávat ve své paměti cache, což je u proměnlivých výstupů z CGI skriptů velmi důležité.

Jinou možností je vrátit klientovi stavový kód:

#!/bin/sh
echo Content-type: text/html
echo Status: 200 OK
echo
Použitelné kódy jsou zejména: Například kód 204 způsobí, že klient nebude stránku natahovat, což se dá využít v případě špatného zadání vstupních hodnot - na obrazovce zůstane stále to stejné.

Samozřejmě, že ne všechny prohlížeče tyto kódy správně interpretují, ale v dnešní době jich už bude většina.

To co se vypisuje v uvedených případech nestačí jako kompletní hlavička HTML dokumentu. Server vždy doplní další hodnoty (nezadáte-li stavový kód, server jej vloží). Některé servery umožňují nakonfigurovat tak, že výstupy zadaných skriptů již nejsou kontrolovány (Non Parsed Headers), což může vést ke zvýšení rychlosti odezvy. Musíte však ve skriptu pečlivě generovat kompletní hlavičku.

Jakým jazykem psát

Ačkoliv příklady v tomto textu jsou psány jako skripty pro unixovský shell, není takový postup obecně vhodný. Důvodem je nepříliš velká efektivita, která znamená vyšší zátež serveru při spouštění (ve výse zmíněném skriptu, který vrací aktuální stav serveru, se například spouští více než 50 procesů).

Pro méně často spouštěné CGI skripty se doporučuje používat (v unixovém prostředí) jazyk PERL, který obsahuje mimo jiné velmi mocné nástroje pro práci s textem. Často spouštěné skripty je optimální vytvořit v nějakém kompilovaném jazyce. C a C++ sice neposkytují při programování skriptu tak silné prostředky, to je však vyváženo efektivitou provádění.

Při troše snahy lze dnes na Internetu nalézt slušné množství knihoven pro C a C++, které umožňují pohodlné použití těchto jazyků pro psaní CGI skriptů.

Prostředí CGI skriptu

Při spuštění CGI skriptu jsou nastaveny různé proměnné prostředí, ze kterých lze zjistit zajímavé informace. Vyzkoušejte následující příklad.
#!/bin/sh
echo Content-type: text/plain
echo
echo "Ukázka některých proměnných:"
echo "REMOTE_HOST = $REMOTE_HOST"
echo "REMOTE_USER = $REMOTE_USER"
echo "QUERY_STRING = $QUERY_STRING"
Předřazení znaku "$" oznámí shellu, že se jedná o proměnnou prostředí. Zavoláte-li tento skript, měli byste obdržet aktuální hodnoty adresy klienta a jméno uživatele (pokud máte unixového klienta).

Definované proměnné prostředí

Uveďme si nekomentovaný výpis skriptu, který vrací hodnoty různých proměnných prostředí (některé názvy hovoří samy za sebe, většinu ostatních pochopíte z dalšího příkladu).
#!/bin/sh
echo Content-type: text/plain
echo
echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
echo GATEWAY_INTERFACE = $GATEWAY_INTERFACE
echo SERVER_PROTOCOL = $SERVER_PROTOCOL
echo SERVER_PORT = $SERVER_PORT
echo REQUEST_METHOD = $REQUEST_METHOD
echo HTTP_ACCEPT = "$HTTP_ACCEPT"
echo PATH_INFO = "$PATH_INFO"
echo PATH_TRANSLATED = "$PATH_TRANSLATED"
echo SCRIPT_NAME = "$SCRIPT_NAME"
echo QUERY_STRING = "$QUERY_STRING"
echo REMOTE_HOST = $REMOTE_HOST
echo REMOTE_ADDR = $REMOTE_ADDR
echo REMOTE_USER = $REMOTE_USER
echo AUTH_TYPE = $AUTH_TYPE
echo CONTENT_TYPE = $CONTENT_TYPE
echo CONTENT_LENGTH = $CONTENT_LENGTH
A abychom okusili také z jiného soudku, tak teď funkčně podobný skript v PERLu.
#!/usr/bin/perl
print "Content-type: text/html\n\n";
while (($key, $val) = each %ENV) {
	print "$key = $val
\n";
}
Jak poznají i ti, kteří PERL nikdy neviděli, vypisuje tento skript v cyklu všechny proměnné prostředí, které jsou definovány. Funkční variantu si můžete vyzkoušet na serveru Kumbál.

Co je to ten QUERY_STRING

Do proměnné QUERY_STRING uloží WWW server při spouštění skriptu to, co bylo při jeho volání v URL za znakem "?". Zkuste zavolat skript předchozího příkladu takhle: http://mujserver/mojecesta/priklad.cgi?kukacka
Výpis QUERY_STRINGu by měl obsahovat slovo kukacka.

Vstup dat do CGI skriptu

Jednu možnost vstupu do CGI skriptu jsme již viděli v podobě QUERY_STRINGu, který lze zadávat přímo v URL. CGI skripty se však často používají ke zpracování dat z HTML formulářů. Představme si následující HTML soubor.
<HTML>
<BODY>
<FORM ACTION="/mojecesta/mujskript.cgi" METHOD="GET">
Zadejte prosím jméno:
<INPUT TYPE="text" NAME="jméno">
<BR>
Zadejte prosím příjmení:
<INPUT TYPE="text" NAME="prijmeni">
<BR>
<INPUT TYPE="submit" VALUE="odeslat">
</FORM>
</BODY>
</HTML>
Hodnota ACTION u tagu formuláře určuje skript, který se spustí po odeslání formuláře. Hodnota METHOD určuje způsob, jakým budou data z formuláře skriptu předána. V našem případě (metoda GET) bude daty naplněna proměnná QUERY_STRING. Formát proměnné bude následující: jmeno=zadanejmeno&prijmeni=zadaneprijmeni
Čili vidíme, že jednotlivá políčka jsou předána ve formě název=hodnota a jsou oddělena znakem "&".

Aby to bylo složitější, jsou speciální znaky vyskytující se v hodnotách proměnných zakódovány následujícím způsobem:

Jednotlivé prohlížeče se mírně liší v tom, které znaky kóduji, ale uvedená pravidla platí obecně. Pro dekódování tedy stačí výskyty znaku "+" nahradit mezerami a výskyty kombinací "%hh" nahradit znakem s příslušným ASCII kódem. Snadno tedy dekódujeme řetězec: Ahoj%20lidi%21.

Metoda GET má jak je zvykem své výhody a nevýhody. Výhodou je, že klient zasílá data pro CGI skript v URL. Pro výše uvedený formulář se bude volat URL: /mojecesta/mujskript.cgi?jmeno=zadanejmeno&prijmeni=zadaneprijmeni. Vidíme, že takový skript lze volat i ručně bez vyplňování formuláře. Řekněme, že bychom chtěli vytvořit skript, který by pracoval jako anglicko-český slovník. Vstup bychom mohli zabezpečit například následujícím formulářem.

<HTML>
<BODY>
<FORM ACTION="/mojecesta/slovnik.cgi" METHOD="GET">
Zadejte prosím hledané slovo:
<INPUT TYPE="text" NAME="slovo">
<BR>
<INPUT TYPE="submit" VALUE="odeslat">
</FORM>
</BODY>
</HTML>
Stejně dobře by ale tento slovník šel použít přímo zápisem: http://muj.server.cz/mojecesta/slovnik.cgi?slovo=meslovo.

To, že se data předávají jako součást URL je však zároveň nevýhodou metody GET. Prohlížeče totiž neumožňují neomezenou délku URL a tak se metoda GET nehodí pro větší objemy dat. Nezanedbatelné není ani to, že tyto řetězce se obvykle uschovávají v logu například na proxy serveru, pokud jej používáte, což znamená snížení již tak malého soukromí.

Vstup metodou POST

Omezení množství dat vstupujících do CGI skriptu odstraňuje metoda POST. CGI skriptu jsou v případě použití metody POST data směřována na standardní vstup. Skript může využít proměnnou prostředí CONTENT_LENGTH, která obsahuje celkovou délku dat v bytech. Postup je pak takový, že se zjistí z proměnné počet bytů k načtení a čte se ze standardního vstupu.

Dlouho jsem na tomto místě sliboval příklad - teď tady konečně je a abych zabil více much jednou ranou, tak je v jazyce C. Ve formě, kterou vidíte, by jej mělo jít bez problémů zkompilovat na Unixu. V jiných OS budou možná malinko jiné potřebné hlavičkové soubory a nahrazení funkce strcasecmp, která porovnává řetězce bez ohledu na velikost písmen.

Funkce příkladu:

Upozorňuji, že příklad neobsahuje žádnou kontrolu na chyby!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
 char *method;
 char *data;
 /* vypis hlavicky */
 puts("Content-type: text/plain\n");
 /* zjisteni zpusobu zaslani dat */
 method=getenv("REQUEST_METHOD");
 /* kontrola definice promenne */
 if (method==NULL)
 {
  puts("Chyba: neni definovana promenna REQUEST_METHOD!");
  exit(1);
 }
 /* zkopirovani QUERY_STRINGu, je-li metoda GET */
 if (!strcasecmp(method,"GET"))
 {
  data=strdup(getenv("QUERY_STRING"));
 }
 /* nacteni dat ze STDIN, je-li metoda POST */
 if (!strcasecmp(method,"POST"))
 {
  int length;
  length=atoi(getenv("CONTENT_LENGTH"));
  data=malloc(length+1);
  /* cteni daneho poctu bytu */
  fread(data,length,1,stdin);
  data[length]=0;
 }
 /* pokusny vypis datoveho retezce */
 puts(data);
 /* uvolneni pameti a konec */
 free(data);
 return(0);
}

Použití vstupních polí

Zatím jsme viděli v jaké podobě se CGI skriptu předají data získaná z políček formuláře typu text. Úplně stejně se předávají data z políček password a textarea.

Další prvky formuláře si uvedeme na příkladu:

Pokusný formulář
Políčko text:
Políčko password:
Políčko textarea:
Políčko checkbox:
1:
2:
3:
Políčko radio button:
1:
2:
3:
Políčko select:

Po odeslání se spustí CGI skript, který vypíše hodnoty všech proměnných zaslaných z formuláře.

Zdrojový kód formuláře si můžete prohlédnout, pokud zvolíte v klientovi zobrazení zdrojového kódu stránky. Kód CGI skriptu pro výpis proměnných je velmi jednoduchý:
#!/bin/sh

echo "Content-type: text/plain"
echo
echo "Výpis proměnných formuláře:"
echo
echo "$QUERY_STRING" | tr "&" "\n"
Vidíme, že pro tento skript je nutné zadávat data metodou GET. Filtr tr pak nahradí všechny znaky ampersand znakem konce řádky. Zaslaná data můžete také vypsat pomocí skriptu uvedeného jako příklad u metody POST.

Vraťme se ještě jednou k políčkům fomuláře v příkladu:

Přesné využití jednotlivých políček a jejich atributu je dobré nastudovat ve specifikaci HTML.

Nad dopisy čtenářů

V e-mailech se často ptáte, proč nechodí to či ono. Bohužel většinou může být příčin velké množství. Proto jen stručně shrnu, v čem mohou být chyby nejčastěji:

Užitečné dodatky

SSI

SSI neboli Server Side Includes jsou definované vsuvky v dokumentu HTML, které jsou interpretovány WWW serverem před předáním dokumentu klientovi. Server pro tuto činnost samozřejmě musí být nakonfigurován. Soubory, které tyto vsuvky obsahují, mají často přípony shtml nebo ssi.

SSI umožňují například vložit do stránky datum její poslední úpravy nebo výstup zadaného příkazu nebo CGI skriptu. Obecný formát vsuvky je následující:
<!--#příkaz parametr1=hodnota parametr2=hodnota ... -->

Použitelné příkazy a jejich parametry:

Příkladem častého využití SSI je počítadlo přístupů volané příkazem exec.
(c)1997,98 Dalibor Šrámek (dali@kumbal.vse.cz). Poslední úprava: 8. února 1998.