Zadaniem skryptu CGI jest odebranie parametrów żądania z metody GET lub POST (na przykład wypełnionego formularza), wykonanie związanych z tym zadań i wygenerowanie odpowiedzi zawierającej stronę (lub fragment) HTML.
Specyfikacja protokołu do komunikacji z serwerem WWW (HTTP 1.1) znajduje się w dokumencie RFC 2616 dostępnym w archiwach rfc.
W perlu pomocny jest moduł CGI. Przed umieszczeniem skryptu na serwerze dobrze jest go przetestować (patrz testowanie skryptów).
use CGI; # zaladowanie modulu
$cgi = new CGI; # nowy obiekt typu CGI
$pliczek = $cgi->param("log"); # pod zmienna pliczek podstaw wartość pola log
#zapisanie całego formularza do pliku o nazwie wartosci open (OUT,">wartosci"); # otwarcie pliku $cgi->save(OUT); # zapis close OUT; # zamkniecie
W zmiennej $pliczek znajdzie się napis z polskimi literami (o ile wystąpiły) i innymi znakami, które będą już zdekodowane. Najprawdopodobniej polskie litery będą w standardzie CP i to trzeba poprawić (patrz poprawianie polskich liter).
Oprócz odebrania danych należy wysłać odpowiedź do przeglądarki.
require 'cgi-lib.pl';
&ReadParse;
$wartosc_query_w_url = $in{'query'};
na
use CGI;
CGI::ReadParse();
$wartosc_query_w_url = $in{'query'};
print "Content-type: text/html\n\n";
linia ta jest obowiązkowa i wraz innymi dodatkowymi tworzy nagłówki. Po wszystkich liniach nagłówków musi być pusta linia. Brak tych dwóch kryteriów daje błąd "500 Internal Server Error".
Każdy skrypt CGI, zanim wypisze na STDOUT zawartość strony, musi wypisać kilka magicznych linijek tworzących tzw. nagłówek - pozwala on np. serwerowi stwierdzić co konkretnie przesyłamy (HTML, tekst, obrazek). Zasadniczo to o czym większość początkujących musi wiedzieć, to że na początku skryptu w perlu trzeba umieścić coś takiego:
print "Content-type: text/html; charset=ISO-8859-2\n\n";
lub
use CGI qw(:standard); $query = new CGI; print $query->header(-type => 'text/html', -charset => 'ISO-8859-2');
Spowoduje to wyświetlenie nagłówka zawierającego jedną linię, która z kolei informuje co będziemy przesyłać (dokument HTML) oraz przy okazji podaje sposób kodowania polskich liter jako ISO-8859-2 (jeżeli w danym dokumencie nie ma polskich znaczków, można pominąć średnik i "charset=...")
Zamiast "text/html" możemy umieszczać także np. "text/plain" jeżeli przesyłamy dokument tekstowy (nie HTML), albo "image/gif" lub "image/jpeg" jeżeli przesyłamy obrazek odpowiedni GIF lub JPG (wtedy po nagłówku wypisujemy zawartość pliku z obrazkiem "jak leci" bajt po bajcie).
Po wypisaniu w ten sposób nagłówka możemy już wypisywać cokolwiek (chociaż dobrze byłoby żeby było to poprawny HTML - chyba że w nagłówku podaliśmy inny typ danych).
Dla obsługi WAP konieczny jest zamiennik:
print "Content-type: text/vnd.wap.wml\n\n";
Niestety to nie wystarczy. Musisz jeszcze dodać linijki:
<?xml version="1.0"?> <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.2//EN" "http://www.wapforum.org/DTD/wml12.dtd>
No w końcu reszta nie może być pisana w HTML, ale w WML. Język to podobny do HTML, ale ma kilka haczyków:))
print "Content-type: application/octet-stream\n"; print "Content-Disposition: attachment; filename=NAZWA_PLIKU_Z_ROZSZERZENIEM\n\n";
Otwieramy plik binarnie i wypluwamy do przeglądarki:
open AA,"plik.bin"; #binmode STDOUT; #w niektorych systemach ta jest linia potrzebna #binmode AA; #jak wyzej print join '',<AA>; #te linie mozna zamienic na: undef $/; print <AA>; close AA;
W IE może wystąpić problem zapisu jako index.html, trzeba wówczas w <a href= wywołojącym skrypt dodać target=_blank.
<form method="post" enctype="multipart/form-data" action="skrypt.cgi"> <input type="file" name="uploaded_file">
a w Perlu:
use CGI;
$query = new CGI;
$fh = $query->upload('uploaded_file'); # nazwa pliku
#$fh = $query->param('uploaded_file'); # dla starszych wersji
#typ zawartosci pliku:
$type = $query->uploadInfo($fh)->{'Content-Type'};
#naglowek:
print $query->header(-type=>$type);
#wypisanie pliku na ekran:
while (<$fh>) { print; }
Niekiedy może być potrzebne użycie konstrukcji
use CGI qw(-private_tempfiles);
a także ustawienie binmode OUTFILE. Dane charakterystyczne można dostać:
%hash=%{$dane->uploadInfo($file)};
foreach (keys(%hash)) {print "$_ -> $hash{$_}\n"};
Przed zbyt dużymi uploadami dobrze jest się zabezpieczyć przez:
$CGI::POST_MAX=$ile_bajtow;
use LWP::Simple;
if ($tekst_strony = get('http://serwer/sciezka/plik'))
{ print "udało się ściągnąć plik"; }
else { print "nie udało się ściągnąć pliku"; }
lub bardziej rozbudowana metoda:
use LWP::UserAgent; $ua = new LWP::UserAgent; #ustawienie czego my $req = new HTTP::Request(GET => 'http://serwer/sciezka/plik'); $ua->credentials("serwer:80","Areajakastam","user","haslo"); #jesli na haslo #sciagniecie my $res = $ua->request($req); #wypisanie if ($res->is_success) { print $res->content; } else {print $res->error_as_HTML; }
use HTTP::Request::Common qw(POST); use LWP::UserAgent; $ua = LWP::UserAgent->new; my $req = POST 'http://www.perl.com/cgi-bin/BugGlimpse', [ search => 'www', errors => 0 ]; print $ua->request($req)->as_string;
lub
use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
$ua->agent('Mozilla/5.0');
my %form = (
'login' => 'user',
'pass' => 'haslo',
);
my $response = $ua->post('http://url.do.strony/', \%form);
my $www = new WWW::Mechanize;
$www->get('http://search.cpan.org');
$www->follow_link( text_regex => qr/jakies wyrazenie regularne/i );
Można także wykorzystać moduł IO::Socket, należy przy tym znać protokół HTTP.
use IO::Socket;
$misie=IO::Socket::INET->new(PeerAddr => "server.com", PeerPort => 80,
Timeout => 1, Proto => 'tcp');
$misie->print("GET /sciezka/plik HTTP/1.0\n\n");
while(<$misie>) { print; }
Alternatywą może też być Net::HTTP.
Dla obsługi https (HTTP+SSL) trzeba zainstalować OpenSSL, a następnie moduł Net::SSLeay lub Crypt::SSLeay.
$ftp = Net::FTP->new("some.host.name", Debug => 0);
$ftp->login("anonymous",'me@here.there');
$ftp->cwd("/pub");
$ftp->get("that.file");
$ftp->quit;
use CGI::Cookie;
my $zmienna;
my %cookies = fetch CGI::Cookie; # hash z ciastkami
if ( defined($cookies{'nazwa_ciacha'}) ) # jesli jest o tej nazwie
{ $zmienna = $cookies{'nazwa_ciacha'}->value; } # sprawdz wartosc
$cookie = $ENV{'HTTP_COOKIE'}; #zwraca wszystkie ciacha dla domeny
lub po prostu
use CGI;
$q = new CGI;
$zmienna = $q->cookie('nazwa_ciacha');
%answers = $q->cookie('answers');
Wysyłanie:
use CGI qw/:standard/; use CGI::Cookie;
$ciastko1 = new CGI::Cookie(-name => 'Liczba',-value => '2'); $ciastko2 = new CGI::Cookie(-name => 'Name',-value => 'Michal');
print header(-cookie=>[$ciastko1,$ciastko2]); # Uwaga: trzeba to wyslac przed wlasciwa trescia strony!!!
Inny moduł to HTTP::Cookies:
use HTTP::Cookies; use LWP::UserAgent;
my $ua = LWP::UserAgent->new; my $cookie_jar = HTTP::Cookies->new; $ua->cookie_jar($cookie_jar);
Nie da się na przykład wysłać cookie przez skrypt uruchamiany jako SSI.
AddType text/html .shtml AddHandler server-parsed .shtml
lub (w starszych wersjach Apache'a)
AddType text/x-server-parsed-html .shtml
oraz (dla pliku httpd.conf w zasięgu sekcji <Directory /ścieżka/do/katalogu>)
Options +Includes +ExecCGI
Dzięki nim (opcja Includes) Apache będzie przeglądał pliki o rozszerzeniu ".shtml" w poszukiwaniu tagów typu
<!--#bla bla bla -->
To, co wygenerują wywołane przez SSI skrypty nie jest już interpretowane: wywołań SSI nie można zagnieżdzać.
Istnieją dwie zasadnicze metody odwoływania się do SSI: exec i include. Ta druga jest zalecana: jest bezpieczniejsza, a skryptom wywoływanym (dzięki opcji ExecCGI) w ten sposób można przekazywać parametry bezpośrednio, poprzez URL np.
<!--#include virtual="skrypt.cgi?parametr=wartosc" -->
Aby przekazać parametry do SSI należy napisać plik.shtml?param=wart Dzięki tej formule wszystkie parametry powinny być widoczne we wszystkich odwołaniach SSI. Parametry będą również widoczne po ich ustawieniu w kodzie HTML tagiem
<!--#set var="nazwa_zmiennej" value="wartosc" -->
Więcej informacji w dokumentacji do Apache np. http://sunsite.icm.edu.pl/pub/www/apache/docs/mod/mod_include.html
perl.exe skrypt.pl
Inna wersja to #!C:\perl\bin\perl.exe -w lub #!C:\perl\bin\wPerl.exe
Zobacz też punkt o testowniu skryptów.
open( F, "+<$czas.dat" ) or die "open: $!"; my $l = <F>; seek F, 0, 0; print F ++$l; close F;
Należy także uwzględnić zagadnienie równoczesności dostępu poprzez ograniczanie dostępu do pliku (patrz dział pliki).
2. Jeśli wpisy do księgi gości pojawiają się na stronie już w postaci gotowego pliku html'a (strona nie jest generowana w locie na podstawie bazy danych) to sprawdź koniecznie ustawienie serwera czy nie jest on parsowany (AddHandler server-parsed "tu rozszerzenie pliku z księgą gości"). Jeśli tak, to np. jakiś wesołek jako wpis wstawi wredną dyrektywę SSI i "obejrzy sobie swój wpis w książce". To co zobaczy zależy od pomysłowości autora i stopnia zabezpieczenia serwera, ale na pewno nie będzie to wpis.
3. Uwaga jeśli wpisy stają się w pewnym miejscu skryptu parametrami poleceń systemowych.
4. Uwaga jeśli wpisy stają się w pewnym miejscu skryptu choćby fragmentami ścieżek do plików. (3 i 4 - naprawdę niebezpieczne gdy skrypt chodzi z wyższymi uprawnieniami niż nobody)
5. Sprawdzenie wielkości przesyłanych danych - to chyba oczywiste
6. Sprawdzenie wpisów pod kątem zawartości tagów html'owych (i ich zamiana)
Mod_perl polega właśnie na tym, że skrypt jest uruchamiany raz, a potem kolejne zapytania obsługuje "w pętli". Jeżeli gdzieś otwierasz połączenie do bazy, to zostanie ono otwarte. Jeżeli gdzieś coś bindujesz, to pozostanie zbindowane. Pisząc pod mod_perla należy o tym pamiętać i robić na końcu swojego skryptu "porządki", tak żeby kolejne wywołanie mogło poprawnie działać.
Przy użyciu modperla nie tylko możesz pisać szybkie CGI, ale też robić znacznie więcej - za pośrednictwem specjalnego API masz dostęp do "bebechów" serwera HTTP (tzn. Apache). Służy też do pisania modułów do Apacha w perlu. Możesz użyć np. Apache::Registry - i wtedy piszesz normalne CGI (przy użyciu modułu CGI albo dowolnego innego do standardowych CGI), tyle że jest ono uruchamiane przez modperla więc działa znacznie szybciej (odpada czas kompilacji) - 20 do kilkuset razy.
Mod_perl lubi być pamięciożerny, co przy wielu jednoczesnych serwerach Apache bywa często problemem.
Instalacja: http://perl.apache.org/guide/install.html
http://www.pckurier.pl/archiwum/artykuly/kozinski_maciej/2001_03_74/list4.aspDokumentacja: http://perl.apache.org/ Apache z mod_perl i in.: http://www.apachetoolbox.com/
perldoc Apache, Apache::Registry, Apache::PerlRun
Do serwera WWW musi być dołączony moduł FastCGI. W pliku httpd.conf musi być linia np.: AddHandler fastcgi-script .fcgi, dzięki temu wszystkie skrypty z rozszerzeniem fcgi powinny być wykonywane przez FastCGI.
Skrypty FastCGI nieco różnią się od zwykłych CGI. Musisz stosować moduł FCGI (lub jakąś "nakładkę" na niego).
W FastCGI jest tak: serwer wcześniej uruchamia skrypt CGI, który jest napisany w specjalny sposób, po wykonaniu inicjalizacji otwiera gniazdko i oczekuje na polecenia. W momencie gdy przyjdzie request obsługiwany przez ten skrypt, serwer łączy się ze skryptem, przesyła mu parametry i czeka na odpowiedź.
Dzięki temu, po pierwsze znika nam czas potrzebny na uruchomienie perla, wczytanie i skompilowaniu skryptu, po drugie - możemy wykonać czasochłonne operacje (jak np. połączenie do bazy) w części inicjalizacyjnej.
Najprościej zmienić wszędzie w programie CGI na CGI::Fast. Ale to niespecjalnie przyspieszy działanie programów. Żeby to miało sens, trzeba skrypt przepisać tak:
#!/usr/bin/perl use CGI::Fast;
# tutaj inicjalizacja - to co wystarczy zrobić raz $dbh = DBI::connect ... $sth = $dbh->prepare( "SELECT ... WHERE x = ?" );
while( my $q = new CGI::Fast ) {
# A tutaj obsługa każdego kolejnego requestu, np.:
print "Content-type: text/plain\n\n";
print "OK\n";
}
W momencie gdy przychodzi nowy request do serwera HTTP skrypt zazwyczaj jest już w miejscu "new CGI::Fast" więc może bardzo szybko obsłużyć zapytanie. Ponowne przejście do tego warunku w pętli powoduje najpierw zakończenie poprzedniego requestu i wysłanie wyników do przeglądarki, a następnie oczekiwanie na kolejny request.
Jak widać, requesty są kolejkowane i obsługiwane sekwencyjnie. W praktyce można konfigurować serwer tak, aby uruchamiał więcej niż jeden proces FastCGI, w ten sposób możemy obsługę bardziej "zrównoleglić".
Przegląd szablonów dostępny jest pod adresem http://perl.apache.org/features/tmpl-cmp.html
Przykład template.tmpl:
<html> <head> <title><TMPL_VAR NAME=title></title> <meta http-equiv="Content-type" content="text/html; charset=iso-8859-2" \> </head> <body bgcolor="#ffffff"> <center><h1><TMPL_VAR NAME=title></h1></center><TMPL_VAR NAME=descr></body> </html>
i fragment kodu w perlu:
use HTML::Template;
$tmpl = new HTML::Template(filename => "template.htmp") $tmpl->param(title => "Test"); $tmpl->param(descr => "Testowa strona"); $tmpl->output(); $tmpl->clear_params();