CGI - sposób na dynamiczne strony WWW

Dynamiczne strony WWW - co to takiego?

Pojęcie "dynamiczne strony WWW" robi ostatnio ogromną karierę. Statyczne pliki HTML o niezmiennej zawartości nie wystarczają już użytkownikom Sieci. Oczekują oni stron interaktywnych, reagujących na działania użytkownika - dynamicznych.

Pamiętać jednak należy, że określenie to rozumieć można dwojako. Zainteresowanie użytkowników skupia się obecnie głównie na stronach wykorzystujących techniki działające po stronie klienta - przeglądarki WWW, takie jak JavaScript, Java (w większości typowych zastosowań) czy DHTML. Dzięki wykorzystaniu tych narzędzi można umieszczać na stronach animacje, przesuwające się napisy, rozwijane menu i inne podobne elementy. Strona może reagować na ruch kursora myszki, zmieniać swój wygląd w zależności od działań użytkownika, a to wszystko bez komunikacji z serwerem, z którego została załadowana.

To ostatnie spostrzeżenie prowadzi nas do wniosku, że strony takie są "dynamiczne" głównie w aspekcie wizualnym, natomiast pod względem treści - czyli tego, co przesyłane jest między serwerem i przeglądarką - strona taka pozostaje przeważnie nadal statyczna. Wszystko to, co może się pojawić na stronie, łącznie ze skryptami sterującymi jej działaniem, jest zazwyczaj nadal zapisane w pliku HTML o ustalonej zawartości, który przy każdym odwołaniu do strony jest w całości, zawsze w takiej samej postaci wysyłany do przeglądarki. Dopiero ona interpretuje skrypty, nadając stronie cały efekt dynamizmu.

W przeciwieństwie do stron "dynamicznych wizualnie", strony "dynamiczne treściowo" powstają przy użyciu programów i skryptów działających nie po stronie przeglądarki, lecz na serwerze WWW. Skrypty takie w momencie odwołania się do nich generują "w locie" stronę HTML, która wysyłana jest do przeglądarki. Zawartość tej strony nie jest zatem z góry ustalona, lecz zależy od wyników działania skryptu, który ją wyprodukował; przy każdym odwołaniu do tego samego adresu możemy uzyskać stronę o innej zawartości.

Rzecz jasna, także i z użyciem JavaScriptu czy DHTML możemy zrealizować stronę, która przy każdym załadowaniu będzie wyglądać inaczej; jednak w tym przypadku cała możliwa do wyświetlenia zawartość musi być z góry zapisana w treści strony, choć zazwyczaj jest niewidoczna (np. zawarta wewnątrz skryptu). Rozważmy prosty przykład: chcielibyśmy, aby na naszej stronie WWW wyświetlał się, przy każdorazowym jej załadowaniu, losowo wybrany cytat ze zbioru - powiedzmy - stu cytatów, które mamy zapisane w jakimś pliku. Rozwiązanie wykorzystujące dynamikę od strony klienta, czyli DHTML i/lub JavaScript, wymagałoby umieszczenia wszystkich tych cytatów w sposób niewidoczny na stronie, przesłania całości do przeglądarki i wybrania w przeglądarce tego jednego, który należy wyświetlić. Oczywiście im więcej możliwych do wylosowania cytatów, tym więcej kilobajtów liczyć będzie strona i tym dłużej trwać będzie jej ładowanie.

W przypadku rozwiązania serwerowego, skrypt umieszczony na serwerze losuje z osobnego pliku jeden cytat, wstawia go w znajdujący się w drugim pliku "szkielet" strony i wysyła do przeglądarki dynamicznie wygenerowaną stronę HTML z tym jednym tylko cytatem. Łatwo zauważyć, że w tym przypadku wszystkich cytatów może być choćby i milion, a z punktu widzenia użytkownika nic się nie zmieni: jego przeglądarka zawsze otrzyma stronę HTML zawierającą dokładnie jeden cytat.

Skrypty serwerowe dają nam zatem o wiele większe możliwości manipulowania treścią informacji, która przesyłana będzie od serwera do przeglądarki, podczas gdy skrypty klienckie - typu JavaScript - znakomicie pozwalają modyfikować postać i sposób prezentacji tej informacji. Nie są one zatem dla siebie konkurencją, lecz znakomicie się uzupełniają; każda z tych dwu technik ma swoje obszary zastosowań.

Istnieje szereg narzędzi służących do dynamicznego generowania stron WWW. Począwszy od wyspecjalizowanych serwerów WWW, od razu sprzężonych z odpowiednimi bazami danych, jak na przykład Lotus Domino, poprzez języki skryptowe osadzane w treści pliku HTML, takie jak PHP3 czy ASP (dokładniejsze opisy tych języków znaleźć można w ostatnich numerach MI), aż po najstarszą i najbardziej ogólną technikę - CGI, która jest tematem niniejszego artykułu.

Podstawy CGI

Standard CGI (Common Gateway Interface) - wbrew temu, co się czasami sądzi - nie jest powiązany z żadnym konkretnym językiem programowania. CGI definiuje jedynie sposób, w jaki dowolny program wykonywalny może współpracować z serwerem WWW. Programy (skrypty) CGI mogą być zatem pisane w dowolnym języku możliwym do zastosowania w systemie, w którym działa serwer: począwszy od skryptów shella Unixa, poprzez bardzo chętnie stosowany w zastosowaniach CGI język Perl, aż po klasyczne języki programowania generujące pliki binarne, takie jak C czy Pascal.

Choć specyfikacja CGI jest już dość stara - istnieje niemal od początku istnienia WWW - wciąż cieszy się dużą popularnością. Jednym ze źródeł tej popularności jest niewątpliwie uniwersalność; podczas gdy inne rozwiązania umożliwiające tworzenie skryptów serwerowych związane są na ogół z konkretnymi platformami serwerowymi - np. język PHP3 jest dość mocno związany z serwerem Apache, zaś ASP działa na platformie MS Internet Information Server - możliwość uruchamiania skryptów CGI ma każdy program serwera WWW, w każdym systemie operacyjnym. Ponadto, ponieważ CGI umożliwia uruchamianie na serwerze całkowicie dowolnych programów, praktycznie nie ma ograniczeń możliwości zastosowań tej techniki. Począwszy od najbardziej klasycznych, takich jak licznik odwiedzin strony czy wyszukiwarka, poprzez zmianę hasła do konta pocztowego z poziomu strony WWW, książkę gości, rozmaite ankiety i głosowania, wysyłanie ze strony WWW faksów czy SMS-ów, aż po sklepy internetowe i systemy elektronicznej bankowości - wszystko to zazwyczaj realizowane jest z mniejszym lub większym udziałem skryptów CGI.

Tworzenie stron wykorzystujących skrypty CGI jest jednak dosyć złożone. W przeciwieństwie do JavaScriptu, a nawet PHP3 czy ASP, gdzie polecenia skryptu wpisujemy wprost w treść pliku HTML, skrypt CGI jest całkowicie osobnym programem, którego wynik działania musi albo stanowić kompletną stronę HTML, albo być przygotowany do włączenia w treść strony za pomocą mechanizmu SSI (Server Side Includes), wykorzystywanego często jako uzupełnienie CGI. Program taki trzeba napisać w odpowiednim języku programowania, ewentualnie - jeżeli jest to język kompilowany - skompilować do postaci wykonywalnej, a następnie - w zależności od konfiguracji serwera - może być potrzebne umieszczenie go w specjalnym katalogu bądź nadanie mu jakiejś określonej nazwy, aby serwer rozpoznawał go jako skrypt CGI i uruchamiał go, a nie wysyłał do przeglądarki jego zawartości. "Sposób użycia" skryptów CGI może być inny na każdym serwerze i w każdym przypadku przed przystąpieniem do tworzenia takich skryptów trzeba skonsultować się z lokalnym administratorem.

Warto zauważyć, że nie każdy serwer w ogóle umożliwia użytkownikom korzystanie ze skryptów CGI, ze względów bezpieczeństwa - jak wspomniałem wcześniej, CGI może mieć praktycznie nieograniczone zastosowania, w tym także szkodliwe dla innych użytkowników... Generalnie, chcąc korzystać ze skryptów CGI, trzeba zapomnieć o serwerach darmowych kont typu Polbox czy Onet; na żadnym takim serwerze uruchamianie skryptów CGI nie jest możliwe. Trzeba wykupić sobie konto płatne, dokładnie sprawdzając przy tym cennik danego providera; często bywa bowiem, że za możliwość uruchamiania na naszym koncie skryptów CGI musimy dodatkowo dopłacić.

Pierwsze skrypty

Załóżmy zatem, że mamy już konto z możliwością uruchamiania skryptów CGI, i że skrypty takie na naszym serwerze - co jest dość częstą praktyką - rozpoznawane są przez rozszerzenie nazwy pliku .cgi. Załóżmy też, że nasz serwer jest serwerem unixowym (może to być np. Linux). Nasz pierwszy skrypt napiszemy w języku shella Unixa:

     #!/bin/sh
     echo Content-type: text/plain
     echo
     echo Witaj! Jestem skryptem CGI!
Umieśćmy ten plik na naszym koncie pod nazwą jestem.cgi. Musimy mu przy tym nadać atrybut wykonywalności (x) - jeżeli mamy do naszego konta dostęp przez telnet, najprościej to zrobić wpisując w sesji telnetowej (w katalogu, w którym znajduje się nasz skrypt) komendę: chmod go+rx jestem.cgi. Jeżeli dostępu przez telnet brak, musimy poszukać odpowiedniej opcji w naszym programie FTP, za pomocą którego wysyłamy plik na serwer.

Jeżeli teraz wywołamy nasz skrypt, wpisując w przeglądarce adres http://naszserwer/naszkatalog/jestem.cgi, na ekranie powinniśmy otrzymać efekt jak na rys.1 (z działaniem wszystkich skryptów omawianych w tym artykule Czytelnicy mogą zapoznać się "na żywo" pod adresem http://www.wsp.krakow.pl/papers/skrypty/)

Zanim przystąpimy do tworzenia bardziej skomplikowanych skryptów, przyjrzyjmy się bliżej budowie powyższego pliku. Rozpoczynający go wiersz o treści #!/bin/sh sygnalizuje, że zawartość pliku ma być interpretowana przez program /bin/sh, czyli podstawowy shell obecny w każdym systemie unixowym. Polecenie echo - jedyne użyte w tym skrypcie - powoduje wypisanie na ekran wiersza tekstu znajdującego się po nim. Skrypt nasz zatem wypisuje trzy wiersze: pierwszy zawiera tekst "Content-type: text/plain", drugi jest pusty, trzeci zaś brzmi "Witaj! Jestem skryptem CGI!" - i tylko ten ostatni wiersz w rzeczywistości pojawia się na ekranie w wyniku wykonania skryptu. Dlaczego?

Zgodnie ze specyfikacją CGI, wynik działania skryptu musi składać się z dwu części. Pierwsza część jest nagłówkiem dla protokołu HTTP, druga właściwą treścią dokumentu. Obie części oddzielone są pustym wierszem. Nagłówek ani pusty wiersz oddzielający go od treści dokumentu nie pojawiają się na ekranie, są jednak niezbędne zarówno serwerowi WWW, jak i przeglądarce do poprawnej interpretacji danych.

Nagłówek HTTP wypisywany przez skrypt nie musi być kompletny - serwer sam uzupełni go o brakujące pola ( obok można zobaczyć kompletny nagłówek HTTP wysyłany przez serwer przy pobieraniu typowego, statycznego pliku HTML). Wymagane jest podanie jedynie jednego pola: Content-type, określającego typ MIME danych, które będą wysyłane przez skrypt (więcej o typach MIME można przeczytać tutaj). W przypadku danych umieszczonych w zwykłych plikach typ danych może zostać określony przez serwer na podstawie rozszerzenia nazwy pliku (np. .html, .gif, .mid); w przypadku skryptu jest to niemożliwe, stąd też każdy skrypt musi w nagłówku określać typ generowanych przez siebie danych. Najczęściej stosowanym w skryptach CGI typem danych jest text/html, oznaczający dokument w języku HTML; w naszym przypadku wybraliśmy jednak dla uproszczenia typ text/plain, czyli zwykły tekst, aby uniknąć używania w dalszej części skryptu znaczników HTML.

Jeżeli typem danych generowanych przez skrypt miałby być text/html, wypisywanemu tekstowi powinniśmy nadać postać poprawnego dokumentu w języku HTML:

     #!/bin/sh
     echo Content-type: text/html
     echo
     echo "<html><head><title>Skrypt</title>"
     echo "</head><body>"
     echo "<h1>Witaj! Jestem skryptem CGI!</h1>"
     echo "</body></html>"
Zauważmy, że fragmenty tekstu zawierające znaczniki HTML ujęliśmy tu w cudzysłowy, aby zapobiec nieprawidłowej interpretacji znaków < i > przez shell (dla shella znaki te są symbolami przekierowania wejścia i wyjścia polecenia). Cudzysłowy te są oczywiście pomijane przez shell przy wypisywaniu tekstu - wypisywane jest jedynie to, co znajduje się między nimi. Wynik działania tego skryptu możemy zobaczyć na rys.2.

Jak do tej pory jednak skrypty, które utworzyliśmy, mają raczej niewielki sens, gdyż wypisują one statyczny tekst - dokładnie to samo znacznie mniejszym nakładem pracy możnaby osiągnąć tworząc zwykłą stronę HTML. Dodajmy zatem naszemu skryptowi nieco rzeczywistej dynamiki:

     #!/bin/sh
     echo Content-type: text/html
     echo
     echo "<html><head><title>Zegar</title>"
     echo "</head><body>"
     echo Aktualny czas na serwerze:
     date
     echo "</body></html>"
W tym skrypcie pojawiło się nowe polecenie Unixa: date, które wypisuje aktualną datę i czas na serwerze. Wynik wykonania skryptu wygląda jak na rys.3.

Przy każdym odwołaniu do skryptu podawany będzie inny czas; można to sprawdzić naciskając kilkakrotnie w przeglądarce przycisk "Reload". Zauważmy, że już za pomocą tego bardzo prostego skryptu osiągnęliśmy efekt niemożliwy do uzyskania przy użyciu narzędzi klienckich typu JavaScript: podanie aktualnego czasu serwera. Często spotykane na stronach WWW zegarki zrealizowane w JavaScripcie podają wszak czas wedle wskazań zegara naszego komputera, tego, na którym uruchamiamy przeglądarkę! Wyświetlenie aktualnego czasu serwera - a może to być istotne, jeżeli np. serwer znajduje się w innej od nas strefie czasowej - jest poza ich możliwościami.

Licznik odwiedzin strony

Jednym z częstych zastosowań skryptów CGI - być może najczęstszym, z jakim styka się początkujący użytkownik - są liczniki odwiedzin stron WWW. Nietrudno zauważyć, że licznika takiego również nie sposób zrealizować korzystając wyłącznie z narzędzi klienckich; aby zliczać wszystkie pobrania strony, musi on przechowywać dane na serwerze. Oto prosty przykład skryptu - tym razem napisanego w języku Perl - realizującego taki licznik:

     #!/usr/bin/perl
     open (FILE,"+<licznik.dat");
     $hits = <FILE>;
     $hits = $hits+1;
     seek (FILE,0,0);
     print FILE $hits;
     close (FILE);

     print "Content-type: text/html\n\n";
     print "<html><head><title>Licznik</title>\n";
     print "</head><body>\n";
     print "<h1>Witaj na mojej stronie!</h1>\n";
     print "Ta strona była czytana ",$hits," razy.\n";
     print "</body></html>\n";
Skrypt powyższy składa się z dwu części: w pierwszej następuje zwiększenie o jeden stanu licznika, przechowywanego w pliku licznik.dat, w drugiej - wypisanie treści właściwej strony HTML. Na początku skryptu zauważamy linijkę "#!/usr/bin/perl", sygnalizującą, iż skrypt ma być wykonany przez interpreter języka Perl (uwaga: na niektórych serwerach ścieżka dostępu do tego interpretera może mieć postać /usr/local/bin/perl - tu również przydatna będzie konsultacja z administratorem). Następująca po tym wierszu instrukcja open otwiera plik licznik.dat - znaki "+<" umieszczone przed nazwą pliku oznaczają, że plik ten otwieramy zarówno do odczytu, jak i do zapisu. Plik ten musi znajdować się w tym samym katalogu na serwerze, co skrypt. Jest to zwykły plik tekstowy, zawierający jedynie aktualną liczbę pobrań strony - na początku, gdy umieszczamy go na serwerze, wpisujemy w nim po prostu liczbę 0 (jako, że strona nie była jeszcze odczytywana ani razu).

W kolejnych dwu wierszach skryptu następuje odczytanie wartości z pliku licznik.dat i zwiększenie jej o 1. Następnie instrukcja seek "przewija" plik z powrotem na początek, przygotowując go do zapisu nowej wartości. Nowa wartośc zostaje zapisana do pliku instrukcją print i plik zostaje zamknięty. (Dla uproszczenia pominięto w skrypcie instrukcje tzw. blokowania pliku, zabezpieczające przed równoczesną modyfikacją jego zawartości przez więcej niż jedną kopię skryptu, co mogłoby się zdarzyć, gdyby naszą stronę oglądało naraz wielu użytkowników - w wersji "produkcyjnej" skryptu takie instrukcje powinny koniecznie się znaleźć.)

Druga część skryptu to znane nam już z poprzednich przykładów wypisanie treści dokumentu HTML. Kombinacja znaków "\n" w perlowej instrukcji print oznacza przejście do nowego wiersza - gdyby jej nie było, wszystkie teksty wypisywane kolejnymi instrukcjami print stanowiłyby jeden długi wiersz (zauważmy przy okazji, że podwójne "\n\n" na końcu pierwszej instrukcji daje nam pusty wiersz niezbędny do oddzielenia nagłówka od treści strony). Z punktu widzenia funkcji tego skryptu najistotniejsza jest przedostatnia instrukcja:

     print "Ta strona była czytana ",$hits," razy.\n";
Ona to właśnie umieszcza w treści generowanej strony wartość zmiennej $hits, podającą liczbę dotychczasowych załadowań strony. Wynik wykonania skryptu może wyglądać np. tak, jak na rys.4. Rzeczą, na którą trzeba koniecznie zwrócić uwagę przy uruchamianiu tego typu skryptów - tzn. takich, które czytają lub zapisują zewnętrzne pliki - są prawa dostępu do tych plików. Ze względu na bezpieczeństwo, skrypty CGI zazwyczaj wykonują się z uprawnieniami specjalnego, fikcyjnego użytkownika przypisanego do serwera WWW - zazwyczaj jest to użytkownik o nazwie "nobody", "www" lub podobnej. Jeżeli na naszym serwerze tak jest, musimy temu użytkownikowi przydzielić prawa odczytu oraz zapisu do pliku licznik.dat - w przeciwnym przypadku skrypt nie będzie działał. Jeżeli nie wiemy, jak przydzielić te uprawnienia, poprośmy również o pomoc administratora. Czasami serwery WWW bywają również konfigurowane tak, że skrypty CGI wykonują się z uprawnieniami użytkownika, do którego należą (tzw. suid-wrapper) - w takim przypadku naturalnie problemu praw dostępu nie ma.

Realizacja licznika w opisany powyżej sposób jest oczywiście niezbyt wygodna, gdyż cała treść strony, na której chcielibyśmy umieścić licznik, musiałaby być wypisywana przez skrypt, mimo że faktycznie zmienny jest tylko niewielki jej fragment. Dla takich właśnie zastosowań przewidziany został mechanizm SSI (Server Side Includes), pozwalający włączyć w treśc pliku HTML "wstawki", generowane dynamicznie przez skrypty CGI.

Aby zrealizować licznik z wykorzystaniem tego mechanizmu, tworzymy zwykłą stronę HTML, w której treści umieszczamy następujący fragment:

     Ta strona była czytana <!--#exec cgi="licznik2.cgi" --> razy.
Skrypt licznik2.cgi ma postać:

     #!/usr/bin/perl
     open (FILE,"+<licznik.dat");
     $hits = <FILE>;
     $hits = $hits+1;
     seek (FILE,0,0);
     print FILE $hits;
     close (FILE);

     print "Content-type: text/plain\n\n";
     print $hits;
Jak widać, jest to uproszczona postać poprzedniego skryptu, która zamiast całej treści strony wypisuje - poza nagłówkiem HTTP - jedynie liczbę jej pobrań. Liczba ta zostanie przez serwer WWW wstawiona zamiast znacznika "<!--#exec cgi=...>" w powyższym wierszu i do przeglądarki zostanie wysłana już "czysta" strona HTML, bez żadnych śladów odwołania do skryptu. Ale uwaga! Aby tak się stało, serwer WWW musi wiedzieć, że w danym pliku HTML ma zinterpretować wstawki SSI - w typowych konfiguracjach serwerów WWW uzyskuje się to poprzez nadanie plikowi zawierającemu SSI rozszerzenia .shtml, a nie .html, jak w przypadku zwykłych statycznych stron. W razie wątpliwości, również warto poradzić się swojego administratora.

Przedstawiony powyżej licznik jest rzecz jasna bardzo prosty; liczba pobrań strony wypisywana jest w postaci tekstowej. Wszyscy znamy oczywiście bardziej skomplikowane liczniki, podające liczbę pobrań strony w postaci pliku GIF. Mają one tę zaletę, że dla ich umieszczenia na stronie nie jest potrzebny mechanizm SSI - wystarczy zwykły znacznik <img src=...> z adresem wskazującym na skrypt licznika. Skrypt ten może nawet znajdować się na innym serwerze - w istocie, istnieje wiele serwerów z ogólnodostępnymi licznikami, które można wykorzystywać na swoich stronach WWW. Liczniki graficzne mają jednakże i wady. Po pierwsze, w ogóle nie "zauważają" wejść na stronę przeglądarką tekstową lub z wyłączonymi obrazkami, bądź też sytuacji, gdy użytkownik przerwie ściąganie strony, zanim ściągnie się licznik (co jest częste zwłaszcza w przypadku, gdy licznik pobierany jest z innego serwera). Po drugie, łatwo można je oszukać, zawyżając liczbę odwiedzin strony poprzez wielokrotne ściąganie samego licznika, a nie całej strony (kilka lat temu złośliwy internauta skompromitował w ten sposób licznik odwiedzin jednego z bardziej znanych polskich serwisów sieciowych). Po trzecie wreszcie - graficzne liczniki rzadko kiedy naprawdę dobrze komponują się pod względem plastycznym z całością strony. Licznik tekstowy w mojej opinii wygląda znacznie bardziej elegancko i profesjonalnie, a przy tym nie jest tak łatwy do oszukania.

Rozpoznajemy przeglądarkę

Ważną cechą dynamicznych stron WWW jest ich interaktywność - skrypt może pobierać od użytkownika pewne dane i w zależności od nich odpowiednio modyfikować zawartość wysyłanej do przeglądarki strony. Jednym z najprostszych zastosowań tej koncepcji jest wyświetlanie stron o różnej treści użytkownikom np. różnych przeglądarek, bądź łączącym się z różnych adresów. Rzecz jasna, rozpoznawanie typu przeglądarki bądź adresu, z którego łączy się użytkownik, można zrealizować także przy pomocy JavaScriptu (czego przykłady często spotykamy na stronach), skrypt CGI ma jednak tę przewagę, iż będzie działał także dla przeglądarek, które nie obsługują JavaScriptu bądź mają obsługę tego języka wyłączoną w konfiguracji.

Poniższy bardzo prosty skrypt jest rzeczywistym przykładem, który wykorzystuję na jednej z moich stron WWW. Skrypt ten wyświetla tabelkę, w której komórkach wykorzystywane są różne kolory i atrybuty czcionek. Tabelkę taką bez kłopotu można skonstruować przy pomocy zwykłych znaczników języka HTML, problem jednakże w tym, że prawidłowo pokażą ją jedynie przeglądarki graficzne. W przeglądarkach tekstowych nie będą właściwie oddane różne kolory ani kroje czcionek, co w tym przypadku jest ważne. Użytkownikom tych przeglądarek trzeba zatem całą tabelkę zaprezentować jako obrazek typu GIF (nota bene obrazek ten został po prostu "zdjęty" z ekranu przeglądarki graficznej). Wbrew pozorom wysyłanie pliku GIF do przeglądarki tekstowej ma sens: użytkownicy takich przeglądarek mają z reguły "podpięte" do nich pomocnicze programy - tzw. helper applications - pozwalające np. na wyświetlanie plików graficznych czy odtwarzanie multimediów. W ostateczności, gdyby takiego programu nie było, przeglądarka zaproponuje użytkownikowi zapisanie pliku na dysk - będzie mógł go sobie później obejrzeć dowolnym programem graficznym.

Skrypt realizujący to zadanie ma taką oto postać:

     #!/bin/sh
     if { echo $HTTP_USER_AGENT | egrep -i "(lynx|links)" >/dev/null ; } then
        echo Content-Type: image/gif
        echo
        cat tabelka.gif
     else
        echo "Content-Type: text/html; charset=ISO-8859-2"
        echo
        cat tabelka.html
     fi
Gdy serwer WWW uruchamia skrypt CGI, przekazuje mu w postaci serii tzw. zmiennych środowiskowych szereg parametrów dotyczących aktualnej sesji. Wśród tych parametrów znajduje się m.in. nazwa, jaką przeglądarka "przedstawia się" serwerowi, wysyłając zapytanie HTTP - jest ona zapisana w zmiennej HTTP_USER_AGENT. Pierwsza instrukcja powyższego skryptu sprawdza - wykorzystując unixową komendę egrep - czy w treści tej zmiennej znajduje się napis "lynx" lub "links", identyfikujący jedną z tekstowych przeglądarek WWW. Jeżeli tak jest, skrypt wysyła do przeglądarki zawartość pliku graficznego tabelka.gif (unixowa komenda cat kopiuje zawartość wskazanego pliku), w przeciwnym natomiast wypadku - czyli gdy mamy do czynienia z przeglądarką graficzną - wysyłana jest zawartość pliku tabelka.html.

Zwróćmy uwagę, że w tym przypadku ten sam skrypt wysyłać może dane dwu różnych typów: dla przeglądarek tekstowych wypisywany jest nagłówek HTTP określający typ danych image/gif, dla graficznych - text/html ze stroną kodową ISO 8859-2. Umieszczenie deklaracji strony kodowej od razu w nagłówku HTTP zwalnia nas od konieczności umieszczania w pliku tabelka.html odpowiedniego znacznika META (znacznik ten zresztą w istocie właśnie symuluje wystąpienie deklaracji strony kodowej w nagłówku HTTP). Wypisywany tekst został w tym przypadku ujęty w cudzysłowy, gdyż dla shella unixowego średnik również ma specjalne znaczenie (jest znakiem rozdzielającym dwie komendy umieszczone w tym samym wierszu).

Zamiast wypisywać zawartość jednego z kilku plików, skrypt może po rozpoznaniu np. adresu, z którego użytkownik się łączy, przekierować jego przeglądarkę na odpowiednią stronę. Na przykład, jeżeli mamy serwis WWW zrealizowany w dwu wersjach językowych, "stroną główną" serwisu może być skrypt, który automatycznie kieruje użytkownika do odpowiedniej wersji: polskiej, jeżeli użytkownik łączy się z adresu kończącego się na .pl, i angielskiej w przeciwnym wypadku:

     #!/bin/sh
     if { echo $REMOTE_HOST | grep -i "\.pl$" >/dev/null ; } then
        echo Location: index_pl.html
        echo
     else
        echo Location: index_en.html
        echo
     fi
Konstrukcja tego skryptu jest bardzo podobna do poprzedniego. Zmienna środowiskowa REMOTE_HOST zawiera adres domenowy komputera, z którego użytkownik ogląda stronę. Komenda grep sprawdza, czy adres ten kończy się tekstem .pl. W zależności od wyniku sprawdzenia wysyłany jest odpowiedni nagłówek HTTP, wskazujący przeglądarce użytkownika przekierowanie na jedną z dwu stron: index_pl.html lub index_en.html (niestety, przy takim sposobie sprawdzania adresu wszystkie komputery nie posiadające adresu domenowego, a tylko IP, będą traktowane jako niepolskie i również przekierowywane na stronę w języku angielskim - nie ma prostego sposobu rozwiązania tego problemu). Zauważmy, że w tym przypadku zbędne jest wysyłanie w nagłówku pola Content-type, jak również nie ma potrzeby generowania jakiejkolwiek treści strony; wystarczy wysłać tylko pole Location, zawierające adres strony, do ktorej należy przejść, oraz pusty wiersz zamykający nagłówek, aby dokonać przekierowania.

Skrypty CGI i formularze

Skrypty CGI mogą wykorzystywać w swoim działaniu nie tylko parametry, które przekaże im serwer WWW w zmiennych środowiskowych, lecz także dane wprowadzone bezpośrednio przez użytkownika. Bodaj najbardziej "klasycznym" zastosowaniem CGI jest obsługa danych pochodzących z formularzy zamieszczanych na stronach WWW. Praktycznie z każdym takim formularzem związany jest jakiś obsługujący go skrypt - jego adres podaje się w parametrze ACTION= znacznika <FORM>. Po kliknięciu na przycisk "Wyślij" w formularzu przeglądarka przesyła wprowadzone dane do serwera, który z kolei przekazuje je uruchomionemu skryptowi.

Dane przekazywane serwerowi przez przeglądarkę są w specjalny sposób kodowane, aby uniknąć ewentualnego pojawienia się znaków specjalnych, ktore mogłyby być źle zinterpretowane przez oprogramowanie czy to przeglądarki, czy to serwera. Na dodatek istnieją dwie, dość znacznie różniące się między sobą, metody przekazywania serwerowi danych z formularza - GET i POST. Wszystko to sprawia, że część skryptu zajmująca się zdekodowaniem danych pochodzących z formularza może być jego najbardziej skomplikowanym fragmentem, gdyby pisać ją "na piechotę".

Dużą pomocą okazuje się tutaj język Perl, ktory w swoich standardowych bibliotekach zawiera gotową funkcję odbierającą i dekodującą dane z formularza. Dlatego też skrypty obsługujące formularze pisze się najczęściej w tym języku. Wykorzystując wspomnianą funkcję, możemy pokusić się o napisanie prostej wyszukiwarki, znajdującej wpisany przez użytkownika tekst we wszystkich plikach *.html zawartych w katalogu, w którym znajduje się skrypt.

Na początek utwórzmy stronę z formularzem, za pomocą którego będziemy podawać tekst do wyszukania. Niech formularz ma np. następującą postać:

     <form method="post" action="szukaj.cgi">
     <b>Wpisz tekst do wyszukania:</b>
     <input type="text" name="slowo" width=40>
     <input type="submit" value="Szukaj">
     </form>
Tak oto wygląda ten formularz w przeglądarce.

Teraz musimy napisać skrypt szukaj.cgi, który będzie obsługiwał formularz: (Uwaga! Przeczytaj koniecznie erratę do tego tekstu!)

     #!/usr/bin/perl
     use CGI;
     CGI::ReadParse(*form);

     @files=`grep -li "$form{'slowo'}" *.html`;

     print "Content-type: text/html; charset=ISO-8859-2\n\n";
     print "<html><head><title>Wyniki wyszukiwania</title>\n";
     print "</head><body><h1>Wyniki wyszukiwania</h1>\n";
     if ($#files == -1) {
        print "Nie znaleziono żadnego pliku zawierającego tekst <b>",
               $form{'slowo'},"</b>\n";
     } else {
        print "Znaleziono ",$#files+1," plików zawierających tekst <b>",
               $form{'slowo'},"</b><p>\n";
        print "<ul>\n";
        foreach $file (@files) {
           chop($file);
           print '<li><a href="',$file,'">',$file,"</a>\n";
        }
        print "</ul>\n";
     }
     print "</body></html>\n";
Dokładne omówienie tego skryptu wymagałoby dość szerokiego objaśnienia samego języka Perl, opiszę więc tutaj tylko zasadniczą ideę jego działania. Na początku skryptu znajduje się deklaracja wykorzystania standardowego modułu CGI, w którym zawarta jest potrzebna nam funkcja ReadParse. Wywołanie CGI::ReadParse(*form) dekoduje dane z formularza i tworzy tablicę o nazwie form, której elementy zawierają wartości wprowadzone do poszczególnych pól formularza. Nasz formularz zawiera tylko jedno pole, o nazwie "slowo", zatem do zawartości tego pola będziemy w dalszej części skryptu odwoływać się przez zapis $form{'slowo'}.

Kolejny wiersz skryptu wywołuje standardową unixową komendę grep w celu znalezienia wszystkich nazw plików zawierających tekst wprowadzony do formularza. Wyniki działania tej komendy zapisywane są w postaci listy o nazwie @files.

Po wypisaniu początkowych wierszy strony HTML sprawdzana jest wartość zmiennej $#files, która zawiera liczbę elementów listy @files pomniejszoną o 1. Jeżeli zatem wartość ta wynosi -1, wypisywany jest komunikat informujący o braku plików zawierających poszukiwany tekst; w przeciwnym razie generowany będzie wykaz znalezionych plików. Wypisywany jest znacznik <ul> otwierający wykaz i rozpoczyna się pętla foreach, która wykonuje zawarte w niej instrukcje dla każdej nazwy pliku zawartej na liście @files. Instrukcja chop obcina z końca nazwy znak przejścia do nowego wiersza, dodany tam przez komendę grep, zaś następująca po niej instrukcja print wypisuje kolejny wiersz wykazu, w taki sposób, że pojawiająca się na ekranie nazwa pliku stanowi zarazem odsyłacz do niego (wiersz wypisany przez tę instrukcję ma postać np. <li><a href="plik.html">plik.html</a>). Całość wygenerowanej strony z wynikami wyszukiwania wyglądać może jak na rys.6.

Na tym kończymy ten krótki przegląd możliwości skryptów CGI. Mam nadzieję, iż przekonał on Czytelników, że CGI jest bardzo użyteczną techniką, którą warto się zainteresować, zwłaszcza gdy chcemy tworzyć bardziej rozbudowane i profesjonalne serwisy WWW.

(Uwaga! Przeczytaj koniecznie erratę do tego tekstu!)


Jarosław Rafa 2000. Tekst udostępniony na licencji Creative Commons (uznanie autorstwa - użycie niekomercyjne - bez utworów zależnych). Kliknij tutaj, aby dowiedzieć się, co to oznacza i co możesz z tym tekstem zrobić. W razie jakichkolwiek wątpliwości licencyjnych bądź w celu uzyskania zgody na rozpowszechnianie wykraczające poza warunki licencji proszę o kontakt e-mailem: raj@ap.krakow.pl.

Wersja HTML opracowana 5.06.2000.


Powrót do wykazu artykułów o Internecie Statystyka