Programy CGI dla baz danych

Zbigniew Jurkiewicz, Instytut Informatyki UW

WWW i bazy danych: programy CGI

Uwaga: zakładam u czytelników pewną minimalną znajomość HTML. Z wyjatkiem formularzy nie jest to dokumentacja HTML --- jest mnóstwo tutoriali w sieci.

Wprowadzenie

Program CGI jest to program wywoływany przez serwer WWW. Program CGI otrzymuje dane od serwera i produkuje w wyniku stronę, wysyłaną przez serwer do przeglądarki. Interfejs używany przy komunikacji między takimi programami a serwerem WWW nosi nazwę CGI (Common Gateway Interface).

Serwer WWW interpretuje komunikaty HTTP pochodzące z przeglądarki. Niektóre w tych komunikatów zawierają żądanie uruchomienia programu (CGI). Część komunikatu określa nazwę programu i jego położenie, a inna część jego argumenty.

To, jakie adresy URL są traktowane jako programy CGI, zależy od serwera WWW i jego administratora. Czasem muszą one być zapisane w ustalonym katalogu, np. cgi-bin, a ich adres ma wtedy postać np. http://www.foo.org/cgi-bin/nazwa-programu. Takie programy często są uruchamiane z prawami użytkownika nobody, a więc nie wolno w nich używać ścieżek względnych.

Model komunikacji oparty na WWW jest bardzo ograniczony:

Programy CGI działają jak typowe filtry w systemie Unix: wczytuja dane ze standardowego wejścia i wypisują wyniki na standardowe wyjscie.

Generowanie HTML z programów w PHP

Programy CGI można pisać w każdym języku programowania, nasze przykłady zapiszemy w PHP. Jest to skryptowy język programowania, którego bloki można zanurzać bezpośrednio w strony HTML (trochę podobnie do ASP Microsofta). Jest to produkt bezpłatny, główna strona to www.php.net.

Krótki opis:

Program w PHP może być wykonywany w dwóch trybach, zależnie od użytego interpretera. Serwer WWW automatycznie wywołuje interpreter dla interfejsu CGI, automatycznie generującego nagłówki stron itp. Będziemy o tym mówić dalej.

Na razie zajmiemy się interpreterem dla wiersza poleceń (CLI - Command Line Interface). Ułatwia on testowanie programów PHP. Aby sprawdzić, czy jest on zainstalowany w naszym systemie, możemy wywołać program php z opcją -v:

[zbyszek@katastrofa2 ~]$ php -v
PHP 5.3.2 (cli) (built: Mar  6 2010 08:33:02) 
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies

PHP z linii poleceń

Prosty program możemy wpisać bezpośrednio. Należy pamiętać, że musi on być otoczony znacznikami <?php i ?>, np.

[zbyszek@katastrofa2 ~]$ php
<?php   
echo "Witaj świecie\n";
exit(0);
?>
Witaj świecie

Po wpisaniu programu i naciśnięciu CTRL-D zostanie on wykonany.

CGI w PHP

Najprostszy przykład to skrypt nie pobierający żadnych danych wejściowych. Powinien on wypisać na standardowe wyjście nagłówek HTTP, a następnie zawartość strony zapisaną w HTML.

Jeśli nie zażądamy inaczej, to PHP wypisze automatycznie standardowy nagłówek HTTP. Zamiast tego możemy w skrypcie umieścić instrukcję

<?php header("Content-type: image/png"); ?>

Musi to być pierwsza linia skryptu, nie może być nawet poprzedzona pustą linią.

Zawartość strony można generować w różny sposób. Najprościej jest użyć standardowych funkcji wyjścia w PHP. W pierwszym przykładzie wygenerujemy stronę z nieśmiertelnym komunikatem.

<html>

<?PHP
echo "<html><head>";
echo "<title>Pierwszy przykład</title></head>";
echo "<body><h1>Witaj świecie!</h1></body>";
?>

</html>
Do wypisywania poszczególnych wierszy HTML użyliśmy funkcji echo.

Programowe generowanie stron

Użycie wyrażeń PHP pozwala na prostsze generowanie skomplikowanych stron. Popatrzmy na prostą pętlę zwiększającą licznik i wypisującą każdorazową wartość licznika:

<html>

<?PHP
echo "<html><head>";
echo "<title>"Ślad licznika"</title></head>";
echo "<body>";
for ($n = 0; $n <= 10; $n++) {
  echo "<P>Bieżąca wartość n wynosi $n</P>";
}
echo "</body>";
?>

</html>

Programy nie pobierające danych wejściowych są zwykle bezużyteczne. Z programami CGI sprawa ma się inaczej, ponieważ mogą one wyświetlać informację, która ulega częstym zmianom, na przykład pochodzić z bazy danych czy skrytki pocztowej użytkownika.

Napiszemy teraz program podający, jakie gatunki zwierzaków znajdują się w naszej zoologicznej bazie danych. Użyjemy wbudowanego w PHP interfejsu do Postgresa:

<html>
<head>
<title>Test</title>
</head>
<body bgcolor="white">

<?php
$link = pg_connect("host=lkdb dbname=bd user=scott password=tiger");
$result = pg_query($link, "select distinct gatunek from Zwierzaki");
$numrows = pg_numrows($result);
?>

<h2 align=center>Nasze gatunki</h2>

<table border="1" align=center>
<tr>
 <th>Gatunek</th>
</tr>
<?php

 // Przechodzimy po wierszach wyniku.

 for($ri = 0; $ri < $numrows; $ri++) {
  echo "<tr>\n";
  $row = pg_fetch_array($result, $ri);
  echo " <td>" . $row["gatunek"] . "</td>
 </tr>
";
 }
 pg_close($link);
?>
</table>
</body>
</html>

Pożyteczne wskazówki

Warto dodawać poniższą instrukcję

error_reporting(E_ALL)

do skryptów PHP, żeby serwer WWW raportował wszystkie błędy.

Konstrukcje sterujące PHP mogą być dzielone na części. Pozwala to na przykład warunkowo umieszczać na wyjściu fragmenty w HTML

<?php if (2 > 3) { ?>
<p>co to jest</p>
<?php } else { ?>
<p>albo to</p>

Dostęp do bazy danych

Standardowe biblioteki PHP obejmują wiele funkcji do komunikacji z bazami danych. W naszych przykładach ograniczymy się do PostgreSQL.

Zaczynamy jak zwykle od połączenia się z bazą danych. Służy do tego funkcja pg_connect

$link = pg_connect("host=lkdb dbname=mrbd user=scott password=tiger");

Zwraca ona deskryptor połączenia, który należy zachować na zmiennej.

Gdy dostęp do bazy danych przestanie nam być potrzebny, należy zamknąć połączenie funkcją pg_close.

pg_close($link);

Pora na zadawanie zapytań. Funkcja pg_querywykonuje polecenie SQL i zwraca deskryptor wyniku.

$result = pg_query($link, "select * from Gatunki");

Po sprawdzenie, czy nie było błędu, poszczególne wiersze można pobierać funkcją pg_fetchrow. Liczbę wierszy w wyniku podaje funkcja pg_numrows.

$numrows = pg_numrows($result);
for($ri = 0; $ri < $numrows; $ri++) {
  $row = pg_fetch_array($result, $ri);
  echo $row["gatunek"] . "<br>"
}

Programy CGI z argumentami

Znacznie ciekawsze są programy CGI, których wyniki zależą od otrzymanych danych wejściowych. Najczęściej użytkownik wypełnia formularz WWW i naciskając przycisk potwierdzenia powoduje wysłanie tej informacji (odpowiednio zapisanej). Serwer przekazuje tę informację do programu CGI określonego w formularzu.

Pobieranie informacji od użytkownika

Dane do programów CGI pobiera się używając formularzy HTML. Mogą one zawierać pola tekstowe, rozmaite przyciski, przewijane tablice itp. Pobieranie informacji składa się z dwóch kroków: należy przygotować dokument HTML zawierający odpowiednie formularze, a następnie w programie CGI zanalizować wczytane dane i określić akcję do wykonania.

Formularze

Formularze umieszcza się w dokumencie HTML używając elementu <form>:

<FORM METHOD="POST" ACTION="http://form.url.com/cgi-bin/cgiprogram">
  ... Zawartość formularza ...
</FORM>
Adres URL podany atrybutem ACTION powinien wskazywać na program CGI. Atrybut METHOD podaje sposób przekazania danych z formularza do programu CGI. W powyższym przykładzie użyto zalecanej metody "POST", inna możliwość to metoda "GET", lecz zwykle jej użycie jest bardziej kłopotliwe. W metodzie GET informacje z formularza przekazuje się do programu CGI poprzedzając je znakiem zapytania i doklejając do podanego adresu akcji. Dla metody POST są one umieszczane w treści komunikatu HTTP i programu CGI będzie mógł jest odczytać ze standardowego wejścia. W przypadku metody POST zamiast uruchamiać program CGI możemy zawartość formularza przesłać pocztą elektroniczną. Atrybut ACTION powinien wtedy mieć postać
mailto:ktoś-tam@gdzieś-tam

Wewnątrz formularza można umieścić dowolny element z wyjątkiem innego formularza. Elementy służace do komunikacji z użytkownikiem to INPUT, SELECT i TEXTAREA.

Najprostsze są elementy INPUT:

<INPUT TYPE="text" NAME="napis" VALUE="domyślna" SIZE=10 MAXLENGTH=20>
<INPUT TYPE="checkbox" NAME="thisbox" VALUE="on" CHECKED>
<INPUT TYPE="radio" NAME="radio1" VALUE="1">
<INPUT TYPE="radio" NAME="radio1" VALUE="2" CHECKED>
<INPUT TYPE="submit" VALUE="gotowe">

Atrybut TYPE określa typ obiektu używanego do komunikacji, dozwolone wartości to "text", "password", "checkbox", "radio", "image", "hidden", "submit" i "reset". Dwa ostatnie pełnią role przycisków wysyłających zawartość formularza do serwera i czyszczących formularz.

Każdy z pozostałych typów ma atrybut NAME podający nazwę ,,zmiennej'', z którą zostanie związana wartość w polu podczas przekazywania zawartości do programu CGI. Poszczególne typy mają następującą postać

Element SELECT służy do tworzenia menu i przewijalnych list wyboru.

<SELECT NAME="menu">
  <OPTION>wybór 1
  <OPTION>wybór 2
  <OPTION>wybór 3
  <OPTION SELECTED>wybór 4
  <OPTION>wybór 5
  <OPTION>wybór 6
  <OPTION>wybór 7
</SELECT>
<SELECT NAME="scroller" MULTIPLE SIZE=7>
  <OPTION SELECTED>wybór 1
  <OPTION SELECTED>wybór 2
  <OPTION>wybór 3
  <OPTION>wybór 4
  <OPTION>wybór 5
  <OPTION>wybór 6
  <OPTION>wybór 7
</SELECT>

Atrybut SIZE określa, czy będzie to rozwijalne menu czy lista wyboru. Jeśli jest pominięty lub ma wartość 1, to domyślnie jest to menu. Jeśli jest większy niż 1, to otrzymamy listę przewijaną z widocznymi SIZE elementami. Obecność opcji MULTIPLE automatycznie wybiera listę przewijaną z wieloma wyborami.

Element OPTION podaje nazwy i wartości dla każdego pola i pozwala określić opcją SELECTED, czy jest ono domyślnie wybrane.

Element TEXTAREA służy do wprowadzania tekstów w wielu wierszach:

<TEXTAREA NAME="area" ROWS=5 COLS=30>
Mary had a little lamb.
A little lamb?
A little lamb!
Mary had a little lamb.
It's fleece was white as snow.
</TEXTAREA>

Atrybut NAME to nazwa ,,zmiennej'', zaś ROWS i COLS określają rozmiar widocznej częsci tekstu. Początkową (domyślną) zawartość podaje się wewnątrz elementu. Spacje są respektowane, podobnie znaki nowej linii (jak w elemencie <PRE>).

Spróbujmy napisać usługę, pobierającą nazwę użytkownika i hasło, a następnie sprawdzającą ich poprawność. Zaczniemy od formularza:

<html>
<title>Drzwi wejściowe</title>
<body>
<h3 align="center">Logowanie</h3>
<form action=".../loguj.php" method="post">
<center>
Login: <input type="text" name="kto"><br>
Hasło: <input type="password" name="klucz"><br>
<input type="submit" value="Loguj">
</center>
</form>
</body>
</html> 
Drzwi wejściowe

Logowanie

Login:
Hasło:

Strona ta oprócz tytułu zawiera jedynie formularz (element form), zawierający trzy elementy input. Pierwsze dwa określają pola tekstowe do wprowadzenia identyfikatora użytkownika (w postaci linii tekstu) i jego hasła (bez ujawniania). Trzeci element to przycisk zatwierdzenia, po jego naciśnięciu zawartość formularza jest przesyłana do programu, którego nazwa jest podana jako atrybut action formularza.

Oprócz nazwy programu formularz powinien podawać metodę przekazania wartości zmiennych (w tym przykładzie są dwie zmienne kto i klucz), pobranych z formularza. Dwie główne metody to get (domyślna, informacje są przekazywane przez zmienną środowiskową QUERY_STRING) i post (informacje są przekazywane przez standardowe wejście programu CGI). Do określenia metody służy atrybut method --- w tym przypadku jego wartością jest post.

Informacja o użytej metodzie zostaje przez serwer WWW zapisana w zmiennej środowiskowej REQUEST_METHOD i może być odczytana przez program CGI (wartości to "GET" i "POST").

Wczytywanie po stronie serwera --- CGI

Zawartość formularza jest przesyłana w postaci zapytania -- napisu o specjalnej postaci. Dla metody GET napis ten staje się wartością zmiennej środowiska QUERY_STRING. Serwer otrzymuje go bowiem jako fragment URL, na przykład:

http://students.mimuw.edu.pl/~ab123456/aa?thisinput=default&thisbox=on&radio1=2
Zapytanie znajduje się po znaku zapytania ('?').

Niezależnie od przyjętej metody, wartości zmiennych formularza sa przekazywane jako ciąg par nazwa/wartość, oddzielanych znakiem '&'. Nazwy od wartości oddziela się znakiem równości '='. Ewentualne spacje w wartościach są zastępowane znakiem '+', zaś wszystkie inne znaki nie alfanumeryczne trójznakową sekwencją zaczynająca się znakiem procenta '%', po którym zapisany jest dwoma cyframi szesnastkowymi kod znaku, np. napis

"20% + 30% = 50%, &c."
będzie zapisany jako
"20%25+%2b+30%25+%3d+50%25%2c+%26c%2e"

W metodzie POST postać zapytania jest taka sama, ale program CGI otrzymuje je przez standardowe wejście. Nie otrzymamy jednak końca pliku. Zamiast tego długość napisu zapytania podawana jest w zmiennej środowiska CONTENT_LENGTH, którą można odczytać funkcją getenv.

Pora na program CGI do naszego przykładu.

<?php
$kto = $_POST["kto"];
$klucz = $_POST["klucz"];

$link = pg_connect("host=lkdb dbname=mrbd user=scott password=tiger");
$wynik = pg_query_params($link,
                         "SELECT upraw 
                          FROM users 
                          WHERE login = $1 AND password = $2",
                         array($kto,$klucz));
$ile = pg_numrows($wynik);

if ($ile == 0) {
  echo "<center><strong>Nie ma takiego użytkownika</strong></center>";
}
else {
  echo "<center><strong>OK</strong></center>";
}
?>

Uwaga: programu zapisanego w powyższej uproszczonej wersji nie należy umieszczać na serwerze --- nie jest on bezpieczny. Złośliwy użytkownik mógłby wpisać w przeglądarce po kluczu apostrof, a następnie dowolne wyrażenie SQL lub średnik i dowolne polecenia SQL (np. ' OR 'a' = 'a). Poprawna pełna wersja powinna najpierw sprawdzić wprowadzone wartości, np. usuwając niedozwolone znaki.

Zwracanie odpowiedzi dla użytkownika

Dokument HTML stanowiący odpowiedź tworzy się dynamicznie na podstawie odpowiedzi na zapytanie użytkownika. Można wykorzystać dowolne techniki formatowania dostępne w HTML.

CGI Output

Programy CGI zwracają skonstruowany dokument HTML przez standardowe wyjście, można więc w PHP po prostu używać funkcji echo.