- předchozí článek - následující článek - obsah - úvodní stránka -

Linuxové noviny 03-04/99

Popis knihovny cgihtml, aneb jak na CGI z C

Marek Sezemský, 23. březen 1999

Pár slov úvodem

Jestli chcete zvládat (a ovládat) CGI na Internetu, a to z jazyka C, tak to čtete ten správný článek (ve správných novinách ;-)). Ale proč C a ne Perl, který se pro CGI používá dosti hojně? Proti Perlu (ani jiným jazykům) rozhodně nic nemám, zcela jistě jsou velice výkonné a použitelné - zvlášť Perl, ale v C už nějaký ten měsíc dělám, a tak je pro mne snažší se naučit používat nějakou tu knihovnu, než se učit Perl (i přes jeho jednoduchost ;-)). Navíc používání C se mi zdá takové "univerzálnější" (teď se asi Perláci smějí, ale je to můj skromný názor a nikdo mi ho neveme). Takže bychom za sebou měli otázky jazyka a jdeme dál.

CGI: O co jde?

O vzniku CGI bych moc rád psal ono "v dávných dobách Internetu", ale bohužel nemohu, neboť bohužel ani netuším, jak to tehdy vypadalo. Ale jedno vím jistě: CGI znamená Common Gateway Interface. Pokud bych se pokusil o překlad, vzniklo by Společné Bránové Rozhraní. Bohužel, tohle nic moc neříká, takže to vysvětlím: CGI je rozhraní, pomocí kterého Web server spouští aplikace a jejich výstup posílá přes Internet klientovi. Tímto výstupem může být GIF(JPEG) obrázek v případě počítadla nebo reklamy, HTML soubor pokud se jedná například o výsledek hledání. Prostě výstupem může být cokoliv. Napsal jsem rozhraní: to z toho důvodu, že se nejedná o žádný nový typ spustitelných souborů, nebo snad o něco, co se kompiluje k serveru (což u MS IIS ani nejde ;-)). CGI programem se rozumí jakákoliv aplikace, schopná zpracovat vstup z formuláře, a posléze něco kloudného poslat na výstup klientovi. Ony parametry se CGI programu předají pomocí hodnot proměnných prostředí (envrionmentu), které nastaví server. Prostě pomocí toho, kde máte PATH, HOME, MAIL a další (pro nechápavé: v shellu zkuste set a uvidíte). Takže server nastaví několik hodnot tohoto prostředí, do kterého mimo jiné vloží i parametry z CGI, spustí program a jeho výstup poté odešle klientovi. Zpravidla je tento výstup odesílán až po ukončení CGI programu, lze jej ale odesílat i postupně. Ale to vše si osvětlíme později. Takže ty proměnné prostředí. Víme že je server nastaví. Ale které? To samé jsem si říkal při psaní předchozí věty (opravdu). Řešením je takový malý testovací program. Jmenuje se test.cgi.c, takže po přeložení u GNU C s parametrem -o máme test.cgi.


#include <stdio.h>

int main(int argc, char **argv, char **env)
{
	int i = 0;

	printf("Content-type: text/plain\n\n");

	while ( env[i] != NULL )
		printf("%s\n", env[i++]);

	return (0);
}

Výpis č. 2: Program test.cgi.c

Jelikož se jedná o C program, stačí ho přeložit a poté zkopírovat tak, aby ho webserver "viděl". Takže pokud jste root, bude to asi (u RedHatů) /home/httpd/, pokud nemáte to štěstí být root, ale máte to štěstí a jste uživatel Linuxu, zkuste ve vašem domovském adresáři něco jako public_html, nebo html. Poté stačí spustit Lynx nebo Netscape (popřípadě něco z KDE) a napsat URL našeho souborku. Takže u mě (já == root ;-)) je uložen jako /net/httpd/html/test.cgi a z Lynxe http://www.sezi.cz/test.cgi. Není to klasické uložení v /cgi-bin, ale nechce se mi to psát tak dlouze.

U programu si všimněte prvního vypisovaného řádku. Pokud jste četli Co je to HTTP a HTML?, tak jistě víte, že Content-type označuje co za data to leze ke klientovi. text/plain značí textový soubor, pokud bychom uvedli text/html, jde o HTML soubor. Odesílat můžete klidně i obrázek nebo video - to záleží na vás. Nenechte se zmýlit dvojicí nových řádků - po ukončení hlavičky dokumentu MUSÍ následovat prázdný řádek, jinak klient nepozná, kdy končí hlavička a začínají data.

Zatím pouze CGI spouštíme, ale to jistě nestačí. Bude potřeba mu nějak předat ony parametry, se kterými se bude pracovat. A to pomocí formuláře z HTML stránky. Ve formulářích jsou vstupní pole (prvky), která mají svá jména (u <input> parametr name) a hodnoty (value). Ty se do CGI předají jako rozšíření URL. Takže např program test.cgi na www.sezi.cz s parametrem jmeno, který má hodnotou Marek se spustí jako http://www.sezi.cz/test.cgi?jmeno=Marek. Pokud bychom měli ještě prijmeni s hodnotou Sezemsky, pak by URL vypadalo takhle:

http://www.sezi.cz/test.cgi?jmeno=Marek\
       &prijmeni=Sezemsky

Pokud ale uživatel do formuláře zadá jiné znaky než abecedu a čísla (např. české háčky a čárky), parametry jsou klientem konvertovány do podoby %XX, kde XX je hexadecimální kód znaku (dvouznakově - %01 a ne %1). Tento styl předávání parametrů (přímo v URL) se nazývá GET (u <form> parametr method). Má několik nedostatků: maximální délka vstupu je něco kolem 240 bajtů (neručím za správnost) a takovýto požadavek se objeví v logu serveru, což je nevhodné např. pro předávání hesel, čísel kreditních karet apod. Druhou metodou je POST. Ten odstraňuje oba nedostatky: velikost vstupu je (teoreticky) neomezená (přenesl jsem trochu přes 3MB a vše bylo v pořádku) a v logu se zobrazuje pouze URL na CGI. Asi se ptáte, jak je to možné? Velice snadno. Jak víte, klient při požadavku o stránku (obrázek, ...) odešle v první řádce text GET, který následuje URL dokumentu rozšířené o parametry pro CGI program. U metody POST odesílá POST ;-), a poté opět URL. Zakončí to verzí HTTP. Jako další řádek přidá Content-length a délku požadavku, který odešle za prázdným řádkem. Pro názornost:

Klient:

POST /test.cgi HTTP/1.1
Content-length: 10

name=marek

Server:

HTTP/1.0 200 OK (zde začíná výstup z CGI):
Content-type: text/plain

data odeslaná CGI programem

a pro GET:

Klient:

GET /test.cgi?name=marek HTTP/1.1

Server:

HTTP/1.0 200 OK (zde začíná výstup z CGI):
Content-type: text/plain

data odeslaná CGI programem

Takže metody přenosu máme za sebou a teď se vrátíme k proměnným prostředí. Jako hlavní nás bude zajímat REQUEST_METHOD. Obsahuje název způsobu předání parametrů: GET nebo POST. Další je QUERY_STRING. Zde jsou v případě GETu umístěny parametry pro CGI (jmeno=Marek&prijmeni=Sezemsky). Pokud použijete POST, je QUERY_STRING prázdný a parametry jsou na stdin (takže použijete gets()). Zajímavé jsou i REMOTE_HOST a REMOTE_ADDR, které obsahují jméno (linux.sezi.cz) a IP adresu (10.0.0.1) počítače, odkud byl CGI vyvolán (klient). To se dá využít v případě logování záznamů o přístupech. Toliko ty proměnné, které používám. Pokud si spustíte moje testovací CGI (samozřejmě přes web a ne z shellu), spatříte všechny hodnoty, které jsou definovány a které tedy můžete použít. Ale pozor na to, že starší servery, popřípadě servery od Microsoftů nemusí podporovat vše.

Knihovna cgihtml

Takže už víme, jak se CGI spouští, jak mu server předává parametry a různé informace, ale pořát nevíme, jak vydolovat nějaké v programu použitelné hodnoty. Máme dvě možnosti: buď si vytvoříte (ano, je to na vás) novou knihovnu, která bude obsluhovat vše od načtení vstupních parametrů z env[], přes kontrolu vstupu, až po upload souborů (ano, i to lze přes CGI vytvořit). Už asi tušíte, že tohle nebude cesta, po které se vydáme. Takže zkuste navštívit stránku E.E. Kima, na které si obstaráte knihovnu http://www.eekim.com/software/cgihtml/. Ve formátu .tar.gz má pouze pár desítek kB, a to včetně dokumentace a příkladů. Lze spustit jak pod Linuxem (takže Unixem), tak i pod Windows. Během tří měsíců se mi velice osvědčila a dobře se s ní píšou i ty nej-CGI aplikace, mezi které patří i onen upload. Samozřejmostí je schopnost načíst data z HTML formulářů, a to jak přes GET, tak i přes POST, dále obsahuje i pár funkcí obsluhujících HTML, a další. Rozhodně inteligentní vlastností je ta, že pokud je CGI program využívající tuto knihovnu spuštěn z terminálu (tj. klasický shell, X-Win, MC, ...) požádá si o parametry. Po jejich zadání program prostě jede dál a neví, jestli jede přes web, nebo shell. Dobře se s tím testuje a ladí, neboď webserver při chybě v CGI (core dump a pod.) zobrazí pouze nic neříkající 5xx Server error.

Práce s knihovnou cgihtml je velice jednoduchá a naučíte se ji ovládat během hodinky. Knihovna obsahuje několik částí:

  • cgi-lib: obstarává vše kolem CGI
  • html-lib: vše kolem HTML (nemusíte používat)
  • cgi-llist: vše kolem seznamu CGI parametrů (nemusíte používat)
  • string-lib: řetězce (také není potřeba)

Ze začátku bude asi nejlepší nějaký ukázkový příklad, který si popíšeme:


#include <stdio.h>
#include "cgihtml/cgi-lib.h"
#include "cgihtml/html-lib.h"

int main()
{
	llist cgi_data;
	int status;

	html_header();
	html_begin("Pokusna stranka");

	switch ( status = read_cgi_input(&cgi_data) ) {
	case -1:
		printf("Chyba v CGI vstupu<br>\n");
		break;

	case 0:
		printf("Bez parametru<br>\n");
		break;

	default:
		printf("Pocet parametru: %d.<br>\n", status);
		break;
	}

	html_end();	
	list_clear(&cgi_data);

	return (0);
}

Výpis č. 3: Program priklad1.cgi.c

Pár řádek pro neznalé: Knihovnu přeložíte pomocí příkazu make v adresáři knihovny (např. cgihtml-1.69). Ten zkompiluje jednotlivé .c soubory a vytvoří statickou knihovnu cgihtml.a (něco kolem 50kB), kterou slinkujete s Vaším CGI programem. Takže pokud knihovna je v podadresáři cgihtml, pro přeložení našeho příkladu stačí provést:

$  make -C cgihtml      # Zkompletuje knihovnu
$  gcc -o priklad1.cgi priklad1.cgi.c\
   cgihtml/cgihtml.a

Pro další programy stačí samozřejmě uvést pouze druhý řádek. Pokud vás děsí velikost výsledného .cgi která koluje kolem 40kB, stačí soubor "odstripovat" pomocí strip priklad1.cgi, což soubor podstatně zeštíhlí na nějakých 13kB, a to je skutečné nic (nestripujte knihovnu). Takže první kompilaci máme za sebou a můžeme program spustit. Činnost programu je opravdu velice minimální: pouze si zjistí, zda byl spuštěn s parametry nebo ne, a vypíše na to svůj názor. Při spouštění z webového klienta se jeví úplně normálně, ale zkuste jej spustit z shellu: při volání f-ce read_cgi_input() se ohlásí dříve zmiňovaná výhoda cgihtml knihovny: vstup můžete zadat ručně! Ale dost chvály a pojďme si jej rozebrat řádek po řádku.

Ihned na začátku je vkládání hlavičkových souborů. Jeden systémový a dva z cgihtml. Funkce main() používá dvě proměnné: cgi_data, což je seznam s CGI parametry a status - návratovou hodnotu pro read_cgi_input(). Všimněte si i konstrukce switch, díky které můžete CGI rozvětvit přehledně a jednoduše podle status-u v případě že znáte možné počty parametrů jdoucí do CGI. read_cgi_input() totiž vrací -1 v případě chyby čtení (např. nesprávně kódované parametry), nulu pokud nebyl zadán žádný parametr a ve všech ostatních případech počet parametrů. Jako první se ale vykonávají html_header() a html_begin(). První odešle HTTP hlavičku ("Content-type: text/html").Druhá začne HTML dokument s titulkem (což je parametr). Takové "uklízecí" jsou html_end() a list_clear(), které ukončí HTML dokument a uvolní dynamicky alokované pole uvnitř cgi_data. Není na škodu si tyto dvě funkce definovat jako exit funkce (viz atexit(3)).

Pro přístup k CGI parametrům použijeme funkci cgi_val(), která má dva parametry: jméno pole s CGI (cgi_data) a jméno parametru z formuláře ("prijmeni"). Funkce vrací textovou podobu parametru: takže pro volání cgi_val(cgi_data, "prijmeni") by hodnota vrácená funkcí byla "Sezemsky". Dávejte si pozor i na to, že u read_cgi_input(), a u list_clear() jsou parametry ukazatele a u cgi_val() a ostatních nejsou. Další užitečnou funkcí je i cgi_val_multi(), která vrací pole hodnot (char **), které mají shodné jméno. Doporučuji si i někde v programu definovat makro třeba CGI, které vám zjednoduší přístup k CGI hodnotám.

#define CGI(Name)   cgi_val(cgi_data, Name)

Toto samozřejmě není kompletní popis knihovny, ale pro základní používání bohatě postačí. Pokud máte zájem si knihovnu nastudovat celou, stačí si přečíst rozsáhlou dokumentaci přiloženou u knihovny.

Nyní si doděláme onen příklad s formulářem z článku Co je to HTTP a HTML?. Pro shrnutí: parametry "jmeno", "vek", "pohlavi", "zajmy", "inet" a "pozn". S těmito parametry nebudeme pro ukázku nic dělat, ale díky jazyku C se dají snadno uložit do souboru....


#include <stdio.h>
#include "cgihtml/cgi-lib.h"
#include "cgihtml/html-lib.h"

#define CGI(Name)   cgi_val(cgi_data, Name)

int main(void)
{
  llist cgi_data;

  html_header();
  html_begin("Výstup z formuláře");

  switch ( read_cgi_input() ) {
    case -1:
      h1("Chybný vstup");
      break;

    case 0:
      h1("Nelze spouštět CGI bez parametrů");
      break;

    /* jmeno, vek, pohlavi, zajmy, inet, pozn */
    case 6:
      printf("<h1>Formulář 
      pro %%s</h1>", CGI("jmeno"));
      /* .... zpracovávání vstupních údajů ... */
      break;

    default:
      h1("Zkuste <a href=form1.html>
      správný formulář</a>");
      break;
  }

  return (0);
}

Výpis č. 4: Příklad formuláře ještě jednou

Složitější kousky: upload

Jak na to? Velice snadno. Do formuláře se vloží tag

<input type=file name=soubor>

Což v klientovi vyjde jako textový řádek a tlačítko na vyhledání souboru. Po stisku odesílacího tlačítka se uživatelem vybraný soubor prostě pošle CGI skriptu. Knihovna cgihtml si jej prostě uloží do adresáře definovaného z Makefile (stanadartně je to /tmp), a jeho jméno uloží do CGI proměnné. Takže taková plná cesta je "/tmp"+cgi_val(cgi_data, "soubor"). Poté stačí soubor přesunout, vymazat, zobrazit, prostě cokoliv.

Složitější kousky: postupné zobrazování výstupu

Příklad: na své webové stránce máte instalovaný vyhledávač, ale jeho odezvy nejsou nejrychlejší, takže si uživatel může myslet, že se někde stala chyba (já vím, je to blbé, ale nic jiného mě nenapadlo). Takže ono vyhledávadlo by mělo vypsat hlavičku, aby uživatel viděl, že se něco děje a poté postupně vyhledávané stránky (pokud půjde řádově o desetiny sekund, tak se nic neděje, ale pokud prohledáváte např 10GB :-O, nebo máte HODNĚ pomalé vyhledávání, může to být i minuta 8-O). CGI by se mělo jmenovat jako nph-jméno_cgi. Tohle nejspíš záleží na serveru, ale u Apache je to takto. Tyto soubory pak server interpretuje postupně. U Lynxe to asi neuvidíte, ale na Mozille je to vidět (používám shellácký script, aby bylo vidět, že nepotřebujete nutně nějakou knihovnu).


#!/bin/sh

echo "HTTP/1.0 200 OK"
echo "Content-type: text/plain"
echo

echo "Šnek.cz: velice pomalý vyhledávač"
echo "byl zadán dotaz na slovo: linux"
echo
echo "Odkaz #1: http://www.linux.org/"
sleep 2
echo "Odkaz #2: http://www.linux.cz/"
sleep 2
echo "Odkaz #3: http://www.linux.sk/"
sleep 2
echo
echo "Nalezeny 3 odkazy"
echo "(C) Marek Pomalý, 1999\
      Optimalizováno na rychlost."

Výpis č. 5: nph-search.cgi

Script si uložte třeba jako nph-search.cgi a zkuste si jej.

Tímto bych zakončil tuto rychlokuchařku pro CGI. Byl to skutečně jen skromný text, a zdaleka neobsahuje vše (zvláště o knihovně cgihtml, bezpečnost), ale pro základní používání a seznámení to snad bohatě stačí. Pokud máte nějaké dotazy, popřípadě připomínky, nebo máte nějaké problémy, napište mi na sezi@sps-jia.cz. Snad vám pomůžu. *


- předchozí článek - následující článek - obsah - úvodní stránka -