Java Script I Java Server Pages
Java Script I Java Server Pages
O autorze
Marty Hall jest starszym specjalistą komputerowym w Research and Technology
Development Center w Laboratorium Fizyki Stosowanej na Uniwersytecie Johna Hopkinsa.
Specjalizuje się w wykorzystaniu języka Java oraz technologiach związanych w WWW. Marty
uczy takŜe języka Java oraz programowania aplikacji WWW na Uniwersytecie Johna Hopkinsa w
ramach programu kursów dokształcających, gdzie zajmuje się zagadnieniami przetwarzania
rozproszonego oraz technologii internetowych. Jeśli tylko ma okazję, prowadzi takŜe krótkie kursy
poświęcone serwletom, JSP oraz innym technologiom związanym z językiem Java. Marty jest takŜe
autorem ksiąŜki Core Web Programming, wydanej przez Wydawnictwo Prentice Hall w 1998 roku.
MoŜna się z nim skontaktować pisząc na następujący adres:
Reseach and Technology Development Center
The Johns Hopkins University Applied Phisics Laboratory
11100 Johns Hopkins Road
Laurel, MD 20723
[email protected]
1
„Eagle Eye” — „Orle oko”
Wprowadzenie
Na początku 1996 roku zacząłem uŜywać języka Java w większości moich projektów
programistycznych. Napisałem kilka programów CGI i w niewielkim stopniu zajmowałem się takŜe
wczesnymi wersjami serwletów, jednak w przewaŜającej mierze tworzyłem aplikacje działające po
stronie klienta. Jednak w ciągu ostatnich kilku lat coraz większy nacisk kładziono na programy
działające po stronie serwera i z tego względu powaŜniej zająłem się serwletami oraz technologią
JavaServer Pages. Zeszłego roku zainteresowanie tymi technologiami programistów, firm
programistycznych oraz twórców specyfikacji platformy języka Java, wzrosło w ogromnym
stopniu. Wzrost zainteresowania tymi technologiami jest tak duŜy, iŜ w szybkim tempie stają się
one standardowym narzędziem do tworzenia dynamicznych aplikacji WWW oraz internetowych
programów umoŜliwiających korzystania z baz danych i aplikacji działających na serwerze.
Niestety, bardzo trudno było jednak znaleźć dobre, praktyczne informacje dotyczące
tworzenia serwletów oraz JSP. Znalazłem trochę ksiąŜek poświęconych serwletom, jednak tylko
kilka z nich zawierało informacje o najnowszych wersjach specyfikacji, zaawansowanych
technikach i odzwierciedlało doświadczenia nabyte podczas realizacji realnie wykorzystywanych
projektów. Spośród tych kilku ksiąŜek, jeśli któraś z nich w ogóle opisywała zagadnienia związane
z JSP, to dotyczyły one specyfikacji JSP 1.0, nigdy JSP 1.1. Jednak w wielu sytuacjach JSP
znacznie lepiej nadaje się do rozwiązania problemu niŜ serwlety; a zatem, cóŜ była by warta ksiąŜka
o servletach, która nie opisywałaby takŜe JSP? W ciągu kilku ostatnich miesięcy na rynku pojawiło
się nieco więcej dobrych ksiąŜek poświęconych JSP. Jednak znaczna większość z nich w ogóle nie
omawia serwletów. Ale czy to ma sens? PrzecieŜ integralną częścią technologii JavaServer Pages
jest wykorzystanie elementów skryptowych do stworzenia kodu serwletu. A zatem, bez dogłębnej
znajomości zasad działania i tworzenia serwletów, nie moŜna efektywnie wykorzystywać JSP. Poza
tym, większość działających na Internecie witryn nigdy nie wykorzystuje tylko jednej z tych
technologii, lecz obie jednocześnie. I w końcu ostatnia sprawa. Podczas prowadzanie kursów w
ramach dokształcających na Uniwersytecie Johna Hopkinsa zauwaŜyłem, Ŝe bardzo niewielu
spośród moich słuchaczy (których większość stanowili profesjonalni programiści) znała
zagadnienia związane z protokołem HTTP 1.1, działaniem formularzy HTML oraz obsługą JDBC
— czyli trzema kluczowymi technologiami pomocniczymi. Zmuszanie tych osób od kupowania
ksiąŜek poświęconych kaŜdemu z tych zagadnień było bezsensowne, gdyŜ w tym przypadku ilość
ksiąŜek, którą programista musiałby kupić i przeczytać w celu tworzenia powaŜnych aplikacji
wykorzystujących serwlety i JSP, wzrosłaby do pięciu.
A zatem, w połowie 1999 roku, stworzyłem krótki kurs tworzenia serwletów oraz stron JSP
poparty kilkunastoma przykładami, opublikowałem go na WWW i spróbowałem przedstawić ten
sam materiał na kilku spośród prowadzonych przeze mnie kursach. Reakcja była oszałamiająca. JuŜ
po kilku miesiącach opublikowany przez mnie kurs odwiedzało kilkaset osób dziennie, nie
wspominając w ogóle o setkach próśb o poszerzenie zamieszczonych informacji. W końcu
pogodziłem się z nieuchronnym losem i zacząłem pisać. Niniejsza ksiąŜka jest efektem mej pracy.
Mam nadzieję, Ŝe okaŜe się przydatna.
13 Wprowadzenie
Zastosowane konwencje
W tekście niniejszej ksiąŜki kody programów oraz generowane przez nie wyniki są
przedstawiane czcionką o stałej szerokości. Na przykład, abstrakcyjnie omawiając programy
działające po stronie serwera i wykorzystujące protokół HTTP, mogę uŜywać takich wyraŜeń jak
„serwlety HTTP” lub, po prostu, „serwlety”. Gdy jednak piszę HTTPServlet, to mam na myśli
konkretną klasę Javy.
Informacje wprowadzane przez uŜytkownika w wierszu poleceń prezentowane są
pogrubioną czcionką, natomiast generowane komunikaty mogą być bądź to ogólne (oznaczane jako
Prompt>) bądź teŜ wskazywać rodzaj uŜywanego systemu operacyjnego (na przykład DOS>).
Przykładowo, poniŜszy fragment tekstu oznacza, iŜ wykonanie polecenia „java PewienProgram” na
dowolnej platformie systemowej spowoduje wygenerowanie wyników o postaci „Odpowiednie
wyniki”:
Prompt> java PewienProgram
Odpowiednie wyniki
O witrynie WWW
Istnieje specjalna witryna WWW poświęcona niniejszej ksiąŜce, jej adres to
http://www.coreservlet.com/. Korzystanie z tej witryny jest bezpłatne, a moŜna na niej znaleźć:
• kody źródłowe wszystkich przykładów podanych w niniejszej ksiąŜce (moŜna ich uŜywać w
nieograniczony sposób bez konieczności jakiejkolwiek rejestracji),
• internetową dokumentację API (w formacie Javadoc) wszystkich klas stworzonych w
ksiąŜce,
• aktualne adresy witryny umoŜliwiających pobranie oprogramowania, które moŜna
wykorzystać przy tworzeniu serwletów i stron JSP,
• informacje o zniŜkach przy zakupie niniejszej ksiąŜki,
• doniesienia dotyczące kursów tworzenia serwletów i stron JSP,
• informacje dotyczące nowych wydań i aktualizacji niniejszej ksiąŜki.
Rozdział 1.
Podstawowe informacje o
serwletach i Java Server Pages
Ten rozdział zawiera krótką prezentację serwletów oraz JavaServer Pages (JSP) i
przedstawia zalety kaŜdej z tych technologii. Podałem w nim takŜe informacje o tym gdzie zdobyć i
jak skonfigurować oprogramowanie konieczne do tworzenia serwletów oraz dokumentów JSP.
1.1 Serwlety
Serwlety to odpowiedź technologii związanych z językiem Java na programy CGI (Common
Gateway Interface). Serwlety są programami wykonywanymi na serwerze WWW. Stanowią one
warstwę pośrednią pomiędzy Ŝądaniami przesyłanymi przez przeglądarkę WWW lub inny program
uŜywający protokołu HTTP oraz bazami danych bądź aplikacjami wykonywanymi na serwerze
HTTP. Ich zadaniem jest:
1) Odczytywanie wszelkich danych przesyłanych przez uŜytkownika.
Dane te są zazwyczaj wprowadzane w formularzu umieszczonym na stronie WWW,
jednak mogą takŜe pochodzić z apletu Javy lub innego programu uŜywającego protokołu
HTTP.
2) Odszukanie wszelkich innych informacji dotyczących Ŝądania, umieszczonych w
Ŝądaniu HTTP.
Informacje te zawierają szczegółowe dane dotyczące moŜliwości przeglądarki,
cookies, nazwy komputera na którym działa program, i tak dalej.
3) Generacja wyników.
Ten proces moŜe wymagać wymiany informacji z bazą danych, wykonania wywołania
RMI lub CORBA, uruchomienia aplikacji bądź bezpośredniego obliczenia wyników.
4) Sformatowanie wyników wewnątrz dokumentu.
W większości przypadków sformatowanie wyników polega na umieszczeniu ich
wewnątrz dokumentu HTML.
5) Podanie odpowiednich parametrów odpowiedzi HTTP.
Oznacza to przekazanie do przeglądarki informacji określających typ przesyłanego
dokumentu (na przykład: HTML), podanie cookies, określenia parametrów zarządzających
przechowywaniem strony w pamięci podręcznej przeglądarki, itp.
6) Wysłanie dokumentu z powrotem do uŜytkownika.
Dokument moŜe zostać przesłany w formacie tekstowym (HTML), binarnym (obrazy
GIF), a nawet w postaci skompresowanej (na przykład gzip) modyfikującej dokument
zapisany w innym formacie.
19 Rozdział 1. Podstawowe informacje o serwletach i Java Server Pages
Wiele nadsyłanych Ŝądań moŜna obsłuŜyć przesyłając gotowe dokumenty. śądania tego
typu są obsługiwane przez serwer bez wykonywania serwletów. Jednak w wielu innych sytuacjach
statyczne wyniki nie są wystarczające, a wynikowa strona musi zostać wygenerowana osobno dla
kaŜdego Ŝądania. Istnieje wiele przyczyn, dla których strony WWW muszą być generowane
dynamicznie. Oto niektóre z nich:
• Strona WWW generowana jest na podstawie informacji przesłanych przez
uŜytkownika.
Na przykład, strony generowane przez mechanizmy wyszukiwawcze oraz strony
zawierające potwierdzenia zamówień składanych w sklepach internetowych budowane są na
podstawie konkretnego Ŝądania.
• Strona WWW jest tworzona na podstawie informacji, które często ulegają zmianie.
Na przykład witryny zawierające prognozy pogody lub serwisy informacyjne mogą
generować strony dynamicznie lub zwracać poprzednio stworzoną stronę jeśli jeszcze jest
aktualna.
• Przy tworzeniu strony WWW wykorzystywane są informacje pochodzące z
korporacyjnej bazy danych lub innych zasobów dostępnych na serwerze.
Na przykład, witryna słuŜąca do handlu internetowego moŜe wykorzystywać
serwlety w celu stworzenia strony prezentującej listę dostępnych artykułów wraz z
informacjami o ich cenach i moŜliwości zakupu.
W zasadzie wykorzystanie serwletów nie ogranicza się w cale do WWW lub serwerów
aplikacji obsługujących Ŝądania HTTP. MoŜna ich takŜe uŜywać w serwerach wszelkich innych
typów. Przykładowo, serwlety moŜna umieścić w serwerze pocztowym lub serwerze FTP,
rozszerzając w ten sposób ich moŜliwości funkcjonalne. Jednak w praktyce ten sposób
wykorzystania serwletów nie zyskał popularności; dlatego teŜ, w niniejszej ksiąŜce, ograniczę się
do omówienia serwletów HTTP.
Efektywność
W przypadku wykorzystania tradycyjnej technologii CGI, dla kaŜdego Ŝądania HTTP
tworzony jest nowy proces. Jeśli sam program CGI jest stosunkowo krótki, to przewaŜającą część
wykonania programu moŜe stanowić uruchomienie procesu. W przypadku serwletów, wirtualna
maszyna Javy działa bez przerwy i obsługuje wszystkie nadsyłane Ŝądania wykorzystując do tego
niewielkie wątki Javy, a nie procesy systemu operacyjnego, które wykorzystują wiele zasobów
systemowych. Co więcej, w przypadku korzystania z tradycyjnych programów CGI, jeśli
jednocześnie zostanie nadesłanych N Ŝądań skierowanych do tego samego programu, jego kod
zostanie N razy załadowany do pamięci. W przypadku serwletów, w takiej sytuacji zostanie
utworzonych N wątków, lecz wykorzystywana będzie wyłącznie jedna kopia klasy serwletu. I
ostatnia sprawa. Gdy program CGI skończy obsługiwać Ŝądanie, zostanie on zakończony. Utrudnia
to przechowywanie wyników obliczeń w pamięci podręcznej, utrzymywanie otwartych połączeń z
bazami danych oraz wykonywanie wszelkich innych optymalizacji bazujących na trwałych
informacjach. Serwlety natomiast pozostają w pamięci nawet po zakończeniu obsługi Ŝądania,
20
dzięki czemu przechowanie dowolnie złoŜonych danych pomiędzy poszczególnymi Ŝądaniami staje
się bardzo proste.
Wygoda
Serwlety dysponują rozbudowanymi narzędziami słuŜącymi do automatycznego
przetwarzania i dekodowania danych przesyłanych z formularzy HTML, odczytywania i określania
nagłówków HTTP, obsługi cookies, śledzenia sesji oraz wieloma innymi narzędziami wysokiego
poziomu. Poza tym znasz juŜ język programowania Java, po co zatem miałbyś się uczyć Perla? Nie
trzeba Cię takŜe przekonywać, Ŝe dzięki uŜyciu języka Java oraz jego technologii moŜna tworzyć
bezpieczniejszy kod zapewniający większe moŜliwości wielokrotnego uŜycia, w porównaniu z
analogicznym kodem napisanym w języku C++. Po co zatem miałbyś wracać do tworzenia
programów działających na serwerze pisanych w C++?
DuŜe moŜliwości
Serwlety udostępniają kilka moŜliwości, których implementacja w tradycyjnych programach
CGI jest wyjątkowo trudna, lub wręcz niemoŜliwa. Serwlety mogą bezpośrednio wymieniać
informacje z serwerami WWW. Programy CGI nie dysponują taką moŜliwością, a przynajmniej nie
bez wykorzystania specjalnego API (interfejsu programistycznego) serwera. Przykładowo,
moŜliwość bezpośredniej komunikacji z serwerem ułatwia translację względnych adresów URL na
ścieŜki dostępu do konkretnych plików. Kilka serwletów moŜe takŜe korzystać ze wspólnych
danych, co znacznie ułatwia implementację wielokrotnego wykorzystywania połączeń z bazami
danych oraz innych, podobnych rozwiązań optymalizujących wykorzystanie zasobów. Serwlety
mogą takŜe przechowywać informacje pomiędzy kolejnymi Ŝądaniami, dzięki czemu ułatwiają
wykorzystanie takich technik jak śledzenie sesji oraz przechowywanie wyników poprzednich
obliczeń.
Przenośność
Serwlety są pisane w języku Java i wykorzystują standardowy interfejs programistyczny. W
rezultacie, serwlety napisane z myślą o, dajmy na to, I-Planet Enterprise Serverze, mogą działać, w
zasadzie, w niezmienionej postaci takŜe na serwerze Apache, Microsoft Internet Information
Serverze (IIS), IBM WebSphere, czy teŜ serwerze WebStar firmy StarNine. Na przykład, niemal
wszystkie przykładowe serwlety oraz dokumenty JSP przedstawione w niniejszej ksiąŜce były
wykonywane na Java Web Serverze firmy Sun, serwerze Tomcat opracowanym przez fundację
Apache Software oraz na JavaServer Web Development Kit firmy Sun, i to bez wprowadzania
jakichkolwiek zmian w kodzie. Wiele z przykładów zostało takŜe przetestowanych na serwerach
BEA WebLogic oraz IBM WebSphere. De facto, serwlety są obsługiwane bezpośrednio lub za
pośrednictwem odpowiednich plug-inów przez niemal wszystkie najpopularniejsze serwery WWW.
Aktualnie serwlety stanowią część Java 2 Platform, Enterprise Edition (J2EE; patrz
http://java.sun.com/j2ee/), dzięki czemu przemysłowe wsparcie serwletów stanie się jeszcze
większe.
Bezpieczeństwo
Jedno z podstawowych zagroŜeń istniejących w tradycyjnych programach CGI wynikało z
faktu, iŜ programy te często były wykonywane przez powłoki systemowe ogólnego przeznaczenia.
Z tego względu programiści tworzący programy CGI musieli zwracać szczególną uwagę na
odnajdywanie i eliminację znaków traktowanych przez powłokę systemową w sposób specjalny,
21 Rozdział 1. Podstawowe informacje o serwletach i Java Server Pages
takich znaków jak odwrotne apostrofy (`) oraz średniki (;). Zadanie to jest trudniejsze niŜ moŜna
przypuszczać, a problemy wynikające z tego powodu wciąŜ są odnajdywane w powszechnie
wykorzystywanych bibliotekach CGI. Kolejnym źródłem problemów jest fakt, iŜ do tworzenia
programów CGI uŜywane są języki, które nie sprawdzają granic tablic i łańcuchów znaków. Na
przykład w języku C lub C++ dopuszczalne jest przydzielenie pamięci dla 100-elementowej tablicy,
a następnie określenie wartości jej 999-go elementu, który w rzeczywistości zostanie zapisany w
bliŜej nie określonym miejscu pamięci programu. A zatem, programiści, którzy zapomną o
samodzielny sprawdzaniu zakresów tablic i łańcuchów znaków, naraŜają swój system na celowe
bądź przypadkowe ataki typu przepełnienie buforu. W przypadku stosowania serwletów zagroŜenia
tego typu nie występują. Nawet jeśli serwlet wykona zdalne wywołanie systemowe w celu
wykonania jakiegoś programu w lokalnym systemie operacyjnym, to i tak do tego celu uŜywana
powłoka systemowa. Jeśli zaś chodzi o sprawdzanie zakresów i inne mechanizmy ochrony pamięci,
to stanowią one integralną część języka Java.
Niewielkie koszty
Dostępnych jest wiele darmowych lub bardzo tanich serwerów WWW doskonale
nadających się do uŜytku „osobistego” lub do obsługi witryn o niewielkim natęŜeniu ruchu. Jednak
za wyjątkiem serwera Apache, który jest dostępny bezpłatnie, większość innych serwerów WWW o
komercyjnej jakości jest dosyć droga. Niemniej jednak, gdy juŜ zdobędziesz serwer WWW to
niezaleŜnie od jego ceny dodanie do niego narzędzi obsługi serwletów (jeśli sam serwer nie jest w
nie wyposaŜony) będzie stanowiło znikomy wydatek. Znacznie odróŜnia to technologie
wykorzystujące serwlety od alternatywnych rozwiązań CGI, w przypadku których konieczny jest
zakup drogich bibliotek lub pakietów programistycznych.
<HTML>
<HEAD><TITLE>Witamy w naszym sklepie</TITLE></HEAD>
<BODY>
<H1>Witamy w naszym sklepie</H1>
<SMALL>Witamy,
<!-- W przypadku uŜytkowników odwiedzających sklep po raz
pierwszy nazwa uŜytkownika ma postać "Nowy uŜytkownik" -->
<%= Utils.getUserNameFromCookie(request) %>.
Dostęp do <A HREF="Account-Settings.html">ustawień Twojego konta</A>
</SMALL>
22
W porównaniu z PHP
PHP jest darmową technologią o ogólnie dostępnym kodzie źródłowym. PHP to język
skryptowy przypominający nieco ASP i JSP, a pisane w nim programy umieszczane są
bezpośrednio w kodzie dokumentów HTML. Zaletą JSP w porównaniu z PHP jest tworzenie
dynamicznych części dokumentu przy uŜyciu języka Java, który juŜ prawdopodobnie znasz i który
dysponuje rozbudowanymi narzędziami programistycznymi do komunikacji sieciowej,
wykorzystania baz danych, obsługi obiektów rozproszonych, itp. Natomiast wykorzystanie PHP
wiąŜe się z koniecznością nauki nowego języka programowania.
W porównaniu z serwletami
W zasadzie absolutnie wszystko co moŜna zrobić przy uŜyciu JSP moŜna takŜe
zaimplementować za pomocą serwletów. W rzeczywistości, w sposób całkowicie niewidoczny,
dokumenty JSP są automatycznie tłumaczone do postaci serwletów. Jednak znacznie wygodniej jest
tworzyć (i modyfikować) kod HTML niŜ tysiące wywołań metody println generujących ten sam
kod. Co więcej, oddzielając zagadnienia prezentacji od treści, moŜna wykorzystać róŜne osoby do
realizacji róŜnych zadań — projektanci stron WWW mogą tworzyć dokumenty HTML przy uŜyciu
swoich ulubionych narzędzi, zostawiając w nich miejsca w których programiści serwletów
umieszczą dynamicznie wygenerowane informacje.
2
MoŜna takŜe zintegrować Tomcata z Internet Information Serverem firmy Microsoft oraz z serwerami firmy Netscape.
3
Od maj 2001 roku firm Sun przestała udostępniać Java Web Server. (przyp. red.)
25 Rozdział 1. Podstawowe informacje o serwletach i Java Server Pages
Ten serwer został w całości napisany w języku Java i był jednym z pierwszych
serwerów WWW, które w pełni obsługiwały specyfikacje Java Servlet 2.1 oraz JSP 1.0.
Choć serwer ten nie jest juŜ aktywnie modernizowany, gdyŜ firma Sun koncentruje swe
wysiłki na serwerze Netscape/I-Planet, to wciąŜ jednak jest on często wykorzystywany do
nauki tworzenia serwletów oraz stron JSP. Bezpłatną, testową wersję serwera (z
ograniczeniem czasowym) moŜna znaleźć pod adresem
http://www.sun.com/software/jwebserver/try/. Informacje na temat bezpłatnej wersji
serwera, której działanie nie jest ograniczone czasowo, dostępnej dla instytucji oświatowych
i przeznaczonej do celów dydaktycznych moŜna znaleźć pod adresem
http://freeware.thesphere.com/.
standardowych folderach systemowych. Jeśli samemu określasz wartość tej zmiennej systemowej,
to nie zapomnij dołączyć do niej znaku kropki (.), oznaczającego aktualny folder.
PoniŜej przedstawiłem krótkie informacje o tym, jak naleŜy określać zmienne środowiskowe
w dwóch platformach systemowych. Przyjmij, Ŝe dir to nazwa katalogu w którym zostały
umieszczone pliki klasowe serwletów oraz JSP.
Unix (C Shell)
setenv CLASSPATH .:dir/servlet.jar:dir/jspengine.jar
Jeśli wartość zmiennej środowiskowej CLASSPATH została juŜ wcześniej określona, a teraz
chcesz dodać do niej nowe katalogi (nie usuwają przy tym jej aktualnej zawartości), to na końcu
polecenia setenv dopisz wyraŜenie :$CLASSPATH. Zwróć uwagę, iŜ w systemach Unix nazwy
poszczególnych katalogów w ścieŜce są od siebie oddzielone znakami ukośnika, natomiast
poszczególne ścieŜki — znakami dwukropka. W systemach Windows do tych samych celów
uŜywane są znaki odwrotnego ukośnika oraz średnika. Aby zmiany w wartości zmiennej
środowiskowej CLASSPATH były trwałe, powinieneś umieścić powyŜsze polecenie w pliku .cshrc.
Windows
set CLASSPATH=.;dir\servlet.jar;dir\jspengine.jar
Jeśli wartość zmiennej środowiskowej CLASSPATH została juŜ wcześniej określona, a teraz chcesz dodać do
niej nowe katalogi, to na końcu polecenia dopisz wyraŜenie ;%CLASSPATH%. Zwróć uwagę, iŜ w systemach Windows
nazwy poszczególnych katalogów w ścieŜce są od siebie oddzielone znakami odwrotnego ukośnika, natomiast
poszczególne ścieŜki — znakami średnika. W systemach Unix do tych samych celów uŜywane są znaki ukośnika oraz
dwukropka. Aby powyŜsze zmiany zostały wprowadzone na stałe, w systemach Windows 95/98 naleŜy dopisać
powyŜsze polecenie do pliku autoexec.bat. W systemie Windows NT naleŜy wybrać z menu opcję
StartSettingsControl Panel, dwukrotnie kliknąć ikonę System, przejść na zakładkę Environment i dodać nazwę
zmiennej środowiskowej oraz jej wartość. W systemie Windows 2000 naleŜy wybrać opcje StartUstawieniaPanel
sterowania, dwukrotnie kliknąć ikonę System, następnie przejść na zakładkę Zaawansowane, kliknąć przycisk Zmienne
środowiskowe i dodać nazwę zmiennej oraz jej wartość.
Skonfiguruj serwer
Zanim uruchomisz serwer, będziesz zapewne chciał określić wartości pewnych parametrów,
takich jak numer portu na którym serwer będzie oczekiwał na Ŝądania, nazwy katalogów w których
będzie szukał plików HTML, itp. Proces ten jest zaleŜy wyłącznie od uŜywanego serwera i w
przypadku serwerów komercyjnych powinien być dokładnie opisany w dokumentacji, w części
poświęconej instalacji serwera. Niemniej jednak w przypadku niewielkich serwerów fundacji
Apache lub firmy Sun dostarczanych jako przykładowe implementacje specyfikacji Java Servlet 2.2
i JSP 1.1 (Tomcat) lub JavaSevlet 2.1 i JSP 1.0 (Sun JSWDK), dostępnych jest kilka waŜnych lecz
słabo udokumentowanych opcji konfiguracyjnych, które opiszę w kolejnych częściach rozdziału.
27 Rozdział 1. Podstawowe informacje o serwletach i Java Server Pages
Numer portu
W celu uniknięcia konfliktów z istniejącymi serwerami WWW, zarówno Tomcat jak i
JSWDK uŜywają niestandardowych portów. Jeśli uŜywasz jednego z tych produktów do wstępnego
tworzenia i testowania serwletów i stron JSP, a nie masz innego serwera WWW, to zapewne
dojdziesz do wniosku, Ŝe wygodniej będzie uŜywać standardowego portu HTTP o numerze 80. W
przypadku Tomcata 3.0 numer portu moŜna zmienić edytując plik katalog_instalacyjny/server.xml4.
W pliku tym naleŜy odszukać poniŜszy wiersz i zamienić w nim liczbę 8080 na 80:
<ContextManager port="8080" hostName="" inet="">
TakŜe Java Web Server 2.0 uŜywa portu o niestandardowym numerze. Aby go zmienić
naleŜy skorzystać z interfejsu do zdalnej administracji serwerem. W tym celu w przeglądarce naleŜy
wyświetlić stronę o adresie http://adres_komputera:9090/, gdzie adres_komputera to prawdziwa
nazwa komputera na jaki działa serwer, lub „localhost” w przypadku gdy serwer działa na lokalnym
komputerze.
4
Poczynając do serwera Tomcat 3.1, wszystkie pliki konfiguracyjne znajdują się w katalogu katalog_instalacyjny/conf/.
(przyp. tłum.)
28
• katalog_instalacyjny/tomcat.bat,
• katalog_instalacyjny/shutdown.bat,
• katalog_instalacyjny/tomcatEnv.bat,
• katalog_instalacyjny/webpages/WEB-INF/web.xml,
• katalog_instalacyjny/examples/WEB-INF/web.xml.
Uruchomienie serwera
Aby uruchomić jeden z „prawdziwych” serwerów WWW, będziesz musiał przejrzeć jego
dokumentację i dowiedzieć się jak naleŜy to zrobić. W wielu przypadkach wymaga to wykonania
programu httpd bądź to z poziomu wiersza poleceń, bądź teŜ poprzez poinformowanie systemu
operacyjnego, Ŝe naleŜy ten program wykonać automatycznie podczas uruchamiania systemu.
W przypadku Tomcata 3.0 serwer uruchamia się poprzez wykonanie skryptu startup
umieszczonego w głównym katalogu instalacyjnym. W przypadku JSWDK 1.0.1 naleŜy uruchomić
podobny skrypt o nazwie startserver.
Tomcat
• katalog_instalacyjny/webpages/WEB-INF/classes
Standardowe miejsce słuŜące do przechowywania plików klasowych serwletów.
• katalog_instalacyjny/classes
Alternatywne miejsce w którym moŜna umieszczać pliki klasowe serwletów.
• katalog_instalacyjny/lib
Katalog słuŜący do przechowywania plików JAR zawierających pliki klasowe
serwletów.
Tomcat 3.1
TuŜ przed opublikowaniem niniejszej ksiąŜki fundacja Apache udostępniła wersję beta
serwera Tomcat 3.15. Jeśli w momencie gdy będziesz kopiował Tomcata będzie dostępna końcowa
wersja tego serwera, to właśnie jej powinieneś uŜyć. PoniŜej przedstawiłem nową strukturę
katalogów uŜywaną w serwerze Tomcat 3.1:
• katalog_instalacyjny/webapps/ROOT/WEB-INF/classes
Standardowe miejsce słuŜące do przechowywania plików klasowych serwletów.
• katalog_instalacyjny/classes
Alternatywne miejsce w którym moŜna umieszczać pliki klasowe serwletów.
5
W momencie oddawania do druku tłumaczenia niniejszej ksiąŜki najnowsza wersja serwera Tomcat miała numer 4.0; w
porównaniu z wersją 3.1 struktura podstawowych katalogów serwera nie uległa zmianie. (przyp. tłum.)
29 Rozdział 1. Podstawowe informacje o serwletach i Java Server Pages
• katalog_instalacyjny/lib
Katalog słuŜący do przechowywania plików JAR zawierających pliki klasowe
serwletów.
JSWDK
• katalog_instalacyjny/webpages/WEB-INF/servlets
Standardowe miejsce słuŜące do przechowywania plików klasowych serwletów.
• katalog_instalacyjny/classes
Alternatywne miejsce w którym moŜna umieszczać pliki klasowe serwletów.
• katalog_instalacyjny/lib
Katalog słuŜący do przechowywania plików JAR zawierających pliki klasowe
serwletów.
6
Nowsze wersje serwera Tomcat — 3.2 i kolejne — dysponują juŜ moŜliwością automatycznego przeładowywania
serwletów. (przyp. tłum.)
33 Rozdział 2. Pierwsze serwlety
w kolejnym (3) punkcie. Katalog ten nosi zazwyczaj nazwę katalog_instalacyjny/classes,
przynajmniej tak jest w przypadku serwerów Tomcat, JSWDK oraz Java Web Server. Ani
Tomcat ani JSWDK nie dysponują moŜliwością automatycznego przeładowywania
serwletów, a zatem w ich przypadku katalog ten pełni tę samą funkcję co katalog opisany w
poprzednim punkcie. Dlatego teŜ większość programistów korzysta z katalogu opisanego
punkcie 1.
3. Katalog przeznaczony do umieszczania plików klasowych serwletów, które nie są
często modyfikowane i zostały zapisane w pliku JAR.
W przypadku katalogu opisanego w poprzednim punkcie, pliki klasowe serwletów są
umieszczane bezpośrednio w katalogu classes, bądź teŜ w jego podkatalogach, których
nazwy odpowiadają nazwom pakietów do których naleŜą serwlety. Kolejną moŜliwością jest
umieszczenie plików klasowych serwletów w pliku JAR i umieszczenie tego pliku w
odpowiednim katalogu. W przypadku serwerów Tomcat, JSWDK, Java Web Server oraz
większości innych serwerów, katalog ten nosi nazwę katalog_instalacyjny/lib. Za kaŜdym
razem gdy zmienisz pliki umieszczone w tym katalogu, aby zmiany zostały uwzględnione,
konieczne jest zatrzymanie i powtórne uruchomienie serwera.
Kiedy juŜ skonfigurowałeś serwer, określiłeś wartość zmiennej środowiskowej CLASSPATH i
umieściłeś plik klasowy serwletu w odpowiednim katalogu, będziesz mógł go skompilować. W tym
celu wystarczy wydać polecenie, na przykład: javac HelloWorld.java. Jednak w środowiskach
produkcyjnych, serwlety są często umieszczane w pakietach, co ma na celu uniknięcie konfliktów
nazw, które mogą się pojawić pomiędzy serwletami pisanymi przez róŜne osoby. Wykorzystanie
pakietów wymaga wykonania kilku dodatkowych czynności, pisanych w podrozdziale 2.4, pt.:
„Umieszczanie serwletów w pakietach”. Poza tym, bardzo często stosuje się formularze HTML
jako „interfejsy uŜytkownika” pośredniczące w wykorzystaniu serwletów (patrz rozdział 16). Aby
skorzystać z tej techniki, będziesz musiał wiedzieć gdzie naleŜy umieszczać pliki HTML, tak aby
były one dostępne dla serwera. PołoŜenie to zaleŜy wyłącznie od serwera; w przypadku serwerów
JSWDK oraz Tomcat pliki HTML naleŜy umieszczać w katalogu
katalog_instalacyjny/webpages/ścieŜka/. Aby odwołać się do dokumentu o nazwie plik.html
(zapisanego jako katalog_instalacyjny/webpages/ścieŜka/plik.html) naleŜy podać w przeglądarce
adres http://localhost/ścieŜka/plik.html (przy czym, w przypadku gdy serwer nie działa na lokalnym
komputerze, wyraŜenie „localhost” naleŜy zastąpić jego poprawną nazwą). Strony JSP mogą być
zapisywane wszędzie tam gdzie normalne dokumenty HTML.
Wywoływanie serwletów
RóŜne serwery pozwalają na umieszczanie plików klasowych serwletów w róŜnych
katalogach — pod tym względem standaryzacja pomiędzy poszczególnymi serwerami jest
niewielka. Niemniej jednak, w przypadku wywoływania serwletów obowiązuje jedna, wspólna
konwencja — naleŜy uŜywać adresu URL o postaci http://komputer/sevlet/NazwaServletu. Zwróć
uwagę, iŜ adres ten odwołuje się do katalogu servlet; pomimo faktu, iŜ w rzeczywistości serwlety
zostały umieszczone w katalogu servlets lub katalogu o zupełnie innej nazwie (np.: classes lub lib).
Rysunek 2.1 przedstawiony we wcześniejszej części rozdziału prezentuje sposób odwołania
się do serwletu w przypadku gdy serwer został uruchomiony na lokalnym komputerze („localhost”
oznacza „lokalny komputer”).
Większość serwerów pozwala takŜe rejestrować nazwy serwletów, dzięki czemu moŜna się
do nich odwoływać za pomocą adresu URL o postaci
http://komputer/dowolna_ścieŜka/dowolny_plik. Czynności jakie naleŜy w tym celu wykonać zaleŜą
od uŜywanego serwera, a informacji na ich temat naleŜy szukać w jego dokumentacji.
34
7
Aby w wynikowej stronie WWW były dostępne polskie znaki diakrytyczne, naleŜy oprócz typu MIME określić takŜe
sposób kodowania strony. W tym celu, po typie MIME "text/html" wystarczy dopisać
"; charset=ISO-8859-2" (lub Windows-1250). A zatem całe wywołanie metody setContentType będzie miało postać
response.setContentType("text/html; charset=ISO-8859-2").
35 Rozdział 2. Pierwsze serwlety
Kolejnym etapem tworzenia serwletu generującego dokument HTML, jest napisanie
wywołań metody println, które wygenerują poprawny kod HTML. Struktura dokumentów HTML
została bardziej szczegółowo omówiona w podrozdziale 2.5 pt.: „Proste narzędzia pomocne przy
tworzeniu dokumentów HTML”, niemniej jednak większość czytelników powinna ją juŜ znać. Na
listingu 2.3 przedstawiłem przykładowy serwlet generujący prosty dokument HTML, a na rysunku
2.2 wyniki jego wykonania.
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String docType =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " +
"Transitional//EN\">\n";
out.println(docType +
"<HTML>\n" +
"<HEAD><TITLE>Witaj WWW</TITLE></HEAD>\n" +
"<BODY>\n" +
"<H1>Witaj WWW</H1>\n" +
"</BODY></HTML>");
}
}
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
set CLASSPATH=C:\JavaWebServer2.0\lib\servlet.jar;
C:\JavaWebServer2.0\lib\jsp.jar;C:\MojeServlety
C:\JDK1.1.8\bin\javac -d C:\JavaWebServer2.0\servlets %1%
38
Być moŜe będzie Cię kusiło by pominąć któryś z elementów tej struktury, a w szczególności
deklarację DOCTYPE, gdyŜ wszystkie główne przeglądarki ignorują ją, niezaleŜnie od tego, iŜ w
specyfikacjach HTML 3.2 oraz 4.0 jest ona wymagana. Odradzam jednak stosowanie takich
rozwiązań. Deklaracja ta ma tę zaletę, iŜ informuje narzędzia sprawdzające poprawność kodu
HTML, jakiej specyfikacji naleŜy uŜyć przy weryfikacji dokumentu. Narzędzia te są niezwykle
przydatne podczas testowania stron WWW, gdyŜ wykrywają błędy składniowe w kodzie HTML,
które w nowych przeglądarkach zostałyby poprawnie zinterpretowane, lecz w starych doprowadziły
do błędnego wyświetlenia strony. Dwoma najpopularniejszymi narzędziami sprawdzającymi
poprawność kodu HTML dostępnymi na WWW są serwisy World Wide Web Consortium
(http://validator.w3.org/) oraz Web Digest Group (http://www.htmlhelp.com/tools/validator/).
Serwisy te pozwalają na podanie adresu URL, a następnie pobierają wskazaną stronę, sprawdzają
jej poprawność syntaktyczną i zgodność z formalną specyfikacją języka HTML i zwracają raport z
informacjami o błędach. Z punktu widzenia uŜytkownika serwlety generujące kod HTML niczym
się nie róŜnią od zwyczajnych stron WWW; z tego względu wyniki ich działania moŜna sprawdzać
przy uŜyciu wspomnianych wcześnie serwisów. Jedynym wyjątkiem są serwlety, które wymagają
przekazania informacji metodą POST. Pamiętaj, Ŝe dane przesyłane metodą GET są dołączane do
39 Rozdział 2. Pierwsze serwlety
adresu URL, a zatem moŜliwe jest przekazanie do serwisu sprawdzającego adresu URL
zawierającego dane, jakie mają zostać przesłane do serwletu metodą GET.
Metoda
UŜywaj serwisów sprawdzających aby skontrolować poprawność dokumentów HTML generowanych przez servlety.
Bez wątpienia generacja kodu HTML przy uŜyciu wywołań metody println jest nieco
niewygodnym rozwiązaniem, zwłaszcza w przypadku generacji długich wierszy kodu, takich jak
deklaracje DOCTYPE. Niektórzy rozwiązują ten problem tworząc w języku Java narzędzia słuŜące do
generacji kodu HTML i wykorzystując je w serwletach. Ja jednak sceptycznie podchodzę do
tworzenia rozbudowanej biblioteki narzędzi tego typu. Przede wszystkim, trudności jakich
nastręcza generacja kodu HTML z poziomu programu jest jednym z głównych problemów
rozwiązywanych przez Java Server Pages (technologię, której poświęcona jest druga część
niniejszej ksiąŜki). JSP jest znacznie lepszym rozwiązaniem, a zatem, nie warto tracić czasu na
tworzenie rozbudowanego pakietu narzędzi słuŜących do generacji kodu HTML z poziomu
serwletów. Po drugie, procedury generujące kod HTML mogą być niewygodne w uŜyciu i
zazwyczaj nie udostępniają wszystkich istniejących atrybutów znaczników HTML (takich jak CLASS
i ID uŜywanych wraz z arkuszami stylów, procedur obsługi zdarzeń języka JavaScript, atrybutu
określającego kolor tła komórek tabel, i tak dalej). Pomimo tych wątpliwych zalet duŜych bibliotek
słuŜących do generacji kodu HTML, jeśli zauwaŜysz, Ŝe bardzo często generujesz te same
fragmenty kodu, to moŜesz stworzyć proste narzędzie, które w przyszłości ułatwi Ci pracę. W
przypadku prostych serwletów istnieją dwa elementy dokumentów HTML (DOCTYPE oraz HEAD)
których postać nie powinna ulegać zmianie i których generację moŜna sobie ułatwić tworząc proste
narzędzia. Przykładową postać takich narzędzi przedstawiłem na listingu 2.6. Listing 2.7 zawiera
zmodyfikowaną wersję serwletu HelloWWW2, wykorzystującą narzędzia z listingu 2.6. W dalszej
części ksiąŜki narzędzie te zostaną bardziej rozbudowane.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
"<H1>Witaj WWW</H1>\n" +
"</BODY></HTML>");
}
}
Metoda init
Metoda init wywoływana jest tylko raz, bezpośrednio po utworzeniu serwletu; nie jest ona
wywoływana podczas obsługi poszczególnych Ŝądań. Metoda ta moŜe zatem słuŜyć do
przeprowadzenia jednokrotnej inicjalizacji serwletu, podobnie jak metoda o identycznej nazwie
stosowana w apletach. W zaleŜności od sposobu zarejestrowania serwletu na serwerze, moŜe on
zostać utworzony w momencie gdy uŜytkownik po raz pierwszy poda adres URL odpowiadający
danemu serwletowi, lub teŜ, podczas uruchamiania serwera. Jeśli serwlet nie został zarejestrowany,
lecz umieszczono go w jednym ze standardowych katalogów serwera, to zostanie on utworzony
podczas obsługi pierwszego Ŝądania. Szczegółowe informacje dotyczące katalogów, słuŜących do
przechowywania serwletów znajdziesz w podrozdziale 2.2 pt.: „Prosty serwlet generujący
zwyczajny tekst”.
Dostępne są dwie wersje metody init — pierwsza z nich nie pobiera Ŝadnych argumentów,
natomiast druga pobiera argument będący obiektem typu ServletConfig. Pierwsza z wersji tej
metody uŜywana jest w przypadkach gdy serwlet nie musi odczytywać Ŝadnych ustawień zaleŜnych
od serwera na którym jest uruchamiany. Definicja tej wersji metody init ma następującą postać:
public void init() throws ServletException {
// kod inicjalizujący servletu
}
Przykład wykorzystania tej formy metody init moŜesz znaleźć w podrozdziale 2.8, pt.:
„Przykład wykorzystania inicjalizacji serwletu i daty modyfikacji strony”. W podrozdziale 18.8, pt.:
„Zarządzanie pulami połączeń: Studium zagadnienia”, w rozdziale poświęconym zagadnieniom
wykorzystania JDBC, znajdziesz bardziej zaawansowany przykład uŜycia metody init w celu
utworzenia wielu połączeń z bazą danych.
Druga wersja metody init stosowana jest w sytuacjach gdy przed zakończeniem
inicjalizacji serwlet musi odczytać ustawienia zaleŜne od serwera na jakim jest uruchamiany. Na
41 Rozdział 2. Pierwsze serwlety
przykład, moŜe się zdarzyć, Ŝe serwlet będzie musiał dysponować informacjami dotyczącymi bazy
danych, plików z hasłami, charakterystycznych dla serwera parametrów związanych z
efektywnością działania, nazw plików liczników lub trwale zachowanych danych przekazanych za
pośrednictwem cookies podczas trwania wcześniejszych sesji. Ta wersja metody init ma
następującą postać:
public void init(ServletConfig config)
throws ServletException {
super.init(config);
// kod inicjalizujący servletu
}
Metoda service
Za kaŜdym razem gdy serwer otrzymuje Ŝądanie dotyczące serwletu, uruchamiany jest nowy
wątek i wywoływana metoda service. Metoda ta sprawdza typ Ŝądania HTTP (GET, POST, PUT,
DELETE, itd.) i w zaleŜności od niego wywołuje odpowiednią metodę — doGet, doPost, doPut,
doDelete, itd. Jeśli tworzysz serwlet, który ma w identyczny sposób obsługiwać Ŝądania GET oraz
POST, to moŜesz zastanawiać się nad zastosowaniem rozwiązania polegającego na bezpośrednim
przesłonięciu metody service (w sposób przedstawiony poniŜej), zamiast implementacji obu metod
doGet oraz doPost.
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// kod servletu
}
42
Nie stosuj jednak takiego rozwiązania. Zamiast niego, wywołaj metodę doPost z metody
doGet (lub na odwrót), jak pokazałem na poniŜszym przykładzie:
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// kod servletu
}
Choć metoda ta wymaga wpisania kilku dodatkowych wierszy kodu, to jednak z pięciu
powodów jest lepsza od bezpośredniego przesłaniania metody service. PoniŜej podałem te
powody:
1. Istnieje moŜliwość dodania obsługi Ŝądań innych typów; wystarczy w tym celu (na przykład
w klasie potomnej) zaimplementować metody doPut, doTrace, itd. W przypadku
bezpośredniego przesłonięcia metody service nie moŜna tego zrobić.
2. Istnieje moŜliwość dodania obsługi dat modyfikacji; w tym celu wystarczy
zaimplementować metodę getLastModified. W przypadku wykorzystania metody doGet,
standardowa implementacja metody service wywołuje metodę getLastModified w celu
podania wartości nagłówków Last-Modified. Podanie wartości tych nagłówków jest
konieczne dla prawidłowej obsługi warunkowych Ŝądań GET (czyli Ŝądań zawierających
nagłówek If-Modified-Since). Stosowny przykład znajdziesz w podrozdziale 2.8, pt.:
„Przykład wykorzystania inicjalizacji serwletu i daty modyfikacji strony”.
3. Uzyskuje się automatyczną obsługę Ŝądań HEAD. W takim przypadku serwer zwraca
wyłącznie nagłówki i kod statusu wygenerowany przez metodę doGet, pomijając wszelką
zawartość wygenerowanego dokumentu. śądania HEAD są bardzo przydatne w przypadku
tworzenia programów korzystających z protokołu HTTP. Na przykład, narzędzia
sprawdzające poprawność hiperpołączeń umieszczonych na stronach WWW, aby
zredukować obciąŜenie serwera, bardzo często posługują się właśnie Ŝądaniami HEAD a nie
GET.
4. Uzyskuje się automatyczną obsługę Ŝądań OPTIONS. Jeśli metoda doGet została
zaimplementowana, to standardowa metoda service odpowiada na Ŝądania OPTIONS
zwracając nagłówek Allow informujący, Ŝe obsługiwane są Ŝądania GET, HEAD, OPTIONS oraz
TRACE.
5. Uzyskuje się automatyczną obsługę Ŝądań TRACE. śądania TRACE stosowane są podczas
testowania programów korzystających z protokołu HTTP — w odpowiedzi na nie serwer
zwraca wyłącznie nagłówki HTTP.
Podpowiedź
Jeśli tworzony serwlet ma w identyczny sposób obsługiwać zarówno Ŝądania GET jak i POST, to wywołuj metodę
doPost z metody doGet, lub na odwrót. Nie stosuj natomiast rozwiązania polegającego na bezpośrednim
przesłonięciu metody service.
Interfejs SingleThreadModel
Standardowo system tworzy jedną kopię serwletu, a następnie uŜywa nowych wątków do
obsługi nadsyłanych Ŝądań; przy czym, w przypadku gdy nowe Ŝądanie nadejdzie zanim
wykonywanie poprzedniego Ŝądania zostanie zakończone, uruchamiane są kolejne wątki
wykonywane jednocześnie. Oznacza to, Ŝe metody doGet oraz doPost muszą bardzo uwaŜnie
synchronizować dostęp do pól oraz innych, wspólnych informacji. Jest to konieczne, gdyŜ wiele
wątków moŜe jednocześnie próbować korzystać z tych danych. Więcej informacji na ten temat
znajdziesz w podrozdziale 7.3, pt.: „Trwałe przechowywanie stanu serwletu i automatyczne
odświeŜanie stron”. Jeśli nie chcesz, aby serwlet działał w ten standardowy — „wielowątkowy”
sposób, wystarczy zaimplementować w nim interfejs SingleThreadModel:
public class MojServlet extends HttpServlet
implements SingleThreadModel {
// ... kod servletu ... //
}
Metoda destroy
Serwer moŜe podjąć decyzję o usunięciu z pamięci załadowanej do niej kopii serwletu.
Decyzja taka moŜe zostać podjęta w wyniku jawnego Ŝądania administratora lub ze względu na
fakt, iŜ serwlet nie był wykorzystywany przez długi okres czasu. Niemniej jednak, nim serwlet
zostanie usunięty z pamięci, serwer wywoła jego metodę destroy. Metoda ta, daje serwletowi
moŜliwość zamknięcia połączeń z bazami danych, zatrzymania wątków wykonywanych w tle,
zapisania listy cookies lub wartości licznika odwiedzin w pliku na dysku, lub wykonania
jakiekolwiek innych czynności porządkowych. Musisz jednak wiedzieć, Ŝe metoda ta moŜe takŜe
spowodować awarię serwera WWW. W końcu nie wszystkie serwery WWW zostały stworzone w
bezpiecznych językach programowania, takich jak Java (chodzi mi tu języki, których nazwy
pochodzą od liter alfabetu), w których bez przeszkód moŜna odczytywać i zapisywać informacje
poza granicami tablic, wykonywać niedozwolone rzutowania typów lub korzystać z
nieprawidłowych wskaźników powstałych na skutek nieudanych prób zwalniania pamięci. Co
więcej, nawet technologia języka Java nie jest w stanie uchronić nas przed wyrwaniem z gniazdka
przewodu zasilającego komputer. A zatem nie naleŜy polegać na metodzie destroy, jako na
jedynym sposobie zapisywania stanu serwletu na dysku. W przypadku liczenia odwiedzin,
gromadzenia list wartości cookies oznaczających specjalne odwołania do stron, oraz wykonywania
innych czynności tego typu, warto co jakiś czas zapisać na dysku aktualny stan.
44
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
<servlet>
<servlet-name>
ShowMsg
</servlet-name>
<servlet-class>
coreservlets.ShowMessage
</servlet-class>
<init-param>
<param-name>
message
</param-name>
<param-value>
Sinobrody
</param-value>
</init-param>
<init-param>
<param-name>
repeats
</param-name>
<param-value>
5
</param-value>
</init-param>
</servlet>
</web-app>
Listing 2.10
# servlets.properties used in JSWDK
# Standard setting
jsp.code=com.sun.jsp.runtime.JspServlet
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
Niemniej jednak, w razie wykorzystania tej metody, wszyscy uŜytkownicy uzyskują te same
wyniki i dlatego metoda init przechowuje takŜe datę modyfikacji strony, która jest uŜywana w
metodzie getLastModified. Metoda ta powinna zwracać czas modyfikacji strony wyraŜony jako
ilość milisekund, które upłynęły od 1970 (to standardowy sposób określania dat w języku Java).
Czas ten jest automatycznie konwertowany do postaci daty zapisanej w formacie GMT,
odpowiadającej sposobowi zapisu wartości nagłówka Last-Modified. WaŜniejsze jest jednak to, Ŝe
jeśli serwer otrzyma warunkowe Ŝądanie GET (zawierające nagłówek If-Modified-Since i
informujące serwer, Ŝe klient poszukuje stron zmodyfikowanych po określonej dacie), to porówna
je z datą zwróconą przez metodę getLastModified i prześle stronę wynikową wyłącznie w
przypadku gdy została ona zmodyfikowana po dacie podanej w Ŝądaniu. Przeglądarki często
wykorzystują takie warunkowe Ŝądania przy pobieraniu stron z pamięci podręcznej, a zatem
obsługa Ŝądań warunkowych nie tylko pomaga uŜytkownikom, lecz takŜe ogranicza obciąŜenie
49 Rozdział 2. Pierwsze serwlety
serwera. Nagłówki Last-Modified oraz If-Modified-Since wykorzystują wyłącznie pełne sekundy,
dlatego teŜ metoda getLastModified powinna zaokrąglać zwracany czas w dół, do pełnych sekund.
Rysunki 2.9 oraz 2.10 przedstawiają wyniki wykonania dwóch Ŝądań dotyczących tego
samego serwletu i zawierających nagłówki If-Modified-Since z dwoma róŜnymi datami. Aby
określić nagłówki Ŝądania i przeanalizować nagłówki odpowiedzi napisałem w języku Java
specjalną aplikację o nazwie WebClient. Jej kod przedstawiony został w podrozdziale 2.10, pt.:
„WebClient: Interaktywna wymiana informacji z serwerem WWW”. Aplikacja ta pozwala
własnoręcznie podawać nagłówki Ŝądania HTTP, przesyłać je i przeanalizować otrzymane wyniki.
8
W przypadku polskojęzycznej wersji Internet Explorera, naleŜy wybrać opcję WidokŹródło; w Netscape Navigatorze
słuŜy do tego opcja ViewPage Source.
52
przez servlet. Klasa EchoServer, przedstawiona w podrozdziale 16.12, pt.: „Testowy serwer WWW”,
pozwala na przesłanie na serwer formularza HTML i otrzymanie wyników prezentujących informacje
dokładnie w taki sposób, w jaki dotarły one na serwer.
6. Sprawdź dane odpowiedzi HTTP.
Gdy juŜ sprawdzisz poprawność Ŝądania HTTP, warto, w podobny sposób,
sprawdzić dane odpowiedzi. Klasa WebClient, przedstawiona w podrozdziale 2.10, pt.:
„WebClient: Interaktywna wymiana informacji z serwerem WWW”, pozwala na
interaktywne nawiązanie połączenia z serwerem WWW, przesyłanie do niego własnoręcznie
podanych nagłówków Ŝądania HTTP i analizę wszelkich informacji otrzymanych w
odpowiedzi (zarówno nagłówków odpowiedzi HTTP, jak i danych).
7. Zatrzymaj i ponownie uruchom serwer WWW.
Większość najlepszych serwerów WWW wyposaŜonych w moŜliwość obsługi
serwletów dysponuje specjalnym katalogiem przeznaczonym do przechowywania aktualnie
tworzonych serwletów. Serwlety umieszczone w tym katalogu (w przypadku Java Web
Servera jest to katalog servlets) są automatycznie przeładowywane w momencie gdy ich plik
klasowy zostanie zmodyfikowany. Niemniej jednak, moŜe się zdarzyć, Ŝe serwer WWW nie
wykryje modyfikacji serwletu; dotyczy to przede wszystkim sytuacji gdy zostanie
zmodyfikowana jedna z klas pomocniczych, a nie główna klasa serwletu. A zatem, jeśli
okaŜe się, Ŝe zmiany wprowadzone w serwlecie nie odpowiadają jego zachowaniu, to
będziesz musiał zatrzymać i powtórnie uruchomić serwer. W przypadku serwerów JSWDK
oraz starszych wersji Tomcat, będziesz to musiał robić za kaŜdym razem gdy zmodyfikujesz
serwlet, gdyŜ te mini-serwery nie dysponują moŜliwością automatycznego przeładowywania
serwletów.
WebClient
WebClient to główna klasa programu, z której będziesz korzystał. Program naleŜy
uruchamiać z poziomu wiersza poleceń, następnie, po pojawieniu się okna, moŜna zmodyfikować
wiersz Ŝądania i nagłówki Ŝądania HTTP i przesłać Ŝądanie na serwer klikając przycisk Wyślij
Ŝądanie.
resultPanel.add(interruptPanel, BorderLayout.SOUTH);
add(resultPanel, BorderLayout.CENTER);
setSize(600, 700);
setVisible(true);
}
HttpClient
Klasa HttpClient odpowiada za komunikację sieciową. Klasa ta przesyła na serwer podany
wiersz Ŝądania i nagłówki Ŝądania, a następnie odczytuje po kolei wszystkie wiersze nadesłane z
serwera i wyświetla je w polu TextArea, aŜ do momentu gdy serwer zamknie połączenie lub gdy
działanie klasy HttpClient zostanie przerwane w wyniku ustawienia flagi isInterrupted.
NetworkClient
NetworkClient jest klasą pomocniczą, którą moŜna wykorzystywać przy tworzeniu
wszystkich programów korzystających z połączeń sieciowych. Stanowi ona klasę bazową klasy
HttpClient.
SocketUtil
SocketUtil to prosta klasa pomocnicza, ułatwiająca tworzenie niektórych typów strumieni
wykorzystywanych w programach sieciowych. Jest ona wykorzystywana przez klasy NetworkClient
oraz HttpClient.
CloseableFrame
ClosableFrame to rozszerzenie standardowej klasy Frame. Klasa ta została wyposaŜona w
narzędzia pozwalające na zamknięcie okna, w momencie gdy uŜytkownik wyda takie polecenie.
Główne okno programu WebClient jest obiektem właśnie tej klasy.
*/
LabeledTextField
LabeledTextField to proste połączenie klas TextField oraz Label, którego uŜywam w
programie WebClient.
Interruptible
Interruptible to bardzo prosty interfejs uŜywany do oznaczania klas dysponujących
metodą isInterrupted. Jest on uŜywany w klasie HttpClient w celu sprawdzenia czy uŜytkownik
nie zaŜądał przerwania transmisji.
dokumentów HTML zaleŜy od uŜywanego serwera WWW. W przypadku serwerów JSWDK 1.0.1
oraz Tomcat 3.0, dokumenty te moŜna umieszczać w katalogu katalog_instalacyjny/webpages lub
w jego podkatalogach. Aby uzyskać dostęp do tych dokumentów, naleŜy podać adres
http://komputer/ścieŜka/nazwa_pliku.html. Na przykład, zakładając, Ŝe formularz przedstawiony na
listingu 3.2 został zapisany w pliku katalog_instalacyjny/webpages/forms/ThreeParamsForm.html i
chcemy go wyświetlić na tym samym komputerze na którym działa serwer, to adres URL, którego
powinniśmy uŜyć będzie miał postać http://localhost/forms/ThreeParamsForm.html.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
<FORM ACTION="/servlet/coreservlets.ThreeParams">
Parameter pierwszy: <INPUT TYPE="TEXT" NAME="param1"><BR>
Parameter drugi: <INPUT TYPE="TEXT" NAME="param2"><BR>
Parameter trzeci: <INPUT TYPE="TEXT" NAME="param3"><BR>
<CENTER>
<INPUT TYPE="SUBMIT" VALUE="Prześlij">
</CENTER>
</FORM>
</BODY>
</HTML>
Rysunki 3.1 oraz 3.2 przedstawiają odpowiednio wygląd formularza oraz wyniki
wygenerowane przez serwlet.
63 Rozdział 3. Obsługa Ŝądań: Dane przesyłane z formularzy
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
</BODY>
</HTML>
66
• bgColor
Określa kolor tła strony.
• name
Ten parametr określa imię osoby podającej swój Ŝyciorys. Imię będzie wyświetlone
na środku strony przy uŜyciu czcionki o określonym kroju i wielkości.
• title
Ten parametr określa tytuł stanowiska osoby podającej swój Ŝyciorys. Zostanie on
wyświetlony na środku strony, poniŜej imienia osoby, przy wykorzystaniu czcionki o nieco
mniejszej wielkości.
• email
Adres poczty elektronicznej osoby podającej swój Ŝyciorys. Zostanie on
wyświetlony poniŜej tytułu stanowiska i umieszczony w hiperpołączeniu typu mailto.
• language
Języki programowania podane w tym polu zostaną wyświetlone na stronie
prezentującej Ŝyciorys, w formie listy punktowej.
• skills
Tekst podany w tym wielowierszowym polu tekstowym zostanie wyświetlony na
końcu strony prezentującej Ŝyciorys, poniŜej nagłówka „Umiejętności i doświadczenia”.
skills + "\n" +
"</BODY></HTML>");
}
Potrafi udowodnić, Ŝe P nie jest równe NP i nie ma zamiaru pracować dla firm, które nie mają
zielonego pojęcia co to oznacza.
</BODY></HTML>
Implementacja filtrowania
Zastąpienie znaków <, >, " oraz & w łańcuchach znaków jest prostym zadaniem, które moŜna
wykonać na wiele róŜnych sposobów. Koniecznie naleŜy jednak pamiętać, Ŝe łańcuchy znaków w
języku Java są niemodyfikowalne; a zatem, konkatenacja łańcuchów wiąŜe się z kopiowaniem i
zwalnianiem z pamięci wielu krótkich łańcuchów. Przykładowo, przeanalizujmy poniŜszy fragment
kodu:
String s1 = "Witam";
String s2 = s1 + " was!";
77 Rozdział 3. Obsługa Ŝądań: Dane przesyłane z formularzy
import javax.servlet.*;
import javax.servlet.http.*;
// ...
// Inne metody klasy ServletUtilities pokazane w innych miejscach
// ...
Przykład
Aby zaprezentować znacznie konwersji znaków specjalnych rozwaŜmy przykład serwletu,
który ma generować stronę WWW zawierającą przedstawiony poniŜej listing:
if (a<b) {
zrobTo();
} else {
zrobCosInnego();
}
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
out.println(ServletUtilities.headWithTitle(title) +
"<BODY>\n" +
"<H1>" + title + "</H1>\n" +
"<PRE>\n" +
getCodeFragment() +
"</PRE>\n" +
"ZauwaŜ, Ŝe <I>musisz</I> uŜyć nawiasów\n" +
"gdy klauzule 'if' or 'else' zawierają\n" +
"więcej niŜ jedno wyraŜenie.\n" +
"</BODY></HTML>");
}
}
9
przyp. tłum. Precyzyjnie rzecz biorąc metoda getDateHeader zwraca liczbę typu long, która reprezentuje wartość typu
Date.
82
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
Rysunek 4.1 Nagłówki Ŝądania przesyłane przez przeglądarkę Netscape Navigator 4.7
działającą w systemie Windows 98.
84
Accept-Charset
Ten nagłówek określa zbiór znaków (na przykład: ISO-8859-2) uŜywany przez
przeglądarkę.
Accept-Encoding
Ten nagłówek podaje typy kodowania, które klient jest w stanie obsługiwać. Jeśli nagłówek
ten zostanie podany, to serwer moŜe zakodować stronę w podanym formacie (zazwyczaj po to, aby
skrócić czas jej transmisji) i określić w jaki sposób została zapisana przesyłając nagłówek
odpowiedzi Content-Encoding. Ten sposób kodowania nie ma niczego wspólnego z typem MIME
generowanego dokumentu (określanym przy uŜyciu nagłówka odpowiedzi Content-Type), gdyŜ
kodowanie jest odtwarzane zanim przeglądarka zdecyduje co zrobić z jego zawartością. Jednak z
drugiej strony, wykorzystanie sposobu kodowania, którego przeglądarka nie zna, spowoduje
wyświetlenie całkowicie niezrozumiałych stron WWW. Z tego względu jest niezwykle istotne, abyś
sprawdził zawartość nagłówka Accept-Encoding zanim wykorzystasz jakiś sposób kodowania
zawartości generowanych dokumentów. Standardowymi wartościami tego nagłówka są gzip oraz
compress.
Kompresja stron przed ich przekazaniem do przeglądarki jest bardzo przydatną
moŜliwością, gdyŜ czas konieczny do zdekodowania strony zazwyczaj jest znacznie krótszy od
czasu jej transmisji. W podrozdziale 4.4, pt.: „Przesyłanie skompresowanych stron WWW”,
znajdziesz przykład serwletu wykorzystującego kompresję generowanych dokumentów, dzięki
której moŜliwe jest 10-krotne skrócenie czasu transmisji stron.
85 Rozdział 4. Obsługa Ŝądań: Nagłówki Ŝądań HTTP
Accept-Language
Ten nagłówek określa język preferowany przez uŜytkownika. MoŜna go zastosować w
serwletach, zdolnych do generacji wyników w róŜnych językach. Wartością tego nagłówka
powinien być jeden ze standardowych kodów określających język, takich jak en, en-us, da, i tak
dalej. Więcej informacji na temat kodów języków znajdziesz w pliku RFC 1766.
Authorization
Ten nagłówek jest uŜywany przez klienty w celu przeprowadzenia ich autoryzacji podczas
Ŝądania dostępu do stron WWW chronionych hasłem. Przykład zastosowania tego nagłówka
podałem w podrozdziale 4.5, pt.: „Ograniczanie dostępu do stron WWW”.
Cache-Control
Ten nagłówek moŜe zostać wykorzystany przez klienta od podania opcji wykorzystywanych
przez serwery pośredniczące w celu określenia sposobów przechowywania stron w pamięci
podręcznej. Nagłówek Ŝądania Cache-Control jest zazwyczaj ignorowany przez serwlety, niemniej
jednak nagłówek odpowiedzi o tej samej nazwie moŜe być bardzo przydatny, gdyŜ informuje, Ŝe
strona ulega ciągłym modyfikacjom i nie powinna być przechowywana w pamięci podręcznej.
Szczegółowe informacje na temat tego nagłówka odpowiedzi znajdziesz w rozdziale 7, pt.:
„Generacja odpowiedzi: Nagłówki odpowiedzi HTTP”.
Connection
Ten nagłówek zawiera informację czy klient jest w stanie obsługiwać trwałe połączenia
HTTP. Połączenia tego typu pozwalają przeglądarkom oraz innym klientom, na pobieranie wielu
plików (na przykład: strony WWW oraz kilku uŜywanych na niej obrazów) przy wykorzystaniu
jednego połączenia. W ten sposób oszczędzany jest czas, który trzeba by poświęcić na nawiązanie
kilku niezaleŜnych połączeń. W przypadku Ŝądań HTTP 1.1, połączenia trwałe są stosowane
domyślnie; a jeśli program chce wykorzystać połączenia starego typu, to musi podać w tym
nagłówku wartość close. W przypadku wykorzystania protokołu HTTP 1.0, aby korzystać z
trwałych połączeń, naleŜy podać w tym nagłówku wartość keep-alive.
KaŜde odebrane Ŝądanie HTTP powoduje nowe uruchomienie serwletu; wykorzystanie
istniejącego lub nowego połączenia, nie ma w tym przypadku najmniejszego znaczenia. Serwer
zawsze uruchamia serwlet po odczytaniu Ŝądania HTTP; co oznacza, Ŝe aby serwlet był w stanie
obsługiwać trwałe połączenia będzie musiał uzyskać pomoc ze strony serwera. A zatem, zadanie
serwletu sprowadza się do umoŜliwienia serwerowi wykorzystania trwałych połączeń, co jest
realizowane poprzez przesłanie nagłówka odpowiedzi Content-Length. Przykład prezentujący
wykorzystanie trwałych połączeń podałem w podrozdziale 7.4, pt.: „Stosowanie trwałych połączeń
HTTP”.
Content-Length
Ten nagłówek jest stosowany wyłącznie w Ŝądaniach typu POST i słuŜy od określenia (w
bajtach) wielkości przesyłanych danych. Aby określić wartość tego nagłówka nie musisz
wywoływać metody request.getIntHeader("Content-Length"), lecz moŜesz posłuŜyć się metodą
request.getContentLength(). Jednak serwlety są w stanie samodzielnie odczytać dane przesyłane z
formularzy (patrz rozdział 3, pt.: „Obsługa Ŝądań: Dane przesyłane z formularzy”), a zatem jest
mało prawdopodobne, abyś jawnie korzystał z tego nagłówka.
Content-Type
Choć ten nagłówek jest zazwyczaj uŜywany w odpowiedziach generowanych przez serwlet,
to jednak moŜe on takŜe znaleźć się wśród nagłówków Ŝądania. MoŜe się to zdarzyć w sytuacji, gdy
klient prześle Ŝądanie PUT lub gdy dołączy jakiś dokument do danych przesyłanych Ŝądaniem POST.
86
Wartość tego nagłówka moŜna pobrać przy wykorzystaniu standardowej metody getContentType
interfejsu HttpServletRequest.
Cookie
Nagłówek ten jest stosowany do przekazania na serwer WWW cookies, które wcześniej
serwer ten przesłał do przeglądarki. Więcej szczegółowych informacji na temat cookies znajdziesz
w rozdziale 8, pt.: „Obsługa cookies”. Z technicznego punktu widzenia nagłówek Cookie nie naleŜy
do protokołu HTTP 1.1. Początkowo stanowił on rozszerzenie standardu wprowadzone przez firmę
Netscape, jednak aktualnie jest powszechnie obsługiwany, w tym takŜe przez przeglądarki firm
Netscape Navigator oraz Microsoft Internet Explorer.
Expect
Ten rzadko stosowany nagłówek pozwala klientowi podać jakiego zachowania oczekuje od
serwera. Jedyna standardowa wartość tego nagłówka — 100-continue — jest przesyłana przez
przeglądarkę, która ma zamiar wysłać załączony dokument i chce wiedzieć czy serwer go przyjmie.
W takim przypadku serwer powinien przesłać kod statusu 100 (Continue) (oznaczający Ŝe moŜna
kontynuować) lub 417 (Expectation Failed) (oznaczający, Ŝe dalsze wykonywanie czynności nie
jest moŜliwe). Więcej informacji o kodach statusu HTTP znajdziesz w rozdziale 6, pt.: „Generacja
odpowiedzi: Kody statusu”.
From
Ten nagłówek zawiera adres poczty elektronicznej osoby odpowiedzialnej za
wygenerowanie Ŝądania. Przeglądarki rzadko kiedy generują ten nagłówek; znacznie częściej jest
on generowany przez „roboty” przeszukujące zasoby Internetu, aby ułatwić określenie sprawców
dodatkowego obciąŜenia serwera lub powtarzających się, błędnych Ŝądań.
Host
Przeglądarki te muszą generować ten nagłówek. Zawiera on określenie komputera (host)
oraz numer portu podany w oryginalnym adresie URL. Ze względu na przekazywanie Ŝądań oraz
wykorzystywanie wielu nazw przez jeden komputer, jest całkiem prawdopodobne, Ŝe informacji
tych moŜna by uzyskać w inny sposób. Nagłówek ten był juŜ dostępny w protokole HTTP 1.0, lecz
jego stosowanie było opcjonalne.
If-Match
Ten rzadko stosowany nagłówek uŜywany jest przede wszystkim w Ŝądaniach PUT. Klient
moŜne zaŜądać listy znaczników elementów, takich jak te zwracane przez nagłówek odpowiedzi
ETag, a operacja jest przeprowadzana wyłącznie jeśli jeden z tych znaczników odpowiada
znacznikowi podanemu w nagłówku.
If-Modified-Since
Ten nagłówek oznacza, Ŝe klient chce pobrać stronę wyłącznie jeśli została ona
zmodyfikowana po określonej dacie. Nagłówek ten jest bardzo przydatny, gdyŜ pozwala
przeglądarkom na przechowywanie dokumentów w pamięci podręcznej i pobieranie ich z serwera
wyłącznie jeśli zostały zmodyfikowane. Niemniej jednak tworząc serwlety nie trzeba bezpośrednio
operować na tym nagłówku. Zamiast tego moŜna zaimplementować metodę getLastModified,
dzięki której system będzie w stanie automatycznie obsłuŜyć daty modyfikacji. Przykład
wykorzystania metody getLastModified przedstawiłem w podrozdziale 2.8, pt.: „Przykład
wykorzystania inicjalizacji serwletu i daty modyfikacji strony”.
If-None-Match
87 Rozdział 4. Obsługa Ŝądań: Nagłówki Ŝądań HTTP
Nagłówek ten przypomina nagłówek If-Match, z tym Ŝe operacja zostanie wykonana jeśli
nie zostaną odnalezione Ŝadne pasujące do siebie znaczniki elementów.
If-Range
Ten rzadko stosowany nagłówek pozwala klientowi który dysponuje fragmentem
dokumentu, zaŜądać jego brakujących fragmentów (jeśli dokument nie został zmodyfikowany) lub
całego, nowego dokumentu (jeśli został on zmodyfikowany po określonej dacie).
If-Unmodified-Since
Nagłówek ten działa przeciwnie niŜ nagłówek If-Modified-Since wskazując, Ŝe operacja powinna zostać
wykonana wyłącznie jeśli dokument nie został zmodyfikowany po podanej dacie. Zazwyczaj nagłówek If-Modified-
Since jest stosowany w Ŝądaniach GET („prześlij mi dokument wyłącznie jeśli jest on nowszy od wersji jaką mam w
pamięci podręcznej”), natomiast nagłówek If-Unmodified-Since w Ŝądaniach PUT („zaktualizuj ten dokument jeśli
nikt inny nie aktualizował go od czasu gdy ja zrobiłem to po raz ostatni”).
Pragma
Nagłówek Pragma o wartości no-cache informuje, Ŝe servlet działający jako pośrednik powinien przekazać
Ŝądanie dalej, nawet jeśli dysponuje kopią lokalną Ŝądanego zasobu. Jedyną standardową wartością tego nagłówka jest
no-cache.
Proxy-Authorization
Ten nagłówek pozwala klientom na przekazanie swej toŜsamości serwerom pośredniczącym, które wymagają
autoryzacji. W przypadku servletów nagłówek ten jest zazwyczaj ignorowany, a zamiast niego wykorzystuje się
nagłówek Authorization.
Range
Ten rzadko stosowany nagłówek pozwala klientom dysponującym częściową kopią dokumentu, zaŜądać od
serwera wyłącznie brakujących fragmentów tego dokumentu.
Referer
Ten nagłówek zawiera adres URL strony z jakiej zostało przesłane Ŝądanie. Na przykład, jeśli aktualnie
oglądasz stronę X i klikniesz umieszczone na niej hiperłącze do strony Y, to w Ŝądaniu dotyczącym strony Y, w
nagłówku Referer, zostanie zapisany adres URL strony X. Wszystkie najpopularniejsze przeglądarki określają wartość
tego nagłówka, dzięki czemu stanowi on wygodny sposób śledzenia skąd pochodzą Ŝądania. MoŜliwości te doskonale
nadają się do gromadzenia informacji o witrynach prezentujących reklamy, z których uŜytkownicy przechodzą na
Twoją witrynę, do zmiany zawartości strony w zaleŜności od witryny z jakiej przeszedł uŜytkownik, lub, po prostu, do
śledzenia skąd przychodzą uŜytkownicy Twej witryny. W tym ostatnim przypadku większość osób korzysta z
dzienników serwera, gdyŜ nagłówek Referer jest w nich zazwyczaj rejestrowany. Choć nagłówek ten jest bardzo
przydatny, to nie naleŜy na nim polegać, gdyŜ programy z łatwością mogą fałszować jego zawartość. I w końcu, naleŜy
zwrócić uwagę na pisownię tego nagłówka — nosi on nazwę Referer a nie, jak moŜna by oczekiwać, Referrer,
gdyŜ jeden z twórców protokołu HTTP popełnił prosty błąd.
Upgrade
Nagłówek Upgrade pozwala przeglądarce lub innemu klientowi na określenie z jakiego protokołu
komunikacyjnego skorzystałaby chętniej niŜ z protokołu HTTP 1.1. Jeśli takŜe serwer będzie obsługiwać ten protokół,
to zarówno program jak i serwer zaczną z niego korzystać. Takie negocjowanie protokołu niemal zawsze odbywa się
przed wywołaniem servletu; a zatem servlety rzadko kiedy korzystają z tego nagłówka.
User-Agent
Ten nagłówek określa jaka przeglądarka lub program nadesłał Ŝądanie. MoŜna go wykorzystać w sytuacjach
gdy zawartość generowanego dokumentu zaleŜy od przeglądarki do jakiej zostanie on przesłany. Stosując ten nagłówek
88
naleŜy jednak zachować duŜą ostroŜność, gdyŜ tworzenie serwletu w oparciu o zakodowaną na stałe listę numerów
wersji przeglądarek oraz skojarzonych z nimi listy moŜliwości, moŜe doprowadzić do powstania niepewnego kodu,
którego modyfikacja będzie znacznie utrudniona. Jeśli to tylko moŜliwe, zamiast tego nagłówka naleŜy korzystać z
innych informacji dostępnych w Ŝądaniu HTTP. Na przykład, zamiast prób zapamiętania wszystkich przeglądarek,
które obsługują kompresje gzip na poszczególnych systemach operacyjnych, wystarczy sprawdzić wartość nagłówka
Accept-Encoding. Oczywiście, jak juŜ wspominałem, nie zawsze jest to moŜliwe; lecz gdy nie jest, zawsze naleŜy
odpowiedzieć sobie na pytanie czy wykorzystanie unikalnej moŜliwości przeglądarki, z której chcesz skorzystać jest
wart ponoszonych kosztów.
Większość wersji Internet Explorera, w pierwszej kolejności, w wierszu nagłówka User-Agent umieszcza
określenie „Mozilla” (Netscape), a dopiero potem podaje faktyczny numer wersji programu. SłuŜy to zachowaniu
zgodności z programami pisanymi w języku JavaScript, gdzie nagłówek User-Agent jest czasami wykorzystywany do
określania dostępnych moŜliwości języka. Pamiętaj takŜe, iŜ wartość tego nagłówka moŜna łatwo sfałszować. Fakt ten
stawia pod duŜym znakiem zapytania wartość witryn, które określają rynkowy udział poszczególnych wersji
przeglądarek na podstawie wartości tego nagłówka. Hmmm... miliony dolarów wydane na reklamę opartą na
statystykach, które moŜna zniekształcić przy uŜyciu programu napisanego w niecałą godzinę? I ja mam wierzyć, Ŝe te
liczby są precyzyjne?
Via
Nagłówek ten jest generowany przez bramy oraz serwery pośredniczące, w celu wskazania miejsc przez jakie
przechodziło Ŝądanie.
Warning
Rzadko stosowany nagłówek ogólnego przeznaczenia, który pozwala klientom na przesyłanie ostrzeŜeń
dotyczących błędów zawiązanych z przekształcaniem zawartości lub gromadzeniem jej w pamięci podręcznej.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.zip.*;
Rysunek 4.3 Dzięki temu, Ŝe przeglądarka Internet Explorer 5.0 przeznaczony dla systemu
Windows obsługuje kompresję zawartości, ta strona została przesłana siecią w formie
skompresowanej, dzięki czemu czas jej przesyłania był znacznie krótszy.
Podpowiedź
Kompresja wykorzystująca algorytm gzip moŜe w ogromnym stopniu zredukować czas pobierania długich stron
tekstowych.
90
Implementacja kompresji jest wyjątkowo prosta, gdyŜ algorytm gzip jest wbudowany w
język Java i dostępny za pośrednictwem klasy java.util.zip. Serwlet, który chce wykorzystać
kompresję danych, w pierwszej kolejności powinien sprawdzić nagłówek Ŝądania Accept-Encoding,
aby upewnić się, Ŝe znajdują się tam informacje o akceptowaniu kodowania gzip. Jeśli klient
akceptuje to kodowanie, to serwlet moŜe wygenerować stronę przy wykorzystaniu klasy
GZIPOutputStream i podać wartość gzip w nagłówku odpowiedzi Content-Encoding. Przy
wykorzystaniu klasy GZIPOutputStream naleŜy jawnie wywołać metodę close. Jeśli okaŜe się, Ŝe
klient nie obsługuje kompresji zawartości, to serwlet musi wygenerować i przesłać stronę przy
uŜyciu klasy PrintWriter. Aby ułatwić przeprowadzanie testów efektywności na jednej
przeglądarce, w przykładowym serwlecie zaimplementowałem moŜliwość wyłączenia kompresji
zawartości, w momencie dodania do adresu URL parametru ?encoding=none.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.Properties;
import sun.misc.BASE64Decoder;
Rysunek 4.4 Początkowe wyniki zwrócone przez serwlet SecretServlet (pod tą nazwą
został zarejestrowany serwlet ProtectedPage)
String passwordFile =
"C:\\InetPub\\Serwery\\Jakarta Tomcat 4.0\\passwords.properties";
FileOutputStream out = new FileOutputStream(passwordFile);
/*
UŜywam JDK 1.1 aby zagwarantować przenaszalność pomiędzy
wszystkimi mechanizmami obsługi servletów.
W przypadku uŜywania JDK 1.2, aby uniknąć ostrzeŜeń o
stosowaniu przestarzałych metod, naleŜy uŜywać metody
"store" zamiast "save".
*/
passwords.save(out, "Passwords");
}
}
Rozdział 5.
Dostęp do standardowych
zmiennych CGI
Jeśli rozpoczynasz naukę tworzenia serwletów dysponując doświadczeniami związanymi z
pisaniem tradycyjnych programów CGI, to zapewne doskonale wiesz czym są i jak się stosuje
„zmienne CGI”. Zmienne te stanowią nieco eklektyczną kolekcję przeróŜnych informacji
dotyczących aktualnego Ŝądania. Niektóre z nich bazują na wierszu Ŝądania oraz na nagłówkach
Ŝądania HTTP (na przykład: danych przesłanych z formularza), wartości innych są określane na
podstawie samego połączenia sieciowego (na przykład: nazwa oraz adres IP komputera
przesyłającego Ŝądanie), a jeszcze inne zawierają informacje o instalacyjnych parametrach serwera
(takie jak skojarzenie adresów URL z fizycznymi ścieŜkami).
NiezaleŜne traktowanie wszystkich źródeł informacji (takich jak nagłówki Ŝądania,
informacje o serwerze, itp.) jest zapewne sensowne. Niemniej jednak doświadczeni programiści
CGI mogą uwaŜać, Ŝe warto by mieć w serwletach moŜliwość dostępu do odpowiedników
wszystkich zmiennych CGI. Nie przejmuj się jeśli nie masz Ŝadnych doświadczeń w tworzeniu
tradycyjnych programów CGI — serwlety są od nich łatwiejsze w uŜyciu, bardziej elastyczne i
efektywne. Poza tym, moŜesz po prostu przejrzeć ten rozdział, zwracając jedynie uwagę na te jego
fragmenty, które nie są bezpośrednio związane z Ŝądaniami HTTP. W szczególności zwróć uwagę,
Ŝe moŜna uŜyć metody getServletContext().getRealPath, aby przekształcić URI (fragmentu
adresu URL podanego po nazwie komputera i adresie portu) na faktyczną ścieŜkę. Poza tym
pamiętaj, Ŝe nazwę oraz adres IP komputera z którego nadesłano Ŝądanie moŜna określić przy
uŜyciu metod request.getRemoteHost oraz request.getRemoteAddress.
AUTH_TYPE
Jeśli w Ŝądaniu został umieszczony nagłówek Authorization, to ta zmienna określa typ
autoryzacji (basic lub digest). Jej wartość moŜna określić przy uŜyciu wywołania
request.getAuthType().
96
CONTENT_LENGTH
Zmienna dostępna wyłącznie dla Ŝądań POST, zawiera wartość określającą ilość wysłanych
bajtów informacji, podaną w nagłówku Content-Length. Zmienna CGI CONTENT-LENGTH jest
łańcuchem znaków, a jej odpowiednikami w serwletach są wyraŜenia String.valueOf
(request.getContentLength()) lub request.getHeader("Content-Length"). Jednak zazwyczaj
będziesz chyba wolał korzystać z metody request.getContentLength, która zwraca wartość typu
int.
CONTENT_TYPE
Zmienna CONTENT_TYPE określa typ MIME dołączonych danych, oczywiście, jeśli został on
podany. W tabeli 7.1 znajdującej się w podrozdziale 7.2, pt.: „Nagłówki odpowiedzi protokołu
HTTP 1.1 oraz ich znaczenie”, zostały podane nazwy oraz znaczenie najczęściej stosowanych
typów MIME. Wartość zmiennej CONTENT_TYPE moŜna pobrać przy uŜyciu wywołania
request.getConetentType().
DOCUMENT_ROOT
Zmienna DOCUMENT_ROOT określa faktyczny katalog odpowiadający adresowi URL
http://host/. Jej wartość moŜna określić przy uŜyciu wywołania
getServletContext().getRealPath("/"). We wcześniejszych specyfikacjach technologii Java
Servlet wartość tej zmiennej moŜna było pobrać przy uŜyciu metody request.getRealPath("/"),
jednak aktualnie metoda ta nie jest juŜ dostępna. Metody getServletContext().getRealPath moŜna
takŜe uŜyć od określenia faktycznego katalogu odpowiadającemu dowolnemu URI (czyli
fragmentowi adresu URL zapisanemu po nazwie komputera i numerze portu).
HTTP_XXX_YYY
Zmienne o postaci HTTP_NAGŁÓWEK_NAZWA umoŜliwiały programom CGI uzyskanie
dostępu do dowolnych nagłówków Ŝądania HTTP. Nagłówek Cookie stawał się zmienną
HTTP_COOKIE, nagłówek User-Agent zmienną HTTP_USER_AGENT, a nagłówek Referer zmienną
HTTP_REFERER, i tak dalej. W serwletach naleŜy pobierać nagłówki przy uŜyciu metody
request.getHeader lub jednej z pozostałych metod opisanych w rozdziale 4, pt.: „Obsługa Ŝądań:
Nagłówki Ŝądań HTTP”.
PATH_INFO
Ta zmienna zawiera informacje o ścieŜce dołączone do adresu URL po nazwie serwletu lecz
przed danymi zapytania. Na przykład, w poniŜszym adresie URL —
http://komputer/servlet/coreservlets.JakisServlet/costam/menu?user=Janek — ścieŜką jest
/costam/menu. Serwlety, w odróŜnieniu od standardowych programów CGI, są w stanie
bezpośrednio komunikować się z serwerem, i z tego względu nie ma konieczności traktowania
informacji o ścieŜki w jakiś szczególny sposób. Informacje te mogą zostać przesłane jako część
zwyczajnych danych pochodzących z formularza, a następnie przekształcone przy uŜyciu metody
getServletContext().getRealPath. Wartość zmiennej CGI PATH_INFO moŜna pobrać przy uŜyciu
wywołania request.getPathInfo().
PATH_TRANSLATED
Zmienna PATH_TRANSLATED zawiera informacje o ścieŜce przekształcone do postaci
faktycznego katalogu na serwerze. TakŜe w tym przypadku nie ma powodu, aby zwracać
szczególną uwagę na te informacje, gdyŜ serwlet zawsze moŜe określić faktyczną ścieŜkę na
podstawie częściowych adresów URL, posługując się w tym celu metodą
getServletContext().getRealPath. Takie odwzorowywanie adresów URL na katalogi nie było
97 Rozdział 5. Dostęp do standardowych zmiennych CGI
moŜliwe w programach CGI, gdyŜ ich realizacja nie była w Ŝaden sposób związana z serwerem.
Wartość tej zmiennej moŜna określić przy uŜyciu wywołania request.getPathTranslated().
QUERY_STING
W przypadku Ŝądań GET zmienna ta zawiera wszystkie dane przesłane z formularza w formie
jednego łańcucha znaków zakodowanego w formacie URL. W serwletach, rzadko kiedy będziesz
korzystał z takich nieprzetworzonych informacji; dostęp do wartości poszczególnych parametrów
moŜna łatwo uzyskać przy uŜyciu metody request.getParameter (opisanej w rozdziale 3, pt.:
„Obsługa Ŝądań: Dane przesyłane z formularzy”). Niemniej jednak, jeśli chcesz uzyskać dostęp do
nieprzetworzonych informacji, moŜesz się posłuŜyć wywołaniem request.getQueryString().
REMOTE_ADDR
Ta zmienna określa adres IP klienta, który przesłał Ŝądanie. Wartość ta zwracana jest w
postaci łańcucha znaków (na przykład: "198.137.241.30"); moŜna ją pobrać przy uŜyciu wywołania
request.getRemoteAddr().
REMOTE_HOST
Zmienna REMOTE_HOST zawiera w pełni kwalifikowaną nazwę domeny (np.: sejm.gov.pl)
klienta, który zgłosił Ŝądanie. Jeśli nie będzie moŜna określić nazwy domeny, metoda zwróci
odpowiadający jej adres IP. Wartość tej zmiennej moŜna określić przy uŜyciu wywołania
request.getRemoteHost().
REMOTE_USER
Jeśli nagłówek Authorization został podany i zdekodowany przez sam serwer WWW, to
zmienna REMOTE_USER będzie zawierać informacje o nazwie uŜytkownika. W witrynach z
ograniczeniami dostępu, informacje te mogą się przydać przy obsłudze sesji. Wartość tej zmiennej
moŜna pobrać przy uŜyciu wywołania request.getRemoteUser(). Więcej informacji o
wykorzystaniu nagłówka Authorization w serwletach znajdziesz w podrozdziale 4.5, pt.:
„Ograniczanie dostępu do stron WWW”.
REQUEST_METHOD
Ta zmienna zawiera typ Ŝądania HTTP; zazwyczaj będzie to GET lub POST, jednak moŜe
takŜe przyjąć jedną z wartości: HEAD, PUT, DELETE, OPTIONS lub TRACE. W serwletach rzadko kiedy
trzeba się bezpośrednio odwoływać do zmiennej REQUEST_METHOD, gdyŜ Ŝądania poszczególnych
typów są zazwyczaj obsługiwane przez odrębne metody (doGet, doPost, itp.). Wyjątkiem jest tu
sposób obsługi Ŝądań HEAD. śądania tego typu są bowiem obsługiwane automatycznie przez metodę
service, która zwraca wiersz statusu oraz wszystkie nagłówki, które zostałyby wygenerowane
przez metodę doGet. Wartość tej zmiennej moŜna pobrać przy uŜyciu wywołania
request.getMethod().
SCRIPT_NAME
Ta zmienna określa ścieŜkę do serwletu względem głównego katalogu serwera. Jej wartość
moŜna pobrać przy uŜyciu wywołania request.getServletPath().
SERVER_NAME
Zmienna SERVER_NAME zawiera nazwę komputera na którym działa serwer. Jej wartość
moŜna pobrać przy uŜyciu wywołania request.getServerName().
SERVER_PORT
98
SERVER_PROTOCOL
Zmienna SERVER_PROTOCOL określa nazwę oraz numer protokołu podany w wierszu Ŝądania
(na przykład: HTTP/1.0 lub HTTP/1.1). Wartość tej zmiennej moŜna pobrać przy uŜyciu wywołania
request.getProtocol().
SERVER_SOFTWARE
Ta zmienna zawiera informacje określające uŜywany serwer WWW. Informacje te moŜna
pobrać przy uŜyciu wywołania getServletContext().getServerInfo().
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
Witam!!
Wiersz statusu składa się z określenia uŜywanej wersji protokołu HTTP (w powyŜszym
przykładzie jest to HTTP/1.1), kodu statusu będącego liczbą całkowitą (w naszym przykładzie jest to
200) oraz bardzo krótkiego komunikatu opisującego kod statusu (w naszym przykładzie jest to OK).
W większości przypadków wszystkie nagłówki odpowiedzi, za wyjątkiem nagłówka Content-Type
określającego typ MIME przesyłanego dokumentu, są opcjonalne. Niemal wszystkie odpowiedzi
zawierają jakiś dokument. Nie jest to jednak regułą, gdyŜ są odpowiedzi, które nie zawierają
Ŝadnych dokumentów. Przykładem mogą tu być odpowiedzi na Ŝądanie HEAD, które nie mogą
zawierać dokumentów. Poza tym istnieje wiele kodów statusu, które określają niepowodzenie i nie
zawierają w odpowiedzi Ŝadnego dokumentu bądź jedynie krótki dokument z informacją o błędzie.
Serwlety mogą wykonywać wiele waŜnych zadań operując na wierszu statusu oraz
nagłówkach odpowiedzi. Na przykład, mogą przekierować uŜytkownika do innej witryny, wskazać,
Ŝe dołączony dokument jest obrazem, plikiem Adobe Acrobat lub dokumentem HTML, zaŜądać
hasła w celu uzyskania dostępu do dokumentu, i tak dalej. W tym rozdziale przedstawię róŜne kody
statusu oraz zadania, które moŜna wykonać przy ich uŜyciu. W następnym rozdziale zajmiemy się
nagłówkami odpowiedzi.
Metoda setStatus pobiera jeden argument typu int (kod statusu); jednak zamiast
stosowania zwyczajnych liczb całkowitych lepiej jest posługiwać się stałymi zdefiniowanymi w
interfejsie HttpServletResponse. Nazwy tych stałych zostały określone na podstawie
standardowych komunikatów protokołu HTTP 1.1 skojarzonych z kodami statusu. Nazwy
zapisywane są wyłącznie wielkimi literami, rozpoczynają się od liter SC (skrót od angielskich słów:
Status Code — kod statusu) a w wszystkie odstępy są w nich zamieniane na znaki podkreślenia (_).
Przykładowo, jeśli komunikat dla kodu 404 to „Not Found” (nie odnaleziono), to odpowiadająca mu
stała będzie miała nazwę SC_NOT_FOUND. W specyfikacji Java Servlet 2.1 istnieją jednak trzy wyjątki
od tej reguły. OtóŜ, stała skojarzona z kodem 302 została określona na podstawie komunikatu
protokołu HTTP 1.0 (Moved Temporarily), a nie na podstawie komunikatu protokołu HTTP 1.1
(Found), natomiast stałe odpowiadające kodom 307 (Temporary Redirect) oraz 416 (Requested
Range Not Satsfiable) w ogóle nie zostały zdefiniowane. W specyfikacji 2.2 dodano stałą dla kodu
416, jednak pozostałe dwie niespójności wciąŜ pozostały.
Choć ogólną metodą określania kodu statusu jest wywołanie o postaci
request.setStatus(int), to istnieją dwie częste sytuacje, w których moŜna uŜyć innych metod
interfejsu HttpServletResponse. NaleŜy jednak pamiętać, Ŝe obie przedstawione poniŜej metody
zgłaszają wyjątek IOException, natomiast metoda setStatus nie robi tego.
• public void sendError(int kod, String komunikat)
Ta metoda przesyła kod statusu (zazwyczaj jest to kod 404) oraz krótki komunikat,
który jest automatycznie umieszczany w dokumencie HTML i przesyłany do klienta.
• public void sendRedirect(String url)
Metoda sendRedirect generuje kod statusu 302 oraz nagłówek odpowiedzi Location
podający adres URL nowego dokumentu. W przypadku specyfikacji Java Servlet 2.1 musi
to być adres bezwzględny. Specyfikacja 2.2 zezwala na podawanie adresów względnych, a
system, przed umieszczeniem adresu w nagłówku Location, przekształca go do postaci
bezwzględnej.
Określenie kodu statusu wcale nie musi oznaczać, Ŝe nie trzeba zwracać dokumentu.
Przykładowo, w przypadku odpowiedzi 404 większość serwerów automatycznie generuje krótki
dokument z komunikat „File Not Found” (nie odnaleziono pliku), niemniej jednak serwlet moŜe
samemu określić zawartość tego dokumentu. Pamiętaj, Ŝe jeśli chcesz samodzielnie generować
treść odpowiedzi, to zanim to zrobisz będziesz musiał wywołać metodą setStatus bądź sendError.
102
200 (OK)
Kod statusu 200 (SC_OK) oznacza, Ŝe wszystko jest w porządku. W odpowiedzi na Ŝądanie
GET i POST zostanie przesłany dokument. Jest to domyślny kod statusu stosowany w serwletach —
jeśli nie uŜyjesz metody setStatus, automatycznie zostanie wygenerowany kod statusu 200.
Jednak metody tej nie da się zastosować do stron automatycznie odświeŜanych przy uŜyciu
nagłówka odpowiedzi Refresh lub odpowiadającego mu znacznika HTML <META HTTP-
EQUIV="Refresh" ...>. Wynika to z faktu, iŜ przesłanie kodu statusu 204 powoduje przerwanie
odświeŜania strony. W takiej sytuacji wciąŜ jednak będzie działać odświeŜanie strony realizowane
przy uŜyciu JavaScriptu. Więcej szczegółowych informacji na ten temat znajdziesz w podrozdziale
7.2, pt.: „Nagłówki odpowiedzi protokołu HTTP 1.1 oraz ich znaczenie”.
Kod statusu 302 jest niezwykle przydatny, gdyŜ przeglądarki automatycznie przechodzą pod
nowy adres URL podany w nagłówku odpowiedzi Location. W praktyce jest on tak przydatny, iŜ
została zdefiniowana specjalna metoda, która go generuje — sendRedirect. Wykorzystanie
wywołania o postaci request.sendRedirect(url) ma kilka zalet w porównaniu z wywołaniami
response.setStatus(response.SC_MOVED_TEMPORARILY) oraz response.setHeader("Location",
url). Po pierwsze, jest ono krótsze i prostsze. Po wtóre, uŜywając metody sendRedirect serwlet
automatycznie tworzy stronę WWW zawierającą adres pod który przeglądarka zostaje
przekierowana; strona ta jest przeznaczona dla starszych typów przeglądarek, które nie są w stanie
automatycznie obsługiwać przekierowań. W końcu, w specyfikacji Java Servlet 2.2 (dostępnej w
J2EE), metoda ta pozwala na stosowanie względnych adresów URL, automatycznie zamieniając je
na adresy bezwzględne. W serwerach zgodnych ze specyfikacją 2.1, w metodzie sendRedirect
moŜna podawać wyłącznie adresy bezwzględne.
Jeśli kierujesz uŜytkownika do innej strony naleŜącej do tej samej witryny, to przed
podaniem jej adresu powinieneś przekształcić go przy uŜyciu metody encodeURL interfejsu
HttpServletResponse. To proste zabezpieczenie na wypadek gdybyś korzystał ze śledzenia sesji
bazującego na przepisywaniu adresów URL. Przepisywanie adresów URL to sposób śledzenia
uŜytkowników, którzy odwiedzają witrynę i mają wyłączoną obsługę cookies. Jest on
implementowany poprzez dodawanie dodatkowych informacji na końcu adresu URL, przy czym
mechanizmy śledzenia sesji, którymi dysponują serwlety, automatycznie zajmują się wszelkimi
szczegółami. Śledzenie sesji zostanie szczegółowo opisane w rozdziale 9. Warto jednak wyrobić
sobie nawyk uŜywania metody encodeURL, aby później moŜna było wzbogacić witrynę o śledzenie
sesji jak najmniejszym kosztem.
Metoda
Jeśli kierujesz uŜytkownika do innej strony naleŜącej do tej samej witryny, to przygotuj się na wypadek gdybyś w
przyszłości chciał korzystać ze śledzenia sesji. W tym celu zamiast wywołania:
response.sendRedirect(url)
uŜyj wywołania postaci:
response.sendRedirect(response.encodeURL(url)).
105 Rozdział 6. Generacja odpowiedzi: Kody statusu
Czasami ten kod statusu jest uŜywany zamiennie z kodem 301. Na przykład, gdy błędnie
zaŜądasz strony http://host/~uŜytkownik (pomijając znak ukośnika na końcu adresu), to niektóre
serwery odpowiedzą kodem statusu 301 a inne, kodem 302. Z technicznego punktu widzenia,
przeglądarki powinne automatycznie obsłuŜyć przekierowanie wyłącznie wtedy, gdy początkowe
Ŝądanie było Ŝądaniem GET. Więcej informacji na ten temat znajdziesz w opisie kodu statusu 307.
HttpServletResponse specyfikacji Java Servlet 2.1, została pominięta stała odpowiadająca temu
kodowi statusu.
Notatka
W specyfikacji Java Servlet 2.1, w interfejsie HttpServletResponse nie została zdefiniowana stała
SC_REQUESTED_RANGE_NOT_SATISFIABLE; a zatem, aby wygenerować ten kod statusu naleŜy jawnie podać
liczbę 416. Stała odpowiadająca temu kodowi statusu jest juŜ dostępna w specyfikacjach 2.2 i późniejszych.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
return;
}
SearchSpec[] commonSpecs = SearchSpec.getCommonSpecs();
for(int i=0; i<commonSpecs.length; i++) {
SearchSpec searchSpec = commonSpecs[i];
if (searchSpec.getName().equals(searchEngine)) {
String url =
searchSpec.makeURL(searchString, numResults);
response.sendRedirect(url);
return;
}
}
reportProblem(response, "Nieznany serwis wyszukiwawczy.");
}
<BODY BGCOLOR="#FDF5E6">
<H1 ALIGN="CENTER">Przeszukiwanie WWW</H1>
<FORM ACTION="/servlet/coreservlets.SearchEngines">
<CENTER>
Poszukiwane wyraŜenie:
<INPUT TYPE="TEXT" NAME="searchString"><BR>
Ilość wyników na stronę:
<INPUT TYPE="TEXT" NAME="numResults"
VALUE=10 SIZE=3><BR>
<INPUT TYPE="RADIO" NAME="searchEngine"
VALUE="google">
Google |
<INPUT TYPE="RADIO" NAME="searchEngine"
VALUE="infoseek">
Infoseek |
<INPUT TYPE="RADIO" NAME="searchEngine"
VALUE="lycos">
Lycos |
<INPUT TYPE="RADIO" NAME="searchEngine"
VALUE="hotbot">
HotBot
<BR>
<INPUT TYPE="SUBMIT" VALUE="Szukaj">
</CENTER>
</FORM>
</BODY>
</HTML>
Rysunek 6.4 Wyniki wykonania serwletu SearchEngines w przypadku gdy nie zostały
określone Ŝadne kryteria wyszukiwania. W Netscape Navigatorze została wyświetlona strona
wygenerowana przez serwlet.
Rozdział 7.
Generacja odpowiedzi: Nagłówki
odpowiedzi HTTP
Odpowiedź generowana przez serwer WWW składa się zazwyczaj z wiersza statusu,
jednego lub kilku nagłówków odpowiedzi, pustego wiersza oraz dokumentu. Aby w jak
największym stopniu wykorzystać moŜliwości serwletów, będziesz musiał wiedzieć nie tylko jak
generować dokument, lecz takŜe jak naleŜy efektywnie uŜywać kodów statusu i nagłówków
odpowiedzi.
Określanie nagłówków odpowiedzi HTTP często jest ściśle powiązane z określaniem kodów
statusu, którym poświęciłem cały poprzedni rozdział. Na przykład, wszystkim kodom statusu
informującym Ŝe „dokument został przeniesiony” (o wartościach z zakresu od 300 do 307)
towarzysz nagłówek odpowiedzi Location, natomiast kod 401 (Unauthorized) zawsze występuje
wraz z nagłówkiem WWW-Authenticate. Niemniej jednak określanie nagłówków odpowiedzi moŜe
mieć waŜne znaczenie takŜe w sytuacjach, gdy Ŝadne niezwykłe kody statusu nie są stosowane.
Nagłówki odpowiedzi mogą słuŜyć do tworzenia cookies, określania daty modyfikacji strony
(informacje te są wykorzystywane przez przeglądarki przy przechowywaniu stron WWW a pamięci
podręcznej), nakazania przeglądarce, aby odświeŜyła stronę po określonym czasie, do podania
wielkości pliku, dzięki czemu będzie moŜna wykorzystać trwałe połączenie HTTP, do określenia
typu generowanego dokumentu oraz wykonywania wielu innych czynności.
Age
Ten nagłówek stosowany jest przez serwery pośredniczące, aby poinformować kiedy
dokument został wygenerowany przez serwer z którego pochodzi. Nagłówek ten został
wprowadzony w protokole HTTP 1.1 i rzadko kiedy jest stosowany w serwletach.
Allow
Nagłówek Allow określa metody Ŝądań (na przykład: GET, POST, HEAD, itp.) obsługiwanych
przez serwer. Nagłówek ten musi zostać podany w przypadku zwracania odpowiedzi zawierającej
kod statusu 405 (Method Not Allowed — niedozwolona metoda). Domyślna metoda service
serwletów, automatycznie generuje ten nagłówek w przypadku obsługi Ŝądań OPTIONS.
Cache-Control
Ten bardzo przydatny nagłówek przekazuje przeglądarce lub innemu klientowi informacje o
okolicznościach w jakich dokument zawarty w odpowiedzi moŜe być bezpiecznie przechowany w
pamięci podręcznej. Nagłówek ten moŜe przybierać poniŜsze wartości:
• public — dokument moŜe być przechowywany w pamięci podręcznej, nawet jeśli normalne
reguły (na przykład: reguły dotyczące stron chronionych hasłem) wskazują, Ŝe nie powinien
być.
• private — dokument jest przeznaczony dla jednego uŜytkownika i moŜe być
przechowywany tylko w „prywatnej” pamięci podręcznej, z której wyłącznie ten jeden
uŜytkownik będzie mógł go pobrać.
117 Rozdział 7. Generacja odpowiedzi: Nagłówki odpowiedzi HTTP
• no-cache — dokument nigdy nie powinien być przechowywany w pamięci podręcznej (czyli
nie powinien być zwracany przy obsłudze kolejnych Ŝądań). Serwer moŜe takŜe uŜyć
nagłówka o postaci "no-cache=nagłówek1,nagłówek2,...,nagłówekN", aby określić
nagłówki, które powinne być usunięte z odpowiedzi, jeśli później zostanie zwrócona
odpowiedź przechowywana w pamięci podręcznej. Przeglądarki zazwyczaj nie przechowują
w pamięci podręcznej dokumentów wygenerowanych w odpowiedzi na Ŝądanie zawierające
informacje podane w formularzu. Niemniej jednak, jeśli serwlet generuje róŜne odpowiedzi
nawet jeśli Ŝądanie nie zawiera informacji z formularza, to konieczne jest przekazanie
przeglądarce informacji, Ŝe odpowiedź nie powinna być zapisywana w pamięci podręcznej.
Starsze typy przeglądarek uŜywają do tego celu nagłówka odpowiedzi Pragma, dlatego teŜ
typowym rozwiązaniem stosowanym w serwletach jest podanie obu nagłówków, tak jak
pokazałem na poniŜszym przykładzie.
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
Connection
Przekazanie wartości close w tym nagłówku odpowiedzi informuje przeglądarkę, aby nie
korzystała z trwałych połączeń HTTP. Z technicznego punktu widzenia, jeśli klient korzysta z
protokołu HTTP 1.1 i nie zostanie uŜyty nagłówek Connection: close, to trwałe połączenia są
stosowane domyślnie. (Trwałe połączenia HTTP będą takŜe wykorzystane w programach
korzystających z protokołu HTTP 1.0, jeśli zostanie podany nagłówek Connection: keep-alive.)
UŜycie trwałych połączeń HTTP wymaga jednak podania nagłówka odpowiedzi Content-Length i z
tego względu serwlety nie muszą jawnie stosować nagłówka Connection. Jeśli bowiem nie chcesz
stosować trwałych połączeń, wystarczy pominąć w odpowiedzi nagłówek Content-Length. Przykład
wykorzystania trwałych połączeń w serwletach został przedstawiony w podrozdziale 7.4, pt.:
„Stosowanie trwałych połączeń HTTP”.
Content-Encoding
Ten nagłówek określa sposób w jaki strona została zakodowana na czas przesyłania.
Przeglądarka powinna odtworzyć kodowanie i przywrócić dokument do oryginalnego stanu, zanim
podejmie decyzję co z nim naleŜy dalej zrobić. Kompresja dokumentów przy uŜyciu algorytmu gzip
jest w stanie w ogromnym stopniu skrócić czas ich transmisji; przykład wykorzystania takiego
rozwiązania przedstawiłem w podrozdziale 4.4, pt.: „Przesyłanie skompresowanych stron WWW”.
118
Content-Language
Ten nagłówek określa język, w jakim dokument został napisany. Wartością tego nagłówka
powinien być jeden ze standardowych kodów języków, takich jak: pl, en, en-us, da, itp. Więcej
szczegółowych informacji na ten temat znajdziesz w pliku RFC 1766 (pliki RFC moŜesz znaleźć na
WWW, na przykład na witrynie http://www.rfc-editor.org/.)
Content-Length
Ten nagłówek określa wielkość dokumentu przekazywanego w odpowiedzi (wyraŜoną w
bajtach). Informacja ta jest potrzebna wyłącznie w sytuacji, gdy przeglądarka korzysta z trwałych
połączeń HTTP. Więcej informacji o sposobie określania, kiedy przeglądarka korzysta z trwałych
połączeń znajdziesz w opisie nagłówka odpowiedzi Connection. Jeśli chcesz korzystać z trwałych
połączeń w serwlecie (oczywiście, o ile na to pozwala przeglądarka), to powinieneś zapisać cały
dokument w strumieniu ByteArrayOutputStream, określić jego długość, zapisać ją w nagłówku
Content-Type przy uŜyciu metody response.setContentLength, a następnie przesłać dokument za
pomocą wywołania o postaci byteArrayStream.writeTo(response.getOutputStream)). Przykład
praktycznego wykorzystania trwałych połączeń HTTP znajdziesz w podrozdziale 7.4.
Content-Location
Ten nagłówek określa alternatywny adres Ŝądanego dokumentu. Jest to nagłówek
informacyjny, a odpowiedzi które go zawierają, zawierają takŜe Ŝądany dokument. A zatem
odpowiedzi te róŜnią się od odpowiedzi zawierających nagłówek Location, do których nie jest
dołączany Ŝaden dokumentu. Nagłówek ten został wprowadzony w protokole HTTP 1.1.
Content-MD5
Nagłówek odpowiedzi Content-MD5 zawiera kod MD5 obliczony dla przesyłanego
dokumentu. Kod ten daje moŜliwość sprawdzenia integralności danych, z której mogą korzystać
przeglądarki jeśli chcą upewnić się, Ŝe otrzymały kompletny dokument w niezmienionej postaci.
Szczegółowe informacje na temat kodu MD5 znajdziesz w pliku RFC 1864. Nagłówek ten został
wprowadzony w protokole HTTP 1.1.
Content-Range
Ten nowy nagłówek, wprowadzony w protokole HTTP 1.1, jest przesyłany w
odpowiedziach zawierających fragmenty zwracanego dokumentu i określa jaka część tego
dokumentu jest aktualnie przesyłana. Na przykład, gdyby wartość tego nagłówka miała postać
bytes 500-999/2345, oznaczałoby to, Ŝe odpowiedź zawiera bajty od 500-nego do 999-go, a cały
dokument liczy 2345 bajtów.
Content-Type
Nagłówek Content-Type określa typ MIME przesyłanego dokumentu. Nagłówka tego uŜywa
się tak często, Ŝe interfejs HttpServletResponse zawiera specjalną metodę, która słuŜy do jego
generacji. Metoda tą jest setContentType. W przypadku oficjalnie zarejestrowanych typów MIME,
ich nazwy zapisywane są w postaci typgłówny/podtyp; niezarejestrowane typy mają natomiast
nazwy postaci typgłówny/x-podtyp. Domyślnym typem MIME stosowanym przez serwlety jest
text/plain, lecz zazwyczaj serwlety jawnie definiują typ text/html. Oczywiście, moŜna takŜe
uŜywać innych typów MIME. Przykład z podrozdziału 7.5, pt.: „Wykorzystanie serwletów do
generacji obrazów GIF” przedstawia serwlet, który na podstawie podanych informacji generuje
obrazy GIF wykorzystując przy tym typ MIME image/gif; natomiast w podrozdziale 11.2, pt.:
119 Rozdział 7. Generacja odpowiedzi: Nagłówki odpowiedzi HTTP
„Atrybut contentType”, przedstawię serwlet oraz strony JSP, które dzięki wykorzystaniu typu
MIME application/vnd.ms-excel generują arkusz kalkulacyjny programu Microsoft Excel.
Tabela 7.1 przedstawia typy MIME, najczęściej stosowane w serwletach.
Wiele najczęściej stosowanych typów MIME zostało podanych w plikach RFC 1521 oraz
1522 (moŜesz je znaleźć na witrynie http://www.rfc-editor.org/). Jedna cały czas rejestrowane są
nowe typy MIME; dlatego w tym przypadku lepiej jest korzystać z listy typów generowanej
dynamicznie. Listę oficjalnie zarejestrowanych typów moŜna znaleźć pod adresem
http://www.isi.edu/in-notes/iana/assignments/media-types/media-types. Informacje o najczęściej
stosowanych, niezarejestrowanych typach MIME moŜna natomiast znaleźć pod adresem
http://www.ltsw.se/knbase/internet/mime.htp.
120
Date
Ten nagłówek określa aktualną datę zapisaną w formacie GMT. Jeśli chcesz określić tę datę
z poziomu serwletu, powinieneś posłuŜyć się metodą setDateHeader. Metoda ta oszczędza
problemów związanych z zapisaniem daty w łańcuchu znaków i jego odpowiednim
sformatowaniem — w przypadku wykorzystania wywołania response.setHeader("Date", "...")
czynności te musiałbyś wykonać samodzielnie. Niemniej jednak większość serwerów
automatycznie generuje ten nagłówek, a zatem w serwletach nie będziesz musiał tego robić.
ETag
Ten nowy nagłówek wprowadzony w protokole HTTP 1.1 nadaje zwracanym dokumentom
nazwy, których klient będzie mógł później uŜyć do odwołania się do danego dokumentu (na
przykład, przy wykorzystaniu nagłówka Ŝądania If-Match).
Expires
Ten nagłówek określa kiedy przesłany dokument ma być uznany za przestarzały i w
związku z tym usunięty z pamięci podręcznej. W serwletach moŜna uŜywać tego nagłówka przy
przesyłaniu dokumentów których zawartość zmienia się relatywnie często; w ten sposób moŜna
zapobiec wyświetlaniu przestarzałych dokumentów pobieranych z pamięci podręcznej przeglądarki.
Na przykład, poniŜszy fragment kodu informuje przeglądarkę, iŜ dokument nie ma być
przechowywany w pamięci podręcznej dłuŜej niŜ 10 minut:
long aktualnyCzaas = System.currentTime();
long dziesiecMinut = 10 * 60 * 1000; // wyraŜony w milisekundach
response.setDateHeader("Expires", aktualnyCzas + dziesiecMinut);
Last-Modified
Ten bardzo przydatny nagłówek określa datę i czas ostatniej modyfikacji dokumentu. Klient
moŜe zapisać dokument w pamięci podręcznej i w późniejszych Ŝądaniach przekazywać tę datę na
serwer w nagłówku If-Modified-Since. śądanie takie jest traktowane jako warunkowe Ŝądanie GET
— wykonanie takiego Ŝądania spowoduje zwrócenie dokumentu wyłącznie wtedy, gdy data podana
w nagłówku Last-Modified jest późniejsza od daty podanej w nagłówku If-Modified-Since. Jeśli
data podana w nagłówku Last-Modified nie jest późniejsza od daty podanej w nagłówku If-
Modified-Since, to serwer zwraca kod statusu 304 (Not Modified), a program powinien wyświetlić
dokument przechowywany w pamięci podręcznej. Jeśli chcesz samemu wygenerować ten
nagłówek, to uŜyj w tym celu metody setDateHeader, która zaoszczędzi Ci problemów związanych
z zapisywaniem daty w formacie GMT. W przewaŜającej większości sytuacji wystarczy jednak
zaimplementować metodę getLastModified i pozwolić, aby Ŝądania zawierające nagłówek If-
Modified-Since były obsługiwane przez standardową metodę service. Przykład praktycznego
wykorzystania tego nagłówka został przedstawiony w podrozdziale 2.8, pt.: „Przykład
wykorzystania inicjalizacji serwletu i daty modyfikacji strony”.
Location
Nagłówek Location informuje przeglądarkę o nowym adresie Ŝądanego dokumentu;
powinien on być umieszczany we wszystkich odpowiedziach zawierających kod statusu z zakresu
300 do 399. W wyniku otrzymania takiej odpowiedzi przeglądarka automatycznie nawiązuje
połączenie i pobiera nowy dokument. Nagłówek Location jest zazwyczaj określany w sposób
niejawny wraz z kodem statusu 302; słuŜy do tego metoda sendRedirect interfejsu
121 Rozdział 7. Generacja odpowiedzi: Nagłówki odpowiedzi HTTP
HttpServletResponse. Przykład zastosowania tego nagłówka odpowiedzi przedstawiłem w
podrozdziale 6.3, pt.: „Interfejs uŜytkownika obsługujący róŜne serwisy wyszukiwawcze”.
Pragma
Podanie nagłówka Pragma o wartości no-cache informuje programy korzystające z protokołu
HTTP 1.0, iŜ nie naleŜy zapisywać dokumentu w pamięci podręcznej. Jednak przeglądarki
korzystające z protokołu HTTP 1.0 nie obsługiwały tego nagłówka w spójny sposób. W protokole
HTTP 1.1, znacznie pewniejszą alternatywą dla tego nagłówka jest nagłówek Cache-Control z
wartością no-cache.
Refresh
Ten nagłówek określa po ilu sekundach przeglądarka powinna zaŜądać aktualizacji strony.
Na przykład, aby poinformować przeglądarkę, by zaŜądała aktualizacji dokumentu po 30 sekundach
moŜesz uŜyć poniŜszego wywołania:
response.setIntHeader("Refresh", 30);
Zwróć uwagę, iŜ nagłówek ten nie oznacza wcale cyklicznego odświeŜania strony —
informuje on jedynie kiedy powinna nastąpić następna aktualizacja. Jeśli chcesz odświeŜać stronę
w sposób cykliczny, będziesz musiał umieszczać nagłówek Refresh we wszystkich kolejnych
odpowiedziach. Aby przerwać odświeŜanie strony naleŜy wygenerować odpowiedź o kodzie statusu
204 (No content — brak zawartości). Przykład zastosowania tego nagłówka odpowiedzi znajdziesz
w podrozdziale 7.3, pt.: „Trwałe przechowywanie stanu serwletu i automatyczne odświeŜanie
stron”.
Przeglądarka moŜe nie tylko odświeŜać aktualnie wyświetloną stronę, lecz takŜe pobrać
stronę o podanym adresie. W tym celu, po liczbie określającej ilość sekund naleŜy umieścić średnik
oraz adres URL dokumentu, który ma zostać wyświetlony. Na przykład, aby poinstruować
przeglądarkę, iŜ po 5 sekundach naleŜy pobrać dokument http://host/sciezka, naleŜy posłuŜyć się
wywołaniem o postaci:
response.setHeader("Refresh", "5; URL=http://host/sciezka");
Retry-After
Nagłówek ten moŜna stosować wraz z kodem statusu 503 (Service Unavailable — usługa
niedostępna), aby poinformować przeglądarkę kiedy moŜe ponowić Ŝądanie.
122
Server
Ten nagłówek określa uŜywany serwer WWW. Serwlety zazwyczaj nie określają jego
wartości — robi to sam serwer WWW.
Set-Cookie
Nagłówek Set-Cookie określa cookie skojarzone z daną stroną. KaŜde cookie wymaga
osobnego nagłówka Set-Cookie. W serwletach nie powinno się stosować wywołania
response.setHeader("Set-Cookie", "..."), lecz zamiast niego naleŜy uŜywać metody addCookie
interfejsu HttpServletResponse. Więcej informacji na temat cookies podam w rozdziale 8, pt.:
„Obsługa cookies”. Początkowo nagłówek ten stanowił rozszerzenie protokołu HTTP wprowadzone
przez firmę Netscape, aktualnie jednak jest on powszechnie wykorzystywane, w tym takŜe przez
przeglądarki Netscape Navigator oraz Internet Explorer.
Trailer
Ten rzadko stosowany nagłówek odpowiedzi został wprowadzony w protokole HTTP 1.1.
Określa on pola nagłówka umieszczane w stopce wiadomości, które są przesyłane przy
wykorzystaniu kodowania typu „chunked”10. Szczegółowe informacje na ten temat znajdziesz w
punkcie 3.6 specyfikacji protokołu HTTP 1.1 (plik RFC 2616). Pamiętasz zapewne, Ŝe pliki RFC
moŜna znaleźć na witrynie http://www.rfc-editor.org/.
Transfer-Encoding
Podanie tego nagłówka z wartością chunked informuje, Ŝe do przesłania dokumentu zostało
wykorzystanie kodowanie typu „chunked”. Szczegółowe informacje na ten temat znajdziesz w
punkcie 3.6 specyfikacji protokołu HTTP 1.1.
Upgrade
Ten nagłówek odpowiedzi stosowany jest gdy wcześniej klient nadesłał nagłówek Ŝądania
Upgrade, prosząc serwer, aby ten zaczął korzystać z jednego spośród kilku nowych, dostępnych
protokołów. Jeśli serwer zgodzi się na zmianę protokołu, przesyła odpowiedź zawierającą kod
statusu 101 (Switching Protocols) oraz nagłówek odpowiedzi Upgrade określający jaki protokół
zostanie wykorzystany. Zazwyczaj to negocjowanie protokołów jest obsługiwane przez sam serwer,
a nie przez serwlet.
Vary
Ten rzadko stosowany, nowy nagłówek protokołu HTTP 1.1 informuje klienta jakich
nagłówków moŜna uŜyć w celu określenia czy przesłany w odpowiedzi dokument moŜe być
przechowywany w pamięci podręcznej.
Via
Nagłówek Via jest uŜywany przez bramy oraz serwery pośredniczące w celu podania
serwerów przez jakie przeszło Ŝądanie. Nagłówek ten został wprowadzony w protokole HTTP 1.1.
Warning
Ten nowy i rzadko stosowany ogólny nagłówek odpowiedzi ostrzega klienty przed błędami
związanymi z przechowywaniem dokumentu w pamięci podręcznej lub jego kodowaniem.
10
przyp. tłum. Przy tym sposobie kodowania przesyłanego dokumentu, jego zawartość jest dzielona na niewielkie
fragmenty (ang.: „chunk”), o określonej wielkości.
123 Rozdział 7. Generacja odpowiedzi: Nagłówki odpowiedzi HTTP
WWW-Authenticate
Ten nagłówek jest zawsze dołączany do odpowiedzi zawierających kod statusu 401
(Unauthorized). Przekazuje on przeglądarce informacje o tym, jaki typ autoryzacji oraz obszar
naleŜy podać w nagłówku Authorization. Bardzo często serwlety nie obsługują autoryzacji
uŜytkowników samodzielnie, lecz pozwalają, aby dostęp do stron chronionych hasłem był
obsługiwany przez odpowiednie mechanizmy serwera WWW (na przykład pliki .htaccess).
Przykład serwletu wykorzystującego te nagłówki przedstawiłem w podrozdziale 4.5, pt.:
„Ograniczanie dostępu do stron WWW”.
standardowy sposób, czyli przy uŜyciu prostego formularza HTML. Kod tego formularza został
przedstawiony na listingu 7.2, a jego wygląd pokazałem na rysunku 7.1. Następnie, przy uŜyciu
metody Integer.parseInt, wartości tych parametrów są konwertowane do postaci liczb
całkowitych (patrz listing 7.5). Te wartości są następnie wykorzystywane w metodzie
findPrimeList do przeszukiwania wektora (obiektu Vector) wyników zakończonych oraz
trwających obliczeń i sprawdzenia czy są dostępne jakieś wyniki odpowiadające podanym
parametrom. Jeśli takie wyniki są dostępne to zostaje uŜyta obliczona wcześniej wartość (obiekt
PrimeList); w przeciwnym przypadku tworzony jest nowy obiekt PrimeList, który następnie zostaje
zapisany w wektorze (obiekcie Vector) aktualnie realizowanych obliczeń; obiekt ten
prawdopodobnie zastąpi najstarszy element tego wektora. Kolejnym krokiem jest sprawdzenie czy
obiekty PrimeList zakończył obliczanie liczb pierwszych, które będą w nim przechowywane. Jeśli
obliczenia nie zostały jeszcze zakończone, to do przeglądarki jest przesyłana odpowiedź
zawierająca nagłówek Refresh, która informuje, Ŝe zaktualizowane wyniki naleŜy pobrać po pięciu
sekundach. Jeśli tylko są dostępne jakieś wyniki, to są one wyświetlane na stronie w formie listy
wypunktowanej.
Listingi 7.3 (PrimeList.java) oraz 7.4 (Primes.java) przedstawiają pomocnicze klasy
wykorzystywane w serwlecie. Kod umieszczony w pliku PrimeList.java obsługuje działający w tle
wątek, słuŜący do generacji listy liczb pierwszych dla konkretnej pary parametrów. Plik Prime.java
zawiera algorytm niskiego poziomu słuŜący do wyboru liczby losowej o określonej długości i
odszukania liczb pierwszych większych od tej wartości. W obliczeniach wykorzystywane są
wbudowane metody klasy BigInteger; uŜyty algorytm określający czy dana liczba jest liczbą
pierwszą jest algorytmem probabilistycznym i dlatego istnieje szansa popełnienia błędu. Istnieje
jednak moŜliwość określenia prawdopodobieństwa popełnienia tego błędu, a w przedstawionym
przykładzie uŜywam w tym celu wartości 100. Jeśli załoŜymy, Ŝe algorytm uŜyty w przewaŜającej
większości implementacji języka Java jest testem Millera-Rabina, to prawdopodobieństwo błędnego
określenia czy liczba złoŜona jest liczbą pierwszą, wynosi 2100. Wartość ta jest bez wątpienia
mniejsza od prawdopodobieństwa wystąpienia błędu sprzętowego lub losowego zdarzenia
powodującego zwrócenie przez algorytm deterministyczny błędnej odpowiedzi. A zatem uŜyty w
przykładzie algorytm moŜna uznać za deterministyczny.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
}
}
}
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Odnajdywanie duŜych liczb pierwszych</H2>
<BR><BR>
<CENTER>
<FORM ACTION="/servlet/coreservlets.PrimeNumbers">
<B>Ilość liczb pierwszych jakie naleŜy obliczyć:</B>
<INPUT TYPE="TEXT" NAME="numPrimes" VALUE=25 SIZE=4><BR>
<B>Ilość cyfr w kaŜdej z tych liczb:</B>
<INPUT TYPE="TEXT" NAME="numDigits" VALUE=150 SIZE=3><BR>
<INPUT TYPE="SUBMIT" VALUE="Rozpocznij obliczenia">
</FORM>
</CENTER>
</BODY>
</HTML>
import java.util.*;
import java.math.BigInteger;
import java.math.BigInteger;
import javax.servlet.*;
import javax.servlet.http.*;
}
129 Rozdział 7. Generacja odpowiedzi: Nagłówki odpowiedzi HTTP
Rysunek 7.2 Częściowe wyniki zwrócone przez serwlet PrimeNumbers. Wyniki takie
mogą zostać wyświetlone gdy przeglądarka automatycznie odświeŜy stronę, lub gdy inna
przeglądarka niezaleŜnie poda parametry odpowiadające parametrom aktualnie obsługiwanych
Ŝądań lub Ŝądań których obsługa została niedawno zakończona. W obu przypadkach przeglądarka
automatycznie odświeŜy stronę, aby uzyskać zaktualizowane wyniki.
130
Rysunek 7.3 Końcowe wyniki zwrócone przez serwlet PrimeNumbers. Wyniki takie mogą
zostać wyświetlone gdy przeglądarka automatycznie odświeŜy stronę, lub gdy inna przeglądarka
niezaleŜnie poda parametry odpowiadające parametrom aktualnie obsługiwanych Ŝądań lub Ŝądań
których obsługa została niedawno zakończona. Po wyświetleniu końcowej wersji wyników
przeglądarka nie będzie juŜ odświeŜać strony.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
}
out.println("</BODY></HTML>");
if (usePersistence) {
response.setContentLength(byteStream.size());
}
byteStream.writeTo(response.getOutputStream());
}
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
PowyŜej przedstawiłem dwie, ogólne czynności jakie naleŜy wykonać, aby wygenerować z
serwletu zawartość inną niŜ kod HTML. Teraz przyjrzyjmy się bliŜej czynnościom jakie trzeba
wykonać aby wygenerować obraz GIF; oto one:
1. Stwórz obraz (obiekt Image).
Obiekt Image tworzy się poprzez wywołanie metody createImage klasy Component.
Programy wykonywane na serwerze nie powinne wyświetlać na ekranie Ŝadnych okien, a
134
zatem muszą one jawnie nakazać systemowi stworzenie obiektu rodzimego okna systemu
graficznego (proces ten jest zazwyczaj wykonywany automatycznie gdy okno jest
wyświetlane). Zadanie to wykonywane jest dzięki wywołaniu metody addNotify. A zatem,
poniŜej przedstawiłem proces tworzenia w serwlecie obiektu Image:
Frame f = new Frame();
f.addNotify();
int width = ...; // szerokość
int height = ...; // wysokość
Image img = f.createImage(width, height);
Na listingach 7.8 oraz 7.9 przedstawiłem serwlet który pobiera trzy parametry — message
(komunikat), fontName (nazwa czcionki) oraz fontSize (wielkość czcionki) — i na ich podstawie
generuje obraz GIF przestawiający komunikat wyświetlony określoną czcionką o określonej
wielkości liter oraz cień tego komunikatu, wyświetlony szarą, pochyloną czcionką. Serwlet ten
wykorzystuje kilka narzędzi dostępnych wyłącznie w platformie Java 2. Po pierwsze, daje on
moŜliwość uŜycia dowolnych czcionek zainstalowanych na serwerze, a nie tylko standardowych
czcionek dostępnych dla programów pisanych w JDK 1.1 (o nazwach Serif, SansSerif,
Monospaced, Dialog oraz DilogInput).
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.awt.*;
import java.awt.*;
import java.awt.geom.*;
import java.io.*;
import Acme.JPM.Encoders.GifEncoder;
<BODY BGCOLOR="#FDF5E6">
<H1 ALIGN="CENTER">Servis generacji obrazów GIF</H1>
Witamy w <I>bezpłatnej</I> testowej wersji naszego serwisu generacji
obrazów GIF. Podaj treść wiadomości, nazwę czcionki oraz wielkość
czcionki. Po przesłaniu formularza, w przeglądarce pojawi się
obraz zawierający podany komunikat wyświetlony
czcionką o określonej wielkości i kroju, oraz "cień" tego komunikatu.
Kiedy uzyskasz obraz który będzie Ci się podobał, zapisz go na
dysku - w tym celu kliknij go lub wciśnij klawisz SHIFT i kliknij.
<P>
Serwer aktualnie działa w systemie Windows, a zatem moŜna
uŜywać standardowych nazw czcionek dostępnych w języku Java
(na przykład: Serif, SansSerif lub Monospaced) bądź nazw
czcionek stosowanych w systemie Windows (na przykład: Arial
Black). Podanie nazwy nieznanej czcionki spowoduje wyświetlenie
komunikatu przy uŜyciu czcionki Serif.
<FORM ACTION="/servlet/coreservlets.ShadowedText">
<CENTER>
Treść komunikatu:
<INPUT TYPE="TEXT" NAME="message"><BR>
Nazwa czcionki:
<INPUT TYPE="TEXT" NAME="fontName" VALUE="Serif"><BR>
Wielkość czcionki:
<INPUT TYPE="TEXT" NAME="fontSize" VALUE="90"><BR><BR>
<Input TYPE="SUBMIT" VALUE="Generuj obraz GIF">
</CENTER>
</FORM>
</BODY>
</HTML>
import java.awt.*;
import javax.swing.*;
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.*;
Dostosowywanie witryny
Wiele „portali” pozwala uŜytkownikom na określanie wyglądu strony głównej. MoŜe to
dotyczyć wyboru prognozy pogody, którą uŜytkownik chce oglądać, wskazania akcji i wyników
sportowych interesujących uŜytkownika, i tak dalej. Określanie wybranej zawartości strony podczas
kaŜdej wizyty na witrynie nie byłoby rozwiązaniem wygodnym dla uŜytkownika, i właśnie dlatego
to witryna zapamiętuje wybrane ustawienia, wykorzystując do tego celu cookies. W prostych
przypadkach, dostosowanie zawartości moŜna zrealizować zapisując ustawienia bezpośrednio w
cookie. Przykład takiego rozwiązania przedstawię w podrozdziale 8.6. Jednak w bardziej złoŜonych
sytuacjach, witryna przesyła do przeglądarki jedynie unikalny identyfikator, natomiast preferencje
uŜytkownika skojarzone z tym identyfikatorem są zapisywane w bazie danych działającej na
serwerze.
Dobór reklam
Większość witryn prezentujących reklamy pobiera znacznie wyŜsze opłaty za wyświetlanie
reklam „dobieranych” niŜ „losowych”. Reklamodawcy zazwyczaj wolą zapłacić znacznie więcej za
to, by ich reklamy trafiły do osób, których zainteresowania (dotyczące ogólnych kategorii
produktów) są znane. Na przykład jeśli korzystasz z serwisu wyszukiwawczego i podasz w nim
hasło „Java Servlets” to witryna obciąŜy reklamodawcę znacznie wyŜszymi kosztami w przypadku
wyświetlenia reklamy środowiska słuŜącego do tworzenia serwletów, niŜ biura podróŜy
specjalizującego się w wycieczkach do Indonezji. Jednak z drugiej strony, jeśli poszukiwane hasło
będzie miało postać „Java Hotele”, to sytuacja będzie dokładnie odwrotna. Bez wykorzystania
cookies, w momencie wejścia na witrynę (czyli w chwili gdy jeszcze nie zostało wykonane Ŝadne
wyszukiwanie) konieczne jest wyświetlenie losowej reklamy, podobnie jak w przypadku
poszukiwania hasła, którego nie da się powiązać z Ŝadną kategorią reklam. Cookies pozwalają
jednak zapamiętać potrzebne dane — „Oho, ten uŜytkownik poprzednio poszukiwał takich
informacji” — i wyświetlić specjalnie dobraną (czyli drogą) a nie losową (tanią) reklamę.
Tworzenie cookies
Cookies tworzy się wywołując konstruktor klasy Cookie. Konstruktor ten wymaga podania
dwóch argumentów —łańcuchów znaków określających odpowiednio nazwę oraz wartość
tworzonego cookie. Ani nazwa, ani wartość cookie nie moŜe zawierać odstępów ani poniŜszych
znaków:
[ ] ( ) = , " / ? @ : ;
Atrybuty cookies
Zanim dodasz cookie do nagłówków odpowiedzi, moŜesz podać jego róŜne cechy. SłuŜą do
tego metody setXxx, gdzie Xxx to nazwa atrybutu którego wartość chcesz określić. KaŜdej z metod
setXxx odpowiada metoda getXxx słuŜąca do pobierania wartości odpowiedniego atrybutu. Za
wyjątkiem nazwy i wartości, wszystkie atrybuty dotyczą wyłącznie cookies przekazywanych z
serwera do przeglądarki; atrybuty te nie są określane dla cookies przesyłanych w przeciwnym
kierunku. Skróconą wersję przedstawionych poniŜej informacji znajdziesz takŜe w dodatku A —
„Krótki przewodnik po serwletach i JSP”.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
TakŜe metoda getCookie przegląda tablicę w poszukiwaniu cookie o podanej nazwie; jednak
zwraca cały obiekt Cookie, a nie samą wartość. Metoda ta jest wykorzystywana w sytuacjach gdy
chcesz wykonać jakieś czynności na cookie, a jego wartość Cię chwilowo nie interesuje.
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
Cookies
Cookies moŜna wykorzystać do przechowywania informacji o sesji w internetowym sklepie,
a kaŜde kolejne połączenie moŜe powodować odszukanie bieŜącej sesji i pobranie informacji o niej,
przechowywanych na serwerze. Na przykład, serwlet mógłby wykonywać następujące czynności:
String idSesji = utworzUnikalnyIdentyfikatorString();
Hashtable infoSesji = new Hashtable();
Hashtable tablicaGlobalna = odszukajTabliceZDanymiSesji();
tablicaGlobalna.put(idSesji, infoSesji);
Cookie cookieSesji = new Cookie("SESSIONID", idSesji);
sessionCookie.setPath("/");
response.addCookie(cookieSesji);
Dzięki temu, podczas obsługi kolejnych Ŝądań, na podstawie wartości cookie "SESSIONID"
serwer będzie mógł pobrać z tablicy tablicaGlobalna tablicę asocjacyjną infoSesji zawierającą
informacje o konkretnej sesji. To doskonałe rozwiązanie, które jest bardzo często wykorzystywane
przy obsłudze sesji. Niemniej jednak wciąŜ warto by mieć do dyspozycji narzędzia programistyczne
wyŜszego poziomu, które obsługiwałyby wszystkie szczegóły. Choć serwlety dysponują łatwymi w
obsłudze narzędziami wysokiego poziomu przeznaczonymi do obsługi cookies (opisałem je w
rozdziale 8), to wciąŜ jednak jest stosunkowo duŜo drobnych czynności które naleŜy wykonać
samodzielnie. Do czynności tych moŜna zaliczyć:
157 Rozdział 9. Śledzenie sesji
• pobranie cookie przechowującego identyfikator sesji (w końcu, cookies nadesłanych z
przeglądarki moŜe być kilka),
• określenie odpowiedniego czasu wygaśnięcia waŜności cookie (sesje, które są nieaktywne
przez ponad 24 godziny, prawdopodobnie powinne zostać zamknięte),
• skojarzenie tablic asocjacyjnych z kaŜdym z Ŝądań,
• generację unikalnego identyfikatora sesji.
Poza tym, ze względu na doskonale znane i oczywiste zagroŜenia prywatności jakie wiąŜą
się z wykorzystaniem cookies (patrz podrozdział 8.2), niektóre osoby wyłączają ich obsługę. A
zatem, oprócz protokołu wysokiego poziomu, warto by mieć jakieś alternatywne rozwiązanie.
Jeśli interesuje Cię czy sesja istniała juŜ wcześniej czy teŜ została właśnie utworzona, to
moŜesz to sprawdzić za pomocą metody isNew.
W większości przypadków będziesz znał nazwę atrybutu, a Twoim celem będzie pobranie
wartości skojarzonej z tą nazwą. Istnieje takŜe moŜliwość pobrania nazw wszystkich atrybutów
skojarzonych z daną sesją — słuŜy do tego metoda getValueNames zwracająca tablicę łańcuchów
znaków. Ta metoda jest jedynym sposobem określenia nazw atrybutów w przypadku korzystania z
mechanizmów obsługi serwletów zgodnych ze specyfikacją Java Servlet 2.1. W przypadku
mechanizmów zgodnych ze specyfikacją 2.2 nazwy atrybutów moŜna pobrać przy uŜyciu metody
getAttributeNames. Metoda ta działa w bardziej spójny sposób, gdyŜ zwraca obiekt Enumeration,
podobnie jak metody getHeaderNames oraz getParameterNames interfejsu HttpServletRequest.
Choć bez wątpienia najbardziej będą Cię interesowały dane bezpośrednio skojarzone z sesją,
to jednak dostępne są takŜe inne, interesujące informacje dotyczące sesji, które czasami mogą być
uŜyteczne. PoniŜej przedstawiłem metody interfejsu HttpSession:
Zakańczanie sesji
Sesja automatycznie stanie się nieaktywna, jeśli odstęp pomiędzy kolejnymi Ŝądaniami
przekroczy czas określony przez metodę getMaxInactiveInterval. Gdy to nastąpi, automatycznie
zostaną usunięte wszelkie skojarzenia obiektów z tą sesją. To z kolei sprawi, Ŝe obiekty
implementujące interfejs HttpSessionBindingListener zostaną zawiadomione o fakcie ich usunięcia
z sesji.
Wcale nie musisz czekać, aŜ zostanie przekroczony limit czasu oczekiwania sesji, moŜesz
jawnie zaŜądać dezaktywacji sesji — słuŜy do tego metoda invalidate.
Drugim miejscem, w którym moŜna podawać adresy URL odwołujące się do tej samej
witryny jest wywołanie metody sendRedirect (oznacza to, Ŝe adresy te zostaną umieszczone w
nagłówku odpowiedzi Location). W tym przypadku obowiązują inne metody określania czy do
adresu naleŜy dodać informacje o sesji czy nie, a zatem nie moŜna posłuŜyć się metodą encodeURL.
Na szczęście interfejs HttpServletResponse udostępnia metodę encodeRedirectURL, której moŜna
uŜyć właśnie w takich sytuacjach. Oto przykład zastosowania tej metody:
String oryginalnyURL = jakisURL; // specyfikacja 2.2 pozwala na uŜywanie
// względnych adresów URL
String zakodowanyURL = response.encodeRedirectURL(oryginalnyURL);
response.sendRedirect(zakodowanyURL);
Często się zdarza, Ŝe tworząc serwlet nie jesteś w stanie przewidzieć czy w przyszłości
stanie się on częścią grupy strony wykorzystujących śledzenie sesji, warto przewidzieć to zawczasu
i kodować w serwlecie wszystkie adresy URL odwołujące się do tej samej witryny.
Metoda
Zaplanuj to zawczasu — wszystkie adresy URL odwołujące się do tej samej witryny przekształcaj przy uŜyciu metod
response.encodeURL lub response.encodeRedirectURL, niezaleŜnie od tego czy servlet wykorzystuje
mechanizmy śledzenia sesji czy nie.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.*;
import java.util.*;
out.println(ServletUtilities.headWithTitle(title) +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H1 ALIGN=\"CENTER\">" + heading + "</H1>\n" +
"<H2>Informacje o Twojej sesji:</H2>\n" +
"<TABLE BORDER=1 ALIGN=\"CENTER\">\n" +
"<TR BGCOLOR=\"#FFAD00\">\n" +
" <TH>Typ<TH>Wartość\n" +
"<TR>\n" +
" <TD>ID\n" +
" <TD>" + session.getId() + "\n" +
"<TR>\n" +
" <TD>Czas utworzenia\n" +
" <TD>" +
new Date(session.getCreationTime()) + "\n" +
"<TR>\n" +
" <TD>Czas ostatniego dostępu\n" +
" <TD>" +
new Date(session.getLastAccessedTime()) + "\n" +
"<TR>\n" +
" <TD>Ilość odwiedzin\n" +
" <TD>" + accessCount + "\n" +
"</TABLE>\n" +
"</BODY></HTML>");
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
Obsługa zamówień
Listing 9.5 przedstawia kod serwletu słuŜącego do obsługi zamówień nadsyłanych przez
róŜne strony katalogowe (przedstawione w poprzedniej części podrozdziału). Serwlet ten kojarzy
kaŜdego uŜytkownika z koszykiem, wykorzystując przy tym mechanizmy śledzenia sesji. PoniewaŜ
kaŜdy uŜytkownik dysponuje osobnym koszykiem, jest mało prawdopodobne, Ŝe wiele wątków
będzie jednocześnie próbowało uzyskać dostęp do tego samego koszyka. Niemniej jednak, gdybyś
popadł w paranoję, mógłbyś wyobrazić sobie kilka sytuacji, w których mógłby nastąpić
jednoczesny dostęp do tego samego koszyka. Na przykład, gdyby ten sam uŜytkownik miał
jednocześnie otworzonych kilka okien przeglądarki i niemal w tym samym czasie wysyłał
zamówienia bądź aktualizacje z kilku róŜnych okien. A zatem, aby zapewnić w miarę wysoki
poziom bezpieczeństwa, kod naszego serwletu będzie synchronizował dostęp do koszyków na
podstawie obiektu sesji. W ten sposób inne wątki korzystające z tej samej sesji nie będą mogły
równocześnie uzyskać dostępu do przechowywanych w niej informacji, choć wciąŜ będzie moŜliwa
równoczesna obsługa Ŝądań nadsyłanych przez róŜnych uŜytkowników. Typowe wyniki wykonania
tego serwletu zostały przedstawione na rysunkach 9.5 oraz 9.6.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.text.NumberFormat;
String formURL =
"/servlet/coreservlets.OrderPage";
// Adresy URL odwołujące się do stron tej samej witryny
// przekształcamy przy uŜyciu metody encodeURL.
formURL = response.encodeURL(formURL);
doGet(request, response);
}
}
import java.util.*;
public ShoppingCart() {
itemsOrdered = new Vector();
}
}
}
ItemOrder newOrder =
new ItemOrder(Catalog.getItem(itemID));
itemsOrdered.addElement(newOrder);
}
}
OstrzeŜenie
Testując strony JSP upewnij się, Ŝe Internet Explorer nie będzie wyświetlał „przyjaznych” komunikatów o błędach
HTTP.
Oprócz zwyczajnego kodu HTML, w dokumentach JSP mogą się pojawiać trzy typy
„konstrukcji” JSP — elementy skryptowe, dyrektywy oraz akcje. Elementy skryptowe pozwalają na
podawanie kodu napisanego w języku Java, który stanie się częścią wynikowego serwletu.
Dyrektywy pozwalają natomiast na określanie ogólnej struktury generowanego serwletu, a akcje —
na wskazywanie istniejących komponentów, których naleŜy uŜyć lub na inną kontrolę działania
mechanizmów obsługi JSP. Aby ułatwić tworzenie elementów skryptowych, programiści piszący
strony JSP mają dostęp do kilku predefiniowanych zmiennych (takich jak zmienna request z
przykładu przedstawionego na samym początku tego rozdziału; więcej informacji na temat tych
zmiennych znajdziesz w podrozdziale 10.5.). W dalszej części tego rozdziału omówię elementy
skryptowe JSP, natomiast zagadnienia związane z dyrektywami i akcjami przedstawię w kolejnych
rozdziałach. Skrócony opis składni JSP znajdziesz takŜe w dodatku A — „Krótki przewodnik po
serwletach i JSP”.
W niniejszej ksiąŜce omawiam wersje 1.0 oraz 1.1 specyfikacji Java Server Pages. NaleŜy
wiedzieć, Ŝe w specyfikacji JSP 1.0 wprowadzono wiele zmian, przez co w ogromny sposób róŜni
się ona od specyfikacji 0.92. Wprowadzone zmiany są korzystne, lecz ze względu na nie nowsze
strony JSP niemal w ogóle nie są zgodne z mechanizmami JSP bazującymi na specyfikacji 0.92;
podobnie zresztą jak starsze strony JSP niemal w ogóle nie są zgodne z mechanizmami JSP
bazującymi na specyfikacji 1.0. Zmiany pomiędzy specyfikacjami JSP 1.0 oraz 1.1 są juŜ znacznie
mniejsze — główną innowacją wprowadzoną w specyfikacji 1.1 są ułatwienia w definiowaniu
nowych znaczników oraz moŜliwość generacji serwletów bazujących na specyfikacji Java Servlet
2.2. Strony JSP bazujące na specyfikacji 1.1, które nie wykorzystują znaczników definiowanych
przez programistę ani jawnych odwołań do moŜliwości serwletów charakterystycznych dla
specyfikacji Java Servlet 2.2, są zgodne z JSP 1.0. Wszystkie strony JSP bazujące na technologii
1.0 są całkowicie i bez wyjątków zgodne ze specyfikacją JSP 1.1.
180
Tekst szablonu
W bardzo wielu przypadkach znaczną częścią strony JSP jest statyczny kod HTML,
określany jako tekst szablonu. Niemal zawsze tekst szablonu wygląda jak zwyczajny kod HTML,
jest tworzony zgodnie z zasadami składni HTML i jest bez Ŝadnych modyfikacji przekazywany do
przeglądarki przez serwlet utworzony w celu obsługi strony. Tekst szablonu nie tylko wygląda jak
zwyczajny kod HTML — moŜna go takŜe tworzyć za pomocą tych samych narzędzi, które są
normalnie uŜywane do tworzenia stron WWW. Na przykład, większość przykładowych stron JSP
przedstawionych w tej ksiąŜce stworzyłem w programie HomeSite firmy Allaire.
Istnieją dwa, drobne wyjątki od reguły mówiącej, Ŝe „tekst szablonu jest przekazywany do
przeglądarki bez Ŝadnych modyfikacji”. Po pierwsze, aby wygenerować łańcuch znaków <%, w
kodzie strony JSP naleŜy zapisać go w postaci <\%. I po drugie, jeśli w kodzie strony JSP chcesz
umieścić komentarze, które nie mają się pojawiać w kodzie HTML generowanej strony WWW, to
naleŜy je zapisać w następującej postaci:
<%-- komentarz w kodzie JSP --%>
Podane wyraŜenie jest obliczane, a jego wynik jest następnie konwertowany do postaci
łańcucha znaków i umieszczany w wynikowym kodzie HTML. Obliczenie wartości wyraŜenia
odbywa się w czasie wykonywania serwletu (po otrzymaniu Ŝądania), co oznacza, Ŝe moŜna w nim
wykorzystać wszystkie informacje przekazane w Ŝądaniu. Na przykład, przedstawiony poniŜej
fragment kodu wyświetla datę i czas otrzymania Ŝądania:
Aktualny czas to: <%= new java.util.Date() %>
Predefiniowane zmienne
JSP udostępnia kilka predefiniowanych zmiennych, które ułatwiają tworzenie wyraŜeń. Te
niejawne obiekty omówię bardziej szczegółowo w podrozdziale 10.5; tu jedynie przedstawię cztery
spośród nich, które mają największe znaczenie przy tworzeniu wyraŜeń:
• request — obiekt HttpServletRequest,
181 Rozdział 10. Elementy skryptowe JSP
• — obiekt HttpServletResponse,
response
• session — obiekt HttpSession, reprezentujący sesję skojarzoną z danym Ŝądaniem (chyba
Ŝe obsługa sesji została wyłączona przy uŜyciu atrybutu session dyrektywy page — patrz
podrozdział 11.4),
• out — obiekt PrintWriter (a konkretnie obiekt klasy JspWriter wyposaŜonej w
mechanizmy buforowania) słuŜący do przekazywania informacji do przeglądarki.
Oto przykład uŜycia tych zmiennych:
Nazwa Twojego komputera: <%= request.getRemoteHost() %>
Większość atrybutów wymaga, aby wartość była łańcuchem znaków zapisanym w znakach
apostrofu lub cudzysłowach (jak na powyŜszym przykładzie). Jednak w kilku przypadkach istnieje
moŜliwość wykorzystania wyraŜeń JSP, których wartości są obliczane w trakcie obsługi Ŝądania.
Jednym z nich jest atrybut value elementu jsp:setProperty. Oznacza to, Ŝe przedstawiony poniŜej
fragment kodu jest całkowicie poprawny:
<jsp:setProperty name="uzytkownik"
property="id"
value='<%= "UserID" + Math.random() %>' />
Przykład
Na listingu 10.1 przedstawiłem przykładową stronę JSP, a na rysunku 10. 1 — wyniki jej
wykonania. Zwróć uwagę, iŜ w nagłówku strony (w znaczniku <HEAD>) umieściłem znaczniki
<META> oraz dołączyłem arkusz stylów. Dołączanie tych elementów stanowi dobry zwyczaj, jednak
istnieją dwa powody dla których są one często pomijane w stronach generowanych przez zwyczajne
serwlety. Po pierwsze, stosowanie koniecznych wywołań metody println jest w serwletach
męczące. Problem ten jest znacznie prostszy w przypadku tworzenia dokumentów JSP; na przykład,
znaczniki te moŜna dołączać do dokumentu przy uŜyciu mechanizmów wielokrotnego uŜywania
kodu, udostępnianych przez uŜywany program do edycji dokumentów HTML. Po drugie, w
serwletach nie moŜna uŜywać najprostszej postaci względnych adresów URL (tych, które odwołują
się do plików przechowywanych w tym samym katalogu). Wynika to z faktu, Ŝe katalogi z
serwletami nie są tak samo kojarzone z adresami URL jak katalogi zawierające normalne
dokumenty HTML. Jednak dokumenty JSP są umieszczane na serwerze w tych samych katalogach
co strony WWW, dzięki czemu adresy URL są przetwarzane poprawnie. A zatem, arkusze stylów
oraz dokumenty JSP mogą być przechowywane w tym samym katalogu. Kod źródłowy uŜytego
arkusza stylów, jak równieŜ kody źródłowe wszystkich przykładów podanych w niniejszej ksiąŜce
moŜna znaleźć pod adresem ftp://ftp.helion.pl/przyklady/jsjsp.zip.
<BODY>
<H2>WyraŜenia JSP</H2>
<UL>
<LI>Aktualny czas: <%= new java.util.Date() %>
<LI>Nazwa komputera: <%= request.getRemoteHost() %>
<LI>Identyfikator sesji: <%= session.getId() %>
<LI>Parametr <CODE>testParam</CODE>:
<%= request.getParameter("testParam") %>
</UL>
</BODY>
</HTML>
183 Rozdział 10. Elementy skryptowe JSP
W tym konkretnym przypadku, ten sam efekt moŜna uzyskać znacznie łatwiej posługując się
wyraŜeniem JSP:
Dane przesłane Ŝądaniem GET: <%= request.getQueryData() %>
Jednak przy uŜyciu skryptletów moŜna wykonać wiele zadań, których nie moŜna
zrealizować przy uŜyciu samych wyraŜeń JSP. Dotyczy to między innymi określania nagłówków
odpowiedzi oraz kodu statusu, wywoływania efektów ubocznych (takich jak zapis informacji w
dziennikach serwera, czy teŜ aktualizacja baz danych) oraz wykonywania kodu zawierającego pętle,
wyraŜenia warunkowe lub inne instrukcje złoŜone. Na przykład, poniŜszy fragment kodu informuje,
Ŝe strona zostanie przesłana do przeglądarki jako zwyczajny tekst, a nie jako dokument HTML (to
domyślny typ informacji generowanych przez serwlety):
<% response.setContentType("text/plain"); %>
Niezwykle waŜne jest to, iŜ nagłówki odpowiedzi oraz kod statusu moŜna określać w
dowolnym miejscu strony JSP, choć pozornie narusza to zasadę, głoszącą iŜ informacje tego typu
muszą zostać wygenerowane przed przekazaniem do przeglądarki jakiejkolwiek treści
generowanego dokumentu. Takie określanie kodu statusu oraz nagłówków odpowiedzi jest moŜliwe
184
dzięki temu, Ŝe strony JSP wykorzystują specjalny typ obiektów PrintWriter (konkretnie rzecz
biorąc uŜywają obiektów JspWriter), które buforują generowany dokument przed jego przesłaniem
do przeglądarki. Istnieje jednak moŜliwość zmiany sposobu buforowania, więcej informacji na ten
temat podałem w podrozdziale 11.6 przy okazji omawiania atrybutu autoflush dyrektywy page.
Na listingu 10.2 przedstawiłem przykład kodu który jest zbyt skomplikowany, aby moŜna
go było wykonać przy wykorzystaniu wyraŜeń JSP. Przykład ten prezentuje stronę JSP, która
określa kolor tła generowanego dokumentu HTML na podstawie atrybutu bgColor. Wyniki
wykonania tej strony zostały przedstawione na rysunkach 10.2, 10.3 oraz 10.4.
<%
String bgColor = request.getParameter("bgColor");
boolean hasExplicitColor;
if (bgColor != null) {
hasExplicitColor = true;
} else {
hasExplicitColor = false;
bgColor = "WHITE";
}
%>
<BODY BGCOLOR="<%= bgColor %>">
<H2 ALIGN="CENTER">Test kolorów</H2>
<%
if (hasExplicitColor) {
out.println("Jawnie określiłeś kolor o wartości " +
bgColor + ".");
} else {
out.println("Zostnie uŜyty domyślny kolor tła - WHITE. " +
"Aby określić kolor podaj w Ŝądaniu atrybut bgColor. " +
"MoŜesz podać wartość RGB koloru (RRGGBB) lub jedną ze " +
"standardowych nazw kolorów (jeśli Twoja przeglądarka je obsługuje).");
}
%>
</BODY>
</HTML>
<% } %>
<BODY>
<H1>Deklaracje JSP</H1>
</BODY>
</HTML>
request
Ta zmienna reprezentuje obiekt HttpServletRequest skojarzony z Ŝądaniem; daje ona dostęp
do parametrów Ŝądania, informacji o jego typie (np.: GET lub POST) oraz otrzymanych nagłówkach
Ŝądania (w tym takŜe o cookies). Gwoli ścisłości naleŜy zaznaczyć, iŜ jeśli Ŝądanie zostało
przesłane protokołem innym niŜ HTTP, to zmienna request moŜe zawierać obiekt, którego typ jest
rozszerzeniem interfejsu ServletResponse lecz nie HttpServletResponse. Jednak aktualnie bardzo
niewiele serwerów obsługuje serwlety, które mogą przyjmować Ŝądania przesyłane innymi
protokołami niŜ HTTP.
188
response
Ta zmienna reprezentuje obiekt HttpServletResponse skojarzony z odpowiedzią, która
zostanie przesłana do przeglądarki uŜytkownika. Zwróć uwagę, iŜ strumień wyjściowy (patrz
zmienna out) jest zazwyczaj buforowany, dzięki czemu w stronach JSP moŜna podawać nagłówki
odpowiedzi oraz kod statusu, choć w serwletach, po wygenerowaniu dowolnego fragmentu
dokumentu, nie moŜna było tego robić.
out
Predefiniowana zmienna out to obiekt PrintWriter, uŜywany do przesyłania wyników do
przeglądarki. Aby obiekt response zachował przydatność, to zmienna out jest w rzeczywistości
obiekt klasy JspWriter będącej zmodyfikowaną wersją klasy PrintWriter wyposaŜoną w
moŜliwości buforowania. Wielkość buforu moŜna określać za pomocą atrybutu buffer dyrektywy
page (patrz podrozdział 11.5). Zwróć takŜe uwagę, iŜ zmienna ta jest niemal wyłącznie uŜywana w
skryptletach, gdyŜ wartości wyraŜeń JSP są automatycznie umieszczane w strumieniu wyjściowym
(przez co nie trzeba ich jawnie generować przy uŜyciu zmiennej out).
session
Ta zmienna zawiera obiekt HttpSession skojarzony z daną sesją. Przypominasz sobie
zapewne, Ŝe sesję są tworzone automatycznie, a zatem zmienna ta jest kojarzona z obiektem nawet
jeśli w nadesłanym Ŝądaniu nie ma Ŝadnego odwołania do sesji. Jedynym wyjątkiem są sytuacje,
gdy obsługa sesji zostanie wyłączona przy uŜyciu atrybutu session dyrektywy page (patrz
podrozdział 11.4). W takim przypadku odwołanie do zmiennej session spowoduje powstanie
błędów podczas procesu translacji strony JSP do postaci serwletu.
application
Ta zmienna zawiera obiekt ServletContext, który moŜna takŜe uzyskać za pomocą
wywołania metody getServletConfig().getContext(). W tym obiekcie serwlety oraz strony JSP
mogą przechowywać trwałe informacje, zamiast uŜywać do tego celu zmiennych instancyjnych.
Interfejs ServletContext udostępnia metody setAttribute oraz getAttribute, które pozwalają na
zachowanie i pobranie dowolnych danych skojarzonych z podanymi kluczami. RóŜnica pomiędzy
przechowywanie danych w obiekcie ServletContext a w zmiennych instancyjnych polega na tym,
iŜ obiekt ten jest wspólnie wykorzystywany przez wszystkie serwlety działające w danym
mechanizmie obsługi serwletów (lub w danej aplikacji WWW, jeśli uŜywany serwer dysponuję taką
moŜliwością). Więcej informacji na temat interfejsu ServletContext znajdziesz w podrozdziale
13.4. (pt.: „Wspólne wykorzystywanie komponentów”) oraz w rozdziale 15. — „Integracja
serwletów i JSP”.
config
Ta zmienna zawiera obiekt ServletConfig dla danej strony.
pageContext
W JSP została wprowadzona nowa klasa o nazwie PageContext, której celem jest udzielenie
dostępu do wielu atrybutów strony oraz stworzenie wygodnego miejsca do przechowywania
wspólnie wykorzystywanych informacji. Zmienna pageContext zawiera obiekt klasy PageContext
skojarzony z bieŜącą stroną. Zastosowanie tego obiektu opisałem dokładniej w podrozdziale 13.4.
— pt.: „Wspólne wykorzystywanie komponentów”.
page
189 Rozdział 10. Elementy skryptowe JSP
Ta zmienna jest synonimem słowa kluczowego this i raczej nie jest często stosowana w
języku Java. Została ona stworzona „na wszelki wypadek” gdyby serwlety i strony JSP mogły być
tworzone w jakimś innym języku programowania.
Rozdział 11.
Dyrektywa page: Strukturalizacja
generowanych serwletów
Dyrektywy JSP mają wpływ na ogólną strukturę serwletu generowanego na podstawie strony
JSP. Przedstawione poniŜej wzory pokazują dwie moŜliwe formy zapisu dyrektyw. Wartości
atrybutów moŜna takŜe zapisywać zarówno w cudzysłowach jak i apostrofach, nie moŜna jednak
pominąć otaczających je znaków (niezaleŜnie od tego czy będą to apostrofy czy cudzysłowy). Aby
umieścić cudzysłów lub apostrof wewnątrz wartości atrybutu, naleŜy poprzedzić go znakiem
odwrotnego ukośnika — \" (aby umieścić cudzysłów) oraz \' (aby umieścić apostrof).
<%@ dyrektywa atrybut="wartość" %>
<%@ dyrektywa atrybut1="wartość1"
atrybut2="wartość2"
...
atrybutN="wartośćN" %>
Przy tworzeniu stron JSP moŜna stosować trzy dyrektywy — page, include oraz taglib.
Dyrektywa page umoŜliwia kontrolę struktury serwletu, poprzez importowanie klas, modyfikowanie
klasy bazowej serwletu, określanie typu zawartości , itp. Dyrektywę tę moŜna umieścić w
dowolnym miejscu dokumentu. Zastosowanie dyrektywy page będzie tematem niniejszego
rozdziału. Druga dyrektywa JSP — include — umoŜliwia wstawianie plików do klasy serwletu na
etapie przekształcania strony JSP do postaci serwletu. Dyrektywę tę naleŜy umieszczać w tym
miejscu strony JSP, w jakim chcesz wstawić plik; sposoby jej uŜycia opisałem szczegółowo w
rozdziale 12. — „Dołączanie plików i apletów do dokumentów JSP”. W specyfikacji 1.1
technologii JSP pojawiła się trzecia dyrektywa — taglib. MoŜna jej uŜywać do definiowania
własnych znaczników. Więcej informacji na ten temat znajdziesz w rozdziale 14. — „Tworzenie
bibliotek znaczników”.
Dyrektywa page umoŜliwia zdefiniowanie następujących atrybutów — import, contentType,
isThreadSafe, session, buffer, autoflush, extends, info, errorPage, isErrorPage oraz language.
Pamiętaj, Ŝe przy podawaniu nazw tych atrybutów jest uwzględniana wielkość liter. Wszystkie
powyŜsze atrybut omówię szczegółowo w dalszych częściach rozdziału.
Atrybut import dyrektywy page jest jedynym atrybutem, który moŜe się wielokrotnie
pojawić w tym samym dokumencie. Choć dyrektywę page moŜna umieszczać w dowolnym miejscu
dokumentu, to jednak tradycyjne polecenia importu umieszcza się na samym początku dokumentu
lub bezpośrednio przed pierwszym fragmentem kodu, w jakim importowana klasa zostaje uŜyta.
Przykład
Listing 11.1 przedstawia stronę JSP wykorzystującą trzy klasy, które standardowo nie są
importowane — java.util.Date, coreservlets.ServletUtilities (patrz listing 8.3) oraz
coreservlets.LingLivedCookie (patrz listing 8.4). Aby uprościć proces odwoływania się do tych
klas, w przykładowej stronie JSP uŜyłem dyrektywy page o następującej postaci:
<%@ page import="java.util.*,coreservlets.*" %>
Typowe wyniki wykonania strony z listingu 11.1 przedstawiłem na rysunkach 11.1 oraz
11.2.
<BODY>
<H2>Atrybut import</H2>
<%-- dyrektywa page --%>
<%@ page import="java.util.*,coreservlets.*" %>
</BODY>
</HTML>
193 Rozdział 11. Dyrektywa page: Strukturalizacja generowanych serwletów
<H2>Atrybut contentType</H2>
<%@ page contentType="text/plain" %>
Strona powinna zostać wyświetlona jako zwyczajny tekst,
a <B>nie</B> jako dokument HTML.
</BODY>
</HTML>
Rysunek 11.3 Zwyczajny dokument tekstowy, Netscape nie musi interpretować znaczników
HTML
195 Rozdział 11. Dyrektywa page: Strukturalizacja generowanych serwletów
Rysunek 11.6 Wyniki odwołania się do strony Excel.jsp, na komputerze, na którym jest
zainstalowany program Microsoft Excel
<BODY>
<CENTER>
<H2>Porównanie sprzedaŜy jabłek i pomarańczy</H2>
<%
String format = request.getParameter("format");
if ((format != null) && (format.equals("excel"))) {
response.setContentType("application/vnd.ms-excel");
}
%>
<TABLE BORDER=1>
<TR><TH></TH><TH>Jabłka<TH>Pomarańcze
<TR><TH>Pierwszy kwartał<TD>2307<TD>4706
<TR><TH>Drugi kwartał<TD>2982<TD>5104
<TR><TH>Trzeci kwartał<TD>3011<TD>5220
<TR><TH>Czwarty kwartał<TD>3055<TD>5287
</TABLE>
</CENTER>
</BODY>
</HTML>
198
W tym przypadku, kod operujący na zmiennej idNum powinien zostać umieszczony w bloku
synchronized,którego składnię przedstawiłem poniŜej:
synchronized(jakiśObiekt) { ... }
Oznacza to, Ŝe jeśli jakiś wątek rozpocznie wykonywanie kodu umieszczonego wewnątrz
bloku, to Ŝaden inny wątek nie będzie mógł rozpocząć wykonywania tego samego bloku (ani
Ŝadnego innego bloku zdefiniowanego przy uŜyciu tego samego obiektu) aŜ do momentu, gdy
pierwszy wątek zakończy wykonywanie bloku. A zatem, przedstawiony powyŜej fragment kodu
powinien zostać zapisany w następujący sposób:
<%! private int idNum = 0; %>
<%
synchronized(this) {
String userID = "userID" + idNum;
out.println("Twój identyfikator to: " + userID + "." );
idNum = idNum + 1;
}
%>
Taki jest standardowy sposób działania serwletów — wiele jednocześnie odebranych Ŝądań,
jest obsługiwanych przez wiele wątków, które współbieŜnie korzystają z jednej kopii serwletu.
Jednak, jeśli serwlet implementuje interfejs SingleThreadModel, to system gwarantuje, Ŝe nie będzie
współbieŜnych odwołań do tej samej kopii serwletu. System moŜe spełnić to załoŜenie na dwa
sposoby. Pierwszy z nich polega na kolejkowaniu Ŝądań i kolejnym przekazywaniu ich do tej samej
kopii serwletu. Drugi sposób bazuje na stworzeniu grupy kopii danego serwletu, przy czym kaŜda z
nich w danej chwili obsługuje tylko jedno Ŝądanie.
Dyrektywa <%@ page isThreadSafe="false" %> oznacza, Ŝe kod strony nie jest bezpieczny z
punktu widzenia działania wielowątkowego i dlatego wynikowy serwlet powinien implementować
interfejs SingleThreadModel. (Więcej informacji na temat interfejsu SingleThreadModel znajdziesz
w podrozdziale 2.6. — „Cykl Ŝyciowy serwletów”). Domyślnie, atrybut isThreadSafe dyrektywy
page ma wartość true. Oznacza to, Ŝe system zakłada, iŜ programista napisał kod w sposób
bezpieczny z punktu widzenia działania wielowątkowego, dzięki czemu moŜna wykorzystać
bardziej efektywny sposób obsługi Ŝądań, polegający na tworzeniu wielu wątków jednocześnie
odwołujących się do jednego egzemplarza serwletu. Jeśli w serwlecie są wykorzystywane zmienne
instancyjne słuŜące do przechowywania trwałych informacji, to dyrektywy <%@ page
isThreadSafe="false" %> naleŜy uŜywać z wielką ostroŜnością. W szczególności zauwaŜ, Ŝe
mechanizmy obsługi serwletów mogą (lecz nie muszą) w takim przypadku tworzyć wiele kopii
serwletu, przez co nie ma Ŝadnej pewności Ŝe wartości zmiennych instancyjnych będą unikalne.
Oczywiście, w takim przypadku rozwiązaniem jest zastosowanie statycznych (static) zmiennych
instancyjnych.
200
Serwer moŜe uŜywać buforu o rozmiarze większym niŜ podany, nie jest natomiast
dopuszczalne uŜycie mniejszego buforu. Na przykład, uŜycie dyrektywy o postaci <%@ page
buffer="32kb" %> oznacza, Ŝe treść generowanego dokumentu powinna być buforowana; przy
czym zawartość buforu ma być przesyłana do przeglądarki dopiero gdy zostanie zgromadzonych 32
kb informacji, chyba Ŝe wcześniej generacja strony zostanie zakończona. Domyślny rozmiar buforu
zaleŜy od uŜywanego serwera, jednak nie moŜe być on mniejszy od 8 kb. Jeśli chcesz wyłączyć
buforowanie wyników, będziesz musiał zachować duŜą ostroŜność, gdyŜ w takim przypadku
wszystkie próby określania nagłówków odpowiedzi oraz kodu statusu w stronach JSP, muszą być
wykonywane zanim zostanie wygenerowana jakakolwiek treść wynikowego dokumentu.
Atrybutu tego naleŜy uŜywać z niezwykłą ostroŜnością, gdyŜ serwer moŜe wykorzystywać
własną klasę bazową serwletów.
201 Rozdział 11. Dyrektywa page: Strukturalizacja generowanych serwletów
<BODY>
<%!
// Zwróć uwagę na brak bloków try/catch przechwytujących
// wyjątki NumberFormatException generowanych w przypadkach
// gdy wartości parametrów nie zostaną podane lub gdy zostaną
// zapisane w niewłaściwym formacie.
202
<%
double furlongs = toDouble(request.getParameter("furlongs"));
double fortnights = toDouble(request.getParameter("fortnights"));
double speed = furlongs/fortnights;
%>
<UL>
<LI>Dystans: <%= furlongs %> furlongów.
<LI>Czas: <%= fortnights %> dwóch tygodni.
<LI>Szybkość: <%= speed %> furlongów na dwa tygodnie.
</UL>
</BODY>
</HTML>
<BODY>
</BODY>
</HTML>
203 Rozdział 11. Dyrektywa page: Strukturalizacja generowanych serwletów
Jak na razie jednak moŜesz zapomnieć o tym atrybucie, gdyŜ jego domyślną a jednocześnie
jedyną dopuszczalną wartością jest java.
Na przykład, oba przedstawione poniŜej metody zapisu dyrektywy page dają ten sam efekt:
<%@ page import="java.util.*" %>
oraz
<jsp:directive.page import="java.util.*" />
Rozdział 12.
Dołączanie plików i apletów do
dokumentów JSP
Technologia JSP udostępnia trzy podstawowe sposoby dołączania zewnętrznych elementów
do dokumentów JSP.
Dyrektywa include pozwala na wykorzystywanie pasków nawigacyjnych, tabel oraz
wszelkich innych elementów w wielu dokumentach. Dołączane elementy mogą zawierać kod JSP i
dlatego są wstawiane zanim strona zostanie przekształcona do postaci serwletu. Zastosowanie tej
dyrektywy omówię szczegółowo w podrozdziale 12.1.
Choć dołączanie elementów zawierających kod JSP daje ogromne moŜliwości, to jednak
moŜe się zdarzyć, Ŝe będziesz wolał poświęcić niektóre z nich z nich w zamian za wygodę jaką daje
moŜliwość aktualizacji dołączanego elementu bez konieczności wprowadzania zmian w głównej
stronie JSP. Na przykład, na witrynie WWW mojego kościoła publikowane są ogłoszenia dotyczące
pomocy przy odgarnianiu śniegu. Strona jest aktualizowana w niedzielę o godzinie 6:30 rano, czyli
wtedy, gdy śnieg ma być odgarniany. Nie naleŜy oczekiwać, Ŝe to twórca witryny będzie
własnoręcznie dokonywać tych aktualizacji — w tym czasie zapewne smacznie sobie śpi. Znacznie
prostszym rozwiązaniem jest przesłanie na serwer zwyczajnego pliku tekstowego i umieszczenie
jego zawartości na stronie przy wykorzystaniu elementu jsp:include. To rozwiązanie omówię w
podrozdziale 12.2.
Choć niniejsza ksiąŜka jest poświęcona głównie tworzeniu programów działających na
serwerze, to jednak wciąŜ duŜą rolę odgrywają aplety — niewielkie programy pisane w języku Java
i wykonywane w przeglądarkach WWW. Rozwiązanie to jest najczęściej wykorzystywane w
szybkich, firmowych aplikacjach intranetowych. Element jsp:plugin umoŜliwia umieszczanie na
stronach JSP apletów korzystających z Java Plug-in. To rozwiązanie omówiłem w podrozdziale
12.3.
OstrzeŜenie
Jeśli zmienisz plik dołączany plik JSP, to będziesz musiał uaktualnić daty modyfikacji wszystkich dokumentów JSP,
które uŜywają tego pliku.
<P>
<HR>
To strona © 2001
<A HREF="http//www.moja-firma.com.pl/">moja-firma.com.pl</A>.
Od czasu uruchomienia serwera, ta strona została wyświetlona
<%= ++accessCount %> razy. Ostatnio została ona wyświetlona przez
komputer <%= accessHost %> w dniu/o godzinie <%= accessDate %>.
<BODY>
<TABLE BORDER=5 ALIGN="CENTER">
<TR><TH CLASS="TITLE">
Jakaś dowolna strona</TABLE>
<P>
Informacje na temat naszych usług i produktów.
<P>
Na razie nic nie oferujemy, ale i tak moŜesz nam zapłacić.
<P>
Nasze produkty są wyrabiane ręcznie w fabrykach na Tajwanie.
</BODY>
</HTML>
Choć dołączany plik nie moŜe zawierać kodu JSP, to jednak moŜe on zostać wygenerowany
przez zasoby uŜywające JSP. Oznacza to, Ŝe dołączany zasób, do którego odwołuje się podany
adres URL, jest w normalny sposób interpretowany przez serwer — czyli moŜe być serwletem lub
stroną JSP. Właśnie w taki sposób działa metoda include klasy RequestDispatcher, która jest
uŜywana przez serwlety w celu dołączania plików. Szczegółowe informacje na ten temat znajdziesz
w podrozdziale 15.3. — „Dołączanie danych statycznych bądź dynamicznych”.
Znacznik akcji jsp:include wymaga podania dwóch atrybutów (patrz poniŜszy przykład) —
page (zawierającego względny adres URL dołączanego pliku) oraz flush (atrybut ten musi mieć
wartość true).
<jsp:include page="względny_URL" flush="true" />
Choć zazwyczaj będziesz w ten sposób dołączał dokumenty HTML i pliki tekstowe, to
jednak nie ma Ŝadnych ograniczeń dotyczących rozszerzenia dołączanego pliku. W serwerze Java
Web Server 2.0 jest jednak błąd, który powoduje przerwanie przetwarzania strony jeśli dołączany
plik nie ma rozszerzenia .html lub .htm (czyli, jeśli jest to na przykład plik tekstowy z
rozszerzeniem .txt). Serwery Tomcat oraz JSWDK, jak równieŜ większość serwerów komercyjnych
nie ma takich ograniczeń.
OstrzeŜenie
Ze względu na błąd, Java Web Server pozwala wyłącznie na dołączanie plików z rozszerzeniami .html oraz .htm.
<BODY>
<CENTER>
<TABLE BORDER=5>
<TR><TH CLASS="TITLE">
Co nowego na witrynie NowościJsp.com</TABLE>
</CENTER>
<P>
stronach w taki sposób muszą korzystać z JDK 1.1 bądź 1.02, gdyŜ ani Netscape Navigator 4.x ani
Internet Explorer 5.x nie są w stanie korzystać z platformy Java 2 (czyli takŜe z JDK 1.2). Aplety
takie mają kilka powaŜnych ograniczeń:
• aby korzystać z klas pakietu Swing, naleŜy przesyłać wymagane pliki klasowe siecią. Proces
ten jest długotrwały, a co gorsze nie moŜna go wykonać w Internet Explorerze 3 oraz
Netscape Navigatorze 3 .x oraz 4.01 – 4.05, gdyŜ przeglądarki te obsługują wyłącznie JDK
1.02, a Swing wymaga JDK 1.1,
• nie moŜna uŜywać technologii Java 2D,
• nie moŜna korzystać z kolekcji dostępnych w Java 2.
• kod apletów jest wykonywany wolniej, gdyŜ większość kompilatorów platformy Java 2
zostało znacznie usprawnionych w porównaniu z ich wcześniejszymi wersjami.
Co więcej, dawne wersje przeglądarek obsługiwały róŜne komponenty AWT w niespójny
sposób, przez co tworzenie apletów z zaawansowanym i skomplikowany interfejsem uŜytkownika
była znacznie trudniejsze i uciąŜliwe niŜ mogło by być. Aby rozwiązać ten problem, firma Sun
stworzyła specjalny plug-in przeznaczony dla przeglądarek Netscape Navigator i Internet Explorer,
który pozwala na wykorzystywanie w apletach technologii dostępnych w platformie Java 2. Plug-in
ten moŜna pobrać z witryny firmy Sun — http://java.sun.com/products/plugin; jest on takŜe
dołączany do JDK w wersji 1.2.2 oraz następnych. Java Plug-in jest całkiem duŜy — ma wielkość
kilku megabajtów — a zatem raczej nie naleŜy oczekiwać, aby przeciętni uŜytkownicy WWW
chcieli go pobierać i instalować tylko po to, aby móc wykonywać Twoje aplety. Jednak z drugiej
strony, jego wykorzystanie jest całkiem rozsądną alternatywą dla szybkich, korporacyjnych
intranetów; zwłaszcza, iŜ w przypadku jego braku, aplety mogą automatycznie zaŜądać od
przeglądarki, aby pobrała go z serwera.
Niestety, normalny znacznik APPLET nie daje moŜliwości uŜycia plug-inu. Wynika to z faktu,
iŜ przeglądarki są tworzone w taki sposób, by aplety umieszczane na stronach przy uŜyciu tego
znacznika mogły być wykonywane wyłącznie przez wirtualne maszyny Javy wbudowane w
przeglądarkę. A zatem, zamiast znacznika APPLET naleŜy stosować inne rozwiązania — w Internet
Explorerze stosowany jest długi i zawiły znacznik OBJECT, a w Netscape Navigatorze równie długi
znacznik EMBED. Co grosze, zazwyczaj nie moŜna z góry wiedzieć jaka przeglądarka zostanie uŜyta
do pobrania strony. A zatem, konieczne jest umieszczenie w stronie obu znaczników jednocześnie
(przy czym znacznik EMBED umieszczany jest w sekcji COMMENT znacznika OBJECT) lub określenie
typu uŜywanej przeglądarki na podstawie nagłówków Ŝądania i wygenerowanie odpowiedniego
znacznika. Choć napisanie odpowiedniego kodu nie nastręcza Ŝadnych trudności, to jednak
rozwiązanie takie jest męczące i czasochłonne.
Znacznik akcji jsp:plugin informuje serwer, iŜ naleŜy wygenerować odpowiedni znacznik,
który umieści aplet na stronie i wykona go przy uŜyciu plug-inu. Serwery nie mają narzuconego
Ŝadnego konkretnego sposobu udostępnienia powyŜszych moŜliwości funkcjonalnych, jednak
większość z nich stosuje znaczniki OBJECT oraz EMBED.
Ten atrybut działa tak samo jak atrybut ARCHIVE znacznika APPLET, czyli określa plik
JAR, z którego naleŜy pobrać pliki klasowe apletu oraz wszystkie pozostałe pliki
multimedialne uŜywane przez niego.
• name
Atrybut ten działa tak samo jak atrybut NAME znacznika APPLET, czyli określa nazwę
apletu identyfikującą go w przypadku wymiany informacji pomiędzy róŜnymi apletami oraz
w językach skryptowych (takich jak JavaScript).
• title
Atrybut ten działa tak samo jak bardzo rzadko stosowany atrybut TITLE znacznika
APPLET (oraz niemal wszystkich pozostałych znaczników języka HTML 4.0). Określa on
tytuł znacznika, który moŜe być wyświetlany na etykietach ekranowych lub uŜyty przy
indeksowaniu.
• jreversion
Atrybut ten określa wymaganą wersję JRE (ang.: Java Runtime Environment —
środowiska wykonawczego Javy). Domyślna wartość tego atrybutu to 1.1.
• iepluginurl
Ten atrybut określa URL z którego moŜna pobrać Java Plug-In przeznaczony dla
Internet Explorera. UŜytkownicy, którzy jeszcze nie zainstalowali tego plug-ina zostaną
zapytani czy naleŜy go pobrać spod podanego adresu. Domyślna wartość tego atrybutu
umoŜliwia pobranie plug-ina z witryny firmy Sun, jednak w przypadku aplikacji
intranetowych moŜna zaŜądać pobrania jego lokalnej kopii.
• nspluginurl
Ten atrybut określa URL z którego moŜna pobrać Java Plug-In przeznaczony dla
Netscape Navigatora. UŜytkownicy, którzy jeszcze nie zainstalowali tego plug-ina zostaną
zapytani czy naleŜy go pobrać spod podanego adresu. Domyślna wartość tego atrybutu
umoŜliwia pobranie plug-ina z witryny firmy Sun, jednak w przypadku aplikacji
intranetowych moŜna zaŜądać pobrania jego lokalnej kopii.
Warto zapamiętać, iŜ w serwerze Java Web Server 2.0 występuje błąd, który sprawia, Ŝe
strony JSP zawierające ten znacznik nie są poprawnie przekształcane do postaci serwletów.
Serwery Tomcat, JSWDK oraz większość serwerów komercyjnych poprawnie obsługuje ten
znacznik.
OstrzeŜenie
Java Web Server nie obsługuje poprawnie znacznika jsp:fallback.
11
Aby strona ShadowedTextApplet.jsp mogła zostać poprawnie wykonana trzeba będzie poczynić pewne przygotowania.
OtóŜ, strona ta korzysta z pliku klasowego apletu ShadowedTextApplet.class naleŜącego do pakietu coreservlets; na podstawie
wartości atrybutów znacznika jsp:plugin moŜna określić, iŜ plik ten będzie poszukiwany w katalogu coreservlets znajdującym
się wewnątrz katalogu, w którym jest umieszczona strona ShadowedTextApplet.jsp. W tym samym katalogu powinne się takŜe
znaleźć wszystkie pliki klasowe z których korzysta sam aplet, są to: LabelPanel.class, MessageImage.class, ShadowedText.class,
ShadowedTextFrame.class oraz WindowUtilities.class. A zatem, aby przykład działał poprawnie, naleŜy utworzyć katalog
coreservlets w katalogu, w którym znajduje się strona ShadowedTextApplet.jsp i skopiować do niego wszystkie wspomniane
wcześniej pliki klasowe.
214
<LINK REL=STYLESHEET
HREF="JSP-Styles.css"
TYPE="text/css">
</HEAD>
<BODY>
</BODY>
</HTML>
<BODY>
</CENTER>
</BODY>
</HTML>
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
została uŜyta duŜa litera). Jeśli klasa udostępnia wyłącznie metodę getXxxx, to mówi się, Ŝe
posiada właściwość xxxx przeznaczoną wyłącznie do odczytu.
Istnieje jeden wyjątek od tej konwencji określania nazw metod dostępowych —
dotyczy on zmiennych instancyjnych przechowujących wartości logiczne (typu boolean).
OtóŜ w tym przypadku, metody zwracające wartość takich zmiennych instancyjnych noszą
nazwy isXxxx. A zatem, nasza przykładowa klasa Samochod moŜe mieć metodę isPozyczony
(która nie wymaga podania Ŝadnych argumentów i zwraca wartość typu boolean) oraz
metodę setPozyczony (która wymaga podania wartości typu boolean i niczego nie zwraca).
W takim przypadku, nasza klasa Samochod miałaby właściwość logiczną (typu boolean) o
nazwie pozyczony (takŜe w tym przypadku nazwa właściwości rozpoczyna się z małej
litery).
Choć w skryptletach i wyraŜeniach JSP moŜna korzystać z dowolnych metod
uŜywanych klas, to jednak standardowe znaczniki akcji JSP słuŜące do operowania na
komponentach JavaBeans mogą posługiwać się wyłącznie metodami stworzonymi zgodnie z
konwencją getXxxx/setXxxx oraz isXxxx/setXxxx.
Takie utoŜsamienie znacznika jsp:useBean z kodem tworzącym kopię obiektu jest bardzo
wygodne; jednak znacznik ten dysponuje dodatkowymi atrybutami, które sprawiają, Ŝe jego
moŜliwości są znacznie większe. W podrozdziale 13.4. (pt.: „Wspólne wykorzystywanie
komponentów”) poznasz atrybut scope, dzięki któremu komponent moŜe być dostępny nie tylko w
obrębie bieŜącej strony JSP. Jeśli moŜna wspólnie korzystać z komponentów, moŜna takŜe pobrać
odwołania do wszystkich istniejących komponentów. To z kolei oznacza, Ŝe uŜycie znacznika
jsp:useBean spowoduje utworzenie nowego egzemplarza komponentu wyłącznie w sytuacji, gdy
nie istnieje Ŝaden inny komponent o identycznych wartościach atrybutów id oraz scope.
Zamiast atrybutu class moŜna uŜywać atrybutu beanName. RóŜnica polega na tym, iŜ atrybut
beanName moŜe się odwoływać nie tylko do klasy lecz takŜe do pliku zawierającego zapisany obiekt.
Wartość tego atrybutu jest przekazywana jako argument wywołania metody instantiate klasy
java.beans.Bean.
W większości przypadków będziesz chciał, aby zmienna lokalna była tego samego typu co
tworzony obiekt. MoŜe się jednak zdarzyć, Ŝe będziesz chciał, aby zmienna została zadeklarowana
jako zmienna typu będącego jedną z klas bazowych klasy tworzonego komponentu lub jako
interfejs, który komponent implementuje. Do tego celu słuŜy atrybut type, którego zastosowanie
przedstawiłem na poniŜszym przykładzie:
<jsp:useBean id="watek1" class="MojaKlasa" type="Runnable" />
221 Rozdział 13. Wykorzystanie komponentów JavaBeans w dokumentach JSP
Zwróć uwagę, iŜ znacznik akcji jsp:useBean jest zapisywany zgodnie z zasadami języka
XML, przez co róŜni się od znaczników HTML z trzech powodów — uwzględniana jest wielkość
liter jakimi zapisywane są nazwy atrybutów, wartości atrybutów muszą być zapisane pomiędzy
znakami cudzysłowu lub apostrofu, koniec znacznika oznaczany jest przy uŜyciu znaków /> a
samego znaku >. Pierwsze dwie róŜnice dotyczą wszystkich elementów JSP o postaci jsp:xxx;
natomiast ostatnia z nich dotyczy wyłącznie tych, które nie mają odrębnych znaczników
otwierających i zamykających.
OstrzeŜenie
Składnia zapisu elementów jsp:xxx róŜni się od składni zapisu elementów HTML z trzech powodów —
uwzględniana jest wielkość liter w nazwach atrybutów, wartości atrybutów muszą być zapisane pomiędzy znakami
cudzysłowu bądź pomiędzy apostrofami oraz jeśli dany element nie jest „pojemnikiem”, to musi się być zakończony
przy uŜyciu kombinacji znaków /> a nie pojedynczego znaku >.
lub
<%= ksiazka1.getTytul() %>
W naszym przypadku zalecane jest wykorzystanie pierwszego sposobu, gdyŜ jest bardziej
zrozumiały dla twórców stron WWW, którzy nie znają języka Java. Niemniej jednak moŜliwość
bezpośredniego dostępu do zmiennej takŜe jest przydatna, szczególnie w przypadkach gdy trzeba
jej uŜywać w pętlach, instrukcjach warunkowych oraz w wywołaniach metod, które nie są dostępne
jako właściwości.
Jeśli nie spotkałeś się jeszcze z pojęciem właściwości stosowanym w komponentach
JavaBeans, to spieszę wyjaśnić, iŜ wyraŜenie „komponent ma właściwość typu T o nazwie cos”
naleŜy rozumieć Ŝe „jest to klasa definiująca metodę getCos zwracającą obiekt typu T oraz metodę
setCos pobierającą argument typu T i zapisującą jego wartość, aby później moŜna ją było pobrać
przy uŜyciu metody getCos”.
222
Zastosowanie znacznika jsp:setProperty ma tę zaletę, iŜ jest łatwiejsze dla osób, które nie
znają języka Java. Z drugiej strony bezpośrednie odwołanie do metody obiektu pozwala na
realizację bardziej złoŜonych czynności, takich jak warunkowe określanie wartości, bądź teŜ
wywoływanie metod innych niŜ setXxxx i getXxxx.
Następnie w treści strony moŜna wyświetlić wartość właściwości message tego komponentu;
moŜna to zrobić na dwa sposoby:
<jsp:getProperty name="stringBean" property="message" />
<%= stringBean.getMessage() %>
<HTML>
<HEAD>
<TITLE>Stosowanie komponentów JavaBeans na stronach JSP</TITLE>
<LINK REL=STYLESHEET
HREF="JSP-Styles.css"
TYPE="text/css">
</HEAD>
<BODY>
224
<OL>
<LI>Wartość początkowa (getProperty):
<I><jsp:getProperty name="stringBean"
property="message" /></I>
</BODY>
</HTML>
<BODY>
<jsp:setProperty
name="entry"
property="itemID"
value='<%= request.getParameter("itemID") %>' />
<%
int numItemsOrdered = 1;
try {
numItemsOrdered =
Integer.parseInt(request.getParameter("numItems"));
} catch(NumberFormatException nfe) {}
%>
<jsp:setProperty
name="entry"
property="numItems"
value="<%= numItemsOrdered %>" />
<%
double discountCode = 1.0;
try {
String discountString =
request.getParameter("discountCode");
// metoda Double.parseDouble nie jest dostępna w JDK 1.1.
discountCode =
Double.valueOf(discountString).doubleValue();
} catch(NumberFormatException nfe) {}
%>
<jsp:setProperty
name="entry"
property="discountCode"
value="<%= discountCode %>" />
<BR>
<TABLE ALIGN="CENTER" BORDER=1>
<TR CLASS="COLORED">
<TH>ID towaru<TH>Cena jednostkowa<TH>Ilość egzemplarzy<TH>Cena
<TR ALIGN="RIGHT">
<TD><jsp:getProperty name="entry" property="itemID" />
<TD>$<jsp:getProperty name="entry" property="itemCost" />
<TD><jsp:getProperty name="entry" property="numItems" />
<TD>$<jsp:getProperty name="entry" property="totalCost" />
</TABLE>
</BODY>
</HTML>
Zwróć uwagę, iŜ wartość parametru value określiłem przy wykorzystaniu wyraŜenia JSP.
Wartości niemal wszystkich atrybutów znaczników JSP muszą być konkretnymi łańcuchami
znaków. Wyjątkiem są tu wartości atrybutów name oraz value znacznika akcji jsp:setProperty,
które moŜna określać przy uŜyciu wyraŜeń obliczanych w czasie obsługi Ŝądania. Jeśli w samym
wyraŜeniu są uŜywane znaki cudzysłowu, to warto pamiętać, iŜ wartości atrybutów moŜna takŜe
zapisywać w apostrofach oraz Ŝe w wartościach atrybutów cudzysłowy i apostrofy mogą być
reprezentowane przez kombinacje znaków \" i \'.
<BODY>
<TR><TH CLASS="TITLE">
Sposoby uŜycia znacznika jsp:setProperty</TABLE>
<jsp:setProperty
name="entry"
property="itemID"
param="itemID" />
<jsp:setProperty
name="entry"
property="numItems"
param="numItems" />
<%-- OSTRZEśENIE!
Zarówno w JSWDK 1.0.1 jak i w Java Web Serverze
jest błąd, który powoduje pojawianie się błędów
przy przeprowadzaniu takiej konwersji do liczby
typu dobule.
--%>
<jsp:setProperty
name="entry"
property="discountCode"
param="discountCode" />
<BR>
<TABLE ALIGN="CENTER" BORDER=1>
<TR CLASS="COLORED">
<TH>ID towaru<TH>Cena jednostkowa<TH>Ilość egzemplarzy<TH>Cena
<TR ALIGN="RIGHT">
<TD><jsp:getProperty name="entry" property="itemID" />
<TD>$<jsp:getProperty name="entry" property="itemCost" />
<TD><jsp:getProperty name="entry" property="numItems" />
<TD>$<jsp:getProperty name="entry" property="totalCost" />
</TABLE>
</BODY>
</HTML>
<BODY>
<BR>
<TABLE ALIGN="CENTER" BORDER=1>
<TR CLASS="COLORED">
<TH>ID towaru<TH>Cena jednostkowa<TH>Ilość egzemplarzy<TH>Cena
<TR ALIGN="RIGHT">
<TD><jsp:getProperty name="entry" property="itemID" />
<TD>$<jsp:getProperty name="entry" property="itemCost" />
<TD><jsp:getProperty name="entry" property="numItems" />
<TD>$<jsp:getProperty name="entry" property="totalCost" />
</TABLE>
</BODY>
</HTML>
błędami jakie mogą się pojawić podczas konwersji danych, w równie wysokim stopniu co
konwersja wykonywana ręcznie. A zatem, w przypadku stosowania automatycznej konwersji typów
moŜesz takŜe wykorzystać własne strony obsługi błędów (patrz listingi 11.9 oraz 11.10). I w końcu
ostatnia sprawa. Zarówno w nazwach właściwości jak i parametrów uwzględniana jest wielkość
liter, dlatego musisz zwrócić baczną uwagę, aby nazwy te były identyczne.
OstrzeŜenie
Aby właściwości zostały poprawnie skojarzone z parametrami wejściowymi ich nazwy muszą być identyczne (dotyczy
to takŜe wielości liter).
Łącznie zarejestrowano
<jsp:getProperty name="counter" property="accessCount" />
odwołań do grupy tych trzech stron.
Listing 13.8 przedstawia pierwszą ze stron JSP wykorzystujących nasz „grupowy licznik”
odwiedzin. Pozostałe dwie strony znajdziesz w pliku archiwalnym zawierającym kody źródłowe
wszystkich przykładów podanych w niniejszej ksiąŜce (ftp://ftp.helion.pl/przyklady/jsjsp.zip).
RóŜnice pomiędzy wszystkimi trzema stronami są minimalne. Przykładowe wyniki wykonania
jednej z tych trzech stron, zostały przedstawione na rysunku 13.3.
<BODY>
</BODY>
</HTML>
233 Rozdział 13. Wykorzystanie komponentów JavaBeans w dokumentach JSP
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
}
}
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>example</name>
<tagclass coreservlets.tags.ExampleTag</tagclass>
<info>Najprostszy przykład: wyświetla wiersz tekstu</info>
<bodycontent>EMPTY</bodycontent>
</tag>
</taglib>
Format zapisu pliku deskryptora opiszę szczegółowo w dalszej części rozdziału. Jak na razie
wystarczy, abyś zwrócił uwagę, Ŝe element tag definiuje główną nazwę znacznika (a w zasadzie,
jak się niebawem przekonasz, jego końcową część) oraz określa klasę obsługującą ten znacznik.
Klasa obsługująca znacznik naleŜy do pakietu coreservlets.tags i dlatego w elemencie tag podana
została jej pełna nazwa — coreservlets.tags.ExampleTag. Zwróć uwagę, iŜ jest to nazwa klasy, a
nie URL bądź względna ścieŜka dostępu. Klasa ta moŜe być umieszczona na serwerze w dowolnym
miejscu, w katalogach przeznaczonych do przechowywania serwletów oraz innych, pomocniczych
klas. W przypadku serwera Tomcat 3.1 standardowym połoŜeniem jest katalog
katalog_instalacyjny/webapps/ROOT/WEB-INF/classes, a zatem plik klasowy ExampleTag.class
powinien zostać umieszczony w katalogu katalog_instalacyjny/webapps/ROOT/WEB-
INF/classes/coreservlets/tags. Choć umieszczanie klas serwletów w pakietach zawsze jest dobrym
237 Rozdział 14. Tworzenie bibliotek znaczników
pomysłem, to jednak zaskakującą cechą serwera Tomcat 3.1 jest to, iŜ wymaga on aby klasy obsługi
znaczników były umieszczane w pakietach.
Plik JSP
Po stworzeniu implementacji obsługi znacznika oraz pliku deskryptora biblioteki
znaczników, moŜna przystąpić do pisania dokumentu JSP wykorzystującego utworzony znacznik.
Przykład takiego pliku przedstawiłem na listingu 14.3. Gdzieś przed pierwszym uŜyciem znacznika
naleŜy umieścić dyrektywę taglib. PoniŜej przedstawiłem jej składnię:
<%@ taglib uri="..." prefix="..." %>
<TITLE><csajsp:example /></TITLE>
<LINK REL=STYLESHEET
HREF="JSP-Styles.css"
TYPE="text/css">
</HEAD>
<BODY>
<H1><csajsp:example /></H1>
<csajsp:example />
</BODY>
</HTML>
Wymagany atrybut uri moŜe zawierać bezwzględny lub względny adres URL odwołujący
się do pliku deskryptora biblioteki znaczników (takiego jak ten, który przedstawiłem na listingu
14.2). Jednak aby wszystko dodatkowo utrudnić, serwer Tomcat 3.1 korzysta z pliku web.xml który
odwzorowuje bezwzględne adresy URL plików deskryptora na ścieŜki w lokalnym systemie
plików. Nie zalecam jednak stosowania tej metody; warto jednak abyś pamiętał o jej istnieniu, na
wypadek gdybyś przeglądając przykłady dostarczane wraz z serwerem, zastanawiał się dlaczego
one działają skoro odwołują się do nieistniejących adresów URL podawanych jako wartości
atrybutu uri dyrektywy taglib.
Kolejny atrybut dyrektywy taglib — prefix — określa prefiks, który będzie umieszczany
przed wszystkimi nazwami znaczników, zdefiniowanych w danym pliku deskryptora. Na przykład,
jeśli w pliku TLD (pliku deskryptora biblioteki znaczników) został zdefiniowany znacznik o nazwie
tag1, a atrybutowi prefix dyrektywy taglib przypisano wartość test, to faktyczna nazwa
znacznika będzie miała postać test:tag1. Sam znacznik moŜna umieszczać w pliku JSP na dwa
sposoby; wybór jednego z nich zaleŜy od tego, czy znacznik został zdefiniowany jako „pojemnik”,
w którym wykorzystywana jest zawartość umieszczona wewnątrz znacznika:
<test:tag1>
dowolny kod JSP
</test:tag1>
temu, dyrektywa taglib umieszczona w pliku JSP moŜe zawierać prosty, względny adres URL
określający wyłącznie nazwę pliku TLD:
<%@ taglib uri="csajsp-taglib.tld" prefix="csajsp" %>
Co więcej, atrybut prefix powyŜszej dyrektywy ma wartość csajsp (od tytułu oryginału
ksiąŜki — „JavaServer and JavaServer Pages”), a zatem w całej stronie JSP moŜna się odwoływać
do znacznika zdefiniowanego we wskazanym pliku deskryptora za pomocą nazwy csajsp:example.
Wyniki wykonania strony uŜywającej tego znacznika przedstawiłem na rysunku 14.1.
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import java.math.*;
import coreservlets.*;
— a są to elementy tag. W przypadku prostych znaczników, które nie mają atrybutów, element tag
powinien zawierać cztery kolejne elementy zapisywane pomiędzy znacznikami <tag> i </tag>:
1. name — definiuje on podstawową nazwę znacznika, do której będzie dodawany prefiks
określony w dyrektywnie taglib. W tym przykładzie znacznik ten ma postać
<name>simplePrime</name>
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>simplePrime</name>
<tagclass>coreservlets.tags.SimplePrimeTag</tagclass>
<info>Wyświetla losową, 50-cio cyfrową liczbę pierwszą.</info>
<bodycontent>EMPTY</bodycontent>
</tag>
</taglib>
Plik JSP
W dokumencie JSP, w którym będzie uŜywany nasz przykładowy znacznik, naleŜy umieścić
dyrektywę taglib. Dyrektywa ta musi zawierać atrybut uri określający połoŜenie pliku deskryptora
biblioteki znaczników oraz atrybut prefix określający krótki łańcuch znaków (tak zwany prefiks),
241 Rozdział 14. Tworzenie bibliotek znaczników
który będzie umieszczany (wraz z dwukropkiem) przed główną nazwą znacznika. Listing 14.6
przedstawia dokument JSP, w którym została umieszczona dyrektywa taglib o następującej
postaci:
<%@ taglib uri="csajsp-taglib.tld" prefix="csajsp" %>
Oznacza ona, Ŝe zostanie uŜyty plik TLD przedstawiony na listingu 14.5, a nazwy
znaczników będą poprzedzane prefiksem csajsp.
Wyniki wykonania strony SimplePrimeExample.jsp przedstawiłem na rysunku 14.2
<BODY>
<H1>50-cio cyfrowe liczby pierwsze</H1>
<UL>
<LI><csajsp:simplePrime />
<LI><csajsp:simplePrime />
<LI><csajsp:simplePrime />
<LI><csajsp:simplePrime />
</UL>
</BODY>
</HTML>
znacznie poprawia elastyczność biblioteki znaczników. W tej części rozdziału opiszę w jaki
sposób moŜna wzbogacić tworzone znaczniki o obsługę atrybutów.
242
Jeśli inne klasy będą mogły odwoływać się do klasy obsługi znacznika, to oprócz metody
setNazwaAtrubutu warto w niej takŜe zaimplementować metodę getNazwaAtrybutu. Wymagana jest
jednak wyłącznie metoda setNazwaAtrybutu.
Na listingu 14.7 przedstawiłem klasę potomną klasy SimplePrimeTag, która została
wyposaŜona w moŜliwość obsługi atrybutu length. Gdy atrybut ten zostanie podany w kodzie
dokumentu, spowoduje to wywołanie metody setLength, która zamieni przekazany łańcuch znaków
na liczbę typu int i zapisze jej wartość w zmiennej instancyjnej len, która jest juŜ wykorzystywana
w metodzie doStartTag klasy bazowej.
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import java.math.*;
import coreservlets.*;
2. required — ten wymagany element określa czy atrybut zawsze musi zostać umieszczony w
znaczniku (w takim przypadku element required musi zawierać wartość true), czy teŜ jest
on opcjonalny (element required zawiera wartość false). W naszym przykładzie atrybut
length jest opcjonalny, a zatem element required będzie miał postać:
<required>false</required>
Jeśli w kodzie dokumentu JSP pominiesz atrybut, to nie zostanie wywołana metoda
setNazwaAtrybut; pamiętaj zatem, aby określić wartość domyślną zmiennej instancyjnej, w
której jest przechowywana wartość atrybutu.
3. rtexprvalue — ten opcjonalny element określa czy wartością atrybutu moŜe być wyraŜenie
JSP o ogólnej postaci <%= wyraŜenie %> (jeśli tak, to element ten powinien zawierać wartość
true), czy teŜ ma to być z góry określony łańcuch znaków (w takim przypadku element
rtexprvalue powinien zawierać wartość false). Domyślną wartością tego elementu jest
false, a zatem jest on zazwyczaj pomijany, chyba Ŝe chcesz, aby wartości atrybutu mogły
być określane w czasie obsługi Ŝądania.
Na listingu 14.8 przedstawiłem pełny element tag umieszczony w pliku deskryptora
biblioteki znaczników. Oprócz elementu attribute definiującego atrybut length, element ten
zawiera takŜe cztery standardowe elementy — name (o wartości prime), tagclass (o wartości
coreservlets.tags.PrimeTag), info (zawierający krótki opis znacznika) oraz bodycontent (o
wartości EMPTY).
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>prime</name>
<tagclass>coreservlets.tags.PrimeTag</tagclass>
<info>Wyświetla N-cyfrową, losową liczbę pierwszą.</info>
<bodycontent>EMPTY</bodycontent>
<attribute>
<name>length</name>
<required>false</required>
244
</attribute>
</tag>
</taglib>
Plik JSP
Na listingu 14.9 przedstawiłem dokument JSP zawierający dyrektywę taglib, która
powoduje załadowanie pliku deskryptora biblioteki znaczników i określa, Ŝe prefiks znaczników
będzie miał postać csajsp. UŜywany w tym przykładzie znacznik prime został zadeklarowany w
taki sposób, iŜ moŜe zawierać atrybut length; a zatem, w dokumencie przedstawionym na listingu
14.9, jest on zapisywany w następującej postaci:
<csajsp:prime length="xxx" />
<BODY>
<H1>Kikla N-cyfrowych liczb pierwszych</H1>
<UL>
<LI>Liczba 20-cyfrowa: <csajsp:prime length="20" />
<LI>Liczba 40-cyfrowa: <csajsp:prime length="40" />
<LI>Liczba 80-cyfrowa: <csajsp:prime length="80" />
<LI>Liczba o długości domyślnej (50-cyforwa): <csajsp:prime />
</UL>
</BODY>
</HTML>
245 Rozdział 14. Tworzenie bibliotek znaczników
W tej części rozdziału pokaŜę jak naleŜy definiować znaczniki, które wykorzystują
umieszczone wewnątrz nich informacje, i są zapisywane w następującej formie:
<prefiks:nazwa>zawartość</prefiks:nazwa>
zwróci wartość EVAL_PAGE nakazując dalsze przetwarzanie dokumentu. Nasz znacznik posiada takŜe
kilka metod setNazwaAtrybutu, słuŜących do obsługi atrybutów, takich jak bgColor lub fontSize.
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
Pozostałe elementy — name, tagclass, info oraz attribute — są stosowane tak samo jak w
poprzednich przypadkach. Kod pliku deskryptora biblioteki znaczników wraz z definicją naszego
nowego znacznika przedstawiłem na listingu 14.11.
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>heading</name>
<tagclass>coreservlets.tags.HeadingTag</tagclass>
<info>Wyświetla nagłówek o postaci tabeli z jedną komórką.</info>
<bodycontent>JSP</bodycontent>
<attribute>
<name>bgColor</name>
<required>true</required> <!-- bgColor jest wymagany -->
</attribute>
<attribute>
<name>color</name>
<required>false</required>
</attribute>
<attribute>
<name>align</name>
<required>false</required>
</attribute>
<attribute>
<name>fontSize</name>
<required>false</required>
</attribute>
<attribute>
248
<name>fontList</name>
<required>false</required>
</attribute>
<attribute>
<name>border</name>
<required>false</required>
</attribute>
<attribute>
<name>width</name>
<required>false</required>
</attribute>
</tag>
</taglib>
Plik JSP
Listing 14.12 przedstawia kod źródłowy dokumentu JSP wykorzystującego zdefiniowany
przed chwilą znacznik heading. PoniewaŜ atrybut bgColor został zdefiniowany jako wymagany,
umieściłem go we wszystkich przykładach uŜycia znacznika. Wyniki wykonania tej strony JSP
zostały przedstawione na rysunku 14.4.
<BODY>
<%@ taglib uri="csajsp-taglib.tld" prefix="csajsp" %>
<csajsp:heading bgColor="#C0C0C0">
Domyślna postać nagłówka
</csajsp:heading>
<P>
<csajsp:heading bgColor="BLACK" color="WHITE">
Biały nagłówek na czarnym tle
</csajsp:heading>
<P>
<csajsp:heading bgColor="#EF8429" fontSize="60" border="5">
Nagłówek w duŜej ramce
</csajsp:heading>
<P>
<csajsp:heading bgColor="CYAN" width="100%">
Nagłówek z tłem o szerokości całej strony
</csajsp:heading>
<P>
<csajsp:heading bgColor="CYAN" fontSize="60"
fontList="Brush Script MT, Times, serif">
Nagłówek wyświetlony niestandardową czcionką
</csajsp:heading>
<P>
</BODY>
</HTML>
249 Rozdział 14. Tworzenie bibliotek znaczników
Rysunek 14.4 Znacznik csajsp:heading daje znacznie większą kontrolę nad wyglądem
nagłówka niŜ standardowe znaczniki Hx języka HTML
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import javax.servlet.*;
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>debug</name>
<tagclass>coreservlets.tags.DebugTag</tagclass>
<info>Dołącza zawartość wyłącznie jeśli określony jest parametr debug.</info>
<bodycontent>JSP</bodycontent>
</tag>
251 Rozdział 14. Tworzenie bibliotek znaczników
</taglib>
Plik JSP
Na listingu 14.15 przedstawiłem dokument JSP zawierający informacje testowe,
umieszczone pomiędzy znacznikami <csajsp:debug> oraz </csajsp:debug>. Na rysunku 14.5
moŜna zobaczyć standardowe wyniki wygenerowane przez tę stronę, a na rysunku 14.6 wyniki
uzyskane w sytuacji gdy został podany parametr debug.
<BODY>
<H1>Zastosowanie znacznika DebugTag</H1>
<csajsp:debug>
<B>Informacje testowe:</B>
<UL>
<LI>Aktualny czas: <%= new java.util.Date() %>
<LI>śądanie przesłano z: <%= request.getRemoteHost() %>
<LI>ID sesji: <%= session.getId() %>
</UL>
</csajsp:debug>
<BR>
Dalsza część strony...
<P>
<I>...<BR>
Ku brzegom angielskim juŜ ruszać nam pora,<BR>
Lecz kiedyś na pewno wrócimy to znów...</I>
</P>
</BODY>
</HTML>
252
BodyTagSupport jest klasą potomną klasy TagSupport, a zatem metody doStartTag oraz
doEndTag są uŜywane tak samo jak wcześniej. Jednak klasa ta udostępnia dwie nowe, waŜne
metody:
1. doAfterBody — tę metodę naleŜy przesłonić w celu wykonania jakiś operacji na zawartości
znacznika. Zazwyczaj powinna ona zwracać wartość SKIP_BODY oznaczającą, Ŝe w Ŝaden
inny sposób nie naleŜy przetwarzać zawartości znacznika.
2. getBodyContent — ta metoda zwraca obiekt klasy BodyContent, w którym zostały
zgromadzone informacje o zawartości znacznika.
Klasa BodyContent udostępnia trzy, bardzo waŜne metody:
1. getEnclosingWriter — metoda ta zwraca obiekt JspWriter uŜywany takŜe przez metody
doStartTag oraz doEndTag.
2. getReader — metoda zwraca obiekt Reader, przy uŜyciu którego moŜna odczytać zawartość
znacznika.
3. getString — metoda zwraca łańcuch znaków zawierający całą treść znacznika.
W podrozdziale 3.4 (pt.: „Przykład: Odczyt wszystkich parametrów”) przedstawiłem
statyczną metodę filter, która pobierała łańcuch znaków i zastępowała wszystkie odszukane w
nim znaki <, >, " oraz & odpowiednimi symbolami HTML — <, >, &qout; oraz &. Metoda
ta jest przydatna w sytuacjach gdy serwlet generuje łańcuchy znaków, które mogą zaburzyć
strukturę i postać strony na jakiej są umieszczane. Na listingu 14.16 przedstawiłem implementację
znacznika, który został wzbogacony o moŜliwości filtrowania zawartości.
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import coreservlets.*;
/** Znacznik zamieniający znaki <, >, " oraz & na odpowiednie
* symbole HTML (<, >, ", and &).
* Po przefiltrowaniu, dowolne łańcuchy znaków mogą zostać
* osadzone bądź to w treści generowanej strony WWW, lub
* teŜ moŜna ich uŜyć jako wartości atrybutów znaczników.
*/
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>filter</name>
<tagclass>coreservlets.tags.FilterTag</tagclass>
<info>Zastępuje znaki specjalne HTML umieszczone w zawartości.</info>
<bodycontent>JSP</bodycontent>
</tag>
</taglib>
Plik JSP
Listing 14.18 przedstawia stronę JSP która wyświetla w tabeli przykładowy kod HTML oraz
jego wyniki. Stworzenie takiej tabeli w języku HTML byłoby dosyć uciąŜliwe, gdyŜ w całej
zawartości komórki prezentującej kod HTML naleŜy zamienić znaki < oraz > na odpowiednie
symbole HTML — < oraz >. Wykonywanie tego zadania jest szczególnie pracochłonne na
etapie tworzenia strony, gdyŜ przykładowy kod HTML moŜe się bardzo często zmieniać. Na
szczęście, cały proces moŜna znacznie uprościć dzięki wykorzystaniu znacznika csajsp:filter
(patrz listing 14.18). Wyniki wykonania strony z listingu 14.18 przedstawiłem na rysunku 14.6.
<BODY>
<H1>Style logiczne języka HTML</H1>
Style fizyczne (na przykład: B, I, itp.) są interpretowane i
wyświetlane tak samo we wszystkich przeglądarkach. Niestety,
style logiczne mogą być wyświetlane w róŜny sposób. Oto jak
Twoja przeglądarka
(<%= request.getHeader("User-Agent") %>)
wyświetla style logiczne języka HTML 4.0:
<P>
256
<TD><PRE>
<csajsp:filter>
<EM>Tekst o większym znaczeniu.</EM><BR>
<STRONG>Tekst o bardzo duŜym znaczeniu.</STRONG><BR>
<CODE>Przykładowy kod.</CODE><BR>
<SAMP>Przykładowy tekst.</SAMP><BR>
<KBD>Tekst wprowadzony z klawiatury.</KBD><BR>
<DFN>Definiowany termin.</DFN><BR>
<VAR>Zmienna.</VAR><BR>
<CITE>Cytat lub odwołanie.</CITE>
</csajsp:filter>
</PRE>
<TD>
<EM>Tekst o większym znaczeniu.</EM><BR>
<STRONG>Tekst o bardzo duŜym znaczeniu.</STRONG><BR>
<CODE>Przykładowy kod.</CODE><BR>
<SAMP>Przykładowy tekst.</SAMP><BR>
<KBD>Tekst wprowadzony z klawiatury.</KBD><BR>
<DFN>Definiowany termin.</DFN><BR>
<VAR>Zmienna.</VAR><BR>
<CITE>Cytat lub odwołanie.</CITE>
</TABLE>
</BODY>
</HTML>
Rysunek 14.7 Znacznik csajsp:filter pozwala wyświetlać na stronach WWW tekst bez
zwracania uwagi na to czy zawiera on znaki specjalne HTML czy nie
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>repeat</name>
<tagclass>coreservlets.tags.RepeatTag</tagclass>
<info>Powtarza zawartość określoną ilość razy.</info>
<bodycontent>JSP</bodycontent>
<attribute>
<name>reps</name>
<required>true</required>
<!-- element rtexprvalue określa czy atrybut
moŜe być wyraŜeniem JSP. -->
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
Plik JSP
Listing 14.21 przedstawia dokument JSP tworzący ponumerowaną listę liczb pierwszych.
Ilość liczb wyświetlanych na liście określana jest w czasie obsługi Ŝądania, na podstawie parametru
o nazwie repeats. Przykładowe wyniki wykonania tej strony zostały przedstawione na rysunku
14.8.
<BODY>
<H1>Lista 40-cyfrowych liczb pierwszych</H1>
KaŜda z liczb przedstawionych na poniŜszej liście, jest
liczbą pierwszą większą od losowo wybranej 40-cyfrowej liczby.
<OL>
<!-- PowtóŜ to N razy. Pominięcie atrybutu reps oznacza, Ŝe
zawartość znacznika ma być wyświetlona tylko raz. -->
259 Rozdział 14. Tworzenie bibliotek znaczników
<csajsp:repeat reps='<%= request.getParameter("repeats") %>'>
<LI><csajsp:prime length="40" />
</csajsp:repeat>
</OL>
</BODY>
</HTML>
wykorzystuje wiele elementów — takich jak taglib, tag, attribute oraz required — które muszą
być zapisywane w ściśle określonej hierarchii.
W tej części rozdziału pokaŜę jak moŜna definiować znaczniki, których zachowanie zaleŜy
od kolejności zagnieŜdŜania, a działanie — od wartości określonych w innych znacznikach.
Pierwszym krokiem jaki naleŜy wykonać aby zrealizować to zadanie, jest zdefiniowanie
klasy IfTag, która będzie obsługiwać znacznik csajsp:if. Klasa ta powinna dysponować metodami
pozwalającymi na podanie i sprawdzenie wartości warunku (będą to metody setCondition oraz
getCondition) jak równieŜ metodami umoŜliwiającymi określenie i sprawdzenie czy warunek
kiedykolwiek został jawnie określony (będą to metody setHasCondition oraz getHasCondition). Ta
druga para metod będzie mam potrzebna gdyŜ, chcemy zabronić przetwarzania znaczników
csajsp:if, w których nie ma podanego znacznika csajsp:condition. Kod klasy IfTag
przedstawiłem na listingu 14.23.
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import java.io.*;
import javax.servlet.*;
<taglib>
<!-- teraz domyślną przestrzenią jest
"http://java.sun.com/j2ee/dtds/jsptaglibrary_1_2.dtd"
-->
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>csajsp</shortname>
<urn></urn>
<info>
Biblioteka znaczników ksiąŜki Core Servlets and JavaServer Pages,
http://www.coreservlets.com/.
</info>
<tag>
<name>if</name>
<tagclass>coreservlets.tags.IfTag</tagclass>
<info>Znacznik warunkowy: if/condition/then/else.</info>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>condition</name>
<tagclass>coreservlets.tags.IfConditionTag</tagclass>
<info>Część warunku (condition) znacznika if/condition/then/else.</info>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>then</name>
<tagclass>coreservlets.tags.IfThenTag</tagclass>
<info>Część "then" znacznika if/condition/then/else.</info>
<bodycontent>JSP</bodycontent>
</tag>
<tag>
<name>else</name>
<tagclass>coreservlets.tags.IfElseTag</tagclass>
<info>Część "else" znacznika if/condition/then/else.</info>
<bodycontent>JSP</bodycontent>
</tag>
</taglib>
265 Rozdział 14. Tworzenie bibliotek znaczników
Plik JSP
Listing 14.28 przedstawia stronę JSP w której znacznik csajsp:if jest wykorzystany na trzy
róŜne sposoby. W pierwszym przykładzie uŜycia znacznika, w jego warunku na stałe została
podana wartość true. W drugim przykładzie, wartość warunku określana jest na podstawie wartości
parametru Ŝądania HTTP. I w końcu w trzecim przykładzie znacznika csajsp:if, wartość jego
warunku określana jest na podstawie porównania liczby pseudolosowej i pewnej stałej wartości.
Przykładowe wyniki wykonania strony IfExample.jsp przedstawiłem na rysunku 14.9.
<BODY>
<H1>Przykłady uŜycia znacznika If</H1>
<csajsp:if>
<csajsp:condition>true</csajsp:condition>
<csajsp:then>Warunek ma wartość true</csajsp:then>
<csajsp:else>Warunek ma wartość false</csajsp:else>
</csajsp:if>
<P>
<csajsp:if>
<csajsp:condition><%= request.isSecure() %></csajsp:condition>
<csajsp:then>śądanie uŜywa SSLa (https)</csajsp:then>
<csajsp:else>śądanie nie uŜywa SSLa</csajsp:else>
</csajsp:if>
<P>
Wyniki rzutu monetą:<BR>
<csajsp:repeat reps="20">
<csajsp:if>
<csajsp:condition>
<%= Math.random() > 0.5 %>
</csajsp:condition>
<csajsp:then><B>Orzeł</B><BR></csajsp:then>
<csajsp:else><B>Reszka</B><BR></csajsp:else>
</csajsp:if>
</csajsp:repeat>
</BODY>
</HTML>
266
Specyfikacja Java Servlet 2.2 udostępnia trzeci sposób przesyłania informacji do strony
docelowej, pod warunkiem, Ŝe zostanie wykorzystane Ŝądanie GET; polega on na dopisaniu tych
informacji do adresu URL. Oto przykład:
String adres = "/sciezka/strona.jsp?nowyParametr=wartość";
RequestDispatcher dispatcher =
getServletContext().getRequestDispatcher(adres);
dispatcher.forward(request, response);
Jeśli strona JSP, w której powyŜszy znacznik zostanie umieszczony, zostanie wykonana na
skutek przekierowanego Ŝądania, to adres my-style.css zostanie zinterpretowany jako adres podany
względem bieŜącego połoŜenia serwletu, który jako pierwszy odebrał Ŝądanie, a nie względem
samej strony JSP. Niemal na pewno spowoduje to pojawienie się błędów. Rozwiązaniem jest
podanie pełnej ścieŜki, określonej względem katalogu głównego serwera, jak to pokazałem na
poniŜszym przykładzie:
<LINK REL="STYLESHEET"
HREF="/ścieŜka/my-style.css"
TYPE="text/css">
Rysunek 15.2 Wynik wykonania serwletu obsługującego internetowe biuro podróŜy (patrz
listing 15.3), który przekazał Ŝądanie do dokumentu BookFlights.jsp (patrz listing 15.4).
Aby umoŜliwić taki sposób działania aplikacji, jej interfejs uŜytkownika (przedstawiony na
listingu 15.2) przesyła Ŝądanie do głównego serwletu, którego kod przedstawiłem na listingu 15.3.
Serwlet ten pobiera informacje o uŜytkowniku (patrz listingi od 15.5 do 15.9), zapisuje je w
obiekcie klasy coreservlets.TravelCustomer, który kojarzy z kluczem customer i umieszcza w
obiekcie HttpSession, a następnie przekazuje Ŝądanie do strony JSP odpowiadającej czynności
jakiej zaŜądał uŜytkownik. Strona docelowa (patrz listing 15.4 oraz rysunek 15.2) pobiera
informacje o uŜytkowniku posługując znacznikiem akcji jsp:useBean o następującej postaci:
<jsp:useBean id="customer"
class="coreservlets.TravelCustomer"
scope="session" />
przygotowanie informacji i udostępnienie ich dokumentom JSP zwraca się z nawiązką, jeśli z
informacji tego samego typu będzie korzystać większa ilość dokumentów.
Po drugie, naleŜy pamiętać, Ŝe wiele serwerów dysponujących moŜliwością automatycznego
przeładowywania serwletów w przypadku modyfikacji ich pliku klasowego, nie pozwala aby pliki
klasowe komponentów JavaBeans uŜywanych w dokumentach JSP były umieszczane w katalogach
umoŜliwiających takie automatyczne przeładowywanie. A zatem, w przypadku korzystania z Java
Web Servera, pliki klasowe klasy TravleCustomer oraz jej klas pomocniczych, muszą być
umieszczone w katalogu katalog_instalacyjny/classes/coreservlets/ a nie w katalogu
katalog_instalacyjny/servlets/coreservlets/. Serwery Tomcat 3.0 oraz JSWDK 1.0.1 nie
udostępniają moŜliwości automatycznego przeładowywania serwletów, a zatem pliki klasowe
komponentów mogą być umieszczane w tych samych katalogach co pliki klasowe serwletów.
<BODY>
<BR>
<H1>Szybkie wyszukiwanie - biuro podróŜy</H1>
12
W przykładach dołączonych do niniejszej ksiąŜki, dokumenty JSP uŜywane w tej aplikacji umieszczone są w katalogu
travel znajdującym się wewnątrz katalogu JSP-Code. Aby aplikacja działała poprawnie naleŜy przenieść katalog travel, na główny
poziom serwera (w przeciwnym razie odwołania /travel/xxx.jsp umieszczone w pliku Travel.java oraz w poszczególnych
dokumentach JSP nie będą poprawne). - przyp. tłum.
275 Rozdział 15. Integracja serwletów i dokumentów JSP
</SMALL>
</TABLE>
</CENTER>
</FORM>
<BR>
<P ALIGN="CENTER">
<B>Jeszcze nie jesteś zarejestrowany w naszym biurze? ZałóŜ
<A HREF="accounts.jsp">konto</A> - to nic nie kosztuje.</B></P>
</BODY>
</HTML>
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
gotoPage("/travel/FindHotels.jsp",
request, response);
} else if (request.getParameter("cars") != null) {
gotoPage("/travel/EditAccounts.jsp",
request, response);
} else {
gotoPage("/travel/IllegalRequest.jsp",
request, response);
}
}
<BODY>
<H1>Najlepsze dostępne przeloty</H1>
<CENTER>
<jsp:useBean id="customer"
class="coreservlets.TravelCustomer"
scope="session" />
Szukam lotów dla
<jsp:getProperty name="customer" property="fullName" />
<P>
<jsp:getProperty name="customer" property="flights" />
<P>
<BR>
<HR>
<BR>
<FORM ACTION="/servlet/BookFlight">
<jsp:getProperty name="customer"
property="frequentFlyerTable" />
<P>
<B>Karta kredytowa:</B>
<jsp:getProperty name="customer" property="creditCard" />
<P>
<INPUT TYPE="SUBMIT" NAME="holdButton" VALUE="Zablokuj na 24 godziny">
<P>
<INPUT TYPE="SUBMIT" NAME="bookItButton" VALUE="Rezerwuje!">
</FORM>
</CENTER>
</BODY>
</HTML>
import java.util.*;
import java.text.*;
flightStartDate,
flightEndDate);
}
return(flightString);
}
Metoda include ma wiele cech wspólnych z metodą forward. Jeśli oryginalne Ŝądanie
wykorzystywało metodę POST, ta sama metoda zostanie uŜyta do dalszego przekazania Ŝądania.
Jakiekolwiek dane były skojarzone z oryginalnym Ŝądaniem, będą takŜe dostępne w Ŝądaniu
pomocniczym; co więcej, w serwletach tworzonych zgodnie ze specyfikacją Java Servlet 2.2 moŜna
dodawać nowe parametry, dopisując je do adresu URL przekazanego w wywołaniu metody
getRequestDispatcher. Specyfikacja Java Servlet 2.2 udostępnia takŜe moŜliwość pobrania obiektu
RequestDispatcher na podstawie nazwy (słuŜy do tego metod getNamedDispatcher) lub uŜycia
względnego adresu URL (w tym celu naleŜy uŜyć metody getRequestDispatcher obiektu
HttpServletRequest); więcej informacji na ten temat znajdziesz w podrozdziale 15.1. —
„Przekazywanie Ŝądań”. Jednak metoda include robi jedną rzecz, której nie robi metoda forward —
automatycznie określa w obiekcie HttpServletRequest atrybuty opisujące oryginalną ścieŜkę
Ŝądania; oczywiście jeśli dołączany serwlet lub strona JSP potrzebuje tych informacji. Atrybuty te
moŜna pobrać w dołączanej stronie przy uŜyciu metody getAttribute interfejsu
HttpServletRequest; poniŜej podałem listę tych atrybutów:
• javax.servlet.include.request_uri,
• javax.servlet.include.context_path,
• javax.servlet.include.servlet_path,
• javax.servlet.include.path_info,
• javax.servlet.include.query_string.
Zwróć uwagę, Ŝe takie dołączanie plików nie jest tym samym co niestandardowa metoda
łączenia serwletów w łańcuch udostępniana jako rozszerzenie przez kilka mechanizmów obsługi
serwletów. Metoda ta pozwala, aby kaŜdy z grupy serwletów obsługujących Ŝądania mógł
„zobaczyć” (i zmodyfikować) wyniki wygenerowane przez poprzedni serwlet. Podczas stosowania
metody include interfejsu RequestDispatcher, dołączany zasób nie ma dostępu do wyników
wygenerowanych przez serwlet, do którego było skierowane Ŝądanie. W rzeczywistości, w
specyfikacji serwletów nie ma Ŝadnego standardowego mechanizmu przypominającego łączenie
serwletów w łańcuch.
Zwróć takŜe uwagę, Ŝe ten typ dołączania plików róŜni się od moŜliwości funkcjonalnych
jakie udostępnia dyrektywa include JSP przedstawiona w podrozdziale 12.1. — „Dołączanie
plików w czasie przekształcania strony”. Dyrektywa ta powodowała bowiem umieszczenie w
stronie kodu źródłowego dołączanego pliku, natomiast metoda include interfejsu
RequestDispatcher powoduje dołączenie wyników wykonania wskazanego zasobu. Z drugiej strony,
znacznik akcji jsp:include omawiany w podrozdziale 12.2. (pt.: „Dołączanie plików podczas
obsługi Ŝądań”) działa podobnie do omawianej tu metody include, z tą róŜnicą, iŜ moŜna go
stosować wyłącznie w stronach JSP (w serwletach jest niedostępny).
284
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
<BODY BGCOLOR="#FDF5E6">
<H1 ALIGN="CENTER">Prezentacja wyników wykonania stron JSP i serwletów</H1>
Podaj względny adres URL o postaci /ścieŜka/nazwa i, opcjonalnie,
wszelkie dodatkowe dane dołączane do adresu URL przy przesyłaniu
Ŝądania typu GET. W wyniku wykonania serwletu zostaną wyświetlone
nieprzetworzone dane wygenerowane przez podany zasób (zazwyczaj
stronę JSP lub serwlet). Ograniczenie: podany zasób nie moŜe generować
wyników zawierających znacznik <CODE></TEXTAREA></CODE>,
a dołączanie danych do Ŝądania GET działa tylko w mechanizmach
obsługi serwletów zgodnych ze specyfikacją Java Servlet 2.2.
<FORM ACTION="/servlet/coreservlets.ShowPage">
<CENTER>
URL:
<INPUT TYPE="TEXT" NAME="url" SIZE=50 VALUE="/"><BR>
Dane GET:
<INPUT TYPE="TEXT" NAME="data" SIZE=50><BR><BR>
<Input TYPE="SUBMIT" VALUE="Wyświetl wyniki">
</CENTER>
</FORM>
</BODY>
</HTML>
286
wyniki pewnego typu, a przekazuje je dalej wyłącznie w sytuacji gdy otrzyma nieprawidłowe lub
nieoczekiwane dane.
Przesłanie Ŝądania do serwletu a nie do strony JSP nie wymaga jakichkolwiek modyfikacji
w sposobie wykorzystania metod interfejsu RequestDispatcher. Jednak istnieje specjalne narzędzie
ułatwiające przekazywanie Ŝądań ze stron JSP. Podczas przekazywania Ŝądań z jednej strony JSP
na drugą, znacznie łatwiej jest posłuŜyć się znacznikiem akcji jsp:forward niŜ tworzyć skryptlet
wykorzystujący metodę forward interfejsu RequestDispatcher. Znacznik jsp:forward ma
następującą postać:
<jsp:forward page="względnyAdresURL" />
Atrybut page moŜe zawierać wyraŜenie JSP, dzięki czemu adres strony docelowej moŜe być
określany w momencie obsługi Ŝądania. Przedstawiony poniŜej, przykładowy fragment kodu
kieruje około połowy uŜytkowników na stronę http://host/przyklad/strona1.jsp, a pozostałą część na
stronę http://host/przyklad/strona2.jsp.
<% String adresDocelowy;
if (Math.random() > 0.5) {
adresDocelowy = "/przykład/strona1.jsp";
} else {
adresDocelowy = "/przykład/strona2.jsp";
}
%>
<jsp:forward page="<%= adresDocelowy %>" />
Rozdział 16.
Formularze HTML
W tym rozdziale omówię zastosowanie formularzy HTML jako interfejsu uŜytkownika
słuŜącego do obsługi serwletów lub innych programów działających po stronie serwera. Formularze
te udostępniają proste i niezawodne elementy sterujące przeznaczone do pobierania danych od
uŜytkowników i przesyłanie ich na serwer. W rozdziale przedstawię takŜe zagadnienia związane z
wykorzystanie apletów jako interfejsu uŜytkownika słuŜącego do posługiwania się serwletami.
Wykorzystanie apletów w takim celu wymaga znacznie większego nakładu pracy, a co więcej,
moŜliwości apletów są ograniczane zasadami bezpieczeństwa. Niemniej jednak, aplety pozwalają
na tworzenie znacznie bogatszych interfejsów uŜytkownika i oferują efektywne i elastyczne
moŜliwości komunikacji sieciowej.
Jeśli chcesz korzystać z formularzy, będziesz musiał wiedzieć gdzie naleŜy umieścić
dokumenty HTML, aby serwer WWW miał do nich dostęp. Konkretne nazwy katalogów zaleŜą od
uŜywanego serwera; i tak, w przypadku serwerów Tomcat 3.0 oraz JSWDK dokumenty HTML
umieszczane są w katalogu katalog_instalacyjny/webpages/sciezka/plik.html, a z poziomu WWW
naleŜy się do nich odwoływać przy uŜyciu adresu http://localhost/sciezka/plik.html (jeśli korzystasz
z serwera, który nie działa na lokalnym komputerze, to localhost w powyŜszym adresie naleŜ
zastąpić poprawną nazwą komputera).
Łańcuch ten moŜe zostać przesłany na serwer na dwa sposoby. Pierwszy z nich polega na
wykorzystaniu metody GET protokołu HTTP. W tym przypadku informacje podane w formularzu są
poprzedzane znakiem pytajnika i dopisywane na końcu podanego adresu URL. Drugim sposobem
jest wykorzystanie metody POST. W tym przypadku, na serwer są kolejno przesyłane: wiersz Ŝądania
HTTP (POST), nagłówki Ŝądania HTTP, pusty wiersz, a następnie łańcuch znaków zawierający
informacje wpisane w formularzu.
Na listingu 16.1 przedstawiłem prosty, przykładowy formularz zawierający dwa pola
tekstowe (patrz rysunek 16.1). Elementy HTML tworzące ten formularz zostaną szczegółowo
omówione w dalszej części rozdziału. Jak na razie powinieneś jednak zwrócić uwagę na kilka
spraw. Po pierwsze, jedno z pól ma nazwę firstName, a drugie lastName. Po drugie, elementy
290
sterujące formularzy są uwaŜane za elementy wpisane (tekstowe) języka HTML, a zatem konieczne
będzie zastosowanie specjalnych sposobów formatowania, aby zapewnić ich odpowiednie
połoŜenie względem opisującego je tekstu. I ostatnia sprawa — zwróć uwagę, iŜ nasz przykładowy
formularza określa, iŜ program przeznaczony do obsługi danych ma adres
http://localhost:8088/Program.
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Przykład formularza uŜywającego metody GET</H2>
<FORM ACTION="http://localhost:8088/Program">
<CENTER>
Imię:
<INPUT TYPE="TEXT" NAME="firstName" VALUE="Janek"><BR>
Nazwisko:
<INPUT TYPE="TEXT" NAME="lastName" VALUE="Hacker"><P>
<INPUT TYPE="SUBMIT" VALUE="Prześlij formularz">
<!-- Kliknij ten przycisk aby wysłać formularz -->
</CENTER>
</FORM>
</BODY>
</HTML>
Rysunek 16.2 śądanie HTTP wygenerowane przez przeglądarkę Netscape Navigator 4.7 po
przesłaniu formularza zdefiniowanego na stronie GetForm.html
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Przykład formularza uŜywającego metody POST</H2>
<FORM ACTION="http://localhost:8088/SomeProgram"
METHOD="POST">
<CENTER>
Imię:
<INPUT TYPE="TEXT" NAME="firstName" VALUE="Janek"><BR>
Nazwisko:
<INPUT TYPE="TEXT" NAME="lastName" VALUE="Hacker"><P>
<INPUT TYPE="SUBMIT" VALUE="Prześlij formularz">
</CENTER>
</FORM>
</BODY>
</HTML>
292
Rysunek 16.4 śądanie HTTP wygenerowane przez przeglądarkę Netscape Navigator 4.7 po
przesłaniu formularza zdefiniowanego na stronie PostForm.html
W dalszej części tego podrozdziału opiszę poszczególne atrybuty elementu FORM — ACTION,
METHOD, ENCTYPE, TARGET, ONSUBMIT, ONRESET, ACCEPT oraz ACCEPT-CHARSET. Zwróć uwagę, iŜ nie będę
opisywał atrybutów takich jak STYLE, CLASS oraz LANG, które moŜna stosować we wszystkich
elementach języka HTML, a jedynie atrybuty charakterystyczne dla elementu FORM.
ACTION
Atrybut ACTION określa adres URL serwletu lub programu CGI, który zostanie uŜyty do
przetworzenia informacji wpisanych w formularzu (na przykład: http://cgi.whitehouse.gov/
bin/schedule-fund-raiser) lub adres poczty elektronicznej na jaki zostaną one przesłane (na
przykład: mailto:[email protected]). Niektórzy dostawcy usług internetowych nie pozwalają
zwyczajnym uŜytkownikom na tworzenie serwletów i programów CGI lub pobierają za ten
przywilej dodatkowe opłaty. W takich przypadkach, jeśli musisz gromadzić informacje podawane
w formularzu lecz nie chcesz zwracać Ŝadnych wyników (na przykład potwierdzenia przyjęcia
zamówienia), przesyłanie danych pocztą elektroniczną jest wygodnym rozwiązaniem. Jeśli
informacje wpisane w formularzu mają być przesłane pod wskazany adres poczty elektronicznej,
konieczne jest uŜycie metody POST (patrz kolejny punkt, poświęcony atrybutowi METHOD).
METHOD
Atrybut METHOD określa w jaki sposób informacje zostaną przesłane na serwer. W przypadku
uŜycia metody GET są one poprzedzane znakiem zapytani i dopisywane do adresu URL podanego w
294
atrybucie ACTION. Przykład wykorzystania tej metody przedstawiłem w podrozdziale 16.1. — „Jak
przesyłane są dane z formularzy HTML”. Jest to standardowa metoda przesyłania informacji
podawanych w formularzach HTML, wykorzystywana przez przeglądarki takŜe podczas pobierania
zwyczajnych stron WWW. W przypadku wykorzystania metody POST, informacje z formularza są
przesyłane w osobnym wierszu.
Wykorzystanie metody GET ma dwie zalety. Po pierwsze, jest ona prostsza. A po drugie, a w
przypadku pisania serwletów pobierających dane tą metodą, uŜytkownicy mogą je testować bez
konieczności tworzenia formularzy — wystarczy podać URL serwletu i dopisać do niego dane.
Jednak z drugiej strony, ze względu na ograniczenia długości adresu URL jakie narzucają niektóre
przeglądarki, uŜycie metody GET ogranicza wielkość danych jakie mogą być przesyłane. W
przypadku stosowania metody POST, wielkość przesyłanych informacji nie jest niczym ograniczona.
Kolejną wadą metody GET jest to, iŜ przewaŜająca większość przeglądarek wyświetla adres URL —
w tym takŜe dołączone do niego dane pochodzące z formularza — w polu adresowym
umieszczonym u góry okna programu. Z tego względu metoda GET zupełnie nie nadaje się do
przesyłania waŜnych i poufnych informacji, zwłaszcza jeśli Twój komputer znajduje się w ogólnie
dostępnym miejscu.
ENCTYP
Ten atrybut określa sposób w jaki informacje podane w formularzu zostaną zakodowane
przed ich przesłaniem na serwer. Domyślnie uŜywanym sposobem kodowania jest application/x-
www-form-urlencoded. Metoda ta polega na zamianie znaków odstępu na znaki plusa (+) oraz
wszelkich znaków, które nie są literą bądź cyfrą, na dwie cyfry szesnastkowe reprezentujące
wartość znaku (w kodzie ASCII lub ISO Latin-1) poprzedzone znakiem procentu. Kodowanie to
odbywa się niezaleŜnie od rozdzielenia nazw pól formularza oraz ich wartości znakami równości
(=), a poszczególnych par nazwa-wartość — znakami &.
Na przykład, na rysunku 16.5 przedstawiłem formularz GetForm.html (patrz listing 16.1), w
którym, w pierwszym polu tekstowym został wpisany łańcuch znaków „Marcin (Java hacker?)”.
Analizując wyniki przedstawione na rysunku 16.6., moŜna się przekonać, Ŝe łańcuch ten został
przesłany w postaci: „Marcin+%28Java+hacker%3F%29”. Dlaczego akurat tak? PoniewaŜ odstępy
zostały zamienione na znaki plusa, 28 to zapisana szesnastkowo wartość kodu ASCII nawiasu
otwierającego, 3F to wartość kod ASCII znaku zapytania, a 29 to wartość kodu ASCII nawiasu
zamykającego.
Rysunek 16.6 śądania HTTP wygenerowane przez Internet Explorera 5.0 podczas
przesyłania formularza GetForm.html z danymi przedstawionymi na rysunku 16.5.
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">UŜycie ENCTYPE="multipart/form-data"</H2>
<FORM ACTION="http://localhost:8088/Program"
ENCTYPE="multipart/form-data">
<CENTER>
Imię:
<INPUT TYPE="TEXT" NAME="firstName" VALUE="Janek"><BR>
Nazwisko:
<INPUT TYPE="TEXT" NAME="lastName" VALUE="Hacker"><P>
296
</BODY>
</HTML>
Nowa wersja formularza oraz wyniki jej przesłania na serwer zostały przedstawione
odpowiednio na rysunkach 16.7 oraz 16.8.
Rysunek 16.8 śądania HTTP wygenerowane przez przeglądarkę Netscape Navigator 4.7
podczas przesyłania formularza MultipartForm.html
TARGET
Atrybut TARGET jest wykorzystywany przez przeglądarki obsługujące ramki, w celu
określenia w jakiej ramce mają zostać wyświetlone wyniki wygenerowane przez serwlet lub inny
297 Rozdział 16. Formularze HTML
program przeznaczony do obsługi informacji podanych w formularzu. Domyślna wartość tego
atrybutu powoduje, Ŝe wyniki zostaną wyświetlone w tej samej ramce, w której był wyświetlony
formularz.
Pola tekstowe
Element HTML: <INPUT TYPE="TEXT" NAME="..." ...>
(brak znacznika zamykającego)
Atrybuty: NAME (wymagany), VALUE, SIZE, MAXLENGTH, ONCHANGE, ONSELECT, ONFOCUS, ONBLUR,
ONKEYDOWN, ONKEYPRESS, ONKEYUP
Ten element tworzy pole tekstowe składające się z jednego wiersza, w którym uŜytkownicy
mogą wpisywać dowolne łańcuchy znaków (patrz rysunki 16.1, 16.2 oraz 16.3). Aby stworzyć
wielowierszowe pole tekstowe, naleŜy się posłuŜyć elementem TEXTAREA opisanym w dalszej części
rozdziału. TEXT jest domyślną wartością atrybutu TYPE elementów INPUT, choć zaleca się, aby
wartość tego atrybutu określać jawnie. NaleŜy pamiętać, iŜ wewnątrz elementu FORM przeglądarki
wykorzystują standardowe sposoby przenoszenia wyrazów; a zatem naleŜ uwaŜać, aby tekst opisu
nie został oddzielony od opisywanego pola tekstowego.
Metoda
UŜyj jawnych konstrukcji języka HTML, aby zgrupować pola tekstowe z ich opisami.
Niektóre przeglądarki wysyłają formularz w momencie gdy kursor znajduje się w polu
tekstowym a uŜytkownik naciśnie klawisz Enter. Nie naleŜy jednak polegać na tym sposobie
298
działania, gdyŜ nie jest on standardowy. Na przykład, przeglądarka Netscape Navigator przesyła
formularza gdy uŜytkownik naciśnie klawisz Enter, wyłącznie jeśli formularz wypełniany w danej
chwili zawiera jedno pole tekstowe i niezaleŜnie od ilości formularzy umieszczonych na danej
stronie WWW. Internet Explorer przesyła formularz tylko wtedy, gdy na stronie jest jeden
formularz, lecz niezaleŜnie od ilości umieszczonych w nim pól tekstowych. Z kolei przeglądarka
Mosaic wysyła formularz wyłącznie gdy kursor znajduje się w ostatnim polu tekstowym na całej
stronie.
OstrzeŜenie
Nie polegaj na moŜliwości wysyłania formularzy po naciśnięciu klawisza Enter, gdy kursor znajduje się w polu
tekstowym. Zawsze powinieneś stosować przycisk lub mapę odnośników, której kliknięcie spowoduje jawne wysłanie
formularza.
W dalszej części tego podrozdziału opiszę atrybuty charakterystyczne dla pól tekstowych;
nie będę tu przedstawiał atrybutów stosowanych we wszystkich elementach HTML (takich jak
STYLE, CLASS, czy teŜ ID). Atrybut TABINDEX, stosowany we wszystkich elementach formularzy,
przedstawię w podrozdziale 16.11. — „Określanie kolejności poruszania się pomiędzy elementami
formularzy”.
NAME
Atrybut NAME identyfikuje dane pole tekstowe podczas przesyłania informacji
wprowadzonych w formularzu na serwer. W standardowym języku HTML atrybut ten jest
wymagany. PoniewaŜ informacje z formularzy są przesyłane na serwer w formie par nazwa-
wartość, jeśli nazwa elementu kontrolnego nie zostanie podana, to Ŝadne informacje z tego pola nie
będą przesłane.
VALUE
Jeśli atrybut VALUE zostanie podany, to będzie on określać początkową zawartość pola
tekstowego. W momencie wysyłania formularza, wysyłana jest zawsze bieŜąca zawartość pola;
mogą to być informacje wpisane przez uŜytkownika. Jeśli w momencie wysyłania formularza pole
tekstowe będzie puste, to para nazwa-wartość przybierze postać samej nazwy pola oraz znaku
równości (na przykład: inne-dane&nazwaPolaTekstowego=&inne-dane).
SIZE
Ten atrybut określa szerokość pola tekstowego, obliczaną na podstawie średniej szerokości
znaków aktualnie uŜywanej czcionki. Jeśli w polu zostanie wpisanych więcej liter niŜ moŜna
wyświetlić, tekst zostanie odpowiednio przesunięty. Sytuacja taka moŜe zaistnieć gdy uŜytkownik
wpisze w polu więcej liter niŜ wynosi wartość atrybut SIZE lub, w przypadku stosowania czcionki
proporcjonalnej, gdy wpisze tyle liter ile wynosi wartość tego atrybutu lecz są to szerokie litery
(takie jak duŜa litera „W”). Przeglądarka Netscape Navigator automatycznie uŜywa w polach
tekstowych czcionki proporcjonalnej. Niestety Internet Explorer tego nie robi, a czcionkę jaka
zostanie uŜyta w polu tekstowym moŜna określić umieszczając element INPUT wewnątrz elementów
FONT lub CODE.
MAXLENGTH
Atrybut MAXLENGTH określa maksymalną ilość znaków jaką moŜna wpisać w polu tekstowym.
Wartość ta nie ma nic wspólnego z ilością znaków wyświetlanych w polu, określaną przy uŜyciu
atrybutu SIZE.
Pola hasła
Element HTML: <INPUT TYPE="PASSWORD" NAME="..." ...>
(brak znacznika zamykającego)
Atrybuty: NAME (wymagany), VALUE, SIZE, MAXLENGTH, ONCHANGE, ONSELECT, ONFOCUS, ONBLUR,
ONKEYDOWN, ONKEYPRESS, ONKEYUP
Pola haseł są tworzone i stosowane tak jak pola tekstowe, jednak róŜnią się od nich pod tym
względem, iŜ w momencie wpisywania tekstu zamiast podawanych znaków wyświetlany jest jakiś
znak „maskujący” — zazwyczaj gwiazdka („*”, patrz rysunek 16.9). Takie maskowanie informacji
jest przydatne podczas podawania takich informacji jak numery kart kredytowych bądź hasła,
których uŜytkownik nie chciałby pokazywać osobom mogącym przebywać blisko niego. W
momencie wysyłania formularza, wartości pól haseł są przesyłane w postaci zwyczajnego tekstu.
Jak wiadomo informacje przesyłane metodą GET są dodawane do adresu URL, z tego względu
przesyłając dane z formularzy zawierających pola haseł, naleŜy stosować metodę POST, gdyŜ dzięki
temu osoby przebywające koło komputera nie będą w stanie przeczytać jawnie zapisanego hasła,
dopisanego do adresu URL i wyświetlonego w polu adresu u góry okna przeglądarki.
Rysunek 16.9 Pole hasła stworzone przy uŜyciu elementu <INPUT TYPE="PASSWORD"
...>
Metoda
Aby chronić prywatność uŜytkownika, formularze zawierające pola haseł zawsze naleŜy przesyłać przy uŜyciu
metody POST.
HTML są wyświetlane dosłownie; interpretowane są wyłącznie symbole HTML, takie jak <,
>, itd. Wszystkie znaki umieszczane w wielowierszowych polach tekstowych są przed
przesłaniem na serwer kodowane według zasad kodowania URL, chyba Ŝe w formularzu zostanie
wykorzystany inny sposób kodowania, określony przy uŜyciu atrybutu ENCTYPE (patrz podrozdział
16.2. — „Element FORM”). A zatem, odstępy są zamieniane na znaki plus (+), a wszystkie znaki
poza literami i cyframi na kombinacje %XX, gdzie XX to numeryczna wartość kodowanego znaku
zapisana szesnastkowo.
NAME
Ten atrybut określa nazwę pola, która zostanie przesłana na serwer.
ROWS
Atrybut ROWS określa ilość widocznych wiersz tekstu. Jeśli uŜytkownik wpisze w polu więcej
wierszy tekstu, to zostanie w nim wyświetlony pionowy pasek przewijania.
COLS
Atrybut COLS określa widoczną szerokość pola, obliczaną na podstawie średniej szerokości
znaków aktualnie uŜywanej czcionki. Zachowanie pola w przypadku wpisania w nim (w jednym
wierszu) większej ilości znaków niŜ wynosi zawartość atrybutu COLS jest zaleŜne od przeglądarki. I
tak, w Netscape Navigatorze, zostaje dodany poziomy pasek przewijania (choć ten sposób działania
moŜe się zmienić w przypadku uŜycia atrybutu WRAP opisanego poniŜej), natomiast w Internet
Explorerze całe słowo zostaje przeniesione do kolejnego wiersza.
WRAP
Atrybut ten wprowadzony przez firmę Netscape i uwzględniany wyłącznie przez jej
przeglądarki, określa co naleŜy zrobić gdy długość jednego wiersza tekstu przekroczy dopuszczalną
wartość określoną przy uŜyciu atrybutu COLS. Zastosowanie domyślnej wartości tego atrybutu —
OFF — powoduje wyłącznie przenoszenia słów do kolejnego wiersza. W takim przypadku
uŜytkownik moŜe jawnie umieścić w tekście znaki nowego wiersza. UŜycie wartości HARD sprawi,
Ŝe słowa będą przenoszone, a dodane znaki nowego wiersza zostaną przekazane na serwer podczas
przesyłania formularza. I w końcu, uŜycie wartości SOFT sprawi, Ŝe wyrazy będą przenoszone do
następnego wiersza w polu tekstowym, lecz dodatkowe znaki końca wiersza nie będą przesyłane na
serwer.
16.4 Przyciski
W formularzach HTML przyciski są stosowane w dwóch podstawowych celach —
przesyłania formularza oraz przywracania oryginalnych wartości pól podanych w dokumencie
HTML. Przeglądarki, które są w stanie wykonywać programy pisane w języku JavaScript, mogą
takŜe uŜywać przycisków w jeszcze jednym celu — aby wykonywać określony skrypt.
Tradycyjnie przyciski były tworzone przy wykorzystaniu elementu INPUT z atrybutem TYPE o
wartościach SUBMIT, RESET lub BUTTON. W języku HTML 4.0 został wprowadzony dodatkowy
element — BUTTON, jak na razie jest on jednak obsługiwany wyłącznie w Internet Explorerze. Ten
nowy element pozwala na tworzenie przycisków, których etykiety są wyświetlane w kilku
wierszach oraz mogą zawierać obrazy, róŜnego rodzaju czcionki, itp. Ze względu na swoje
moŜliwości, stosowanie tego znacznika jest zalecane w sytuacjach gdy moŜna mieć pewność, Ŝe
uŜytkownicy będą korzystać z przeglądarek, które są w stanie go poprawnie obsłuŜyć (na przykład
w korporacyjnych intranetach). Jak na razie Netscape Navigator nie obsługuje tego znacznika —
przynajmniej wersja 4.7 — a zatem jego zastosowanie naleŜy ograniczyć wyłącznie do witryn
intranetowych, których uŜytkownicy korzystają z Internet Explorera.
OstrzeŜenie
Przeglądarka Netscape Navigator nie obsługuje elementu BUTTON.
Przycisk SUBMIT
Element HTML: <INPUT TYPE="SUBMIT" ...>
(brak znacznika zamykającego)
Atrybuty: NAME, VALUE, ONCLICK, ONDBLCLICK, ONFOCUS, ONBLUR
Po kliknięciu przycisku tego typu formularz jest przesyłany do serwletu lub innego
programu działającego na serwerze i określonego przy uŜyciu atrybutu ACTION elementu FORM. Choć
przesłanie formularza moŜe zostać wyzwolone takŜe w inny sposób (na przykład poprzez kliknięcie
mapy odnośników), to jednak przewaŜająca większość formularzy zostaje wyposaŜonych w
przynajmniej jeden przycisk SUBMIT. Przyciski te, podobnie jak i inne elementy kontrolne
formularzy, są prezentowane w sposób zaleŜny od uŜywanego systemu operacyjnego. Oznacza to,
Ŝe na róŜnych platformach komputerowych, będą wyglądały nieco inaczej. Rysunek 16.11
przedstawia przycisk SUBMIT utworzony w systemie Windows 98 przy uŜyciu znacznika o
następującej postaci:
<INPUT TYPE="SUBMIT">
302
<FORM ACTION="http://localhost:8088/SomeProgram">
<CENTER>
<BUTTON TYPE="SUBMIT">Etykieta jednowierszowa</BUTTON>
<BUTTON TYPE="SUBMIT">Etykieta<BR>wielowierszowa</BUTTON>
<P>
<BUTTON TYPE="SUBMIT">
<B>Etykieta</B> ze <I>zmianami</I> czcionki.
</BUTTON>
<P>
<BUTTON TYPE="SUBMIT">
<IMG SRC="images/Java-Logo.gif" WIDTH=110 HEIGHT=101
ALIGN="LEFT" ALT="Java Cup Logo">
Etykieta<BR>z obrazkiem
</BUTTON>
</CENTER>
</FORM>
</BODY>
</HTML>
Przyciski RESET
Element HTML: <INPUT TYPE="RESET" ...>
(brak znacznika zamykającego)
Atrybuty: NAME, VALUE, ONCLICK, ONDBLCLICK, ONFOCUS, ONBLUR
Przyciski RESET słuŜą do przywracana oryginalnych wartości (czyli wartości określonych
przy uŜyciu atrybutu VALUE) wszystkich elementów kontrolnych formularza. Wartości tych
przycisków nigdy nie są przesyłane na serwer wraz z wartościami pozostałych pól formularzy.
304
VALUE
Atrybut VALUE określa etykietę jaka zostanie wyświetlona na przycisku. Domyślną etykietą
przycisków tego typu jest słowo „Resetuj”.
NAME
PoniewaŜ wartości przycisków typu RESET nie są umieszczane wśród danych
przekazywanych z formularza na serwer, to w standardzie języka HTML nie trzeba określać nazw
tych przycisków. Niemniej jednak w przypadku stosowania skryptów pisanych w języku JavaScript
moŜna stosować atrybutu NAME, aby uprościć sposób odwoływania się do tych elementów.
Przyciski JavaScript
Element HTML: <INPUT TYPE="BUTTON" ...>
(brak znacznika zamykającego)
Atrybuty: NAME, VALUE, ONCLICK, ONDBLCLICK, ONFOCUS, ONBLUR
Element BUTTON jest rozpoznawany wyłącznie przez przeglądarki, które są w stanie
wykonywać skrypty pisane w języku JavaScript. Element ten tworzy przyciski wyglądające tak
samo jak przyciski SUBMIT i RESET i jednocześnie pozwala kojarzyć skrypty z atrybutami ONCLICK,
ONDBLCLICK, ONFOCUS oraz ONBLUR. Para nazwa-wartość skojarzona z takim przyciskiem nie jest
przesyłana na serwer wraz z pozostałymi informacjami podanymi w formularzu. Choć z
przyciskami tego typu moŜna kojarzyć całkowicie dowolny kod, to jednak są one najczęściej
wykorzystywane w celu sprawdzania czy wartości pozostałych pól formularza zostały zapisane
poprawnie, jeszcze zanim zostaną one przesłane na serwer. Na przykład, przedstawiony poniŜej
fragment kodu tworzy przycisk, którego kliknięcie spowoduje wykonanie zdefiniowanej przez
uŜytkownika funkcji sprawdzFormularz:
<INPUT TYPE="BUTTON" VALUE="Sprawdź dane"
onClick="sprawdzFormularz()">
Pola wyboru
Element HTML: <INPUT TYPE="CHECKBOX" NAME="..." ...>
(brak znacznika zamykającego)
Atrybuty: NAME (wymagany), VALUE, CHECKED, ONCLICK, ONFOCUS, ONBLUR
Ten element tworzy parę nazwa-wartość, która zostanie przesłana na serwer wyłącznie
wtedy, gdy podczas przesyłania formularza pole wyboru będzie zaznaczone. Na przykład, poniŜszy
fragment kodu tworzy pole wyboru przedstawione na rysunku 16.14.
<P>
<INPUT TYPE="CHECKBOX" NAME="bezPoczty" CHECKED>
Zaznacz to pole jeśli <I>nie</I> chcesz dostawać
naszego biuletynu.
Warto zauwaŜyć, iŜ tekst opisujący pole wyboru jest zwyczajnym kodem HTML i naleŜy
zwrócić duŜą uwagę, aby zapewnić, Ŝe zostanie on wyświetlony obok pola. Z tego względu, na
samym początku powyŜszego fragmentu kodu został umieszczony znacznik <P>, który zapewnia, Ŝe
pole wyboru nie będzie naleŜało do wcześniejszego akapitu.
Metoda
Akapity tekstu wewnątrz elementów FORM są wypełniane i łamane tak samo jak wszystkie pozostałe akapity
umieszczone na stronie. Nie zapomnij uŜyć odpowiednich znaczników HTML, aby zapewnić, Ŝe elementy kontrolne
formularzy będą wyświetlane wraz z opisującym je tekstem.
NAME
Ten atrybut określa nazwę, która zostanie przesłana na serwer. W standardzie języka HTML
atrybut ten jest wymagany, jednak w przypadku uŜywania pól wyboru wraz ze skryptami pisanymi
w języku JavaScript, staje się on opcjonalny.
VALUE
Atrybut VALUE jest opcjonalny, a jego domyślaną wartością jest on. Przypominasz sobie
zapewne, Ŝe para nazwa-wartość jest przesyłana na serwer wyłącznie wtedy, gdy w momencie
przesyłania dane pole wyboru będzie zaznaczone. W powyŜszym przykładzie, do danych
przesyłanych z formularza zostałby dopisany łańcuch znaków bezPoczy=on, gdyŜ pole jest
zaznaczone; gdyby jednak nie było, to do pozostałych danych nie zostałyby dodane Ŝadne
306
informacje. Z tego powodu, serwlety oraz wszelkie inne programy CGI bardzo często sprawdzają
samą obecność nazwy pola wyboru, zupełnie ignorując jego wartość.
CHECKED
Jeśli atrybut CHECKED zostanie podany w dokumencie HTML, to bezpośrednio po jego
wyświetleniu w przeglądarce, pole wyboru będzie zaznaczone. Jeśli atrybut nie zostanie podany, to
bezpośrednio po wyświetleniu strony, pole wyboru będzie puste.
Przyciski opcji
Element HTML: <INPUT TYPE="RADIO" NAME="..." VALUE="..." ...>
(brak znacznika zamykającego)
Atrybuty: NAME (wymagany), VALUE (wymagany), CHECKED, ONCLICK, ONFOCUS, ONBLUR
Przyciski opcji róŜnią się od pól wyboru tym, iŜ tylko jeden przycisk naleŜący do jakiejś
grupy moŜe być w danej chwili zaznaczony. Grupę tworzą przyciski opcji o identycznej wartości
atrybutu NAME. W danej chwili, tylko jeden przycisk w grupie moŜe być „wciśnięty” — czyli
zaznaczony. Wybór jednego z przycisków powoduje usunięcie zaznaczenia przycisku, który był
wcześniej zaznaczony. Wartość zaznaczonego przycisku jest przesyłana na serwer w raz z
pozostałymi informacjami podanymi w formularzu. Choć z technicznego punktu widzenia
poszczególne przyciski opcji naleŜące do jednej grupy nie muszą być wyświetlane blisko siebie, to
jednak niemal zawsze zaleca się, aby na stronie były one umieszczone tuŜ obok siebie.
PoniŜej przedstawiłem przykład grupy przycisków opcji. PoniewaŜ elementy kontrolne
stanowią część zwyczajnych akapitów tekstu, uŜyłem elementu DL, dzięki któremu poszczególne
przyciski opcji zostaną wyświetlone na wynikowej stronie WWW jeden poniŜej drugiego, a
dodatkowo będą przesunięte względem umieszczonego nad nimi nagłówka. Wygląd poniŜszego
fragmentu strony przedstawiłem na rysunku 16.15. W przypadku przesłania formularza
przedstawionego na tym rysunku, oprócz pozostałych podanych w nim informacji, na serwer
zostałyby przekazane dane o postaci kartaKredytowa=java.
<DL>
<DT>Karta kredytowa:
<DD><INPUT TYPE="RADIO" NAME="kartaKredytowa" VALUE="visa">
Visa
<DD><INPUT TYPE="RADIO" NAME="kartaKredytowa" VALUE="mastercard">
Master Card
<DD><INPUT TYPE="RADIO" NAME="kartaKredytowa" VALUE="java" CHECKED>
Java Smart Card
<DD><INPUT TYPE="RADIO" NAME="kartaKredytowa" VALUE="amex">
American Express
<DD><INPUT TYPE="RADIO" NAME="kartaKredytowa" VALUE="discover">
Discovery
</DL>
307 Rozdział 16. Formularze HTML
Rysunek 16.15 Przyciski opcji
NAME
W odróŜnieniu od atrybutu NAME większości elementów kontrolnych formularzy, w
przypadku przycisków opcji atrybut ten nie musi przybierać unikalnych wartości. Wszystkie
przyciski opcji które mają tę samą wartość atrybutu NAME są grupowane logicznie, tak iŜ w danej
chwili tylko jeden z nich moŜe być zaznaczony. NaleŜy zwrócić uwagę, iŜ w wartościach tego
atrybutu uwzględniana jest wielkość liter, a zatem, w poniŜszym fragmencie kodu zostały
zdefiniowane dwa przyciski opcji, które nie są ze sobą logicznie powiązane:
<INPUT TYPE="RADIO" NAME="Grupa1" VALUE="wartosc1">
<INPUT TYPE="RADIO" NAME="GRUPA1" VALUE="wartosc1">
OstrzeŜenie
Upewnij się, Ŝe wartości atrybutu NAME we wszystkich przyciskach opcji naleŜących do tej samej grupy logicznej, są
identyczne (takŜe pod względem wielkości liter).
VALUE
Atrybut VALUE określa wartość przycisku opcji, która podczas przesyłania formularza
zostanie przekazana na serwer wraz z jego nazwą (wartością atrybutu NAME). Wartość tego atrybutu
nie ma Ŝadnego wpływu na sposób prezentacji przycisku opcji — przyciski opcji, podobnie jak pola
wyboru, są umieszczane wewnątrz zwyczajnego tekstu lub kodu HTML.
CHECKED
Jeśli ten atrybut zostanie podany, to bezpośrednio po wyświetleniu strony WWW, dany
przycisk opcji będzie zaznaczony. W przeciwnym przypadku, po wyświetleniu strony przycisk
opcji nie będzie zaznaczony.
Specyfikacja języka HTML 4.0 sugeruje, aby stosować element OPTGROUP (posiadający jeden
atrybut — LABEL). Element ten słuŜy do grupowania elementów OPTION i pozwala na tworzenie list
hierarchicznych. Jak na razie jednak ani Netscape Navigator ani Internet Explorer nie obsługują
tego elementu.
308
Element SELECT tworzy listę bądź listę rozwijaną z której uŜytkownik moŜe wybrać jedną
lub kilka spośród przedstawionych opcji. KaŜda z opcji definiowana jest przy uŜyciu elementu
OPTION zapisywanego pomiędzy znacznikami <SELECT> i </SELECT>.
NAME
Atrybut NAME określa nazwę listy, która zostanie przekazana do serwletu bądź innego
programu CGI.
SIZE
Ten atrybut określa ilość wierszy jakie będą widoczne na danej liście. Jeśli wartość tego
atrybutu zostanie podana, to element SELECT będzie zazwyczaj przedstawiany w formie listy a nie
listy rozwijanej. Elementy SELECT są przedstawiane w formie list rozwijanych gdy nie został podany
atrybut MULTIPLE ani wartości atrybutu SIZE.
MULTIPLE
Atrybut MULTIPLE określa, Ŝe jednocześnie moŜna zaznaczyć wiele opcji danej listy. Jeśli
atrybut ten nie zostanie podany, to na liście będzie moŜna zaznaczyć wyłącznie jedną opcję.
Elementy OPTION moŜna umieszczać wyłącznie wewnątrz elementów SELECT; określają one
opcje list.
VALUE
Atrybut VALUE określa wartość jaka zostanie przesłana wraz z nazwą listy (wartością
atrybuty NAME elementu SELECT), jeśli dana opcja zostanie zaznaczona. Atrybut ten nie określa tekstu
danej opcji, który będzie wyświetlony na liście; tekst ten jest podawany za znacznikiem OPTION.
SELECTED
Jeśli atrybut ten zostanie podany, to bezpośrednio po wyświetleniu strony odpowiednia
opcja list będzie zaznaczona.
Kolejny przykład przedstawia element SELECT wyświetlony w formie zwyczajnej listy. Jeśli
podczas wysyłania formularza na liście będzie zaznaczony więcej niŜ jeden element, to
przekazanych zostanie więcej niŜ jedna wartość (przy czym kaŜda z tych wartości zostanie podana
w odrębnej parze nazwa-wartość, a w kaŜdej z tych para nazwa będzie taka sama). Na przykład, w
przykładzie przedstawionym na rysunku 16.18 do informacji przesyłanych na serwer zostanie
dodany łańcuch znaków language=java&language=perl. To właśnie ze względu na moŜliwość
przesyłania wielu par nazwa-wartość o tej samej nazwie, autorzy serwletów muszą znać nie tylko
popularną metodę getParameter, lecz takŜe rzadziej stosowaną metodę getParameterValues
interfejsu HttpServletRequest. Szczegółowe informacje na temat tych metod znajdziesz w
rozdziale 3. — „Obsługa Ŝądań: Dane przesyłane z formularzy”.
Ulubiony język programowania:
<SELECT NAME="language" MULTIPLE>
<OPTION VALUE="c">C
<OPTION VALUE="c++">C++
<OPTION VALUE="java" SELECTED>Java
<OPTION VALUE="lisp">Lisp
<OPTION VALUE="perl">Perl
<OPTION VALUE="smalltalk">Smaltalk
</SELECT>
Rysunek 16.18 Elementy SELECT w których określono wartość atrybutu SIZE lub
uŜyto atrybutu MULTIPLE są prezentowane w formie zwyczajnych list
310
UŜycie tego elementu kontrolnego spowoduje wyświetlenie na stronie pola tekstowego oraz
przycisku Przeglądaj. UŜytkownicy mogą podać pełną ścieŜkę dostępu do przesyłanego pliku
bezpośrednio w polu tekstowym, lub kliknąć przycisk Przeglądaj, aby wyświetlić okno dialogowe
pozwalające interaktywnie określić połoŜenie pliku. Jeśli w elemencie FORM został umieszczony
atrybut ENCTYPE o wartości multipart/form-data, to w momencie wysyłania formularza, na serwer
zostanie przesłana zawartość wybranego pliku. Element ten moŜna z powodzeniem stosować przy
tworzeniu stron słuŜących do pomocy uŜytkownikom, na których mogą oni podać opis napotkanych
problemów a wraz z nim przesłać dodatkowe dane lub pliki konfiguracyjne.
Podpowiedź
W formularzach wykorzystujących elementy kontrolne umoŜliwiające przesyłanie plików na serwer, zawsze naleŜy
stosować atrybut ENCTYPE o wartości multipart/form-data.
NAME
Atrybut NAME identyfikuje element kontrolny podczas przesyłania informacji z formularza na
serwer.
VALUE
Ze względów bezpieczeństwa ten atrybut jest ignorowany. Wyłącznie końcowi uŜytkownicy
mogą podawać nazwy plików.
ACCEPT
Ten atrybut umoŜliwia podanie listy typów MIME słuŜącej do ograniczenia typów plików
wyświetlanych w oknie dialogowym; poszczególne typy MIME podawane na tej liście są od siebie
oddzielane przecinkami. Większość przeglądarek nie obsługuje tego atrybutu.
NAME
Atrybut NAME identyfikuje element podczas przesyłania na serwer informacji podanych w
formularzu.
SRC
Atrybut SRC określa adres URL obrazu, który będzie wyświetlony jako mapa odnośników.
ALIGN
Atrybut ALIGN moŜe przybierać te same wartości co analogiczny atrybut elementu IMG i jest
stosowany w identyczny sposób (moŜliwe wartości tego atrybutu to: TOP, MIDDLE, BOTTOM, LEFT oraz
RIGHT, przy czym wartością domyślną jest BOTTOM).
Listing 16.5 przedstawia prostą stronę WWW zawierającą formularz, którego atrybut ACTION
(poprzez część adresu URL określającą komputer i numer portu) odwołuje się do programu
EchoServer. Program ten przedstawię w podrozdziale 16.12. Rysunek 16.21 przedstawia wygląd
strony WWW, a rysunek 16.22 wyniki wygenerowane po kliknięciu obrazka.
<BODY>
<H1 ALIGN="CENTER">Element kontrolny IMAGE</H1>
Która z tych wysp to Jawa? Kliknij i sprawdź czy masz rację.
<FORM ACTION="http://localhost:8088/TestGeograficzny">
<INPUT TYPE="IMAGE" NAME="map" SRC="images/indonesia.gif"
BORDER="0">
</FORM>
</BODY>
</HTML>
313 Rozdział 16. Formularze HTML
Rysunek 16.21 Element kontrolny IMAGE którego atrybut NAME ma wartość "map"
<BODY>
</BODY>
</HTML>
Ten element przechowuje nazwę oraz wartość, lecz w przeglądarce nie jest prezentowany w
widocznej postaci. Para nazwa-wartość jest dodawana do pozostałych informacji podczas
przesyłania formularza. W poniŜszym przykładzie, za kaŜdym razem gdy zostanie wysłany
formularz, do przesyłanych informacji zostanie dodany łańcuch znaków IDtowaru=agd111.
<INPUT TYPE="HIDDEN" NAME="IDtowaru" VALUE="agd111">
Zwróć uwagę, iŜ termin „hidden”13, nie oznacza wcale, Ŝe uŜytkownik nie będzie mógł
dowiedzieć się o istnieniu pola — będzie ono bowiem widoczne w kodzie źródłowym strony. Nie
istnieje Ŝaden bezpieczny sposób „ukrywania” kodu HTML strony i z tego względu nie zaleca się
przechowywania w polach ukrytych waŜnych informacji, takich jak hasła.
Ten element jest stosowany jako swoisty „pojemnik”, w którym moŜna umieszczać
elementy kontrolne formularzy oraz, ewentualnie, element LEGEND. Nie posiada on Ŝadnych
atrybutów, za wyjątkiem tych, które moŜna stosować we wszystkich znacznikach HTML, czyli
STYLE, LANGUAGE, itd. Kod strony wykorzystującej te elementy przedstawiłem na listingu 16.7, a jej
wygląd w przeglądarce Internet Explorer — na rysunku 16.25.
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Grupowanie elementów kontrolnych w Internet Explorerze</H2>
<FORM ACTION="http://localhost:8088/SomeProgram">
<FIELDSET>
<LEGEND>Groupa pierwsza</LEGEND>
Pole 1A: <INPUT TYPE="TEXT" NAME="pole1A" VALUE="Pole A"><BR>
Pole 1B: <INPUT TYPE="TEXT" NAME="pole1B" VALUE="Pole B"><BR>
13
ang.: ukryty
317 Rozdział 16. Formularze HTML
Pole 1C: <INPUT TYPE="TEXT" NAME="pole1C" VALUE="Pole C"><BR>
</FIELDSET>
<FIELDSET>
<LEGEND ALIGN="RIGHT">Groupa druga</LEGEND>
Pole 2A: <INPUT TYPE="TEXT" NAME="pole2A" VALUE="Pole A"><BR>
Pole 2B: <INPUT TYPE="TEXT" NAME="pole2B" VALUE="Pole B"><BR>
Pole 2C: <INPUT TYPE="TEXT" NAME="pole2C" VALUE="Pole C"><BR>
</FIELDSET>
</FORM>
</BODY>
</HTML>
ALIGN
Ten atrybut określa połoŜenie etykiety. Wartości, które moŜe on przybierać to: TOP, BOTTOM,
LEFT oraz RIGTH, przy czym wartością domyślną jest TOP. Na rysunku 16.25 etykieta pierwszej grupy
elementów kontrolnych została wyświetlona w domyślnym połoŜeniu, natomiast druga została
przesunięta na prawo przy uŜyciu atrybutu ALIGN="RIGHT". Język HTML udostępnia takŜe inny
sposób określania połoŜenia etykiet, który często jest lepszy od atrybutu ALIGN — są nim arkusze
stylów. Dzięki nim moŜna w jednym miejscu dokumentu HTML określić opcje prezentacji, które
będą dotyczyły wielu elementów strony.
318
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Kontrola przechodzenia pomiędzy elementami</H2>
<FORM ACTION="http://localhost:8088/SomeProgram">
Pole 1 (wybierane jako pierwsze):
<INPUT TYPE="TEXT" NAME="field1" TABINDEX=1><BR>
Pole 2 (wybierane jako drugie):
<INPUT TYPE="TEXT" NAME="field2" TABINDEX=3><BR>
Pole 3 (wybierane jako trzecie):
<INPUT TYPE="TEXT" NAME="field3" TABINDEX=2><BR>
</FORM>
</BODY>
</HTML>
EchoServer
Listing 16.9 przedstawia kod źródłowy głównej klasy programu EchoServer. Zazwyczaj
program będzie uruchamiany z poziomu wiersza poleceń systemu, przy czym naleŜy jawnie podać
numer portu, na którym program ma działać, bądź uŜyć domyślnego portu 8088. Serwer moŜe
odczytywać powtarzane Ŝądania przesyłane przez klientów i zwracać strony WWW zawierające
wszystkie informacje przesłane w Ŝądaniu HTTP. W większości przypadków serwer odczytuje
informacje do momentu napotkania pustego wiersza, który oznacza koniec Ŝądań GET, POST oraz
większości innych typów Ŝądań HTTP. Jednak w przypadku Ŝądań POST, serwer określa wartość
nagłówka Ŝądania Content-Length, a następnie odczytuje określoną w ten sposób ilość bajtów po
pustym wierszu.
}
new EchoServer(port, 0);
}
ThreadedEchoServer
Listing 16.10 przedstawia wielowątkową wersję serwera EchoServer, przydatną w
sytuacjach gdy serwer musi przyjmować i obsługiwać wiele, jednocześnie nadsyłanych Ŝądań
HTTP.
NetworkServer
Listingi 16.11 oraz 16.12 przedstawiają kilka klas pomocniczych, ułatwiających
komunikację sieciową. Klasy te wykorzystuje program EchoServer.
Jednak gdy przeglądarka przesyła dane, są one zakodowane w formacie URL, co oznacza, Ŝe
odstępy są zamieniane na znaki plusa (+), a wszystkie pozostałe znaki, za wyjątkiem liter i cyfr, na
kombinacje znaku procenta (%) oraz dwucyfrowej liczby szesnastkowej określającej wartość danego
znaku (więcej informacji na ten temat znajdziesz w podrozdziale 16.2. — „Element FORM”).
PowyŜszy przykład zakłada, Ŝe dane zostały juŜ poprawnie zakodowane, jeśli jednak nie zostały, to
przedstawiony kod nie będzie działać poprawnie. JDK 1.1 udostępnia klasę URLEncoder definiującą
statyczną metodę encode, która zapisuje podany łańcuch znaków w formacie URL. A zatem, jeśli
aplet kontaktuje się z programem działającym na serwerze, który zazwyczaj otrzymuje dane
przekazywane z formularzy HTML metodę GET, to będzie on musiał zakodować wartości
wszystkich pól, za wyjątkiem znaków równości (=) oddzielającym nazwy pól od ich wartości, oraz
znaków "&" oddzielających poszczególne pary nazwa-wartość. Oznacza to, Ŝe nie moŜna
zakodować wszystkich przesyłanych informacji przy uŜyciu jednego wywołania o postaci
URLEncoder.encode(dane), lecz naleŜy zakodować wyłącznie wartości kaŜdej z par nazwa-wartość.
Czynności te moŜna wykonać w następujący sposób:
String dane =
nazwa1 + "=" + URLEncoder.encode(wartosc1) + "&" +
nazwa2 + "=" + URLEncoder.encode(wartosc2) + "&" +
...
nazwaN + "=" + URLEncoder.encode(wartoscN);
try {
URL programURL = new URL(bazowyURL + "?" + dane);
getAppletContext().showDocument(programURL);
} catch (MalformedURLException mue) { ... }
14
Autor ma tu na myśli metodę showDocument interfejsu AppletContext. Obiekt AppletContext dla danego
apletu moŜna pobrać przy uŜyciu metody getAppletContext klasy Applet. (przyp. tłum.)
327 Rozdział 17. UŜycie apletów jako interfejsu uŜytkownika dla serwletów
serwisów wyszukiwawczych. Klasy tej moŜna takŜe uŜyć przy tworzeniu apletów. Listing 17.1
przedstawia aplet wyświetlający pole tekstowe słuŜące do pobierania informacji od uŜytkowników.
Kiedy uŜytkownik zaŜąda przesłania danych, aplet koduje zawartość pola tekstowego w formacie
URL a następnie generuje trzy róŜne adresy URL i dołącza do nich zakodowane informacje.
Wygenerowane adresy URL odwołują się do trzech serwisów wyszukiwawczych — Google,
Go.com15 oraz Lycos. Następnie aplet uŜywa metody showDocument, aby nakazać przeglądarce
wyświetlenie tych trzech adresów URL w trzech róŜnych ramkach. Wygląd apletu oraz wyniki jego
działania przedstawiłem na rysunkach 17.1 oraz 17.2. Przy tworzeniu takiej aplikacji nie moŜna
wykorzystać formularzy HTML, gdyŜ umoŜliwiają one przesłanie danych tylko pod jeden adresu
URL.
15
dawniej Infoseek
328
<FRAMESET ROWS="120,*">
<FRAME SRC="SearchAppletFrame.html" SCROLLING="NO">
<FRAMESET COLS="*,*,*">
<FRAME SRC="GoogleResultsFrame.html" NAME="results0">
<FRAME SRC="InfoseekResultsFrame.html" NAME="results1">
<FRAME SRC="LycosResultsFrame.html" NAME="results2">
</FRAMESET>
</FRAMESET>
<BODY BGCOLOR="WHITE">
<CENTER>
<APPLET CODE="SearchApplet.class" WIDTH=600 HEIGHT=100>
<B>Ten przykład wymaga przeglądarki obsługującej język Java.</B>
</APPLET>
</CENTER>
</BODY>
</HTML>
2. Stwórz kopię obiektu URLConnection. Obiekt ten zwraca metoda openConnection klasy URL,
a uŜyjemy go do pobrania strumieni wejściowych.
URLConnection polaczenie = daneURL.openConnection();
4. Określ wszelkie dodatkowe nagłówki Ŝądania HTTP, które chcesz wygenerować. Jeśli
chcesz określić nagłówki Ŝądania HTTP (patrz rozdział 4), to moŜesz to zrobić przy uŜyciu
metody setRequestProperty.
polaczenie.setRequestProperty("naglowek", "wartosc");
5. Utwórz strumień wejściowy. Istnieje wiele strumieni, których moŜna uŜyć, jednak
najczęściej jest stosowany strumień BufferedReader. To właśnie podczas tworzenia
331 Rozdział 17. UŜycie apletów jako interfejsu uŜytkownika dla serwletów
strumienia wejściowego, w niewidoczny sposób, jest tworzone połączenie sieciowe z
serwerem WWW.
BufferedReader in =
new BufferedReader(new InputStreamReader(
polaczenie.getInputStream()));
Po stronie klienta
Aby aplet mógł odczytywać serializowane dane przesyłane z serwera, będzie musiał
wykonywać siedem, opisanych poniŜej czynności. Jedynie piąty i szósty punkt poniŜszej procedury
róŜni się od czynności wykonywanych podczas odczytywania danych tekstowych (zapisanych w
kodzie ASCII). Przedstawione poniŜej czynności zostały nieco uproszczone poprzez pominięcie
bloków try/catch, w jakich powinne być zapisane.
1. Stwórz kopię obiektu URL odwołującą się do komputera, z którego aplet został pobrany.
PoniewaŜ uŜyty adres URL musi się odwoływać do komputera z którego aplet został
pobrany, a zatem, takŜe tym razem, najbardziej sensownym rozwiązaniem jest podanie
końcówki adresu i automatyczne określenie jego pozostałych elementów.
URL aktualnaStrona = getCodeBase();
String protokol = aktualnaStrona.getProtocol();
String host = aktualnaStrona.getHost();
int port = aktualnaStrona.getPort();
String urlSuffix = "/servlet/jakisSerwlet";
URL daneURL = new URL(protokol, host, port, urlSuffix);
2. Stwórz kopię obiektu URLConnection. Obiekt ten zwraca metoda openConnection klasy URL,
wykorzystamy go do pobrania strumieni wejściowych.
URLConnection polaczenie = daneURL.openConnection();
4. Określ wszelkie dodatkowe nagłówki Ŝądania HTTP, które chcesz wygenerować. Jeśli
chcesz określić nagłówki Ŝądania HTTP (patrz rozdział 4), to moŜesz to zrobić przy uŜyciu
metody setRequestProperty.
polaczenie.setRequestProperty("naglowek", "wartosc");
6. Odczytaj strukturę danych przy uŜyciu metody readObject. Metoda ta zwraca wartość
typu Object, a zatem będziesz musiał wykonać rzutowanie typów, aby uzyskać obiekt klasy
przesłanej przez serwer.
JakasKlasa wartosc = (JakasKlasa) in.readObject();
zrobCosZ(wartosc);
Po stronie serwera
W celu przesłania serializowanych informacji do apletu, serwlet musi wykonać cztery,
opisane poniŜej czynności. Zakładam, Ŝe zmienne request oraz response zawierają odpowiednio
obiekty HttpServletRequest oraz HttpServletResponse przekazywane jako argumenty wywołania
metod doGet oraz doPost. TakŜe w tym przypadku, prezentowane czynności zostały nieco
uproszczone poprzez pominięcie bloków try/catch, w jakich naleŜy je zapisać w kodzie serwletu.
1. Określ, Ŝe przesyłane są dane binarne. MoŜna to zrobić podając, Ŝe typem MIME
odpowiedzi będzie application/x-java-serialized-object. To standardowy typ MIME
obiektów kodowanych przez strumień ObjectOutputStream, jednak w naszym przypadku nie
odgrywa on szczególnego znaczenia, gdyŜ to aplet, a nie przeglądarka, odczytuje wyniki.
Więcej informacji na temat typów MIME znajdziesz w podrozdziale 7.2. — „Nagłówki
odpowiedzi protokołu HTTP 1.1 oraz ich znaczenie”, w części poświęconej nagłówkowi
Content-Type.
String contentType = "application/x-java-serialized-object";
response.setContentType(contentType);
3. Zapisz strukturę danych przy uŜyciu metody writeObject. W ten sposób moŜna przesłać
większość wbudowanych struktur danych. Jednak abyś mógł przesyłać swoje własne klasy,
muszą one implementować interfejs Serializable. Na szczęście wymóg ten jest bardzo
łatwy do spełnienia, gdyŜ interfejs ten nie definiuje Ŝadnych metod. A zatem, wystarczy
jedynie zadeklarować, Ŝe klasa go implementuje.
JakasKlasa wartosc = new JakasKlasa(...);
out.writeObject(wartosc);
setBackground(Color.white);
setLayout(new BorderLayout());
queryArea = new TextArea();
queryArea.setFont(new Font("Serif", Font.PLAIN, 14));
add(queryArea, BorderLayout.CENTER);
Panel buttonPanel = new Panel();
Font buttonFont = new Font("SansSerif", Font.BOLD, 16);
startButton = new Button("Start");
startButton.setFont(buttonFont);
startButton.addActionListener(this);
buttonPanel.add(startButton);
stopButton = new Button("Stop");
stopButton.setFont(buttonFont);
stopButton.addActionListener(this);
buttonPanel.add(stopButton);
clearButton = new Button("Usuń zapytania");
clearButton.setFont(buttonFont);
clearButton.addActionListener(this);
buttonPanel.add(clearButton);
add(buttonPanel, BorderLayout.SOUTH);
currentPage = getCodeBase();
// ZaŜądaj zbioru przykładowych zapytań. Zostaną
// one pobrane przy wykorzystaniu wątku działającego w tle,
// a przed próbą pobrania łańcuchów znaków aplet sprawdzi
// czy pobieranie danych zostało zakończone.
currentQueries = new QueryCollection(address, currentPage);
nextQueries = new QueryCollection(address, currentPage);
}
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
8. Wygeneruj nagłówek Content-Length. ChociaŜ przesyłając Ŝądania GET nie trzeba uŜywać
tego nagłówka, to jednak przypadku uŜycia Ŝądań POST musi on zostać podany.
polaczenie.setRequestProperty
("Content-Length", String.valueOf(strumienBajtowy.size()));
12. Odczytaj wyniki. Konkretny sposób wykonania tej czynności zaleŜy od rodzaju informacji
przesyłanych z serwera. W przykładzie przedstawionym poniŜej „robimy coś” z kaŜdym
wierszem odczytanych informacji.
String wiersz;
while((wiersz = in.readLine()) != null) {
zrobCosZ(wiersz);
}
13. Pogratuluj sobie. O tak, procedura przesyłania danych metodą POST jest długa i męcząca. Na
szczęście, jest takŜe stosunkowo schematyczna. A poza tym, zawsze moŜesz skopiować
odpowiedni przykład z serwera FTP Wydawnictwa HELION
(ftp://ftp.helion.pl/przyklady/jsjsp.zip).
W kolejnym podrozdziale przedstawiłem przykład apletu przesyłającego dane w powyŜszy
sposób.
currentPage = getCodeBase();
// metoda getHost zwraca pusty łańcuch znaków
// jeśli aplet został pobrany z lokalnego dysku.
String host = currentPage.getHost();
String resultsMessage = "Tutaj zostaną wyświetlone wyniki...";
if (host.length() == 0) {
resultsMessage = "Błąd: musisz pobrać ten aplet z \n" +
"prawdziwego serwera WWW za pośrednictwem HTTP,\n" +
"a nie jako plik z lokalnego dysku.\n" +
"Nawet jeśli serwer działa na Twoim \n" +
"lokalnym komputerze.";
setEnabled(false);
}
hostField.getTextField().setText(host);
inputPanel.add(hostField);
portField =
new LabeledTextField("Port (-1 oznacza domyślny):", 4);
String portString = String.valueOf(currentPage.getPort());
portField.getTextField().setText(portString);
inputPanel.add(portField);
uriField =
new LabeledTextField("URI:", 40);
String defaultURI = "/servlet/coreservlets.ShowParameters";
uriField.getTextField().setText(defaultURI);
inputPanel.add(uriField);
Canvas separator2 = new Canvas();
inputPanel.add(separator2);
sendButton = new Button("Wyślij dane");
sendButton.addActionListener(this);
Panel buttonPanel = new Panel();
buttonPanel.add(sendButton);
inputPanel.add(buttonPanel);
342
add(inputPanel, BorderLayout.NORTH);
resultsArea = new TextArea();
resultsArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
resultsArea.setText(resultsMessage);
add(resultsArea, BorderLayout.CENTER);
}
ByteArrayOutputStream byteStream =
new ByteArrayOutputStream(512); // Powiększ jeśli konieczne
// Strumień zapisuje dane do buforu
PrintWriter out = new PrintWriter(byteStream, true);
String postData =
"firstName=" + encodedValue(firstNameField) +
"&lastName=" + encodedValue(lastNameField) +
"&emailAddress=" + encodedValue(emailAddressField);
BufferedReader in =
new BufferedReader(new InputStreamReader
(connection.getInputStream()));
String line;
String linefeed = "\n";
resultsArea.setText("");
while((line = in.readLine()) != null) {
resultsArea.append(line);
343 Rozdział 17. UŜycie apletów jako interfejsu uŜytkownika dla serwletów
resultsArea.append(linefeed);
}
} catch(IOException ioe) {
// Wyświetl komunikat na konsoli Javy.
System.out.println("IOException: " + ioe);
}
}
}
344
Rysunek 17.4 Wyniki uŜycia serwletu SendPost do przesłania danych metodą POST do
serwletu ShowParameters (przedstawionego w podrozdziale 3.4. — „Przykład: Odczyt wszystkich
parametrów”)
Rysunek 17.5 Wyniki uŜycia serwletu SendPost do przesłania danych metodą POST do
serwera HTTP EchoServer (przedstawionego w podrozdziale 16.12. — „Testowy serwer WWW”)
Załadowanie sterownika
Sterownik to niewielki program, który „wie” jak naleŜy komunikować się z serwerem bazy
danych. Aby załadować sterownik wystarczy jedynie załadować odpowiednią klasę — statyczny
blok kodu wewnątrz niej automatycznie stworzy kopię sterownika i zarejestruje go w narzędziu
zarządzającym sterownikami JDBC — tak zwanym menadŜerze sterowników JDBC. Aby zapewnić
jak największą elastyczność tworzonego kodu, naleŜy unikać podawania „na stałe” nazwy uŜywanej
klasy.
PowyŜsze wymagania skłaniają do zadania dwóch interesujących pytań. Po pierwsze, w jaki
sposób moŜna załadować klasę bez tworzenia jej kopii? A po drugie, jak odwołać się do klasy,
której nazwa nie jest znana podczas kompilacji kodu? Odpowiedź na oba te pytania jest identyczna
— tajemnica tkwi w uŜyciu metody Class.forName. Metoda ta pobiera jeden argument — w pełni
kwalifikowaną nazwę klasy (czyli nazwę klasy wraz z nazwami wszystkich pakietów do których
ona naleŜy), i ładuję tę klasę. Wywołanie tej metody moŜe spowodować zgłoszenie wyjątku
ClassNotFoundException, a zatem naleŜy je umieścić wewnątrz bloku try/catch. Oto przykład
uŜycia tej metody:
try {
Class.forName("connect.microsoft.MicrosoftDriver");
Class.forName("oracle.jdbc.driver.OracleDriver");
Class.forName("com.sybase.jdbc.SybDriver");
} catch (ClassNotFoundException cnfe) {
System.out.println("Błąd podczas ładowania sterownika: " + cnfe);
}
Jedną z najwspanialszych cech JDBC jest to, iŜ zmiana uŜywanego serwera WWW nie
wymaga wprowadzania jakichkolwiek zmian w kodzie. To sterownik JDBC (działający po stronie
klienta) tłumaczy wywołania napisane w języku Java do formatu wymaganego przez serwer bazy
danych. Taki sposób działania oznacza, Ŝe trzeba dysponować odpowiednim sterownikiem,
przeznaczonym do obsługi uŜywanej bazy danych. Informacje na temat w pełni kwalifikowanej
nazwy klasy sterownika, której będziesz mógł uŜywać w swoich programach, powinieneś znaleźć w
jego dokumentacji. Większość firm tworzących bazy danych, udostępnia bezpłatne wersje
sterowników JDBC przeznaczonych do obsługi tych baz; istnieje jednak wiele innych firm, które
takŜe udostępniają sterowniki dla starszych typów baz danych. Aktualną listę wszystkich
dostępnych sterowników moŜna znaleźć na stronie WWW pod adresem
http://java.sun.com/products/jdbc/ drivers.html. Wiele firm podanych na tej liście udostępnia
demonstracyjne wersje sterowników, które zazwyczaj mają ograniczony czas działania lub
narzucają ograniczenia na ilość jednocześnie obsługiwanych połączeń. A zatem, moŜna się nauczyć
JDBC bez konieczności płacenia za sterowniki.
Ogólnie rzecz biorąc, metody Class.forName moŜna uŜywać do załadowania kaŜdej klasy
znajdującej się w katalogach określonych w zmiennej środowiskowej CLASSPATH. Jednak w
praktyce, sterowniki JDBC są zazwyczaj dostarczane w formie plików JAR. A zatem, nie zapomnij
dodać ścieŜki dostępu do tych plików, do zmiennej środowiskowej CLASSPATH na swoim
komputerze.
danych (lub o odwołaniu do niej). Konkretny format zapisu takiego adresu URL będzie podany w
dokumentacji dostarczonej wraz z konkretnym sterownikiem JDBC. PoniŜej podałem dwa typowe
przykłady:
String host = "dbhost.firma.com.pl";
String dbNazwa = "nazwaBazy";
int port = 1234;
String oracleURL = "jdbc:oracle:thin:@" + host +
":" + port + ":" + dbNazwa;
String sybaseURL = "jdbc:sybase:Tds:" + host +
":" + port + ":" + "?SERVICENAME=" + dbNazwa;
Nawiązanie połączenia
Aby nawiązać połączenie sieciowe, w wywołaniu metody getConnection klasy
DriverManager naleŜy podać adres URL, nazwę uŜytkownika bazy danych oraz hasło; tak jak
pokazałem na poniŜszym przykładzie. Zwróć uwagę, iŜ wywołanie metody getConnection moŜe
zgłosić wyjątek SQLException, a zatem naleŜy je zapisać wewnątrz bloku try/catch. W poniŜszym
przykładzie pominąłem instrukcje try oraz catch, gdyŜ metody przedstawione w kolejnych
częściach rozdziału takŜe mogą zgłaszać te same wyjątki i dlatego wszystkie są zazwyczaj
umieszczane w jednym bloku try/catch.
String nazwaUzytkownika = "jarek_debesciak";
String haslo = "tajemne";
Connection polaczenie =
DriverManager.getConnection(oracleURL, nazwaUzytkownika, haslo);
Opcjonalną czynnością jaką moŜna wykonać na tym etapie, jest odszukanie informacji
dotyczących baz danych, przy uŜyciu metody getMetaData klasy Connection. Metoda ta zwraca
obiekt DatabaseMetaData. Udostępniane przez niego metody pozwalają na określenie nazwy oraz
numeru wersji samej bazy danych (słuŜą do tego odpowiednio metody: getDatabaseProductName
oraz getDatabaseProductVersion) lub sterownika JDBC (słuŜą do tego odpowiednio metody
getDriverName oraz getDriverVersion). PoniŜej przedstawiłem stosowny przykład:
DatabaseMetaData dbMetadane = polaczenie.getMetaData();
String nazwaBazy = dbMetadane.getDatabaseProductName();
System.out.println("Baza danych: " + nazwaBazy);
String numerWersji = dbMetadane.getDatabaseProductVersion();
System.out.println("Numer wersji bazy: " + numerWersji;
Inne przydatne metody klasy Connection to: prepareStatement (metoda ta, omówiona w
podrozdziale 18.6, tworzy kopię obiektu PreparedStatement), prepareCall (ta metoda tworzy kopię
obiektu klasy CallableStatement), rollback (metoda odtwarza wszystkie czynności wykonane od
momentu ostatniego wywołania metody commit), commit (metoda zatwierdza wszystkie czynności
wykonane od czasu poprzedniego wywołania tej metody), close (metoda zamyka połączenie) oraz
isClosed (która sprawdza czy połączenie zostało zakończone bądź czy upłynął czas jego waŜności).
349 Rozdział 18. JDBC oraz zarządzanie pulami połączeń
Stworzenie polecenia
Do przesyłania zapytań i poleceń do bazy danych uŜywane są obiekty Statement. Kopię
obiektu Statement moŜna uzyskać wywołując metodę createStatement klasy Connection:
Statement polecenie = polaczenie.createStatement();
Wykonanie zapytania
Dysponując obiektem Statement, moŜna juŜ przesłać zapytanie SQL. Do tego celu
wykorzystywana jest metoda executeQuery, zwracająca obiekt ResultSet. PoniŜej przedstawiłem
stosowny przykład:
String zapytanie = "SELECT kol1, kol2, kol3 FROM tabela";
ResultSet zbiorWynikow = polecenie.executeQuery(zapytanie);
Aby zmodyfikować bazę danych zamiast metody executeQuery, naleŜy uŜyć metody
executeUpdate i podać w jej wywołaniu łańcuch znaków zawierający polecenie SQL UPDATE, INSERT
lub DELETE. Inne przydatne metody klasy Statement to: execute (która wykonuje dowolne
polecenie) oraz setQueryTimeout (która określa maksymalny czas oczekiwania na wyniki). MoŜna
takŜe tworzyć zapytania z parametrami — w takim przypadku wartości są przekazywane do
prekompilowanych zapytań, o ściśle określonej postaci. Więcej informacji na ten temat znajdziesz
w podrozdziale 18.6.
Przetworzenie wyników
Najprostszym sposobem obsługi wyników jest indywidualne przetworzenie kaŜdego
zwróconego wiersza. Do tego celu moŜna wykorzystać metodę next klasy RecordSet, której
wywołanie powoduje przejście do następnego wiersza tabeli wyników. Podczas przetwarzania
wierszy moŜna posługiwać się metodami getXxx, które pobierają jako argument indeks kolumny lub
jej nazwę, a zwracają zawartość wskazanej kolumny w formie wartości róŜnych typów. Na
przykład, jeśli pobrana wartość ma być liczbą całkowitą, naleŜy się posłuŜyć metodą getInt, jeśli
łańcuchem znaków — metodą getString, i tak dalej. Dostępne są metody getXxx zwracające
wartości niemal wszystkich podstawowych typów danych dostępnych w języku Java. Jeśli chcesz
po prostu wyświetlić wyniki, to moŜesz uŜyć metody getString niezaleŜnie od faktycznego typu
danych przechowywanych w danej kolumnie. W przypadku posługiwania się wersjami metod
wykorzystującymi indeksy kolumn, naleŜy zwrócić uwagę, iŜ kolumny są indeksowane od wartości
1 (zgodnie z konwencją przyjętą w języku SQL), a nie od 0 jak jest w przypadku tablic, wektorów
oraz wielu innych struktur danych stosowanych w języku Java.
OstrzeŜenie
Pierwsza kolumna zbioru wyników (w obiekcie ResultSet) ma indeks 1 a nie 0.
PoniŜszy przykład wyświetla wartości trzech pierwszych kolumn wszystkich wierszy zbioru
wyników.
while(zbiorWynikow.next()) {
System.out.println(zbiorWynikow.getString(1) + " " +
zbiorWynikow.getString(2) + " " +
zbiorWynikow.getString(3));
}
Oprócz metod getXxx i next, klasa ResultSet udostępnia jeszcze inne, przydatne metody.
MoŜna do nich zaliczyć metodę findColumn (która zwraca indeks kolumny o podanej nazwie),
wasNull (która sprawdza czy ostatnie wywołanie dowolnej metody getXxx zwróciło wartość null; w
przypadku łańcuchów znaków moŜna to sprawdzić porównując uzyskany wynik z wartością null)
350
oraz getMetaData (która pobiera informacje o zbiorze wyników i zwraca je w formie obiektu klasy
ResultSetMetaData).
Niezwykle przydatna jest metoda getMetaData. Dysponując jedynie obiektem klasy
ResultSet, aby poprawnie przetworzyć uzyskane wyniki, konieczna jest znajomość nazw, ilości
oraz typów kolumn. W przypadku zapytań o znanym i ustalonym formacie, moŜna załoŜyć Ŝe
będziemy dysponowali tymi informacjami. Jednak w przypadku pytań tworzonych „na bieŜąco”,
warto jest mieć moŜliwość dynamicznego określenia informacji wysokiego poziomu dotyczących
uzyskanych wyników. I w tym momencie uwidacznia się znaczenie klasy ResultSetMetaData.
Pozawala ona określać liczbę, nazwy oraz typy kolumn dostępnych w obiektach klasy ResultSet.
Przydatne metody klasy ResultSetMetaData to: getColumnCount (która zwraca ilość kolumn),
getColumnName(numerKolumny) (która zwraca nazwę kolumny o podanym indeksie, przy czym
numeracja kolumn rozpoczyna się od wartości 1), getColumnType (metoda ta zwraca wartość typu
int, którą naleŜy porównywać ze stałymi zdefiniowanymi w klasie java.sql.Types), isSearchable
(która określa czy danej kolumny moŜna uŜyć w klauzuli WHERE), isNullable (która określa czy
dana kolumna moŜe zawierać wartości null) oraz kilka innych metod, które udostępniają
szczegółowe informacje na temat typu oraz precyzji wybranej kolumny. Klasa ResultSetMetaData
nie zawiera jednak Ŝadnych informacji na temat ilości pobranych wierszy. Oznacza to, Ŝe jedynym
sposobem pobrania wszystkich wierszy jest cykliczne wywoływanie metody next klasy ResultSet,
aŜ do momentu gdy zwróci ona wartość false.
Zamknięcie połączenia
Aby jawnie zamknąć połączenie naleŜy posłuŜyć się wywołaniem o następującej postaci:
polaczenie.close();
Jeśli planujesz wykonywać jeszcze inne operacje na bazie danych, to powinieneś odłoŜyć
zamknięcie połączenia na później, gdyŜ koszt otworzenia nowego połączenia jest zazwyczaj dosyć
duŜy. W rzeczywistości wielokrotne wykorzystywanie istniejących połączeń jest tak waŜną metodą
optymalizacji, iŜ w podrozdziale 18.7 przedstawiłem bibliotekę słuŜącą właśnie do tego celu, a w
podrozdziale 18.8 pokazałem typowe oszczędności czasowe jakie moŜna dzięki niej uzyskać.
Listing 18.1 Wyniki wykonania aplikacji FruitTest (połączenie z bazą Oracle działającą w
systemie Solaris)
Prompt> java coreservlets.FruitTest dbhost1.apl.jhu.edu PTE
hall xxxx oracle
Database: Oracle
Version: Oracle7 Server Release 7.2.3.0.0 - Production Release
PL/SQL Release 2.2.3.0.0 - Production
Listing 18.2 Wyniki wykonania aplikacji FruitTest (połączenie z bazą Sybase działającą w
systemie Windows NT)
Prompt> java coreservlets.FruitTest dbhost2.apl.jhu.edu 605741
hall xxxx sybase
Baza danych: Adaptive Server Anywhere
Wersja: 6.0.2.2188
import java.sql.*;
Działanie przedstawionego przykładu nie zaleŜy do sposobu utworzenia tablic bazy danych,
a jedynie od ich ostatecznego formatu. A zatem, przykładowo, moŜna by uŜyć interaktywnego
narzędzia słuŜącego do obsługi i zarządzania bazami danych. Jednak w rzeczywistości, do
stworzenia przykładowej tabeli, takŜe wykorzystałem JDBC — aplikację uŜytą do tego celu
przedstawiłem na listingu 18.5. Jak na razie moŜesz pobieŜnie przejrzeć kod tego programu, gdyŜ
uŜyłem w nim narzędzi, które przedstawię dopiero w dalszej części rozdziału.
import java.sql.*;
Chciałem podać jeszcze jedną informację przeznaczoną dla osób, które do tej pory nie
zetknęły się jeszcze z pakietami. OtóŜ, klasa FruitTest naleŜy do pakietu coreservlets, a zatem jej
plik klasowy będzie przechowywany w katalogu o nazwie coreservlets. Przed kompilacją tej klasy,
do zmiennej środowiskowej CLASSPATH dodałem nazwę katalogu zawierającego katalog coreservlets
(pliki JAR zawierające sterowniki JDBC takŜe powinne się znajdować w katalogu, którego nazwa
została podana w zmiennej środowiskowej CLASSPATH). Dzięki temu mogę skompilować klasę
FruitTest wydając polecenie javac FruitTest.java z poziomu podkatalogu coreservlets. Jednak
aby uruchomić przykład, muszę się posłuŜyć pełną nazwą pakietu — java coreservlets.FuitTest
... .
Główny kod klays został przedstawiony na listingu 18.6, natomiast listing 18.7 zawiera kod
pomocniczej klasy DBResults, która przechowuje wszystkie uzyskane wyniki i zwraca je w formie
tablic łańcuchów znaków (metoda getRow) lub w postaci tabel HTML (metoda toHTMLTable).
Przedstawione poniŜej, dwa przykładowe polecenia wykonują zapytanie SQL, pobierają jego
wyniki i wyświetlają w postaci tabeli HTML, której nagłówki zawierają nazwy kolumn tabeli i są
wyświetlone na jasnoniebieskim tle.
DBResults wyniki =
DatabaseUtilities.getQueryResults(sterownik, url,
nazwaUzytkownika, haslo,
zapytanie, true);
out.println(wyniki.toHTMLTable("CYAN"));
import java.sql.*;
String password,
String query,
boolean close) {
try {
Class.forName(driver);
Connection connection =
DriverManager.getConnection(url, username, password);
return(getQueryResults(connection, query, close));
} catch(ClassNotFoundException cnfe) {
System.err.println("Błąd ładowania sterownika: " + cnfe);
return(null);
} catch(SQLException sqle) {
System.err.println("Błąd przy nawiązywaniu połączenia: " + sqle);
return(null);
}
}
String tableName,
int entryWidth,
boolean close) {
String query = "SELECT * FROM " + tableName;
DBResults results =
getQueryResults(connection, query, close);
printTableData(tableName, results, entryWidth, true);
}
import java.sql.*;
import java.util.*;
import java.sql.*;
employees:
==========
+-------------+-------------+-------------+-------------+-------------+
| ID | FIRSTNAME | LASTNAME | LANGUAGE | SALARY |
+-------------+-------------+-------------+-------------+-------------+
| 1 | Wye | Tukay | COBOL | 42500 |
| 2 | Britt | Tell | C++ | 62000 |
| 3 | Max | Manager | none | 15500 |
| 4 | Polly | Morphic | Smalltalk | 51500 |
| 5 | Frank | Function | Common Lisp | 51500 |
| 6 | Justin |Timecompiler | Java | 98000 |
| 7 | Sir | Vlet | Java | 114750 |
| 8 | Jay | Espy | Java | 128500 |
+-------------+-------------+-------------+-------------+-------------+
employees:
==========
+-------------+-------------+-------------+-------------+-------------+
| id | firstname | lastname | language | salary |
+-------------+-------------+-------------+-------------+-------------+
| 1 | Wye | Tukay | COBOL | 42500.0 |
| 2 | Britt | Tell | C++ | 62000.0 |
| 3 | Max | Manager | none | 15500.0 |
| 4 | Polly | Morphic | Smalltalk | 51500.0 |
| 5 | Frank | Function | Common Lisp | 51500.0 |
| 6 | Justin |Timecompiler | Java | 98000.0 |
362
import java.sql.*;
import java.sql.*;
Czasami zapytanie ma z góry określony format, choć niektóre jego wartości się zmieniają.
W takich przypadkach naleŜy stosować wstępnie przygotowywane zapytania, które opiszę
szczegółowo w podrozdziale 18.6. Jednak w innych przypadkach zmienia się nawet format całego
zapytania. Na szczęście nie stanowi to Ŝadnego problemu, gdyŜ dzięki klasie ResultSetMetaData
moŜna określić ilość, nazwy oraz typy kolumn przechowywanych w obiekcie RecordSet (co
opisałem w podrozdziale 18.1. — „Podstawowe etapy wykorzystania JDBC”). W rzeczywistości,
pomocnicze narzędzia przedstawione na listingu 18.6 przechowują „meta dane” w obiekcie klasy
DBResults zwracanym przez metodę showQueryData. Dzięki tym „meta danym” implementacja
interaktywnej, graficznej przeglądarki zapytań staje się bardzo prostym zadaniem (patrz rysunki 10
18.1 do 18.5). Kod źródłowy przeglądarki prezentowanej na tych rysunkach przedstawiłem w
następnym podrozdziale.
Rysunek 18.4 Wygląd przeglądarki po wyświetleniu całej zawartości tabeli fruits pobranej z
bazy danych Sybase
I w końcu, program wyświetla tabelę w dolnym regionie obiektu JFrame i wywołuje metodę
pack, informując w ten sposób obiekt JFrame, Ŝe powinien uaktualnić swoje wymiary dopasowując
je do wymiarów tabeli.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
public QueryViewer () {
super("Przeglądarka wyników zapytań");
WindowUtilities.setNativeLookAndFeel();
addWindowListener(new ExitListener());
contentPane = getContentPane();
contentPane.add(makeControlPanel(), BorderLayout.NORTH);
pack();
setVisible(true);
}
import javax.swing.table.*;
import javax.swing.*;
import java.awt.*;
package coreservlets;
import java.awt.*;
import java.awt.event.*;
import java.sql.*;
statement.setFloat(1, i*5000);
ResultSet results = statement.executeQuery();
if (print) {
showResults(results);
}
}
long stopTime = System.currentTimeMillis();
double elapsedTime = (stopTime - startTime)/1000.0;
System.out.println("40-krotne wykonanie przygotowanego " +
"zapytania zajęło " +
elapsedTime + " sekund.");
} catch(SQLException sqle) {
System.out.println("Błąd przy wykonywaniu zapytania: " + sqle);
}
}
sprawdzić czy moŜna kontynuować ich realizację (na przykład, poprzez nawiązanie nowego
połączenia).
public synchronized Connection getConnection()
throws SQLException {
if (!availableConnections.isEmpty()) {
Connection existingConnection =
(Connection)availableConnections.lastElement();
int lastIndex = availableConnections.size() - 1;
availableConnections.removeElementAt(lastIndex);
if (existingConnection.isClosed()) {
notifyAll(); // wolne miejsce dla oczekujących wątków
return(getConnection()); // powtórz proces
} else {
busyConnections.addElement(existingConnection);
return(existingConnection);
}
}
}
MoŜe się zdarzyć, Ŝe będziesz chciał aby klienci nie czekali i, w sytuacji gdy nie
będzie Ŝadnego dostępnego połączenia a limit ilości połączeń został juŜ wyczerpany,
zdecydujesz się zgłosić wyjątek. W takim przypadku moŜesz posłuŜyć się następującym
fragmentem kodu:
throw new SQLException("Przekroczono limit ilości połączeń");
import java.sql.*;
import java.util.*;
niego wyniki w 25 ramkach. Wygląd wynikowej strony WWW moŜna zobaczyć na rysunku 18.6.
Nasz serwlet wymusza aby wyniki nie by przechowywane w pamięci podręcznej przeglądarki, a
zatem wyświetlenie strony przedstawionej na listingu 18.21 powoduje wygenerowanie 25 niemal
jednoczesnych Ŝądań HTTP i 25 odwołań do bazy danych, realizowanych przy uŜyciu zarządzania
pulą połączeń. PowyŜszy sposób zgłaszania Ŝądań przypomina to, co mogłoby się zdarzyć na
bardzo popularnej witrynie WWW, nawet gdyby do obsługi kaŜdej ze stron byłyby uŜywany
osobny serwlet.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
</FRAMESET>
<FRAMESET COLS="*,*,*,*,*">
<FRAME SRC="/servlet/coreservlets.ConnectionPoolServlet">
<FRAME SRC="/servlet/coreservlets.ConnectionPoolServlet">
<FRAME SRC="/servlet/coreservlets.ConnectionPoolServlet">
<FRAME SRC="/servlet/coreservlets.ConnectionPoolServlet">
<FRAME SRC="/servlet/coreservlets.ConnectionPoolServlet">
</FRAMESET>
</FRAMESET>
</HTML>
Listing 18.22 przedstawia nową wersję serwletu z listingu 18.20, który wykorzystuje pulę
zawierającą wyłącznie jedno połączenie, a kolejny listing — 18.23 — prezentuje kolejną wersję
która w ogóle nie korzysta z puli połączeń. Dwa powyŜsze serwlety są wykorzystywane przy
wyświetlaniu stron WWW, niemal takich samych jak strona przestawiona na listingu 18.21. W
tabeli 18.1 przedstawiłem czasy konieczne do wyświetlenia stron odwołujących się do powyŜszych
serwletów.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
}
}
Tabela 18.1 Czas wyświetlania stron odwołujących się do serwletów, które w róŜnym
stopniu korzystały z puli połączeń
Warunki Średni
czas
Wolne połączenie modemowe z bazą danych, 10 11
początkowo dostępnych połączeń, dopuszczalny limit 50 połączeń sekund
(ConnectionPoolServlet).
Wolne połączenie modemowe z bazą danych, wielokrotnie 22
wykorzystywane pojedyncze połączenie (ConnectionPoolServlet2). sekundy
Wolne połączenie modemowe z bazą danych, pula połączeń 82
nie uŜywana (ConnectionPoolServlet3). sekundy
Szybkie połączenie z bazą danych poprzez sieć lokalną, 10 1,8
początkowo dostępnych połączeń, dopuszczalny limit 50 połączeń sekundy
(ConnectionPoolServlet).
Szybkie połączenie z bazą danych poprzez sieć lokalną, 2,0
wielokrotnie wykorzystywane pojedyncze połączenie sekundy
(ConnectionPoolServlet2).
Szybkie połączenie z bazą danych poprzez sieć lokalną, pula 2,8
połączeń nie uŜywana (ConnectionPoolServlet3). sekundy
I jeszcze słowo przypomnienia — serwlety ładują sterownik JDBC, a zatem, serwer WWW
musi mieć do niego dostęp. W przypadku przewaŜającej większości serwerów, wystarczy w tym
celu umieścić plik JAR zawierający sterownik w katalogu lib serwera lub rozpakowując zawartość
tego pliku do katalogu classes. Wszelkie informacje na ten temat powinieneś znaleźć w
dokumentacji serwera.
private BookPool(...) {
super(...); // wywołaj konstruktor klasy bazowej
...
}
Zalety JSP
• w porównaniu z ASP — lepszy język do generacji dynamicznych informacji, przenośność,
• w porównaniu z PHP — lepszy język do generacji dynamicznych informacji,
• w porównaniu z samymi serwletami — wygodniejsza generacja kodu HTML,
• w porównaniu z SSI — większa elastyczność i moŜliwości,
• w porównaniu z JavaScript — wykonywanie programów na serwerze, bogatszy język,
• w porównaniu ze statycznym kodem HTML — moŜliwość dynamicznej generacji
informacji.
Dokumentacja
• http://java.sun.com/products/jsp/download.html,
• http://java.sun.com/products/servlet/2.2/javadoc/index.html,
• http://www.java.sun.com/j2ee/j2sdkee/techdocs/api/.
response.setContentType("text/html; encoding=ISO-8859-2");
PrintWriter out = response.getWriter();
String docType =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " +
"Transitional//EN\">\n";
out.println(docType +
"<HTML>\n" +
"<HEAD><TITLE>Witaj WWW</TITLE></HEAD>\n" +
"<BODY>\n" +
"<H1>Witaj WWW</H1>\n" +
"</BODY></HTML>");
}
}
Instalacja serwletów
• umieść plik klasowy serwletu w kartotekach podanych w punkcie A.1,
• umieść plik klasowy serwletu w podkartotekach odpowiadających pakietowi do jakiego
serwlet naleŜy.
Uruchamianie serwletów
• http://host/servlet/NazwaSerwletu,
• http://host/servlet/pakiet.NazwaSerwletu,
• dowolne połoŜenie zdefiniowane poprzez odpowiednie skonfigurowanie serwera.
Przykład serwletu
ThreeParams.java
package coreservlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
<FORM ACTION="/servlet/coreservlets.ThreeParams">
Parameter pierwszy: <INPUT TYPE="TEXT" NAME="param1"><BR>
Parameter drugi: <INPUT TYPE="TEXT" NAME="param2"><BR>
Parameter trzeci: <INPUT TYPE="TEXT" NAME="param3"><BR>
<CENTER>
<INPUT TYPE="SUBMIT" VALUE="Prześlij">
</CENTER>
</FORM>
</BODY>
</HTML>
-1. Jeśli wartość nagłówka nie jest poprawnie zapisaną liczbą całkowitą, to metoda zgłasza
wyjątek NumberFormatException.
• public Cookie[] getCookies()
Zwraca tablicę obiektów Cookie. Jeśli w Ŝądaniu nie zostały przesłane Ŝadne cookies,
to zwracana jest pusta tablica (o zerowej długości). Więcej informacji na temat cookies
znajdziesz w rozdziale 8.
• public int getContentLength()
Zwraca liczbę typu int reprezentującą wartość nagłówka Content-Type. W
przypadku gdy wartość ta nie jest znana, zwracana jest liczba -1.
• public String getContentType()
Zwraca zawartość nagłówka Content-Type, jeśli został on umieszczony w Ŝądaniu
(na przykład dla dołączanych plików). Jeśli nagłówek nie został podany, zwracana jest
wartość null.
• public String getAuthType()
Zwraca jeden z łańcuchów znaków: "BASIC", "DIGEST" lub "SSL", bądź wartość null.
• public String getRemoteUser()
Jeśli jest wykorzystywane uwierzytelnianie, to wywołanie tej metody zwraca nazwę
uŜytkownika; w przeciwnym przypadku zwracana jest wartość null.
Witaj Świecie
z protokołu HTTP 1.0, warto wraz z tym nagłówkiem wygenerować takŜe nagłówek Pragma
o wartości no-cache.
• Content-Encoding — określa sposób kodowania dokumentu. Przeglądarki odtwarzają
kodowanie, przywracając oryginalną postać dokumentu przed jego przetworzeniem. Przed
uŜyciem kodowania serwlet musi upewnić się, Ŝe przeglądarka je obsługuje (sprawdzając
wartość nagłówka Ŝądania Accept-Encoding). Przykład kompresji przesyłanych
dokumentów przedstawiłem w podrozdziale 4.4
• Content-Length — ilość bajtów przesyłanych w odpowiedzi. Patrz metoda
setContentLength opisana we wcześniejszej części podrozdziału.
• Content-Type — typ MIME zwracanego dokumentu. Patrz metoda setContentType opisana
we wcześniejszej części podrozdziału.
• Expires — czas, po którym dokument naleŜy uznać za nieaktualny i usunąć z pamięci
podręcznej przeglądarki. Nagłówek ten naleŜy generować przy wykorzystaniu metody
setDateHeader.
• Last-Modified — czas ostatniej modyfikacji dokumentu. Wartości tego nagłówka nie
powinno się podawać wprost. Zamiast tego naleŜy zaimplementować metodę
getLastModified. Stosowny przykład przedstawiłem w podrozdziale 2.8.
• Location — adres URL pod który przeglądarka powinna przesłać kolejne Ŝądanie.
Nagłówka tego nie naleŜy podawać wprost, lecz wygenerować przy uŜyciu metody
sendRedirect. Stosowny przykład przedstawiłem w podrozdziale 6.3.
• Pragma — wartość no-cache tego nagłówka powoduje, Ŝe przeglądarki wykorzystujące
protokół HTTP 1.0 nie będą przechowywać zawartości otrzymanego dokumentu w pamięci
podręcznej. Patrz takŜe nagłówek odpowiedzi Cache-Control opisany w podrozdziale 7.2.
• Refresh — ilość sekund, po upłynięciu których przeglądarka powinna ponownie odświeŜyć
stronę. Nagłówek moŜe takŜe zawierać adres URL strony, którą przeglądarka ma pobrać.
Stosowny przykład przedstawiłem w podrozdziale 7.3.
• Set-Cookie — cookie, które przeglądarka powinna zapamiętać. Nagłówka tego nie naleŜy
generować wprost, lecz przy uŜyciu metody addCookie. Wszelkie informacje na temat
wykorzystania cookies podałem w rozdziale 8.
• WWW-Authenticate — typ oraz obszar autoryzacji jaki przeglądarka powinna podać w
nagłówku Authorization przesłanym w kolejnym Ŝądaniu. Stosowny przykład
przedstawiłem w podrozdziale 4.5
Tekst szablonu
• aby został wygenerowany łańcuch znaków <% naleŜy uŜyć łańcucha <\%,
• <%-- komentarz JSP --%>,
• <!-- komentarz HTML -->,
• pozostały tekst, który nie zawiera Ŝadnych elementów specjalnych JSP, jest przekazywany
do strony wynikowej.
Predefiniowane zmienne
PoniŜej przedstawiłem niejawne obiekty, które zawsze są dostępne w wyraŜeniach i
skryptletach JSP (lecz nie w deklaracjach).
• request — obiekt HttpServletRequest skojarzony z nadesłanym Ŝądaniem.
• response — obiekt HttpServletResponse skojarzony z odpowiedzią przesyłaną do klienta.
• out — obiekt JspWriter (jest to zmodyfikowana wersja klasy PrintWriter) uŜywany do
przesyłania wyników do klienta.
• session — obiekt HttpSession skojarzony z danym Ŝądaniem; więcej informacji na temat
sesji znajdziesz w rozdziale 9.
• application — obiekt ServletContext, który moŜna uzyskać przy uŜyciu wywołania
getServletConfig().getContext(). Obiekt ten jest wspólnie wykorzystywany przez
wszystkie serwlety oraz dokumenty JSP uruchamiane na danym serwerze lub tworzące daną
aplikację WWW. Więcej informacji na ten temat podałem w rozdziale 15.
• config — obiekt ServletConfig dla danej strony JSP.
• pageContext — obiekt PageContext skojarzony z bieŜącą stroną JSP. Informacje na temat
uŜycia obiektów tego typu przedstawiłem w podrozdziale 13.4.
• page — odpowiednik słowa kluczowego this (reprezentuje egzemplarz serwletu); aktualnie
niezbyt przydatny; zarezerwowany do uŜycia w przyszłości.
Atrybut contentType
• <%@ page contentType="typMIME" %>,
• <%@ page contentType="typMIME; charset=zbiór_znaków" %>,
• przy uŜyciu dyrektywy page uŜywanego zbioru znaków nie moŜna określać warunkowo;
jeśli jednak konieczne będzie warunkowe określenie wartości tego atrybutu, to naleŜy się
posłuŜyć skryptletem o postaci <% response.setContentType("..."); %>.
<BODY>
<CENTER>
<H2>Porównanie sprzedaŜy jabłek i pomarańczy</H2>
<%
String format = request.getParameter("format");
if ((format != null) && (format.equals("excel"))) {
response.setContentType("application/vnd.ms-excel");
}
%>
399 Dodatek A. Krótki przewodnik po serwletach i JSP
<TABLE BORDER=1>
<TR><TH></TH><TH>Jabłka<TH>Pomarańcze
<TR><TH>Pierwszy kwartał<TD>2307<TD>4706
<TR><TH>Drugi kwartał<TD>2982<TD>5104
<TR><TH>Trzeci kwartał<TD>3011<TD>5220
<TR><TH>Czwarty kwartał<TD>3055<TD>5287
</TABLE>
</CENTER>
</BODY>
</HTML>
Atrybut isThreadSafe
• <%@ page isThreadSafe="true" %> <%-- wartość domyślna --%>,
• <%@ page isThreadSafe="false" %>,
• przypisanie wartości true temu atrybutowi oznacza Ŝe kod został przystosowany do
działania wielowątkowego i system moŜe uŜywać go do jednoczesnej obsługi wielu Ŝądań;
natomiast przypisanie temu atrybutowi wartości false oznacza, Ŝe serwlet wygenerowany
na podstawie strony JSP ma implementować interfejs SingleThreadModel (patrz podrozdział
2.6),
• kod nieprzystosowany do wykonywania wielowątkowego:
<%! private int idNum = 0; %>
<% String userID = "userID" + idNum;
out.println("Twój identyfikator uŜytkownika to: " + userID + "." );
idNum = idNum + 1; %>
Atrybut session
• <%@ page session="true" %> <%-- wartość domyślna --%>,
• <%@ page session="false" %>.
Atrybut buffer
• <%@ page buffer="rozmiar_w_kb" %>,
• <%@ page buffer="none" %>,
• serwery mogą korzystać z bufora większego niŜ podany, jednak gwarantuje się, Ŝe uŜywany
bufor nie będzie mniejszy od podanego. Na przykład, dyrektywa <%@ page buffer="32kb"
%> oznacza, Ŝe wynikowy dokument ma być buforowany i nie powinien być przesyłany do
klienta aŜ do momentu nagromadzenia 32 kilobajtów informacji lub zakończenia jego
generacji.
Atrybut autoflush
• <%@ page autoflush="true" %> <%-- wartość domyślna --%>,
• <%@ page autoflush="false" %>,
• w przypadku przypisania wartości none atrybutowi buffer, przypisanie wartości false
atrybutowi autoflush nie jest dozwolone.
Atrybut extends
• <%@ page extends="pakiet.klasa" %>.
Atrybut info
• <%@ page info="Jakiś dowolny komunikat" %>.
Atrybut errorPage
• <%@ page errorPage="względnyAdresURL" %>,
• zgłoszony wyjątek będzie automatycznie dostępny na wskazanej stronie błędu, jako
zmienna exception. Przykłady wykorzystania stron błędów przedstawiłem na listingach
11.5 oraz 11.6.
Atrybut isErrorPage
• <%@ page isErrorPage="true" %>,
• <%@ page isErrorPage="false" %> <%-- wartość domyślna --%>,
• stosowne przykłady przedstawiłem na listingach 11.5 oraz 11.6.
Atrybut language
• <%@ page language="cobol" %>,
• jak na razie nie warto zawracać sobie głowy tym atrybutem, gdyŜ jedyną jego dozwoloną
wartością jest java.
Zapis XML-owy
• Zapis zwyczajny:
<%@ page atrybut="wartość" %>
<%@ page import="java.util.*" %>
• Zapis XML-owy:
<jsp:directive.page atrybut="wartość" />
<jsp:directive.page import="java.util.*" />
Tekst alternatywny
• Postać zwyczajna:
<APPLET CODE="MojAplet.class"
WIDTH="470" HEIGHT="350">
<B>Błąd: ten przykład wymaga przeglądarki obsługującej język Java.</B>
</APPLET>
name="licznik"
property="ilosc"
param="ilosc" />
Plik JSP
• <%@ taglib uri="jakisPlikDeskryptora.tld" prefix="prefiks" %>,
• <prefiks:nazwaZnacznika />,
• <prefiks:nazwaZnacznika>zawartość</prefiks:nazwaZnacznika>.
• Atrybuty: ACTION (wymagany), METHOD, ENCTYPE, TARGET, ONSUBMIT, ONRESET, ACCEPT, ACCEPT-
CHARSET.
Pola tekstowe
• Standardowa postać:
<INPUT TYPE="TEXT" NAME="..." ...> (brak znacznika zamykającego)
• Atrybuty: NAME (wymagany), VALUE, SIZE, MAXLENGTH, ONCHANGE, ONSELECT, ONFOCUS, ONBLUR,
ONKEYDOWN, ONKEYPRESS, ONKEYUP.
• RóŜne przeglądarki w róŜny sposób obsługują przesyłanie formularza po naciśnięciu
klawisza Enter w polu tekstowym. Z tego względu warto umieszczać w formularzach
specjalne przyciski (typu SUBMIT) lub mapy odnośników, które jawnie przesyłają formularz.
Pola hasła
• Standardowa postać:
<INPUT TYPE="PASSWORD" NAME="..." ...> (brak znacznika zamykającego)
• Atrybuty: NAME (wymagany), VALUE, SIZE, MAXLENGTH, ONCHANGE, ONSELECT, ONFOCUS, ONBLUR,
ONKEYDOWN, ONKEYPRESS, ONKEYUP.
• Jeśli formularz zawiera pola hasła, to zawsze naleŜy go przesyłać metodą POST.
Obszary tekstowe
• Standardowa postać:
<TEXTAREA NAME="..." ROWS=xxx COLS=yyy ...>
Jakiś tekst
</TEXTAREA>
Przyciski SUBMIT
• Standardowa postać:
<INPUT TYPE="SUBMIT" ...> (brak znacznika zamykającego)
• Atrybuty: NAME, VALUE, ONCLICK, ONDBLCLICK, ONFOCUS, ONBLUR.
• Po kliknięciu przycisku tego typu formularz jest przesyłany do serwletu bądź innego
programu wykonywanego na serwerze, określonego poprzez atrybut ACTION elementu FORM.
Alternatywna postać przycisków SUBMIT
• Standardowa postać:
<BUTTON TYPE="SUBMIT" ...>
kod HTML
</BUTTON>
Przyciski RESET
• Standardowa postać:
<INPUT TYPE="RESET"...> (brak znacznika zamykającego)
• Atrybuty: VALUE, NAME, ONCLICK, ONDBLCLICK, ONFOCUS, ONBLUR . (Za wyjątkiem atrybutu VALUE,
wszystkie pozostałe atrybuty są przeznaczone wyłącznie do uŜytku w skryptach JavaScript.)
Przyciski JavaScript
• Standardowa postać:
<INPUT TYPE="BUTTON" ...> (brak znacznika zamykającego)
• Atrybuty: NAME, VALUE, ONCLICK, ONDBLCLICK, ONFOCUS, ONBLUR.
Pola wyboru
• Standardowa postać:
<INPUT TYPE="CHECKBOX" NAME="..." ...> (brak znacznika zamykającego)
• Atrybuty: NAME (wymagany), VALUE, CHECKED, ONCLICK, ONFOCUS, ONBLUR.
• Para nazwa-wartość jest przesyłana na serwer wyłącznie w przypadku gdy pole wyboru
zostało zaznaczone.
409 Dodatek A. Krótki przewodnik po serwletach i JSP
Przyciski opcji
• Standardowa postać:
<INPUT TYPE="RADIO" NAME="..." VALUE="..." ...>
(brak znacznika zamykającego)
• Atrybuty: NAME (wymagany), VALUE (wymagany), CHECKED, ONCLICK, ONFOCUS, ONBLUR.
• Grupę przycisków opcji tworzy się nadając tą samą nazwę wszystkim naleŜącym do niej
przyciskom.
Listy rozwijane
• Standardowa postać:
<SELECT NAME="nazwa" ...>
<OPTION VALUE="wartosc1">Tekst opcji 1
<OPTION VALUE="wartosc2">Tekst opcji 2
...
<OPTION VALUE="wartoscN">Tekst opcji N
</SELECT>
• Atrybuty elementu SELECT: NAME (wymagany), SIZE, MULTIPLE, ONCLICK, ONFOCUS, ONBLUR,
ONCHANGE.
• Atrybuty elementu OPTION: SELECTED, VALUE.
Pola ukryte
• Standardowa postać:
<INPUT TYPE="HIDDEN" NAME="..." VALUE="..."> (brak znacznika zamykającego)
• Atrybuty: NAME (wymagany), VALUE.
4. Podaj wszystkie nagłówki HTTP, których chcesz uŜyć. Jeśli chcesz podać jakieś
nagłówki Ŝądania HTTP (patrz rozdział 4) to będziesz mógł to zrobić przy wykorzystaniu
metody setRequestProperty.
polaczenie.setRequestProperty("naglowek", "wartosc");
5. Stwórz strumień wejściowy. Jest kilka rodzajów strumieni którymi moŜesz się posłuŜyć,
jednak najczęściej stosowanym jest BufferedReader. To właśnie w momencie tworzenia
strumieni, w niewidoczny sposób jest nawiązywane połączenie sieciowe z serwerem.
BufferedReader in =
new BufferedReader( new InputStreamReader(
polaczenie.getInputStream()));
6. Odczytaj dane przy uŜyciu metody readObject. Metoda readObject zwraca dane typu
Object; a zatem otrzymane wyniki trzeba rzutować do konkretnego typu, odpowiadającego
typowi danych przesłanych przez serwlet.
JakasKlasa wartosc = (JakasKlasa) in.readObject();
3. Zapisz dane przy uŜyciu metody writeObject. W ten sposób moŜna przesyłać znaczą
większość wbudowanych typów danych. Jednak abyś mógł przesyłać w ten sposób klasy,
które samemu zdefiniowałeś, będziesz musiał zaimplementować w nich interfejs
Serializable.
JakasKlasa wartosc = new JakasKlasa(...);
out.writeObject(wartosc);
4. OpróŜnij strumień, aby mieć pewność, Ŝe wszystkie zapisane w nim informacje zostały
przesłane do klienta.
out.flush();
7. Zapisz dane w buforze. W przypadku zapisu informacji podanych w formularzy, posłuŜ się
metodą print, natomiast serializowane obiekty wysokiego poziomu zapisuj przy uŜyciu
metody writeObject.
String wartosc1 = URLEncoder.encode(jakasWartosc1);
String wartosc2 = URLEncoder.encode(jakasWartosc2);
String dane = "param1=" + wartosc1 +
"¶m2=" + wartosc2; // Zwróć uwagę na "&"!
out.print(dane); // Zwróć uwagę na uŜycie metody print, a nie println!
out.flush(); // Konieczne gdyŜ nie uŜywamy metody println
8. Wygeneruj nagłówek Content-Length. W przypadku uŜycia metody POST nagłówek ten jest
konieczny, choć w Ŝądaniach GET nie jest on uŜywany.
polaczenie.setRequestProperty
("Content-Length", String.valueOf(strumBajtowy.size()));
formularzu, aby zapewnić przenaszalność aplikacji, powinieneś jawnie określić typ MIME
przesyłanych danych. W przypadku przesyłania serializowanych danych, określanie
wartości tego nagłówka nie ma znaczenia.
polaczenie.setRequestProperty
("Content-Type", "application/x-www-form-urlencoded");
11. Otwórz strumień wejściowy. Do odczytu danych ASCII lub danych binarnych jest
zazwyczaj uŜywany strumień BufferedReader, natomiast do odczytu serializowanych
obiektów Javy — strumień ObjectInputStream.
BufferedReader in =
new BufferedReader(new InputStreamReader(
polaczenie.getInputStream()));
12. Odczytaj wyniki. Konkretny sposób odczytywania danych będzie zaleŜny od rodzaju
informacji przesyłanych przez serwer. Przedstawiony poniŜej przykład robi coś z kaŜdym
wierszem odczytanych wyników:
String wiersz;
while ((wiersz = in.readLine()) != null) {
zrobCosZ(wiersz);
}
2. Zdefiniowanie adresu URL połączenia. Konkretny format adresu jaki naleŜy zastosować,
będzie podany w dokumentacji uŜywanego sterownika.
String host = "dbhost.firma.com.pl";
String nazwaBazy = "jakasBaza";
int port = 1234;
String oracleURL = "jdbc:oracle:thin:@" + host +
":" + port + ":" + "?SERVICENAME=" + nazwaBazy;
3. Nawiązanie połączenia.
String nazwaUzytkownika = "janek_kowalski";
String haslo = "supertajne";
Connection polaczenie =
DriverManager.getConnection(oracleURL,nazwaUzytkownika,haslo);
7. Zamknięcie połączenia.
polaczenie.close();