Plan zajęć
- Technologia CGI - wprowadzenie
- Przykłady aplikacji uruchamianych w standardzie CGI
- Zmienne środowiskowe oraz pliki STDIN i STDOUT
- Obsługa formularzy w technologii CGI
CGI (Common Gateway Intreface) jest to standard interfejsu opracowanego do wymiany informacji pomiędzy serwerami WWW a zewnętrznymi programami. Jest to najstarszy sposób tworzenia interaktywnych stron WWW. Standard CGI definiuje komunikację pomiędzy graficznym interfejsem użytkownika (np. stroną WWW), serwerem WWW oraz zewnętrznym programem obsługującym interfejs CGI. Aplikacja ta może być uruchomiona na dowolnym komputerze i mieć dostęp do różnych zasobów (np. baz danych). Do obsługi standardu CGI wymagana jest odpowiednia implementacja tej technologii na serwerze WWW oraz umiejętność tworzenia aplikacji z obsługą standardu CGI. Wymiana danych pomiędzy serwerem WWW a aplikacją realizowana jest poprzez udostępnienie aplikacji zmiennych środowiskowych serwera WWW oraz obsługę operacji plikowych: STDIN - przekazanie danych z serwera WWW do aplikacji, STDOUT - odbiór danych z aplikacji przez serwer WWW oraz plik STDERR - plik zawierający komunikaty diagnostyczne tworzone przez aplikację (plik ten nie jest obsługiwany przez większość serwerów WWW lub komunitaty dołączane są do logu serwera WWW). Na rys. 1 przedstawiono transefer danych pomiędzy serwerem WWW a aplikacją zgodną z standardem CGI.
W ramach standardu serwer WWW staje się bramą (ang. gateway) - pośrednikiem do danego źródła informacji. Poniżej przedstawiono przykładowe zmienne środowiskowe dostępne w aplikacjach wykorzystujących interfejs CGI.
| Zmienna środowiskowa | Opis |
|---|---|
| SCRIPT_NAME | Ścieżka URL wykonywanego skryptu |
| DOCUMENT_ROOT | Katalog z którego pobierane są dokumenty statyczne |
| CONTENT_TYP | Typ treści żądania |
| CONTENT_LENGTH | Długość danych (w bajtach) przekazanych do programu CGI poprzez wejście standardowe. |
| REQUEST_METHODE | Metoda żądania HTTP użyta w danym żądaniu. |
| QUERY_STRING | Zapytanie zawarte w żądaniu URL. |
| REMOTE_IDENT | Jeżeli serwer HTTP wspiera identyfikację opisaną w raporcie RFC 931, zmienna zawiera nazwę użytkownika zgłaszającego żądanie. |
| REMOTE_USER | Nazwa użytkownika, uwierzytelniona przez serwer WWW. |
| REMOTE_HOST | Nazwa zdalnego hosta klienta zgłaszającego żądanie. |
| REMOTE_ADDR | Adres IP zdalnego hosta klienta zgłaszającego żądanie. |
| SERVER_NAME | Nazwa hosta serwera lub jego adres IP. |
| SERVER_SOFTWARE | Nazwa i wersja oprogramowania na serwerze WWW. |
| SERVER_PROTOCOL | Nazwa i wersja protokołu żądania. |
| SERVER_PORT | Numer portu hosta, na którym serwer nasłuchuje. |
| GATEWAY_INTERFACE | Wersja interfejsu CGI wykorzystywanego przez serwer |
| PATH_INFO | Dodatkowa informacja o ścieżce przekazywana do programu CGI. |
| PATH_TRANSLATED | Przesunięta wersja ścieżki podanej w zmiennej PATH_INFO. |
| AUTH_TYPE | Metoda uwierzytelniania użyta do zidentyfikowania użytkownika. |
| HTTP_USER_AGENT | Nazwa i wersja przeglądarki klienta. |
| HTTP_ACCEPT | Lista typów nośnika, które klient może zaakceptować. |
| HTTP_ACCEPT_LANGUAGE | Lista języków, które klient może zaakceptować. |
| HTTP_ACCEPT_ENCODING | Lista sposobów kodowania, które klient może zaakceptować. |
| HTTP_REFERER | URL dokumentu, który skierował użytkownika do danego programu CGI (hiperłącze, formularz) |
| HTTP_COOKIE | Para nazwa-wartość określona przez serwer. |
Skrypty CGI uruchamiane są przez serwer WWW, a więc najczęściej wykonują się z uprawnieniami, które zostały przyznane serwerowi WWW. Rodzi to różne niebezpieczeństwa dla systemu operacyjnego w których działa zarówno serwer WWW jak i skrypt CGI. Z tego też powodu, administratorzy zezwalają na uruchamianie aplikacji CGI z odpowiednich katalogów dla których przygotowano odpowiednie zabezpieczenia. Na serwerze Pascal skrypty CGI mogą być uruchamiane z katalogu "cgi-bin" który znajduje się w katalogu "public_html" użytkownika. Dodatkowo należy nadać plikom uprawnienia dające możliwość uruchomienia przez serwer WWW.
Zadaniem aplikacji (skryptu) działającego poprzez interfejs CGI jest przygotowanie pliku zawierającego dokument HTML oraz informacyjne wiersze nagłówkowe protokołu HTTP. Informacja ta jest przesyłana poprzez plik STDOUT z aplikacji do serwera WWW. Serwer WWW po odebraniu informacji dodaje pozostałe wiersze nagłówkowe łącznie z pierwszym wierszem informującym o wersji protokołu HTTP i kodzie odpowiedzi. Przykładowa odpowiedź generowana przez aplikację CGI przedstawiona została poniżej.
Content-type: text/html <html> <head><title>Pierwszy skrypt CGI</title></head> <body> <p>Skrypt CGI</p> </body> <html>
Konstrukcja powyższej odpowiedzi jest następująca. Po wymaganych wierszach nagłówkowych występuje jedna linia wolna (CR,LF) a następnie dane wynikowe wygenerowane przez aplikację CGI (np. kod HTML, kod dokumentu PDF lub kod zawierający rysunek w standardzie BMP). W przypadku niepoprawnej konstrukcji odpowiedzi wygenerowanej przez aplikację serwer WWW wyśle do przeglądarki komunikat o wewnętrznym błędzie serwera (kod 500 - Intrernal Server Error).
Podsumowując, tworząc aplikację wykorzystującą interfejs CGI należy:
Programy lub skrypty CGI można tworzyć przy pomocy popularnych języków - najczęściej jest to C/C++, Java, Visual Basic, Fortran oraz języków skryptowych python, perl, TCL czy poleceń powłoki uniksowej. Warunkiem jest jednak, aby dane były pobierane i wysyłane z zachowaniem reguł określonych przez standard CGI oraz aby kod wynikowy (w przypadku programów kompilowanych) lub skrypt można było uruchomić w systemie pod kontrolą którego pracuje serwis.
Na początek przedstawiony zostanie skrypt CGI wykorzystujący polecenia powłoki systemu Linux.
#!/bin/sh echo Content-type: text/html echo echo "<html><head><title>Skrypt w shell'u</title></head>" echo "<body>" echo "<h2>Zalogowani uzytkownicy w systemie:</h2>" echo "<pre>" who echo "</pre>" echo "</body>" echo "</html>"
Skrypt przedstawiony poniżej drukuje zmienne środowiskowe dostępne z poziomu powłoki bash (dla serwisu WWW).
#!/bin/bash echo "Content-type: text/html" echo "" echo "<html>" echo "<head>" echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">" echo "<title>Environment Variables</title>" echo "</head>" echo "<body>" echo "Environment Variables:" echo "<pre>" /usr/bin/env echo "</pre>" echo "</body>" echo "</html>" exit 0
Duże możliwości przed projektantami stron WWW daje możliwość wykorzystania języka C/C++ w serwisach WWW. Dostęp do zmiennych środowiskowych w języku C otrzymuje programista poprzez funkcję getenv() zawartą w bibliotece "stdlib.h", natomiast poprzez obsługę standardowego pliku wejściowego "STDIN" otrzymujemy dostęp do ciała żądania (metoda POST).
W przykładzie umieszczonym poniżej zaprezentowano odczyt zmiennych środowiskowych zrealizowane w języku C.
/* Program nalezy skompilowac i udostepnic jego wersje wykonywalna
gcc -o env_c.cgi env_c.c */
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
/* generaja naglowka czastowego */
printf("Content-type: text/html\n\n");
/* Wydruk kodu HTML na STDOUT */
printf("<html><head><title>Przyklad 1</title></head>\n");
printf("<body><h2>Przyklad 1</h2><br>\n");
/* Pobranie informacji o kliencie */
printf("Nazwa serwera klienta: %s<br>\n", getenv("REMOTE_HOST") );
printf("Adres IP klienta: %s<p>\n", getenv("REMOTE_ADDR") );
/* dane serwera przechowywane w zmiennych srodowiskowych */
printf("Nazwa serwera: %s<br>\n", getenv("SERVER_NAME") );
printf("Oprogramowanie na serwerze: %s<br>\n", getenv("SERVER_SOFTWARE") );
printf("Protokol serwera WWW: %s<br>\n", getenv("SERVER_PROTOCOL") );
printf("Numer portu na serwerze: %s<br>\n", getenv("SERVER_PORT") );
printf("</body>\n</html>\n");
return(0);
}
Na koniec zostanie zaprezentowany skrypt odczytujacy zmienne środowiskowe zrealizowany w języku python.
#!/usr/bin/python3
import sys
sys.stderr = sys.stdout
import os
from html import escape
print ("Content-type: text/html")
print ()
print ("<html><head><title>CGI ENV from python</title></head><body><p>")
print ("Running:")
print ("<b>Python %s</b><br><br>" %(sys.version) )
print ("Environmental variables:<br>")
print ("<ul>")
for k in sorted(os.environ):
print ("<li><b>%s:</b>\t\t%s<br>" %(escape(k), escape(os.environ[k])) )
print ("</ul></p></body></html>")
Formularze HTML jest to interfejs użytkownika, który umożliwia wprowadzanie danych i przekazywanie ich do serwera WWW. W ramach aplikacji WWW formularze realizują następujące funkcjonalności: przekazywanie danych użytkownika do aplikacji i sterowanie działaniem aplikacji. Dane z formularza przeglądarka umieszcza w żądaniach do serwera WWW. I tak w żądaniu GET dane są zawarte w łańcuchu zapytania będącego cześcią URL'a w wierszu żądania. W przypadku metody POST dane są zawarte w treści żądania HTTP. Przesyłane dane są kodowane. Domyślnym sposobem kodowania danych jest "application/x-www-form-urlencoded". Możliwe jest też kodowanie "multiform/form-data", stosowane w przypadku formularzy, które umożliwiają przesyłanie plików na serwer WWW.
Przesyłane dane z formularza przeglądarka łączy w pary nazwa=wartość, natomiast poszczególne pary łączone są przy pomocy łącznika & i przesyłane do serwera WWW w następujący sposób:
get1=aaa&get2=bbb&get3=ccc
Uwaga. W czasie kodowania znaki specjane zastępowane znakiem % i dwucyfrową liczbą szesnatkową. Znaki spacji zastępowane są znakami plus.
Na początek przygotujemy odpowiedni formularz, ktory wykorzystamy do przesłania danych na serwer zgodnie z metodą GET i POST protokołu HTTP.
Poniżej zaprezentowano przykład formularza.
<html> <head> <title>Test CGI</title></head> <body> <fieldset> <legend>Form - method=get</legend> <form action="cgi-bin/read.cgi" method="get"> <input type="text" name="get1"><br> <input type="text" name="get2"><br> <input type="password" name="get3" ><br> <input type="submit"> </form> </fieldset> <fieldset> <legend>Form - method=post</legend> <form action="cgi-bin/read.cgi" method="post"> <input type="text" name="post1"><br> <input type="text" name="post2"><br> <input type="password" name="post3" ><br> <input type="submit"> </form> </fieldset> </body> </html>
Prosty przykład prezentujący odbiór danych przesłanych z formularza w ramach skryptu powłoki systemu Linux.
#!/bin/bash full="$QUERY_STRING" echo Content-type: text/html echo echo "<html>" echo "<head><title>HTML Form - GET</title></head>" echo "<body>" echo "$full" echo "<h2>Environment</h2>" echo "<pre>$(env)</pre>" echo "</body>" echo "</html>"
W kolejnym przykładzie zaprezentowano odczyt danych z wiersza żądania dla metody GET zrealizowane w języku C.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main( void )
{
char *ptr;
/* Tworzenie naglowka odpowiedzi */
printf("Content-type: text/html\n\n");
/* Drukowanie kodu HTML na STDOUT */
printf("<html><head><title>Jezyk C</title></head>\n");
printf("<body><h2>Obsluga zadania GET</h2>\n");
/* Czytanie zmiennej srodowiskowej - QUERY_STRING */
printf("Informacja przeslana w zadaniu: %s<p>\n", getenv("QUERY_STRING") );
/* Wyszukanie zawartosci zmiennej */
ptr = strstr( getenv("QUERY_STRING"), "=" );
ptr = ptr + 1;
printf("Hello %s!<br>\n", ptr );
printf("</body>\n</html>\n");
return(0);
}
Kolejny przykład prezentuje kod aplikacji w języku C przetwarzający dane przesyłane przy pomocy metody POST.
#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 80
int main(void)
{
char *lenstr;
char input[MAXLEN];
long len;
printf("%s%c%c\n","Content-Type:text/html;charset=iso-8859-2",13,10);
printf("<TITLE>Response</TITLE>\n");
lenstr = getenv("CONTENT_LENGTH");
if(lenstr == NULL || sscanf(lenstr,"%ld",&len)!=1 || len > MAXLEN)
printf("<p>Error in invocation - wrong FORM probably.</p>");
else {
fgets(input, len+1, stdin);
printf("\n %s",input);
}
return 0;
}
Poniżej skrypty w języku html i języku python.
<html> <head> <title>Test CGI - jezyk python</title></head> <body> <fieldset> <legend>Form - method=get</legend> <form action="cgi-bin/form_py.py" method="get"> <input type="text" name="data1"><br> <input type="text" name="data2"><br> <input type="password" name="data3" ><br> <input type="submit"> </form> </fieldset> <fieldset> <legend>Form - method=post</legend> <form action="cgi-bin/read_py.py" method="post"> <input type="text" name="data1"><br> <input type="text" name="data2"><br> <input type="password" name="data3" ><br> <input type="submit"> </form> </fieldset> </body> </html>
#!/usr/bin/env python3
import cgi
form = cgi.FieldStorage()
text1 = form.getvalue("data1","(no data)")
text2 = form.getvalue("data2","(no data)")
text3 = form.getvalue("data3","(no data)")
# print HTTP/HTML headers
print ("Content-type: text/html")
print ()
print ("""<!DOCTYPE html>
<html><head>
<title>A CGI Script</title>
</head><body>
""")
# print HTML body using form data
print ("<p>Zawartosc pola data1 - " + text1 + ".</p>")
print ("<p>Zawartosc pola data2 - " + text2 + ".</p>")
print ("<p>Zawartosc pola data3 - " + text3 + ".</p>")
#print "<p>TEST OK"
print ("</body></html>")
Na początek odczyt zmiennych środowiskowych przy pomocy skryptu w języku perl. Zmienne środowiskowe dostępne są w tablicy asocjacyjnej $ENV.
#!/usr/bin/perl -wT
print "Content-type: text/html";
print "\n\n";
print <<KONIEC_HTML;
<html>
<head><title>Informacje o serwerze</title></head>
<body>
<h1>Parametry serwera</h1>
<hr><br />
<table>
<tr><td>Nazwa serwera</td> <td>$ENV{SERVER_NAME}</td></tr>
<tr><td>Oprogramowanie serwera</td><td>$ENV{SERVER_SOFTWARE}</td></tr>
<tr><td>Protokol serwera</td> <td>$ENV{SERVER_PROTOCOL}</td></tr>
<tr><td>Wersja CGI</td> <td>$ENV{GATEWAY_INTERFACE}</td></tr>
</table>
</body>
<html>
KONIEC_HTML
Kolejny skrypt w języku perl wyświetli wszystkie zmienne środowiskowe serwera WWW zawarte w tabeli $ENV.
#!/usr/bin/perl
print "Content-type: text/plain\n\n";
foreach $var (sort(keys(%ENV))) {
$val = $ENV{$var};
$val =~ s|\n|\\n|g;
$val =~ s|"|\\"|g;
print "${var}=\"${val}\"\n";
}
Na koniec zaprezenotowny zostanie skrypt w języku perl do przetwarzania danych przesłanych na serwer zgodnie z metodą GET i POST protokołu HTTP.
Przykładowy formularz i odpowiedni skrypt w języku perl przetwarzający przesłane przedstawiono poniżej.
<html> <head> <title>Test CGI</title></head> <body> <fieldset> <legend>Form - method=get</legend> <form action="cgi-bin/read.cgi" method="get"> <input type="text" name="get1"><br> <input type="text" name="get2"><br> <input type="password" name="get3" ><br> <input type="submit"> </form> </fieldset> <fieldset> <legend>Form - method=post</legend> <form action="cgi-bin/read.cgi" method="post"> <input type="text" name="post1"><br> <input type="text" name="post2"><br> <input type="password" name="post3" ><br> <input type="submit"> </form> </fieldset> </body> </html>
Skrypt w perlu dekodujący przesłane dane z formularza.
#!/usr/bin/perl
# Program przetwarzajacy formularz
# Generowanie naglowka HTTP
print "Content-type: text/html\n\n";
# Generowanie dokumentu HTML
print "<html><head>\n";
print "<title> Display query string data </title></head> \n";
print "<body>\n";
# Rozpoznanie metody przeslania danych przez formularz
$request_method = $ENV{'REQUEST_METHOD'};
if ($request_method eq "GET")
{ $query_string = $ENV{'QUERY_STRING'};
print " Metoda przeslania formularza - GET <br><hr>" ; }
elsif ($request_method eq "POST")
{ read(STDIN, $query_string, $ENV{'CONTENT_LENGTH'});
print " Metoda przeslania formularza - post <br><hr>" ; }
else
{ print "Error - request method is illegal \n"; }
# Podzial "query string" na pare wartosci "name=value"
@name_value_pairs = split(/&/, $query_string);
foreach $name_value (@name_value_pairs)
{
($name, $value) = split (/=/, $name_value);
print "Para 'name=value' zawiera nastepujace dane: $name, $value \n<br>";
}
print "</body> </html> \n";