O Autorach Jason Hunter jest starszym technologiem w firmie CollabNet (http://collab.net), firmie dostarczającej narzędzia i usługi dla współpracy Open Source. Oprócz bycia autorem książki „Java Servlet — programowanie” jest także redaktorem witryny Servlets.com, twórcą biblioteki com.oreilly.servlet, współpracownikiem projektu Apache Jakarta, który tworzy serwer Tomcat (od czasów, kiedy projekt był jeszcze wewnętrzną częścią firmy Sun), członkiem grupy ekspertów odpowiedzialnej za tworzenie API Servlet/JSP i JAXP oraz jest członkiem Komitetu Wykonawczego JCP nadzorującego platformę Javy, jako reprezentant Apache Software Foundation. Pisze również artykuły dla JavaWorld oraz przemawia na wielu konferencjach programistycznych i Open Source. W ostatnich czasach współtworzył bibliotekę Open Source JDOM (http://jdom.org), pozwalającą na optymalizację integracji Javy i XML oraz przewodzi grupie ekspertów odpowiedzialnej za tworzenie JDOM. Jason poprzednio pełnił funkcję głównego technologa w firmie K&A Software, specjalizującej się w treningach i konsultacjach związanych z Javą i działał jako wynajęty ekspert dla wielu przedsiębiorstw włączając w to Sun Microsystems. Jeszcze wcześniej pracował w Silicon Graphics, gdzie był odpowiedzialny za tworzenie (i niszczenie) różnego rodzaju technologii WWW. Jason ukończył z najwyższym wyróżnieniem kierunek nauki komputerowe w Willamette University (Salem, Oregon) w 1995. Rozpoczął programowanie w Javie w lecie 1995, a z serwletami i innymi technologiami programowania po stronie serwera jest związany od grudnia 1996. Jeżeli jakimś cudem nie pracuje, przypuszczalnie można go znaleźć na górskiej wędrówce. William „Will” Crawford związał się z tworzeniem stron WWW w 1995. Pracował przy programie informatycznym szpitala Children's Hospital w Bostonie, gdzie pomagał przy tworzeniu pierwszego elektronicznego systemu zapisów medycznych opartego na sieci WWW i był związany z jednymi z pierwszych korporacyjnych zastosowań języka Java. Był konsultantem projektów sieci Intranet w między innymi Children's Hospital w Massachusetts, General Hospital w Brigham, Women's Hospital, Boston Anesthesia Education Foundation i Harvard Medical Center. Will obecnie przewodzi zespołowi projektanckiemu w firmie Invantage, Inc. w Cambridge, Massachusetts, która tworzy oparte na Javie narzędzia intranetowe dla przemysłu farmaceutycznego. W wolnym czasie jest zapalonym amatorem fotografii, pisarzem i studentem ekonomii na Yale University.
Kolofon Wygląd naszych książek jest wynikiem komentarzy czytelników, naszych własnych eksperymentów oraz komentarzy od dystrybutorów. Wyróżniające się okładki dopełniają nasze wyróżniające się podejście do tematów technicznych, tchnące osobowość i życie w potencjalnie suche tematy. Obrazek na okładce książki „Java Servlet — programowanie. Wydanie drugie.” przedstawia miedziany imbryk. Collen Gorman była redaktorem produkcji, a Norma Emory edytorem kopii dla „Java Servlet — programowanie. Wydanie drugie.” Catherine Moris i Leanne Soylemez były odpowiedzialne za kontrolę jakości. Firma Frameworks Consulting dostarczyła obsługi produkcji. Ellen Troutman-Zaig napisała indeks.
Hanna Dyer zaprojektowała okładkę niniejszej książki w oparciu o projekt serii autorstwa Ediego Freedmana. Obrazek został sfotografowany przez Kevina Thomasa i dostosowany przy pomocy Adobe Photoshop przez Michaela Snowa. Emma Colby utworzyła pozostałą część okładki w programie QuarkXPress 4.1 przy pomocy czcionki Bodoni Black firmy URW Software i Bodoni Bold Italic firmy Bitstream. David Futato zaprojektował wnętrze książki w oparciu o projekt serii autorstwa Nancy Priest. Judy Hoer dokonała konwersji plików Microsoft Word na FrameMaker 5.5.6, przy pomocy narzędzi utworzonych przez Mike'a Sierra. Czcionka nagłówków to Bodoni BT, czcionka tekstu to New Baskerville, a czcionka kodu to Constant Willison. Rysunki pojawiające się w książce zostały utworzone przez Roberta Romano przy pomocy Macromedia FreeHand 8 i Adobe Photoshop 5.
Wstęp Od czasu, kiedy napisane zostało pierwsze wydanie niniejszej książki, serwlety i platforma Javy działająca po stronie serwera zyskała popularność, której nie można było spodziewać się w najśmielszych marzeniach. Postępuje przyłączanie tych mechanizmów do istniejących. Producenci serwerów WWW oferują obecnie obsługę serwletów jako standardową własność swojego oprogramowania. W specyfikacji Java 2, Enterprise Edition (J2EE) serwlety istnieją jako podstawowy składnik, a niemożliwym jest obecnie znalezienie producenta serwerów aplikacji, którego produkt nie zawierałby skalowalnej implementacji serwletów. Jest to jednak więcej niż zjawisko napędzane przez producentów. Serwlety stały się podstawą dla JavaServer Pages (JSP) i innych szkieletów tworzenia stron WWW, a technologia serwletów obsługuje aktualnie tak często odwiedzane witryny, jak ESPN.com i AltaVista.com. W związku z tym nie jest zaskakującym fakt, że krajobraz serwletów wygląda nieco inaczej niż w czasach pierwszego wydania. Interfejs serwletów (Servlet API) został poddany dwóm przeglądom, a trzeci jest w trakcie przygotowań. Znajome z początków istnienia serwletów firmy Live Software i New Atlanta, które niegdyś zarabiały sprzedając mechanizmy serwletów (nazywane teraz kontenerami serwletów) Jrun i ServletExec, zostały zauważone i wykupione przez większe firmy zorientowane na WWW, odpowiednio przez Allaire i Unify. Oferują one teraz wiele własności wykraczających poza podstawową obsługę serwletów w celu odróżnienia się od innych. Co dziwne, oficjalne pakiety javax.servlet i javax.servlet.http były pierwszymi klasami Javy, które zostały oficjalnie rozprowadzone jako Open Source. Zostały one przeniesione do projektu Apache Software Foundation (ASF), i można je aktualnie odnaleźć pod adresem http://jakarta.apache.org. Pakiety te dalej zgodne są ze specyfikacją Servlet API, jednak poprawa błędów i uaktualnianie specyfikacji znajduje się teraz w rękach w zaufanych programistów Open Source — włączając autora, który miał niedawno okazję poprawienia obsługi warunkowego żądania GET w HttpServlet. Dodatkowo, serwer, który jest traktowany jako wzorcowa implementacja Servlet API, został również przeniesiony do ASF i udostępniony jako Open Source pod nazwą Apache Tomcat. Od tego czasu Tomcat stał się jednym z najpopularniejszych kontenerów serwletów. Większa ilość informacji na ten temat dostępna jest pod adresem http://opensource.org. Świat serwletów zmienił się, a niniejsza książka zawiera uaktualnione informacje. Całą wiedzę potrzebną do programowania serwletów Javy, od początku do końca. Pierwsze pięć rozdziałów opisuje podstawy — czym są serwlety, jakie działania wykonują oraz w jaki sposób pracują. Następne 15 rozdziałów zawiera informacje zaawansowane — opisuje działania podejmowane najczęściej przy pomocy serwletów oraz najpopularniejsze narzędzia do tego służące. Można tam znaleźć wiele przykładów, kilka wskazówek i ostrzeżeń, a nawet opisy kilku prawdziwych błędów, które umknęły uwagi korektorów technicznych.
Servlet API 2.2 Niniejsze wydanie książki opisuje wersję 2.2 Servlet API, która osiągnęła stan „wersji publicznej” w sierpniu 1999, a stan „wersji ostatecznej” w grudniu 1999. Wydanie pierwsze opisywało wersje 2.0. Zmiany pomiędzy wersjami 2.0 i 2.2 są znaczne: •
Zostały wprowadzone zasady definiujące dystrybucje serwletów pomiędzy kilkoma serwerami wspierającymi.
•
Serwlety korzystają aktualnie z dołączanych aplikacji WWW, które mogą być konfigurowane i wdrażane w sposób niezależny od serwera.
•
Znacznie poprawione zostało bezpieczeństwo serwletów.
•
Serwlety mogą teraz przekazywać obsługę żądań innym składnikom serwera.
•
Serwlety mogą teraz dzielić się informacjami przy pomocy ich ServletContext
•
Istnieje sposób przystosowania serwletów do obsługi dostępu rozproszonego.
•
Serwlety posiadają teraz ściślejszą kontrolę nad zarządzaniem sesją.
•
Dodane zostało buforowanie odpowiedzi.
•
Rozszerzona została kontrola nad nagłówkami HTTP.
•
Aktualnie może być zastosowana bardziej zaawansowana obsługa błędów.
•
API został „wyczyszczony” w celu nadania większej spójności i przewidywalności nazwom metod.
•
Servlet API jest teraz zdefiniowany poprzez formalny dokument specyfikacji, a przyszłe uaktualnienia API są zarządzane przez formalny proces Java Specification Request (JSR).
•
Serwlety są teraz zintegrowane z podstawową specyfikacją platformy Java 2, Enterpise Edition (J2EE).
Wszystkie te zmiany, oraz wiele innych drobnych usprawnień, są w pełni opisane w niniejszym nowym wydaniu. Drugie wydanie zawiera również obszerny opis najciekawszego obszaru programowania serwletów — technik tworzenia prawdziwych dynamicznych witryn opartych na serwletach. W niniejszym wydaniu znajdują się samouczki pięciu najpopularniejszych technologii tworzenia zawartości opartej na serwletach, należących do Open Source: •
JavaServer Pages (JSP), standard firmy Sun, tworzony i udostępniany w połączeniu z serwletami
•
Tea, technologia utworzona przez Walt Disney Internet Group (dawniej GO.com), zastosowany w wielu bardzo często odwiedzanych stronach, takich jak ESPN.com, NFL.com, Disney.com, DisneyLand.com, GO.com i Movies.com
•
WebMacro, utworzony przez Semiotek i wykorzystywany przez wyszukiwarkę AltaVista
•
XMLC, utworzony przez Lutris Technologies w celu udostępnienia mocy technologii XML sieci WWW, wykorzystywany przez innowacyjne witryny takie jak customatix.com
•
Element Construcion Set (ECS), utworzony przez Apache w celu obsługi najbardziej wymagających potrzeb programistycznych
Niniejsze drugie wydanie opisuje również WAP, Wireless Application Protocol (Protokół Aplikacji Bezprzewodowych) oraz wyjaśnia, jak tworzyć oparte na serwletach aplikacje WWW dla urządzeń bezprzewodowych.
Servlet API 2.3 W czasie pisania niniejszej książki, Servlet API 2.3 jest w trakcie tworzenia. Jednak nie został on jeszcze ukończony. W związku z tym tekst niniejszego wydania zawiera w różnych miejscach krótkie uwagi na temat zmian spodziewanych w z Servlet API 2.3. Dodatkowo, ostatni rozdział książki zawiera dokładniejszy opis próbnej specyfikacji Servlet API 2.3, udostępnionej w październiku 2000, który pozwala na zapoznanie się z najnowszymi własnościami Servlet API 2.3. Należy jednak zaznaczyć, że specyfikacje te ciągle podlegają zmianom, a ostateczna wersja może się nieco różnić od materiału tu przedstawionego.
Czytelnicy pierwszego wydania Czytelnicy książki „Java Servlet Programming, 1st ed.” zorientują się, że niniejsza książka została obszernie uaktualniona do Servlet API 2.2 i, gdzie to tylko możliwe, Servlet 2.3. Każdy rozdział został znacząco poprawiony w porównaniu z pierwszym wydaniem, a także dodano sześć nowych rozdziałów opisujących techniki tworzenia zawartości opartej na serwletach, jak również nowy rozdział siódmy, „Serwlety korporacyjne i J2EE”, który opisuje integrację serwletów w platformie J2EE. Ze względu na znaczący wpływ modelu aplikacji WWW na wszystkie aspekty programowania serwletów, poleca się czytelnikom pierwszego wydania przeczytanie każdego interesującego ich rozdziału oraz zwrócenie
uwagi na nowe mechanizmy, które pozwalają na wykonanie tradycyjnych zadań. Czytelnicy dysponujący ograniczonym czasem powinni przejrzeć listę najbardziej znaczących zmian w podrozdziale „Organizacja”.
Czytelnicy Dla kogo jest ta książka? Dla osób zainteresowanych tworzeniem aplikacji umieszczanych w sieci WWW. Dokładniej rzecz biorąc, niniejszą książką powinni zainteresować się: •
Programiści J2EE — serwlety są integralną częścią standardu Java 2, Enterpise Edition. Programiści tworzący aplikacje dla serwerów J2EE mogą nauczyć się jak najlepiej zintegrować serwlety z innymi podobnymi technologiami.
•
Programiści JSP — JavaServer Pages (JSP) tworzone są na podstawie serwletów. Wykorzystanie pełnej mocy JSP wymaga zrozumienia serwletów, co też umożliwia niniejsza książka. Zawiera ona również samouczek JSP oraz czterech podstawowych konkurencyjnych technologii.
•
Programiści apletów Javy — porozumiewanie się apletów z serwerem zawsze sprawiało problemy. Serwlety ułatwiają to zadanie poprzez dostarczenie apletom prostego w połączeniu agenta na serwerze.
•
Programiści CGI — CGI jest popularną metodą rozszerzania funkcjonalności serwera WWW. Serwlety są elegancką i wydajną alternatywą tej techniki.
•
Programiści innych technik serwerów — istnieje wiele alternatyw dla CGI, między innymi FastCGI, PHP, NSAPI, WAI, ISPAI, ASP, a teraz ASP+. Każda z nich posiada ograniczenia związane z przenośnością, bezpieczeństwem, wydajnością i/lub integracją z innymi źródłami danych. Serwlety przewyższają je w każdym z tych obszarów.
Co należy wiedzieć Podczas rozpoczynania pracy z niniejszą książką, niespodzianką dla autorów okazało się, że jedną z najtrudniejszych do określenia rzeczy jest docelowy czytelnik. Czy zna on Javę? Czy ma doświadczenie w programowaniu CGI lub innych aplikacji WWW? Czy miał już kontakt z serwletami? Czy zna HTTP i HTML, czy te skróty brzmią dla niego zupełnie niezrozumiale? Niezależnie od przyjmowanego poziomu doświadczenia, zawsze okazywało się, że książka będzie zbyt uproszczona dla jednych użytkowników, a zbyt zaawansowana dla drugich. Ostatecznie zdecydowano się na zasadę, że niniejsza książka powinna zawierać w przeważającej części materiał oryginalny — można pominąć obszerne opisy tematów i koncepcji dobrze opisanych w sieci lub innych książkach. W tekście znaleźć można odwołania do tych zewnętrznych źródeł informacji. Oczywiście zewnętrzne źródła informacji nie są wystarczające. Niniejsza książka zakłada, że czytelnicy dobrze znają język Java oraz podstawowe techniki programowania obiektowego. Jeżeli nie spełnia się tych założeń, polecane jest przygotowanie się poprzez przeczytanie ogólnej książki na temat programowania w Javie, takiej jak „Learning Java” autorstwa Patricka Niemeyera i Jonathana Knudsena (O'Reilly). W książce tej można jedynie krótko zapoznać się z rozdziałami na temat apletów i programowania Swing (graficznego), a skupić się na sieci i programowaniu wielowątkowym. Aby zacząć od razu naukę serwletów i uczyć się Javy w trakcie, polecane jest przeczytanie niniejszej książki równocześnie z „Java in a Nutshell” autorstwa Davida Flanagana (O'Reilly) lub innym podręcznikiem. Niniejsza książka nie wymaga od czytelników doświadczenia w programowaniu WWW, HTTP i HTML. Nie zawiera jednak pełnego wprowadzenia lub wyczerpującego opisu tych technologii. Opisane zostaną podstawy potrzebne do efektywnego programowania serwletów, a szczegóły (takie jak pełna lista znaczników HTML i nagłówków HTTP 1.1) pozostawione zostaną innym źródłom.
Przykłady W niniejszej książce znaleźć można ponad 100 przykładów serwletów. Ich kod jest całkowicie zawarty wewnątrz tekstu, możliwe jest jednak także pobranie przykładów zamiast ręcznego ich wpisywania. Kod przykładów, spakowany i gotowy do pobrania, można znaleźć pod adresem
http://www.oreilly.com/catalog/jservlet2. Wiele z tych serwletów można zobaczyć w działaniu pod adresem http://www.servlets.com. Wszystkie przykłady zostały przetestowane przy pomocy serwera Apache Tomcat 3.2 działającego w trybie samodzielnym, wirtualnej maszyny Javy (Java Virtual Machine — JVM) zawartej w Java Development KIT 1.1.8 i 1.2.2, zarówno pod Windows jak i Uniksem. Kilka zaawansowanych przykładów wymaga własności, których nie obsługuje Tomcat w trybie samodzielnym. W tym przypadku przykłady były testowane na różnych innych serwerach, jak opisano w tekście. Serwer Apache Tomcat jest oficjalną wzorcową implementacją Servlet API, i jest dostępny w licencji Open Source pod adresem http://jakarta.apache.org. Niniejsza książka zawiera również zbiór klas narzędziowych — wykorzystywane są one przez serwlety przykładowe, mogą się także okazać przydatne przy tworzenie własnych. Klasy te zawarte są w pakiecie com.oreilly.servlet. Między innymi są to klasy pomagające serwletom w analizie parametrów, obsłudze wysyłania plików, generowaniu wieloczęściowych odpowiedzi (przepychanie serwera), negocjacji ustawień lokalnych i internacjonalizacji, zwracaniu plików, zarządzaniu połączeniami i pracy jako serwer RMI. Pakiet te zawiera również klasę wspomagającą komunikację apletów z serwletami. Od czasu pierwszego wydania dodane zostały nowe klasy pomagające serwletom w wysyłaniu wiadomości poczty elektronicznej, przechowywaniu odpowiedzi w pamięci podręcznej oraz automatycznym wykrywaniu obsługi Servlet API. Kod źródłowy większości pakietu com.oreilly.servlet zawarty jest w tekście, a pełna, aktualna wersja jest dostępna w formie elektronicznej (razem z dokumentacją javadoc) pod adresem http://www.servlets.com.1
Organizacja Niniejsza książka składa się z 20 rozdziałów i 6 dodatków, są one następujące: •
Rozdział 1, „Wprowadzenie”. Wyjaśnia rolę i zalety serwletów Javy w tworzeniu aplikacji WWW. W drugim wydaniu dodane zostały dodatkowe informacje na temat serwerów.
•
Rozdział 2, „Podstawy serwletów HTTP”. Zawiera krótkie wprowadzenie do HTTP i funkcji, jakie mogą pełnić serwlety HTTP. Przedstawia tworzenie prostej strony i wprowadza pojęcie dołączanej aplikacji WWW. Drugie wydanie opisuje aplikacje WWW i ich deskryptory oparte na XML.
•
Rozdział 3, „Cykl życia serwletów”. Wyjaśnia szczegółowe informacje na temat sposobu i czasu ładowania serwletów, sposobu i czasu ich wykonywania, zarządzania wątkami oraz obsługi kwestii synchronizacji w systemie wielowątkowym. Opisane są również stany trwałe. Drugie wydanie zawiera nowe zasady kontekstowego przeładowywania i rejestracji serwletów, nowy podrozdział na temat pamięci podręcznej po stronie serwera oraz uwagę na temat super.init(config).
•
Rozdział 4, „Pobieranie informacji”. Wprowadza najpopularniejsze metody wykorzystywane przez serwlety w celu pobrania informacji — na temat klienta, serwera, żądań klienta oraz samego siebie. Przedstawia również działanie ogólnej klasy służącej do wysyłania plików. Drugie wydanie opisuje ustawianie informacji w deskryptorze, pobieranie nazwy serwletu, dostęp do katalogów tymczasowych, obsługę kontekstowych parametrów początkowych, określanie wersji Servlet API, przypisywanie odwzorowania serwletów oraz dostęp do zasobów abstrakcyjnych. Przestawia również poprawiony, bardziej elastyczny składnik służący do wysyłania plików.
•
Rozdział 5, „Wysyłanie informacji HTML”. Opisuje sposoby tworzenia kodu HTML przez serwlet, zwracania błędów, buforowania odpowiedzi, przekierowywania żądań, zapisywania danych w dzienniku zdarzeń serwera oraz wysyłania dostosowanych nagłówków HTML. Drugie wydanie zawiera nowy opis buforowania odpowiedzi, bardzo przydatny przykład przekierowywania oraz nowe podrozdziały na temat konfiguracji stron zawierających błędy i obsługi błędów.
1 Niniejsza książka nie zawiera CD-ROM-u. Dołączenie CD-ROM-u podnosi koszty produkcji a w związku z tym cenę książki. Założono, że każdy Czytelnik posiada dostęp do Internetu, a w związku z tym może oszczędzić pewną ilość pieniędzy poprzez pobranie kodu przykładów przez sieć WWW. Nie uważa się również za sensowne dołączanie wersji próbnych różnych serwerów WWW i aplikacji. Zważywszy na nieustanny szybki postęp na rynku serwletów, dołączone serwery stałyby się przestarzałe jeszcze przed wydrukowaniem książki. Te same wersje próbne dostępne są w sieci i poleca się pobranie ich własnoręcznie. Proszę pamiętać, że jeżeli zamierza się czytać niniejszą książkę offline, polecane jest pobranie kodu przykładów i serwera WWW Apache Tomcata, kiesy tylko będzie to możliwe. Łącza do pobrań umieszczone są pod adresem http://www.servlets.com.
•
Rozdział 6, „Wysyłanie zawartości multimedialnej”. Opisuje różne interesujące dane, które może zwracać serwlet — zawartość WAP/WML dla urządzeń bezprzewodowych, dynamicznie tworzone obrazki, zawartość skompresowana oraz odpowiedzi wieloczęściowe. W drugim wydaniu dodano opis WAP/WML, listy plików powitalnych, dyskusję na temat PNG, usprawnioną pamięć podręczną rysunków po stronie serwera oraz więcej szczegółów na temat tworzenia zawartości skompresowanej.
•
Rozdział 7, „Śledzenie sesji”. Opisuje sposoby tworzenia śledzenia stanu w bezstanowym protokole HTTP. Pierwsza część rozdziału opisuje tradycyjne techniki śledzenia sesji stosowane przez programistów CGI. Druga część opisuje sposoby zastosowania wbudowanej w Servlet API obsługi śledzenia sesji. Drugie wydanie zawiera zasady tworzenia sesji aplikacji WWW, materiał na temat nowych nazw metod sesji, dyskusję na temat zarządzania przekraczaniem czasu oraz śledzenie sesji oparte na apletach.
•
Rozdział 8, „Bezpieczeństwo”. Wyjaśnia kwestie bezpieczeństwa związane z programowanie rozproszonym. Opisuje sposoby korzystania ze standardowych funkcji serwletów związanych z zarządzaniem kontami użytkowników oraz sposoby tworzenia bardziej zaawansowanego systemu przy pomocy dodatkowego uwierzytelniania i autoryzacji. Wyjaśni również rolę serwletów w bezpiecznej komunikacji SSL. W drugim wydaniu całkowicie przeredagowany.
•
Rozdział 9, „Łączność z bazami danych”. Opisuje sposoby wykorzystania serwletów w wysokowydajnej łączności z bazami danych WWW. Zawiera samouczek JDBC. Drugie wydanie zawiera przykłady konfiguracji połączeń z plikami właściwości, nowy przykład księgi gości oraz nowy podrozdział opisujący JDBC 2.0.
•
Rozdział 10, „Komunikacja aplet-serwlet”. Opisuje sposoby wykorzystania serwletów przez aplet, który musi porozumieć się z serwerem. Uaktualniony w drugim wydaniu.
•
Rozdział 11, „Współpraca serwletów”. Opisuje powody komunikacji serwletów i sposoby ich współpracy przez dzielenie się informacjami lub wywoływanie sienie nawzajem. W drugim wydaniu całkowicie przeredagowany.
•
Rozdział 12, „Serwlety korporacyjne i J2EE”. Opisuje zaawansowane własności serwletów wykorzystywane w witrynach korporacyjnych — dystrybucję ładunku i integrację składników J2EE. Nowość w drugim wydaniu.
•
Rozdział 13, „Internacjonalizacja”. Opisuje sposoby, dzięki którym serwlet może odczytywać i tworzyć zawartość w różnych językach. Drugie wydanie opisuje zastosowanie javadoc w zarządzaniu kodowaniem i sposoby wykorzystywania nowych metod API w zarządzaniu wersjami lokalnymi.
•
Rozdział 14, „Szkielet Tea”. Przedstawia szkielet Tea, elegancki, ale zarazem potężny mechanizm szablonów. Nowość w drugim wydaniu.
•
Rozdział 15, „WebMacro”. Opisuje szkielet WebMacro, podobny do Tea lecz z kilkoma innymi decyzjami projektanckimi. Nowość w drugim wydaniu.
•
Rozdział 16, „Element Construction Set”. Zawiera krótki opis ECS, obiektowego podejścia do tworzenia strony. Nowość w drugim wydaniu.
•
Rozdział 17, „XMLC”. Przegląd XMLC, podejścia do tworzenia strony opartego na XML. Nowość w drugim wydaniu.
•
Rozdział 18, „JavaServer Pages”. Wyjaśnia JSP, standardową technologię firmy Sun, w której strony WWW są automatycznie wkompilowane w serwer. Nowość w drugim wydaniu.
•
Rozdział 19, „Informacje dodatkowe”. Przedstawia dodatkowe przykłady serwletów i podpowiedzi, które nie zmieściły się w żadnym z poprzednich rozdziałów. Drugie wydanie zawiera analizator parametrów zlokalizowanych, nową klasę poczty elektronicznej oraz uaktualniony podrozdział na temat wyrażeń regularnych, nowy podrozdział na temat dodatkowych narzędzi oraz dodatkowe podpowiedzi na temat wydajności.
•
Rozdział 20, „Zmiany w Servlet API 2.3”. Opisuje zmiany w nadchodzącej wersji 2.3 Servlet API, który ma zostać udostępniony w połowie 2001. Nowość w drugim wydaniu.
•
Dodatek A, „Krótki opis Servlet API”. Zawiera pełny opis klas, metod i zmiennych w pakiecie javax.servlet. W drugim wydaniu uaktualniony do Servlet API 2.2
•
Dodatek B, „Krótki opis HTTP Servlet API”. Zawiera pełny opis klas, metod i zmiennych w pakiecie javax.servlet.http. W drugim wydaniu uaktualniony do Servlet API 2.2
•
Dodatek C, „Krótki opis deskryptorów DTD”. Przedstawia opis deskryptora Document Type Definition (Definicja Typu Dokumentu) web.xml. Nowość w drugim wydaniu.
•
Dodatek D, „Kody stanu HTTP”. Lista kodów stanu określonych przez HTTP, a także stałe mnemoniczne wykorzystywane przez serwlety.
•
Dodatek E, „Encje znakowe”. Lista encji znakowych zdefiniowanych w HTML, a także równoważne do nich wartości kodów ucieczkowych Uniksa.
•
Dodatek F, „Kodowania”. Lista sugerowanych kodowań wykorzystywanych przez serwlety w celu tworzenia zawartości w różnych językach.
Proszę czuć się swobodnie i czytać rozdziały w niniejszej książce w dowolnej kolejności. Czytanie prosto od początku do końca zapewnia uniknięcie wszelkich niespodzianek, jako że starano się unikać odwołań do dalszych części książki. Przeskakiwanie jest jednak możliwe, zwłaszcza po rozdziale 5 — pozostała część rozdziałów została zaprojektowana w celu oddzielonego istnienia. Jedna ostatnia sugestia — proszę przeczytać podrozdział „Usuwanie błędów” w rozdziale 19, jeżeli kiedykolwiek napotka się fragment kodu pracujący nieprawidłowo.
Konwencje wykorzystywane w tej książce Kursywa wykorzystywana jest do: •
Ścieżek, nazw plików i programów
•
Nowych terminów podczas ich definiowania
•
Adresów internetowych, takich jak nazwy domen i URL-e
Czcionka pogrubiona wykorzystywana jest do: •
Konkretnych klawiszy na klawiaturze
•
Nazw przycisków interfejsu użytkownika i menu
Czcionka o stałej szerokości wykorzystywana jest do: •
Wszystkich danych pojawiających się dokładanie w programie Javy, takich jak słowa kluczowe, typy danych, stałe, nazwy metod, zmienne, nazwy klas oraz nazwy interfejsów
•
Wszystkich wydruków kodu Javy
•
Dokumentów HTML, znaczników i atrybutów
Czcionka o stałej szerokości z kursywą wykorzystywana jest do: •
Ogólnych obszarów zablokowanych wskazujących, że dany element jest zastępowany w programie przez konkretną wartość.
Pogrubiona czcionka o stałej szerokości wykorzystywana jest do: •
Wpisów w wierszu poleceń
Prośba o komentarze Prosimy o pomoc w poprawieniu następnych wydań poprzez zgłaszanie wszystkich błędów, nieścisłości, niejasnych lub niewłaściwych wyrażeń oraz zwykłych literówek, które można odnaleźć w dowolnym miejscu niniejszej książki. Proszę wysyłać komunikaty o błędach i komentarze pod adres
[email protected]. (Przed wysłaniem komunikatu o błędzie prosimy sprawdzić erratę na stronie http://www.oreilly.com/catalog/jservlet2 w celu sprawdzenia, czy dany błąd nie został już opisany.)
Prosimy również o opinie, co powinno znaleźć się w tej książce, aby stała się ona bardziej przydatna. Wydawnictwo traktuje takie komentarze bardzo poważnie i próbuje dołączyć rozsądne sugestie do przyszłych wydań książki.
Podziękowania Kiedy pracowałem nad niniejszą książką, przyjaciel powiedział mi „Łatwiej musi być pisać drugie wydanie; napisałeś już raz tę książkę”. Pomyślałem nad tym przez chwilę, roześmiałem się i odpowiedziałem, „To jest łatwiejsze, ale ani trochę nie aż tak łatwe, jak się spodziewałem!”. Patrząc wstecz, myślę że powód tego ma niewiele wspólnego z książkami, a bardziej z technologią. Pierwsze wydanie opisywało Servlet API 2.0, specyfikację tworzoną przez około dwa lata. Niniejsze drugie wydanie przedstawia Servlet API 2.2 i 2.3, co daje mniej więcej dwa dodatkowe lata pracy projektantów. Tak więc jedynie z tej perspektywy można dostrzec, że jeżeli pierwsze wydanie zabrało mniej więcej rok aktywnego pisania, to drugie powinno zabrać mniej więcej tyle samo czasu. I rzeczywiście tak było — około 9 miesięcy. Wiele osób pomogło mi w tworzeniu tej książki. Jestem im głęboko wdzięczny. Po pierwsze są to redaktorzy techniczni książki — James Duncan Davidson, przewodniczący specyfikacji Servlet API 2.1 i 2.2, oraz Danny Coward, przewodniczący nadchodzącej wersji 2.3. Wszystko, co można o nich powiedzieć dobrego, to za mało. Nie tylko dostarczyli mi nieocenionej pomocy i rad w trakcie pisania książki, lecz stworzyli wszystkim doskonałą platformę do programowania dla WWW. Dziękuję również wielu programistom, którzy swoim doświadczeniem wspomogli tworzenie rozdziałów na temat tworzenia zawartości (i w wielu przypadkach tworzyli opisywaną technologię) — Reece Wilton i Brian O'Neill dla Tea, Justin Wells dla WebMacro, Jon Stevens dla ECS, Mark Diekhnas i Christian Cryder dla XMLC oraz Hans Bergsten i Craig McClanahan dla JSP. Chciałbym również podziękować Bobowi Ecksteinowi, redaktorowi książki, którego ręczne notatki były zawsze celne, choć czasami niemożliwe do odcyfrowania. Bob przejął obowiązki redaktorskie od Pauli Ferguson, po tym, jak zajęła się ona zarządzaniem książkami O'Reilly na temat WWW i skryptów. Dziękuję również Jimowi Grishamowi, który pomógł zlokalizować wszystkie rodzaje komputerów i przeglądarek wykorzystywane przy testowaniu przykładów; Magnusowi Stenmanowi z firmy Orion, który wyjaśnił mi implementację J2EE w serwerze Orion; Justynie Horwat, zwanej przez niektórych Boginią Biblioteki Znaczników, za odpowiedzi na pytania dotyczący biblioteki znaczników JSP oraz Ethanowi Henry, który pomógł sugestiami na temat poprawiania wydajności serwletów. Nie mogę zapomnieć o Brett'cie McLaughlinie, autorze książki „Java and XML” (O'Reilly) i współtwórcy JDOM. Jego współpraca ze mną na temat JDOM właściwie spowolniła pisanie tej książki, lecz prędkość, z jaką on pisze inspiruje mnie, a ponieważ wspomniał mnie on w swojej książce, muszę napisać coś tutaj. I ostatecznie dziękuję mojej dziewczynie, Kathlyn Bautista, która nie narzekała, kiedy pracowałem w niedziele, lecz sprawiała, że wcale pracować nie chciałem. Jason Hunter Listopad 2000
Podziękowania z wydania pierwszego Historia tej książki rozpoczęła się właściwie 20 marca 1997, w księgarni „Computer Literacy” w San Jose w Kalifornii. Tam — po ciekawej rozmowie z Larrym Wallem i Randallem Schwartzem, w której Larry wyjaśniał, jak automatyzuje swój dom przy pomocy Perla — spotkałem po raz pierwszy szacownego Tima O'Reilly. Przedstawiłem się i bezczelnie powiedziałem, że pewnego dnia (w dalekiej przyszłości, myślałem), planuję napisać książkę dla O'Reilly. Czułem się jakbym mówił Stevenowi Spielbergowi, że chcę zagrać główną rolę w jego filmie. Ku mojemu kompletnemu zaskoczeniu, Tim odpowiedział, „Na jaki temat?”. Tak rozpoczęła się szaleńcza jazda prowadząca do powstania tej książki. Wystąpiło w tym czasie kilka jasnych punktów, które z dumą pamiętam — poznanie mojej redaktorki (świetnie, też jest młoda!), podpisania oficjalnego kontraktu (czy wiecie, że cały papier firmowy O'Reilly jest ozdobiony zwierzętami?), napisanie pierwszego zdania (znowu i znowu), drukowanie pierwszego rozdziału (i sprawienie,
żeby wyglądał on jak książka O'Reilly), po czym oglądanie rosnącej sterty wydruków, do momentu, kiedy nie zostało już nic do napisania (oprócz podziękowań). Było również kilka trudnych chwil. W pewnym momencie, kiedy książka była ukończona w połowie, uświadomiłem sobie, że Servlet API zmieniał się szybciej, niż mogłem nadążyć. Wierzę w powiedzenie „Jeżeli coś się nie udaje, poproś o pomoc”, tak więc po krótkich poszukiwaniach poprosiłem Williama Crawforda, który pracował w tym czasie nad książką „Java Enterprise in a Nutshell”, czy pomógłby mi w przyśpieszeniu pracy nad książką. Wspaniałomyślnie zgodził się on i pomógł w napisaniu dwóch rozdziałów, a także części dodatków. Wielu innych ludzi pomogło mi w napisaniu niniejszej książki, zarówno bezpośrednio jak i pośrednio. Chciałbym podziękować Pauli Ferguson, redaktorowi książki oraz Mike'owi Loukidesowi, redaktorowi serii Java, za ich starania o zapewnienie (i poprawę) jakości tej książki. Oraz Timowi O'Reilly za danie mi szansy spełnienia marzeń. Dziękuję również moim menedżerom w firmie Silicon Graphics, Kathy Tansill i Waltowi Johnsonowi, za dostarczenie większej pomocy i elastyczności niż miałem prawo się spodziewać. Każde podziękowania są niewystarczające dla inżynierów firmy Sun, którzy odpowiadali na niezliczone pytania, informowali mnie o zmianach w Servlet API i naprawiali niemal każdy błąd, jaki zgłosiłem — są to James Duncan Davidson (Wyglądający niemal jak James Gosling), Jim Driscoll, Rob Clark i Dane Brownell. Dziękuję również członkom listy dystrybucyjnej jserv-interest, których pytania i odpowiedzi ukształtowały zawartość tej książki; Willowi Rameyowi, staremu przyjacielowi, który nie pozwolił, aby przyjaźń przesłoniła jego krytyczne oko; Mike'owi Engberowi, człowiekowi, do którego zwróciłem się po ucieczce z eleganckich miejsc pracy i byłem gotowy na zaakceptowanie jego szalonych pomysłów; Dave'owi Vandergriftowi, pierwszej osobie, która przeczytała wiele rozdziałów; Billowi Dayowi, autorowi „Java Media Players”, który pomagał poprzez przechodzenie przez proces tworzenia książki równolegle ze mną; Michaelowi O'Connellowi i Jill Steinberg, redaktorom „JavaWorld”, dzięki którym napisałem mój pierwszy profesjonalny tekst; Dougowi Youngowi, który dzielił się za mną technicznymi sztuczkami poznanymi przy pisaniu siedmiu własnych książek technicznych oraz Shoji Kuwabara'rze, Mieko Aono, Song'owi Yung'owi, Matthew Kim'owi oraz Alexandrowi Pashintsev'owi za ich pomoc w przetłumaczeniu skryptu „Witaj Świecie” w rozdziale 13. Chciałbym gorąco podziękować recenzentom technicznym książki, których konstruktywny krytycyzm pomógł znacznie w usprawnieniu pracy — są to Mike Slinn, Mike Hogarth, James Duncan Davison, Dan Protchett, Dave McMurdie i Rob Clark. Ciągle jestem w szoku, po tym jak dowiedziałem się, że jednemu recenzentowi zabrało trzy dni, aby przeczytać to, nad czego stworzeniem pracowaliśmy rok! Ostatecznie, dziękuję Mamie i Tacie, za ich miłość i wsparcie i za czas, który poświęciliście dawno temu dna nauczenie mnie podstaw pisania. Dziękuję też Kristi Taylor, która sprawiła, że ta niewielka część czasu, która nie była wypełniona pracą, stała się przyjemnością. Oraz Dziadkowi, chciałbym, żebyś mógł to zobaczyć. Jason Hunter Czerwiec 1998 Po pierwsze dziękuję Shelley Norton, dr Isaacowi Kohane, dr Jamesowi Facklerowi i dr Richardowi Kitzowi (a także pozostałej części zespołu, której wkład pozostaje nieoceniony), których pomoc i wsparcie sprawiła, że wszystko to stało się możliwe. A także Martinowi Streeterowi z firmy Invantage, Inc., za jego wsparcie w trakcie trwania tego projektu. Bez Roba Leitha, Rogera Stacey i Freda Sterbeigha, przypuszczalnie ciągle trwałbym w stronie biernej. Dale Dogherty zaoferował mi pieniądze w zamian za słowa, wydarzenie, którego ciągle nie potrafię pojąć. Andy Kwak, Joel Pomerantz i Matthew Proto, wspaniali ludzie, zechcieli przeczytać próbne wydruki i słuchać skarg o godzinie pierwszej w nocy. I, oczywiście Mamie i Tacie za ich lata wsparcia, oraz mojej siostrze Faith za (zazwyczaj) wybaczanie mi bycia durniem. William Crawford Lipiec 1998
W niniejszym rozdziale: •
Historia aplikacji WWW
•
Obsługa serwletów
•
Moc serwletów
Rozdział 1.
Wprowadzenie Rozwój aplikacji Javy działających po stronie serwera — wszystko od działających samodzielnie serwletów do pełnej platformy Java 2, Enterprise Edition (J2EE) — był jednym z najbardziej ekscytujących trendów w programowaniu Javy. Język Java został utworzony pierwotnie w celu zastosowania w małych, osadzonych urządzeniach. Był on opisywany jako język do tworzenia zawartości WWW po stronie klienta w formie apletów. Jednak aż do kilku ostatnich lat potencjał Javy jako platformy do programowania po stronie serwera był niestety pominięty. Aktualnie Java jest uważana za język idealnie nadający się do programowania po stronie serwera. Szczególnie szybko rozpoznały potencjał Javy w serwerach firmy biznesowe — Java idealnie pasuje do dużych aplikacji typu klient-serwer. Niezależna od platformy natura Javy jest niezwykle użyteczna dla organizacji posiadających heterogeniczny zbiór serwerów pracujących pod różnymi odmianami systemów operacyjnych UNIX i Windows (oraz coraz bardziej Mac OS X). Nowoczesny, obiektowy i chroniący pamięć projekt Javy pozwala programistom na skrócenie cyklów programistycznych i zwiększenie niezawodności. Dodatkowo, wbudowana w Javę obsługa sieci i interfejsów korporacyjnych dostarcza możliwości dostępu do starych danych, ułatwiając przejście ze starszych systemów klient-serwer. Serwlety Javy są kluczowym składnikiem programowania Javy po stronie serwera. Serwlet to małe, dołączane rozszerzenie serwera, które rozszerza jego funkcjonalność. Serwlety pozwalają programistom na rozszerzanie i dostosowywanie każdego serwera WWW lub aplikacji z obsługą Javy do wcześniej nieznanego poziomu przenośności, elastyczności i łatwości. Jednak przed przejściem do szczegółów, należy spojrzeć na sprawę z pewnej perspektywy.
Historia aplikacji WWW Chociaż serwlety mogą być wykorzystywane do rozszerzenia funkcjonalności każdego serwera z obsługą Javy, najczęściej używane są do rozszerzania serwerów WWW, stanowiąc potężny i wydajny zamiennik dla skryptów CGI. Kiedy wykorzystuje się serwlet do utworzenia dynamicznej zawartości strony WWW lub podniesienia w inny sposób funkcjonalności serwera WWW, w efekcie tworzy się aplikację WWW. Podczas, gdy strona WWW wyświetla jedynie zawartość statyczną i pozwala użytkownikowi na nawigację poprzez tę zawartość, aplikacja WWW dostarcza doświadczenia bardziej interaktywnego. Aplikacja WWW może być tak prosta jak
wyszukiwanie słowa kluczowego w archiwum dokumentów lub tak złożona, jak sklep elektroniczny. Aplikacje WWW są umieszczane w Internecie oraz korporacyjnych sieciach intranet i extranet, w których posiadają one potencjał do zwiększania produktywności i zmiany sposobu prowadzenia biznesu przez małe i duże firmy. Aby zrozumieć potęgę serwletów, należy cofnąć się i spojrzeć na pewne inne podejścia do tworzenia aplikacji WWW.
Common Gateway Interface Common Gateway Interface (Wspólny Interfejs Bramek), w skrócie CGI, był jednym z pierwszych praktycznych technik tworzenia zawartości dynamicznej. Przy pomocy CGI serwer WWW przekazuje konkretne żądania do programu zewnętrznego. Wynik tego programu jest później przesyłany do klienta w miejscu statycznego pliku. Powstanie CGI pozwoliło na implementację wielu nowych rodzajów funkcjonalności na stronach WWW, a CGI szybko stał się de facto standardem, zaimplementowanym w ogromnej ilości serwerów WWW. Interesujący jest fakt, że możliwość tworzenia przez programy CGI dynamicznych stron WWW jest ubocznym efektem ich początkowego przeznaczenia — zdefiniowania standardowej metody porozumiewania się serwera informacji z aplikacjami zewnętrznymi. To źródło wyjaśnia, dlaczego CGI posiada przypuszczalnie najgorszy do wyobrażenia okres trwałości. Kiedy serwer otrzymuje żądanie, które uzyskuje dostęp do programu CGI, musi on utworzyć nowy proces w celu uruchomienia programu CGI i potem przekazania mu, poprzez zmienne środowiskowe i standardowe wpisy, każdego bitu informacji, który może być potrzebny do wygenerowania odpowiedzi. Tworzenie procesu dla każdego takiego żądania wymaga czasu i znaczących zasobów serwera, co ogranicza liczbę żądań, które serwer może obsługiwać równocześnie. Rysunek 1.1 przedstawia okres trwałości CGI.
Rysunek 1.1. Okres trwałości CGI Chociaż program CGI może być utworzony w prawie każdym języku, język programowania Perl stał się podstawową opcją. Zaawansowane możliwości formatowania tekstu Perla stanowią ogromną pomoc w zarządzaniu szczegółami interfejsu CGI. Tworzenie skryptu CGI w Perlu pozwala na uniezależnienie o platformy, ale wymaga również uruchomienia osobnego interpretatora Perla dla każdego żądania, co zabiera jeszcze więcej czasu i wymaga dodatkowych zasobów. Innym często przeoczonym problemem z CGI jest niemożność interakcji programu CGI z serwerem WWW lub skorzystania z możliwości serwera po rozpoczęciu działania tego programu, ponieważ działa on jako osobny proces. Na przykład, skrypt CGI nie potrafi zapisywać informacji w dzienniku zdarzeń serwera. Większa ilość informacji na temat programowania CGI jest dostępna w książce „CGI Programming on the World Wide Web” autorstwa Shishira Gundavarama (O'Reilly).
FastCGI Firma o nazwie OpenMarket stworzyła alternatywę dla standardu CGI o nazwie FastCGI. W większości aspektów, FastCGI działa podobnie do CGI — ważną różnicą jest tworzenie przez FastCGI jednego trwałego procesu dla każdego programu FastCGI, jak przedstawiono na rysunku 1.2. Eliminuje to konieczność tworzenia nowego procesu dla każdego żądania.
Rysunek 1.2. Okres trwałości FastCGI Chociaż FastCGI jest krokiem we właściwym kierunku, ciągle posiada on problem z mnożeniem się procesów — istnieje co najmniej jeden proces dla każdego programu FastCGI. Jeżeli program FastCGI musi obsługiwać żądania równoległe, potrzebuje puli procesów, jednego na każde żądanie. Pamiętając, że każdy proces może wykonywać interpretator Perla, podejście na to nie jest skalowalne na tyle, na ile można się tego spodziewać. (Chociaż trzeba przyznać, że FastCGI może rozkładać procesy pomiędzy wieloma serwerami.) Innym problemem z FastCGI jest to, że nie pozwala on swoim programom na bliższą interakcję z serwerem. Poza tym, programy FastCGI są przenośne jedynie tak, jak język, w którym zostały napisane. Większa ilość informacji na temat FastCGI jest dostępna pod adresem http://www.fastcgi.com.
PerlEx PerlEx, utworzony przez ActiveState, zwiększa wydajność skryptów CGI napisanych w Perlu pracujących na serwerach WWW pod Windows NT (Internet Information Server Microsoftu, Website Professional O'Reilly oraz FastTrack Server i Enterpise Server iPlanet). Posiada on zalety i wady podobne do FastCGI. Większa ilość informacji na temat PerlEx dostępna jest pod adresem http://www.activestate.com/plex.
mod_perl Przy korzystaniu z serwera WWW Apache, inną opcją zwiększenia wydajności CGI jest wykorzystanie mod_perl. mod_perl jest modułem serwera Apache osadzającym kopię interpretatora Perla w pliku wykonywalnym Apache'a, dostarczającym pełnej funkcjonalności Perla wewnątrz Apache'a. Jego efektem jest prekompilowanie skryptów CGI przez serwer i wykonywanie ich bez rozdzielania, a związku z tym ich praca jest dużo szybsza i wydajniejsza. Jego wadą jest możliwość wykorzystania jedynie w serwerze Apache. Większa ilość informacji na temat mod_perl jest dostępna pod adresem http://perl.apache.org.
Inne rozwiązania CGI/Perl posiada zaletę bycia mniej lub bardziej niezależnym od platformy sposobem na tworzenie dynamicznej zawartości WWW. Inne dobrze znane technologie tworzenia aplikacji WWW takie jak ASP i JavaScript działający po stronie serwera, są opatentowanymi rozwiązaniami pracującymi jedynie z określonymi serwerami WWW.
Interfejsy rozszerzeń serwera Kilka firm utworzyło własne interfejsy API rozszerzeń serwera dla swoich serwerów WWW. Na przykład, iPlanet/Netscape dostarcza wewnętrzny API o nazwie WAI (dawniej NSAPI), a Microsoft dostarcza ISAPI. Przy pomocy każdego z tych interfejsów, można utworzyć rozszerzenia serwera zwiększające lub zmieniające jego podstawową funkcjonalność, pozwalającą mu na obsługę zadań wcześniej delegowanych do zewnętrznych programów CGI. Jak można dostrzec na rysunku 1.3, rozszerzenia serwera występują wewnątrz głównego procesu serwera WWW.
Rysunek 1.3. Okres trwałości rozszerzeń serwera Ponieważ specyficzne dla serwera interfejsy wykorzystują połączony kod C lub C++, rozszerzenia serwera działają niezwykle szybko i w pełni wykorzystują zasoby serwera. Nie są one jednak rozwiązaniem doskonałym. Poza tym, że są one ciężkie w tworzeniu i utrzymaniu, stanowią poważne zagrożenia dla bezpieczeństwa i niezawodności — załamanie rozszerzenia może prowadzić do załamania całego serwera, złośliwe rozszerzenie może kraść hasła użytkowników i numery kart kredytowych. Oraz, oczywiście konkretne rozszerzenia są nierozerwalnie związane z API serwera, dla którego zostały napisane, a także do konkretnego systemu operacyjnego.
JavaScript działający po stronie serwera iPlanet/Netscape posiada również technikę skryptów działających po stronie serwera, nazywaną server-side JavaScript, w skrócie SSJS. Podobnie jak ASP, SSJS pozwala na osadzanie fragmentów kodu w stronach HTML w celu utworzenia dynamicznej zawartości WWW. Różnica jest taka, że SSJS wykorzystuje JavaScript jako język skryptowy. Z SSJS, strony są prekompilowane w celu poprawienia wydajności. Obsługa SSJS jest możliwa jedynie w serwerach iPlanet/Netscape. Większa ilość informacji na temat programowania przy pomocy JavaScript działającego po stronie serwera dostępna jest pod adresem http://developer.netscape.com/tech/javascript/ssjs/ssjs.html.
Active Server Pages Microsoft posiada technikę tworzenia dynamicznej zawartości WWW o nazwie Active Server Pages (Aktywne Strony Serwera), w skrócie ASP. Przy pomocy ASP, strona HTML może zawierać fragmenty osadzonego kodu (zazwyczaj VBScript lub Jscript — chociaż możliwe jest zastosowanie niemal każdego języka). Kod ten jest odczytywany i wykonywany przez serwer WWW przed wysłaniem strony do klienta. ASP został optymalizowany do tworzenia niewielkich porcji zawartości dynamicznej, większe pozostawiając składnikom COM. Obsługa ASP jest wbudowana w Internet Information Server Microsoftu w wersji 3.0 i wyższych, dostępnym bezpłatnie pod adresem http://www.microsoft.com/iis. Obsługa innych serwerów WWW jest dostępna jako komercyjny produkt firmy Chili!Soft pod adresem http://www.chilisoft.com. Proszę pamiętać, że strony ASP działające na platformie nie będącej Windows mogą mieć problemy z powodu braku biblioteki Windows COM. Większa ilość informacji na temat programowania ActiveServerPages jest dostępna pod adresem http://www.microsoft.com/workshop/server/default.asp oraz http://www.activeserverpages.com/.
JavaServer Pages JavaServer Pages, często nazywana po prostu JSP, jest opartą na Javie alternatywą dla ASP, utworzoną i wystandaryzowaną przez firmę Sun. JSP wykorzystuje składnię podobną do ASP poza tym, że językiem skryptowym w tym przypadku jest Java. Odwrotnie niż ASP, JSP jest otwartym standardem implementowanym przez wielu producentów na wszystkich platformach. JSP jest ścisłe związana z serwletami, ponieważ strona JSP jest przetwarzana do serwletu, co stanowi część jej wykonania. JSP jest bardziej szczegółowo opisana w dalszych częściach niniejszej książki. Większa ilość informacji na temat JSP jest dostępna pod adresem http://java.sun.com/products/jsp.
Serwlety Javy W tym miejscu pojawiają się serwlety Javy. Jak wspomniano wcześniej, serwlet jest ogólnym rozszerzeniem serwera — klasą Javy, która może być dynamicznie ładowana w celu rozszerzenia funkcjonalności serwera. Serwlety są często używane w serwerach WWW, gdzie zajmują miejsce skryptów CGI. Serwlet jest podobny do poprzednio omawianego rozszerzenia serwera, poza tym, że działa on wewnątrz wirtualnej maszyny Javy (Java Virtual Machine — JVM) na serwerze (proszę spojrzeć na rysunek 1.4), tak więc są one bezpieczne i przenośne. Serwlety działają wyłącznie w domenie serwera — inaczej niż aplety, nie wymagają one obsługi Javy przez przeglądarkę WWW.
Rysunek 1.4. Okres trwałości serwletu Inaczej niż CGI i FastCGI, które muszą wykorzystywać wiele procesów w celu obsługi oddzielnych programów i/lub oddzielnych żądań, serwlety mogą być obsługiwane przez osobne wątki w tym samym procesie, z wieloma procesami rozciągniętymi na klika serwerów wspierających. Oznacza to, że serwlety są również wydajne i skalowalne. Ponieważ serwlety działają z komunikacją dwustronną do serwera WWW, mogą bardzo ściśle współpracować z serwerem w celu wykonania działań niemożliwych dla skryptów CGI. Inną zaletą serwletów jest ich przenośność — zarówno pomiędzy systemami operacyjnymi jak w przypadku Javy, jak i pomiędzy serwerami WWW. Jak zostanie to opisane poniżej, większość głównych serwerów WWW obsługuje serwlety. Uważa się, że serwlety stanowią najlepszą możliwą platformę dla tworzenia aplikacji WWW, a więcej informacji na ten temat zostanie podane w dalszej części tego rozdziału.
Obsługa serwletów Podobnie jak sama Java, serwlety zostały zaprojektowane w celu zapewnienia maksymalnej przenośności. Serwlety obsługiwane są przez wszystkie platformy obsługujące również Javę, oraz pracują w większości podstawowych serwerów WWW1. Serwlety Javy zdefiniowane przez dział Java Software firmy Sun Microsystems (dawniej znany jako JavaSoft), stanowią Pakiet Opcjonalny (Optional Package) dla Javy (dawniej znany jako Rozszerzenie Standardowe — Standard Extension). Oznacza to, że serwlety zostały oficjalnie pobłogosławione przez Sun'a i stanowią część języka Java, lecz nie są częścią podstawowego API Javy. Zamiast tego, są one znane jako część platformy J2EE. W celu ułatwienia tworzenia serwletów, Sun i Apache udostępniły klasy API niezależnie od żadnego mechanizmu WWW. Pakiety javax.servlet i javax.servlet.http składają się na Servlet API. Najnowsza wersja tych klas jest dostępna do pobrania pod adresem http://java.sun.com/products/servlet/download.html2. Wszystkie serwery WWW obsługujące serwlety muszą
1
Proszę zauważyć, że niektórzy producenci serwerów WWW posiadają swoje własne implementacje Javy działającej po stronie serwera, niektóre z nich noszą również nazwę serwletów. Są one generalnie niekompatybilne z serwletami Javy utworzonymi przez Sun'a. Większość z tych producentów konwertuje swoją obsługę Javy do standardowych serwletów lub wprowadzają obsługę standardowych serwletów równolegle, w celu zapewnienia wstecznej kompatybilności.
2
W pewnym momencie planowano dołączenie tych klas do JDK 1.2. Później jednak zdecydowano na utrzymanie ich niezależności od JDK w celu ułatwienia dokonywania poprawek do Servlet API.
wykorzystywać te klasy wewnętrznie (chociaż mogą stosować alternatywną implementację), tak więc generalnie ten plik JAR może zostać znaleziony gdzieś wewnątrz dystrybucji serwera WWW obsługującego serwlety. Nie jest ważne, skąd pobiera się klasy serwletów, należy posiadać je jednak w swoim systemie w celu kompilowania serwletów. Dodatkowo konieczny jest program uruchamiający serwlety (technicznie nazywany kontenerem serwletów, czasami mechanizmem serwletów), w celu przetestowania i udostępnienia serwletów. Wybór kontenera serwletów zależy po części od działającego w danym systemie serwera(ów) WWW. Istnieją trzy odmiany kontenerów serwletów — samodzielne, dołączane i osadzane.
Samodzielne kontenery serwletów Samodzielny kontener serwletów to serwer zawierający wbudowaną obsługę serwletów. Taki kontener posiada tę przewagę, że wszystko w nim działa niejako od razu. Jednak wadą jest konieczność oczekiwania na nową wersję serwera WWW w celu uzyskania obsługi najnowszych serwletów. Inną wadą jest także fakt, że producenci serwerów generalnie obsługują jedynie JVM dostarczoną przez samych siebie. Serwery WWW dostarczające samodzielnej obsługi to między innymi:
3
•
Tomcat Server Apache, oficjalna wzorcowa implementacja sposobu obsługi serwletów przez kontener. Napisany całkowicie w Javie, dostępny bezpłatnie w licencji Open Source. Dostępny jest cały kod źródłowy i każdy może pomóc w jego tworzeniu. Serwer ten może działać samodzielnie lub jako dodatek dostarczający obsługi serwletów Apache'owi lub innym serwerom. Może być również wykorzystywany jako kontener osadzony. Równolegle z Tomcatem, Apache tworzy standardową implementację pakietów javax.servlet i javax.servlet.http. W trakcie pisania niniejszej książki serwlety są jedynymi pakietami java.* lub javax.* utrzymywanymi jako Open Source3. Proszę zobaczyć http://jakarta.apache.org.
•
iPlanet Web Server Enterprise Edition Netscape'a (wersja 4.0 i późniejsze), przypuszczalnie najpopularniejszy serwer WWW zawierający wbudowaną obsługę serwletów. Niektóre testy wykazują, że posiada on najszybszą implementację serwletów. Proszę pamiętać, że chociaż wersje 3.51 i 3.6 zawierały wbudowaną obsługę serwletów, to jednak był to wczesny Servlet API 1.0 i zawierały one dużą liczbę błędów tak poważnych, że obsługa serwletów była praktycznie bezużyteczna. W celu wykorzystania serwletów z serwerami Netscape'a w wersji 3.x należy wykorzystać dołączany kontener. Proszę zobaczyć http://www.iplanet.com.
•
WebSite Professional O'Reilly, o podobnej funkcjonalności do Enterprise Server iPlanet, lecz za niższą cenę. Proszę zobaczyć http://website.oreilly.com.
•
Zeus Web Server, serwer WWW uważany przez niektórych za najszybszy z dostępnych. Jego lista własności jest dość długa i zawiera obsługę serwletów. Proszę zobaczyć http://www.zeus.co.uk.
•
Resin Caucho, kontener Open Source, uważany za bardzo wydajny. Może być uruchamiany w trybie samodzielnym lub jako dodatek do wielu serwerów. Proszę zobaczyć http://www.caucho.com.
•
LiteWebServer Gefion Software, niewielki (nieco ponad 100 KB) kontener serwletów utworzony dla zastosowań, takich jak dołączanie do wersji demonstracyjnych, gdzie niewielki rozmiar ma znaczenie. Proszę zobaczyć http://www.gefionsoftware.com/LiteWebServer.
•
Jigsaw Server World Wide Web Consortium Open Source, napisany całkowicie w Javie. Proszę zobaczyć http://www.w3.org/Jigsaw.
•
Java Web Server firmy Sun, serwer, od którego wszystko się rozpoczęło. Serwer ten był pierwszym serwerem implementującym serwlety oraz działał jako efektywna wzorcowa implementacja dla Servlet API 2.0. Jest on napisany całkowicie w Javie (poza dwoma bibliotekami kodu macierzystego, które powiększają funkcjonalność, lecz nie są konieczne). Sun nie kontynuuje już prac nad serwerem, koncentrując się na produktach iPlanet/Netscape w ramach sojuszu Sun-Netscape. Proszę zobaczyć http://java.sun.com/products.
Implementacja javax.servlet i javax.servlet.http w standardzie Open Source spowodowała naprawienie wielu błędów (na przykład, autor miał okazję poprawić obsługę warunkowego GET w HttpServlet) i kwestii niekompatybilności. Istnieje nadzieja, że przykład ten wspomoże w udostępnieniu większej ilości oficjalnych pakietów Javy jako Open Source
Serwery aplikacji są rosnącym obszarem tworzenia. Serwer aplikacji oferuje obsługę po stronie serwera dla tworzenia aplikacji korporacyjnych. Większość aplikacji opartych na Javie obsługuje serwlety i pozostałą część specyfikacji Java 2, Enterprise Edition (J2EE).Serwery te to miedzy innymi: •
WebLogic Application Server BEA System, jeden z pierwszych i najsłynniejszych opartych na Javie serwerów aplikacji. Proszę zobaczyć http://www.beasys.com/products/weblogic.
•
Orion Application Server, wysoko wydajny serwer o stosunkowo niskiej cenie, napisany całkowicie w Javie. Proszę zobaczyć http://www.orionserver.com.
•
Enhydra Application Server, serwer Open Source firmy Lutris. Proszę zobaczyć http://www.enhydra.org.
•
Borland Application Server 4, serwer ze specjalnym naciskiem na technologię CORBA. Proszę zobaczyć http://www.borland.com/appserver.
•
WebSphere Application Server IBM, wysokowydajny serwer oparty w części na kodzie Apache'a. Proszę zobaczyć http://www-4.ibm.com/software/webservers.
•
Dynamo Application Server 3 ATG, kolejny wysokowydajny serwer napisany całkowicie w Javie. Proszę zobaczyć http://www.atg.com.
•
Application Server Oracle, serwer zaprojektowany do integracji z bazą danych Oracle. Proszę zobaczyć http://www.oracle.com/appserver.
•
iPlanet Application Server, zgodny z J2EE większy brat iPlanet Web Server Enterprise Edition. Proszę zobaczyć http://www.iplanet.com/products/infrastructure/app_servers/nas.
•
GemStone/J Application Server, serwer Javy stworzony przez firmę poprzednio znaną z serwera Smalltalk. Proszę zobaczyć http://www.gemstone.com/products/j.
•
Jrun Server Allaire (poprzednio Live Software), prosty kontener serwletów, który rozrósł się do zaawansowanego kontenera dostarczającego wiele technologii J2EE włączając w to EJB, JTA i JMS. Proszę zobaczyć http://www.allaire.com/products/jrun.
•
Silverstream Application Server, w pełni zgodny z J2EE serwer, który rozpoczął również od skupienia się na serwletach. Proszę zobaczyć http://www.silverstream.com.
Dołączane kontenery serwletów Dołączany kontener serwletów działa jako moduł rozszerzający do istniejącego serwera — dodaje obsługę serwletów do serwera, który w oryginale nie był do tego przeznaczony, lub do serwera ze słabą lub nieaktualną implementacją serwletów. Dołączane kontenery serwletów zostały utworzone dla wielu serwerów, między innymi Apache'a, FastTrack Server i Enterprise Server iPlanet, Internet Information Server i Personal Web Server Microsoftu, Website O'Reilly, Go Webserver Lotus Domino, WebSTAR StarNine oraz AppleShare IP Apple. Dołączane kontenery serwletów to między innymi: •
ServletExec New Atlanta — moduł rozszerzający zaprojektowany do obsługi serwletów we wszystkich popularnych serwerach na wszystkich popularnych systemach operacyjnych. Zawiera bezpłatny program uruchomieniowy. Proszę zobaczyć http://www.servletexec.com.
•
Jrun Allaire (dawniej Live Software), dostępny jako moduł rozszerzający do obsługi serwletów we wszystkich popularnych serwerach na wszystkich popularnych systemach operacyjnych. Proszę zobaczyć http://www.allaire.com/products/jrun/.
•
Moduł Jserv projektu Java-Apache, bezpłatny kontener serwletów Open Source, który dodaje obsługę serwletów do niezwykle popularnego serwera Apache. Tworzenie Jserv zakończyło się, a Tomcat Server (działający jako moduł rozszerzający) jest jego następcą. Proszę zobaczyć http://java.apache.org/.
•
Tomcat Server Apache, jak opisano poprzednio. Tomcat może być dołączony do innych serwerów takich jak Apache, iPlanet/Netscape i IIS.
Osadzane kontenery serwletów Osadzany kontener jest ogólnie niewielką platformą programistyczną, która może być osadzana w innych aplikacjach. Aplikacja ta staje się prawdziwym serwerem. Osadzane kontenery serwletów to między innymi: •
Tomcat Server Apache, podczas gdy ogólnie używany samodzielnie lub dołączany, może być również osadzany w innej aplikacji, kiedy jest to potrzebne. Ponieważ serwer ten to Open Source, tworzenie większości innych osadzanych serwerów zatrzymano.
•
Nexus Web Server autorstwa Andersa Kristensena, dostępny bezpłatnie program uruchamiający serwlety, implementujący większą część Servlet API, który może być w łatwy sposób dołączany do aplikacji Javy. Proszę zobaczyć http://www-uk.hpl.hp.com/people/ak/java/nexus/.
Uwagi dodatkowe Przed przejściem do następnej części należy zapamiętać, że nie wszystkie kontenery serwletów są tworzone w jednakowy sposób. Tak więc przed wybraniem kontenera serwletów (i prawdopodobnie serwera) przy pomocy którego udostępniane będą serwlety, należy go wypróbować, prawie do granic możliwości. Sprawdzić listy dystrybucyjne. Należy zawsze sprawdzać, czy serwlety zachowują się tak, jak we wzorcowej implementacji Tomcata. Można również sprawdzić, jakie narzędzia programistyczne są dostarczane, które technologie J2EE są wspierane i jak szybko można uzyskać pomoc u producenta. W przypadku serwletów nie trzeba się martwić o zgodność z najsłabszą implementacją, tak więc należy pobrać kontener serwletów, który posiada wszystkie pożądane właściwości. Kompletna, aktualna lista dostępnych kontenerów serwletów razem z ich obecnymi cenami jest dostępna pod adresem http://www.servlets.com.
Potęga serwletów Jak dotychczas serwlety zostały opisane jako alternatywa dla innych technologii dynamicznej zawartości WWW, lecz nie zostało tak naprawdę powiedziane, dlaczego powinny być one, zdaniem autorów, stosowane. Co sprawia, że serwlety są jednym z najlepszych sposobów programowania WWW? Zdaniem autorów posiadają one kilka zalet ponad innymi podejściami, włączając w to przenośność, moc, wydajność, wytrzymałość, bezpieczeństwo, elegancję, integrację, rozszerzalność i elastyczność. Każda z tych własności zostanie kolejno omówiona.
Przenośność Ponieważ serwlety są pisane w Javie według dobrze zdefiniowanego i szeroko akceptowanego API, są one w dużym stopniu przenośne pomiędzy systemami operacyjnymi i implementacjami serwerów. Można stworzyć serwlet na komputerze pod Windows NT i z serwerem Tomcat, po czym bez problemu udostępnić go na wysoko wydajnym serwerze Uniksowym z iPlanet/Netscape Application Server. Stosując serwlety można naprawdę „napisać raz, udostępniać wszędzie”. Przenośność serwletów nie jest tak ważną sprawą jak w przypadku apletów, z dwóch powodów. Po pierwsze, przenośność serwletów nie jest obowiązkowa. Inaczej niż w przypadku apletów, które muszą zostać przetestowane na wszystkich możliwych platformach klientów, serwlety muszą pracować jedynie na serwerach, które są wykorzystywane do tworzenia i udostępniania. Dopóki nie sprzedaje się swoich serwletów, nie trzeba się martwić o kompletną przenośność. Po drugie, serwlety unikają najbardziej pełnej błędów i niespójnie zaimplementowanej części języka Java — Abstract Windowing Toolkit (AWT), który stanowi bazę graficznych interfejsów Javy, takich jak Swing.
Moc Serwlety mogą wykorzystywać pełną moc jądra API Javy — pracę w sieci i dostęp do URL-i, wielowątkowość, kompresję danych, łączność z bazami danych (JDBC), serializację obiektów, zdalne wywoływanie metod (RMI)
oraz integrację ze starymi programami (CORBA). Serwlety mogą również korzystać z platformy J2EE, która zawiera obsługę Enterprise JavaBeans (EJBs), transakcji rozproszonych (JTS), standaryzowanych wiadomości (JMS), wyszukiwania katalogów (JNDI) oraz zaawansowanego dostępu do baz danych (JDBC 2.0). Lista standardowych API dostępnych serwletom rośnie, czyniąc tworzenie aplikacji WWW szybszym, łatwiejszym i bardziej niezawodnym. Jako autor serwletów, można wykorzystać dowolną z mnóstwa niezależnych klas Javy i składników JavaBeans. Serwlety mogą używać niezależnego kodu w celu obsługi zadań takich jak wyszukiwanie według wyrażeń regularnych, tworzenie wykresów danych, dostosowany dostęp do baz danych, zaawansowana praca w sieci, analiza składniowa XML oraz tłumaczenia XSLT. Serwlety są również sprawne w umożliwianiu komunikacji klient-serwer. Posiadając oparty na Javie aplet i oparty na Javie serwlet, można wykorzystać RMI i serializację obiektu w komunikacji klient-serwer, co oznacza, że ten sam kod można wykonać zarówno na maszynie klienta, jak i na serwerze. Wykorzystywanie po stronie serwera języków innych niż Java jest znacznie bardziej skomplikowane, jako że konieczne jest tworzenie swoich własnych protokołów do obsługi komunikacji.
Wydajność i wytrzymałość Wywoływanie serwletów charakteryzuje się bardzo wysoką wydajnością. Kiedy serwlet zostaje załadowany, pozostaje w pamięci serwera jako pojedynczy egzemplarz obiektu. Następnie serwer wywołuje serwlet do obsługi żądania przy pomocy prostego wywołania metody. Inaczej niż w przypadku CGI, nie trzeba wywoływać procesu ani interpretatora, tak więc serwlet może rozpocząć obsługę żądania niemal natychmiast. Wielokrotne, równoległe żądania są obsługiwane przez osobne wątki, tak więc serwlety są w wysokim stopniu skalowalne. Serwlety są obiektami z natury trwałymi. Ponieważ serwlet zostaje w pamięci serwera jako pojedynczy egzemplarz obiektu, automatycznie zachowuje swój stan i może utrzymywać kontakt z zasobami zewnętrznymi, takimi jak połączenia z bazami danych. W innym przypadku przywrócenie połączenia mogłoby zabrać kilkanaście sekund.
Bezpieczeństwo Serwlety obsługują bezpieczne praktyki programowania na różnych poziomach. Ponieważ są one pisane w Javie, dziedziczą po niej silne bezpieczeństwo typów. Podczas gdy większość wartości w programie CGI, włączając w to element numeryczny taki, jak numer portu serwera, są traktowane jako łańcuchy, wartości w Servlet API są manipulowane przy pomocy ich naturalnych typów, tak więc numer portu serwera jest reprezentowany jako integer. Automatyczne zbieranie śmieci przez Javę i brak wskaźników oznaczają, że serwlety są generalnie bezpieczne od problemów z zarządzaniem pamięcią, takich, jak uszkodzone wskaźniki, niewłaściwe odwołania do wskaźników oraz uszczerbki pamięci. Serwlety mogą bezpiecznie obsługiwać błędy, dzięki mechanizmowi obsługi wyjątków lub kontrolerowi dostępu Javy. Jeżeli serwlet wykona dzielenie przez zero lub inne nieprawidłowe działanie, wyrzuca wyjątek, który może być bezpiecznie wychwycony i obsłużony przez serwer, który zapisze błąd w dzienniku zdarzeń i przeprosi użytkownika. Jeżeli podobny wyjątek napotkałoby rozszerzenie serwera oparte na C++, przypuszczalnie nastąpiłoby załamanie serwera. Serwer może chronić siebie w większym stopniu poprzez zastosowanie menedżera bezpieczeństwa lub kontrolera dostępu Javy. Serwer może wykonywać swoje serwlety pod ochroną dokładnego kontrolera dostępu który, na przykład wymusza politykę bezpieczeństwa zaprojektowaną do strzeżenia przed złośliwym lub źle zaprojektowanym serwletem dążącym do zniszczenia systemu plików serwera.
Elegancja Elegancja kodu serwletów jest uderzająca. Kod serwletów jest czysty, obiektowy, modularny i zadziwiająco prosty. Jednym z powodów tej prostoty jest sam Servlet API, który zawiera metody i klasy obsługujące wiele rutynowych elementów programowania serwletów. Nawet zaawansowane operacje, takie jak obsługa cookies i śledzenie sesji, są rozkładane na odpowiednie klasy. Kilka bardziej zaawansowanych, lecz także popularnych zadań zostało pozostawione poza API, i w tych przypadkach autorzy próbowali to naprawić i tak powstał zbiór przydatnych klas w pakiecie com.oreilly.servlet.
Integracja Serwlety są ściśle zintegrowane z serwerem. Ta integracja pozwala serwletowi na współpracę z serwerem w sposób niedostępny dla programów CGI. Na przykład, serwlet może wykorzystywać serwer w celu przetłumaczenia ścieżek plików, dokonania logowania, sprawdzenia uwierzytelnienia oraz wykonania odwzorowania typu MIME. Właściwe dla konkretnego serwera rozszerzenia mogą wykonać większość tej pracy, lecz proces ten jest zazwyczaj znacznie bardziej złożony i obfity w błędy.
Rozszerzalność i elastyczność Servlet API jest zaprojektowany w celu zapewnienia łatwej rozszerzalności. W obecnym czasie, API zawiera klasy z wyspecjalizowaną obsługą serwletów HTTP. Lecz w późniejszym okresie może być ona rozszerzona i zoptymalizowana dla innego typu serwletów, czy to produkcji Suna, czy innej firmy. Jest również możliwe, że jego obsługa serwletów HTTP może być dalej rozwijana. Serwlety cechują się również elastycznością w tworzeniu zawartości. Mogą tworzyć prostą zawartość przy pomocy wyrażeń out.println(), lub generować skomplikowany zbiór stron przy pomocy mechanizmu szablonów. Mogą tworzyć stronę HTML przez traktowanie strony jako zestawu obiektów Javy, lub tworzyć stronę HTML przez wykonanie transformacji XML do HTML. Serwlety mogą być nawet łączone w celu utworzenia całkowicie nowych technologii takich jak JavaServer Pages. Nie wiadomo, do czego jeszcze zostaną wykorzystane.
W tym rozdziale: •
Podstawy HTTP
•
Interfejs API (Servlet API)
•
Tworzenie strony
•
Aplikacje WWW
•
Przejdźmy dalej
Rozdział 2.
Aplety Http — wprowadzenie Ten rozdział to krótki samouczek pisania i uruchamiania prostych apletów HTTP. Opisane tutaj zostanie jak wdrożyć aplet do standardowej aplikacji WWW i jak skonfigurować jego zachowanie przy użyciu XML-owego deskryptora rozmieszczenia. W przeciwieństwie do pierwszej edycji niniejszej książki, ten rozdział obecnej nie opisuje opartych na apletach plików dołączanych serwera (SSI) lub wiązania łańcuchowego oraz filtrowania apletu. Mimo tego, iż techniki te były bardzo przydatne oraz zostały umieszczone w serwerze WWW (Java Web Server), nie zostały zatwierdzone w specyfikacji apletu, (która ukazała się po pierwszej edycji niniejszej pozycji). SSI zostały zastąpione przez nowe techniki tworzenia plików dołączanych programu. Wiązanie łańcuchowe apletu zostało uznane za zbyt nieczytelne dla oficjalnego zatwierdzenia, mimo tego jest wielce prawdopodobne, że sama jego idea zostanie wykorzystana w Interfejsie API 2.3 (Servlet API 2.3) jako część oficjalnego mechanizmu ogólnego zastosowania przed i po — filtrującego.
Zwróćmy uwagę, iż możliwe jest ściągnięcie kodów dla każdego z przykładów zamieszczonych w tym oraz innych rozdziałach tej książki, zarówno w formie źródłowej jak i skompilowanej (jak zostało to opisane w przedmowie). Jednakże, co się tyczy tego rozdziału, wydaje się, iż rzeczą najbardziej pomocną w nauce będzie zapisanie przykładów ręcznie (za pomocą klawiatury). Lektura tego rozdziału może prowadzić do wniosku, iż niektóre zagadnienia zostały potraktowane zbyt ogólnie. Aplety to narzędzia dające wiele możliwości i czasem bywają skomplikowane, dlatego też rozdział ten ma na celu wprowadzenie w ogólne zasady ich działania i zorientowania się w temacie. Czytelnik po lekturze niniejszej książki będzie w stanie samodzielnie tworzyć najrozmaitsze aplety.
Podstawy HTTP Zanim przejdziemy do omawiania prostych apletów HTTP, musimy sprawdzić znajomość podstaw działania protokołu HTTP. Będąc doświadczonym w programowaniu w CGI (lub mając doświadczenie w tworzeniu stron WWW na serwerach) można z powodzeniem pominąć czytanie tego podrozdziału. W takim przypadku korzystnym wydaje się zatrzymanie się na istotnych zagadnieniach metod GET i POST. Jednakże będąc osobą stawiającą pierwsze kroki w tworzeniu stron WWW na serwerach, należy przeczytać wspomniany materiał uważnie, ponieważ zrozumienie dalszej części książki wymaga znajomości protokołu HTTP. Protokół HTTP został szczegółowo omówiony w „Pocket Reference” Clintona Wong’a (wydawnictwo O’Reilly).
Zlecenia, odpowiedzi, nagłówki HTTP jest prostym, międzynarodowym protokołem. Klient, np. przeglądarka WWW składa zlecenie, serwer WWW odpowiada i dokonywana jest tzw. „obsługa zlecenia”. Kiedy klient składa zlecenie, pierwszą rzeczą którą wykonuje, jest komenda HTTP zwana metodą, za pomocą której serwer orientuje się jaki rodzaj zlecenia jest składany. Pierwszy wiersz zlecenia określa adres dokumentu (URL) oraz używaną wersję protokółu HTTP. Oto przykład: GET/intro.html Http/1.0
W tym zleceniu chodzi o uzyskanie dokumentu o nazwie intro.html za pomocą wersji 1.0 HTTP. Po przesłaniu zlecenia klient może przesłać informacje nagłówkową w celu dostarczenia serwerowi dodatkowych informacji o zleceniu, takich jak: jakie oprogramowanie jest używane przez klienta oraz jaka forma informacji potrzebna jest klientowi do jej zrozumienia. Takie informacje nie odnoszą się bezpośrednio do tego, co było przedmiotem zlecenia, jednakże mogą być wykorzystane przez serwer w tworzeniu odpowiedzi. Oto dwa przykłady nagłówków zleceń: User-Agent : Mozilla/4.0 (compatabile; MSIE 4.0; Windows 95) Accept : image/gif, image/jpeg, text/*, */*
Nagłówek User-Agent dostarcza informacji o oprogramowaniu klienta, podczas gdy nagłówek Accept określa rodzaj nośnika (MIME) najkorzystniejszy dla klienta (nagłówki zleceń zostaną
omówione szerzej przy omawianiu apletów, w rozdziale 4 „Odczytywanie informacji”). Celem zaznaczenia końca sekcji nagłówkowej, po przesłaniu nagłówków, klient przesyła nie zapisany wiersz. Jeżeli wymaga tego używana metoda, klient może również przesłać inne dodatkowe dane, tak jak w przypadku metody POST, która zostanie zaraz omówiona. Jeżeli zlecenie nie zawiera żadnych danych, to kończy się nie zapisanym wierszem. Po przesłaniu przez klienta zlecenia, serwer przetwarza je i przesyła odpowiedź. Pierwszy wiersz odpowiedzi zawiera wiersz statusu oraz jego opis. Oto przykład: HTTP/1.0 200 OK.
Powyższy wiersz statusu zawiera kod statusu 200, co oznacza że zlecenie zostało wykonane, stąd opis OK. Kolejny często spotykany kod to 404 z opisem Not Found (nie znaleziono), jak łatwo się domyśleć opis ten oznacza, że dokument nie został odnaleziony. W rozdziale 5 „Przesyłanie informacji HTML” zostały omówione najczęściej spotykane kody statusu oraz w jaki sposób można je wykorzystać w apletach. Dodatek D „Kody statusu HTTP” zawiera kompletną listę kodów statusu HTTP. Po przesłaniu wiersza statusu serwer przesyła nagłówki odpowiedzi, które są informacją dla klienta taką jak np.: jakiego oprogramowania używa serwer oraz nośnika informacji użytego do zapisania odpowiedzi serwera. Oto przykład: Date: Saturday, 23-May-00 03:25:12 GMT Server: Tomcat Web Server/3.2 MIME-version:1.0 Content-type: text/html Content-length: 1029 Last-modified: Thursday, 7-May-00 12:15:35 GMT
Nagłówek Server dostarcza informacji o oprogramowaniu serwera, nagłówek Content-type określa rodzaj rozszerzenia MIME odnośnie danych zawartych w odpowiedzi (nagłówki odpowiedzi zostaną omówione szerzej w rozdziale 5). Po nagłówkach serwer przesyła „czysty wiersz” celem zakończenia sekcji nagłówkowej. Jeżeli zlecenie zostało wykonane, żądane dane są następnie przesyłane jako część odpowiedzi. W przeciwnym wypadku odpowiedź może zawierać informację tekstową dla osoby obsługującej przeglądarkę, która będzie wyjaśniała dlaczego serwer nie mógł wykonać zlecenia.
Metody GET i POST Klient łącząc się z serwerem może złożyć zlecenie w kilku różnych formach, zwanych metodami. Najczęściej używane metody to GET i POST. Metoda GET służy do uzyskiwania informacji (dokumentów, wykresów, informacji bez danych), podczas, gdy metoda POST została zaprojektowana z myślą o wysyłaniu informacji (numerów kart kredytowych, danych statystycznych lub informacji baz danych). Wykorzystując analogię elektronicznego biuletynu informacyjnego, GET służy do czytania a POST do zamieszczania w nim tekstu. GET to metoda używana do wprowadzania URL-u bezpośrednio do przeglądarki lub podczas klikania na hiperlink; jednakże zarówno metoda GET jak i POST mogą być używane do dostarczania formularzy HTML. Mimo, iż metoda GET została zaprojektowana w celu odczytywania informacji, może zawierać jako część zlecenia informacje własne, które dokładniej precyzują to zlecenie. Może to być np. układ współrzędnych x,y dla wykresów. Takie informacje są przesyłane jako ciąg znaków dołączonych do URL-u w formie znanej ciągiem zapytań. Ten sposób zamieszczania dodatkowych informacji w URL-u umożliwia przesłanie strony e-mailem bądź utworzenie z niej zakładki.
Ponieważ zlecenia GET nie są przeznaczone do przesyłania dużych partii informacji, niektóre serwery ograniczają długość URL-ów i ciągów zapytań do około 240 znaków. W metodzie POST używana jest odmienna technika przesyłania informacji do serwera, ponieważ niekiedy metody tej używa się do przesyłania większych partii informacji. Zlecenie złożone za pomocą metody POST przesyła bezpośrednio wszystkie informacje (nie ograniczone co do długości) w nim zawarte za pomocą połączenia gniazdowego jako część swego zlecenia HTTP. Klient nie jest informowany o tej zamianie, URL nie ulega w ogóle zmianie. W efekcie zlecenia POST nie mogą być ani zapisane jako zakładki, ani wysłane e-mailem, ani też w niektórych przypadkach nie mogą być w ogóle powtórnie załadowane. Powód jest prosty — sytuacja taka wynika z odpowiedniego zaprojektowania — informacja jak np. numer naszej karty kredytowej, powinna być przesyłana do serwera tylko raz. Stosując metodę POST uzyskujemy dodatkowo pewien stopień zabezpieczenia przy przesyłaniu poufnych informacji, ponieważ dziennik zdarzeń, który zapisuje wszystkie zgłoszenia URL-ów, nie rejestruje danych metody POST. W praktyce użycie metod GET i POST odbiega od celu, dla którego zostały zaprojektowane. Powszechną praktyką przy składaniu długich parametryzowanych zleceń na informacje jest użycie POST zamiast GET w celu uniknięcia problemów związanych z nadmiernie długimi URL-ami. Metoda GET jest również często wykorzystywana do ładowania informacji przez proste formularze ponieważ, no cóż, po prostu da się to w ten sposób zrobić. Powyższe problemy nie są jednak aż tak dramatyczne, wystarczy tylko pamiętać, iż zlecenia GET (z powodu tego, iż mogą być w prosty sposób zamieniane na zakładki) mogą wywołać zmianę na serwerze, za którą odpowiedzialny będzie klient. Chodzi o to, że zlecenia GET nie powinny być używane do składania zleceń, uaktualniania baz danych oraz do innych działań umożliwiających identyfikację klienta (w przypadku wystąpienia zmian na serwerze).
Pozostałe metody Http Poza GET i POST istnieje jeszcze wiele, rzadziej używanych metod HTTP, takich jak na przykład metoda HEAD. Metoda ta jest używana przez klienta tylko do uzyskiwania nagłówków odpowiedzi, w celu określenia rozmiaru dokumentu, czasu modyfikacji lub ogólnej dostępności. Inne metody to PUT — do zamieszczania dokumentów bezpośrednio na serwerze, DELETE — wykorzystywana do ich usuwania stamtąd. Dwie ostatnie metody nie współpracują ze wszystkimi serwerami z powodu skomplikowanych procedur. Metoda TRACE jest powszechnie używana do usuwania błędów — umożliwia przesłanie klientowi dokładnej treści jego zlecenia. Metoda OPTIONS może być użyta do zapytania serwera, z którymi metodami współpracuje lub jak dotrzeć do poszczególnych jego zasobów.
Interfejs API (Servlet API) Po zapoznaniu się z podstawami HTTP, możemy przejść do omówienia interfejsów API, których z kolei używa się do tworzenia apletów HTTP, lub innych rodzajów apletów odpowiednich dla tej materii. Aplety używają klas i interfejsów z dwóch pakietów: javax.servlet i javax.servlet.http. Pakiet javax.servlet zawiera klasy i współpracuje ze standardowymi protokołowo–niezależnymi apletami. Klasy te są rozszerzane przez klasy w pakiecie
java.servlet.http w celu dodania funkcjonalności specyficznej dla HTTP. Pakiet najwyższej klasy nazywa się javax zamiast zwykłego java, aby zasygnalizować, iż Interfejs API jest pakietem dodatkowym (uprzednio zwanym Standardowym Rozszerzeniem). Każdy aplet musi wdrożyć interfejs javax.servlet. Większość apletów wdraża ten interfejs przez rozszerzenie jednej z dwóch specjalnych klas: javax.servlet.GenericServlet lub javax.servlet.http.HttpServlet. Aplet niezależny protokołowo powinien być podrzędny do GenericServlet, a aplet HTTP powinien być podrzędny w stosunku do HTTPservlet, który sam jest podklasą GenericServlet z dodana funkcjonalnością HTTP.
W przeciwieństwie do zwykłego programu Java, i dokładnie tak jak zwykły aplet, aplet wykonywany na serwerze nie zawiera metody main(). Zamiast tego pewne metody apletów wywoływane są przez serwer w procesie obsługi zleceń. Za każdym razem, kiedy serwer wysyła zlecenie do apletu, wywołuje jego metodę service(). Standardowy aplet w celu poprawnej obsługi zlecenia powinien zignorować swoją metodę service(). Metoda service() akceptuje dwa parametry: obiekt zlecenia i obiekt odpowiedzi.
Obiekt zlecenia informuje aplet o zleceniu, a obiekt odpowiedzi używany jest do wysyłania odpowiedzi. Rysunek 2.1. ukazuje jak standardowy aplet obsługujący zlecenie.
Rysunek 2.1. Standardowy aplet obsługujący zlecenie Aplet HTTP zwykle nie ignoruje metody service(), tylko metodę doGet() (do obsługi zleceń GET) i metodę doPost() (do obsługi zleceń POST). Aplet HTTP może zignorować jedną z powyższych metod lub obie, w zależności od tego, jaki jest typ zlecenia, które ma obsłużyć. Metoda service() HttpServlet obsługuje instalację oraz transfer do wszystkich metod doXXX(), dlatego właśnie zwykle nie powinna być ignorowana. Na rysunku 2.2 został ukazany sposób, w jaki aplet HTTP obsługuje zlecenia metod GET i POST.
Rysunek 2.2. Aplet HTTP obsługujący zlecenia GET i POST Aplet HTTP może zignorować odpowiednio metody doPut() i doDelete() celem obsłużenia zleceń PUT i DELETE. Jednakże aplety HTTP generalnie nie modyfikują metod doTrace() czy doOptions(). Dla nich prawie zawsze wystarczające są implementacje domyślne. Pozostałe klasy w pakietach javax.servlet i javax.servlet.http to w większości klasy wspomagające. Na przykład klasy ServletRequest i ServletResponse w javax.servlet umożliwiają dostęp do zleceń i odpowiedzi standardowych serwerów, natomiast klasy HttpServletRequest i HttpServletResponse w javax.servlet.http umożliwiają dostęp do zleceń i odpowiedzi HTTP. Pakiet javax.servlet.http zawiera również klasę HttpSession, która oferuje wbudowaną funkcjonalność śledzenia sesji oraz klasę Cookie, która pozwala na szybkie instalowanie i przetwarzanie cookies.
Tworzenie strony Najbardziej podstawowy typ apletu HTTP tworzy pełną stronę HTML. Taki aplet ma zwykle dostęp do takich samych informacji, co przesyłane do skryptu CGI oraz do pewnej partii innych. Aplet, który tworzy strony HTML może zostać użyty do wykonywania wszystkich zadań, tych które obecnie są wykonywane przy pomocy CGI, jak np. przetwarzanie formularzy HTML, tworzenie listy z bazy danych, przyjmowanie zamówień, sprawdzanie zamówień, sprawdzanie identyfikacji, itd.
Pisanie „Hello World” Przykład 2.1 ukazuje aplet HTTP tworzący kompletną stronę HTML. Dla uproszczenia sprawy aplet ten ,za każdym połączeniem się z nim za pomocą przeglądarki WWW, wyświetla napis „Hello World”.*
*Pierwszy przykład zarejestrowanego programu „Hello World” pojawił się w „A Tutorial Introduction to the Language B”, napisanym przez Braiana Kernighana w 1973. Dla tych z czytelników, zbyt młodych, by pamiętać, język B był prekursorem języka C. Więcej informacji o języku programowania B oraz link do tej książki można znaleźć na stronie: http://cm.bell-labs.com/who/dmr/bintro.html.
Przykład 2.1. Aplet, który wyświetla „Hello World” import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HellWorld extends HttpServlet
{
public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType ("text/html"); PrintWriter out = res.getWriter ( ); out.println(""); out.println("<TITLE>Hello World"); out.println ("") ; out.println (Hello World"); out.println (""); } }
Powyższy aplet rozszerza klasę HttpServlet oraz ignoruje odziedziczoną z niej metodę doGet(). Kiedy serwer WWW otrzymuje zlecenie GET z tego apletu, każdorazowo wywołuje metodę doGet(), przekazując mu obiekty HttpServletRequest oraz HttpServletResponse. Obiekt HttpServletRequest reprezentuje zlecenie klienta. Obiekt ten daje apletowi dostęp do informacji o kliencie, parametrach zlecenia, nagłówkach HTTP przekazywanych razem ze zleceniem oraz inne. W rozdziale 4. omówione zostały wszystkie możliwości obiektu zlecenia. Dla tego przykładu możemy jednak je pominąć jako, ze niezależnie od typu zlecenia aplet ten wyświetla „Hello World”. Obiekt HttpServletResponse reprezentuje odpowiedź apletu. Aplet może wykorzystać ten obiekt do dostarczenia danych klientowi. Mogą to być dane różnego typu, typ ten jednak powinien być określony jako część odpowiedzi. Aplet może również użyć tego obiektu do ustalenia nagłówków odpowiedzi HTTP. W rozdziałach 5. i 6 „Przesyłanie treści multimedialnej” zostało omówione wszystko co może zrobić aplet jako część odpowiedzi. Nasz aplet ustala najpierw za pomocą metody setContentType() typ zawartości swojej odpowiedzi do text/html, standardowego typu zawartości MIME dla stron HTML. Następnie używa metody getWriter() w celu odczytania PrintWriter, międzynarodowego odpowiednika PrintStream. PrintWriter przekształca kod UNICODE Javy na kodowanie specyficzne lokalnie. Dla kodowania lokalnego — angielskiego zachowuje się tak jak PrintStream. Aplet używa wreszcie PrintWriter do wysłania swojego HelloWorld HTML do klienta.
Uruchamianie „Hello World” Do tworzenia apletów potrzebne są dwie rzeczy: pliki klasy interfejsu API, które używane są w tłumaczeniu programu źródłowego na język wynikowy oraz pojemnik apletu np. serwer WWW, który używany jest z kolei przy uruchamianiu apletów. Wszystkie popularne pojemniki apletów oferują pliki klasy Interfejsu API, więc można spełnić oba warunki (potrzebne do tworzenia apletów) za jednym ładowaniem.
Istnieją dziesiątki parametrów dostępnych pojemników apletów dla wdrażania apletów, kilkanaście z nich zostało zamieszczonych w rozdziale 1 „Wprowadzenie”. Dokonując wyboru serwera należy pamiętać, że musi on współpracować z wersją 2.2 Interfejsu API (Servlet API 2.2) lub późniejszą. Wersja 2.2 była pierwszą wersją Interfejsu API, która zapewniała dostęp do aplikacji WWW, jak zostało to omówione w tym rozdziale. Aktualna lista pojemników apletów oraz tego do jakiego poziomu API zapewniają one dostęp i jest dostępna pod adresem: http://www.servlets.com. Tak więc zapytajmy, co należy zrobić z naszym kodem, aby zadziałał w serwerze WWW? — to zależy od rodzaju serwera. W przykładach zaprezentowanych w tej książce występuje serwer „Apache Tomcat 3.2”, serwer implementacji odniesienia API, napisany całkowicie w Javie, dostępny pod adresem: http://jakarta.apache.org. Serwer „Tomcat” zawiera mnóstwo dokumentacji, w której jest wyjaśnione jego zastosowanie. W niniejszej książce dlatego właśnie omówione zostaną tylko ogólne zasady dotyczące pracy z serwerem. Poniższe omówienia powinny być także zgodne z innymi serwerami (niż „Tomcat”) jednakże nie można tego zagwarantować. Jeżeli używamy serwera „Apache Tomcat”, to powinniśmy umieścić kod źródłowy dla apletu w katalogu server_root/webapps/ROOT/WEB-INF/classes (gdzie server_root jest katalogiem , w którym zainstalowaliśmy nasz serwer), jest to standardowa lokalizacja plików klasy apletu. Powód, dla którego aplety występują w tym katalogu zostanie omówiony później w tym rozdziale. Kiedy już mamy właściwie umiejscowiony kod źródłowy HelloWorld, musimy go skompilować. To zadanie możemy wykonać przy pomocy standardowego kompilatora javac (lub naszego ulubionego środowiska graficznego Javy). Należy się tylko upewnić, że mamy pakiety javax.servlet i javax.servlet.http w naszej ścieżce klasy. Pracując z serwerem „Tomcat”, wystarczy tylko zamieścić server_root/lib/servlet.jar (lub przyszły odpowiednik) gdzieś w naszej ścieżce klasy. Nazwa pliku oraz lokalizacja zależą od serwera, więc w razie problemów trzeba zajrzeć do dokumentacji serwera, z którym mamy do czynienia. Jeżeli wyświetlony zostanie komunikat informujący o błędzie taki jak np. Package javax.server not found in import, oznacza to, że pakiety apletów nie są odnajdywane przez nasz kompilator, należy więc sprawdzić nasza ścieżkę klasy i spróbować jeszcze raz. Teraz, kiedy już skompilowaliśmy nasz pierwszy aplet, możemy uruchomić nasz serwer i wejść do apletu. Uruchomienie serwera nie jest rzeczą trudną, należy uaktywnić skrypt startup.sh (lub plik startowy startup.bat w Windows) znajdujący się w katalogu server_root/bin. To powinno wystarczyć do uruchomienia naszego serwera, jeżeli pracujemy w Solairs lub w Windows. W przypadku pracy na innych systemach operacyjnych może zdarzyć się sytuacja, że będziemy musieli dokonać pewnych modyfikacji w skryptach startowych. W przypadku konfiguracji domyślnych, serwer oczekuje na porcie 8080. Istnieje wiele sposobów dostępu do apletów. Dla przykładu możemy zrobić to przez wyraźne wprowadzenie do URL-u nazwy klasy apletu. Możemy wprowadzić URL do swojej ulubionej przeglądarki:http://server:8080/servlet/HelloWorld. Wyraz server zamieniamy na nazwę naszego komputera — serwera lub na localhost — jeżeli serwer jest na naszym lokalnym komputerze. Powinna zostać wyświetlona strona podobna do tej poniżej.
Rysunek 2.3. Aplet „HelloWorld” Jeżeli aplet byłby częścią pakietu musiałby zostać umieszczony w server_root/webapps/ROOT/WEB-INF/package/name.HelloWorld. Nie wszystkie serwery zezwalają automatycznie na dostęp do apletów przez użycie rodzajowego przedrostka /servlet/. Funkcja ta może zostać wyłączona ze względów bezpieczeństwa, aby zapewnić, że dostęp do apletów jest możliwy tylko przez określoną konfigurację URL-u, w czasie administrowania serwerem. W celu zapoznania się ze szczegółami wyłączania i załączania przedrostka /servlet/ należy zapoznać się z dokumentacją serwera, na którym pracujemy.
Przetwarzanie danych formularzowych Aplet „HelloWorld” nie jest zbyt skomplikowany, przejdźmy więc do rzeczy bardziej zaawansowanych. Tym razem utworzymy aplet, który będzie pozdrawiał użytkownika jego imieniem (i nazwiskiem). Nie jest to rzecz specjalnie skomplikowana, potrzebny jest nam najpierw formularz HTML, który spyta użytkownika o jego imię i nazwisko. Następująca strona powinna być w tym celu wystarczająca: <TITLE>Introductions Pozwól że spytam, jak się nazywasz?
Na rysunku 2.4 został ukazany sposób, w jaki strona ta zostanie wyświetlona na stronie użytkownika.
Rysunek 2.4. Formularz HTML Formularz ten powinien znaleźć się w pliku HTML, w katalogu serwera document_root. Jest to miejsce, w którym serwer szuka plików statycznych. Dla serweru „Tomcat” katalog ten to server_root/webapps/ROOT. Dzięki umieszczeniu pliku w tym katalogu, może być on dostępny bezpośrednio pod adresem: http://server:8080/form.html. Kiedy użytkownik przesyła ten formularz, jego imię (i nazwisko) jest przesyłane do apletu „Hello” ponieważ uprzednio zainstalowaliśmy atrybut ACTION celem wskazania ich apletowi. Formularz używa metody GET, więc jakiekolwiek dane są dodawane do URL-u zlecenia jako pasmo zapytań. Jeżeli np. użytkownik wprowadzi imię, nazwisko „Inigo Montoya”, URL zlecenia będzie wyglądał następująco: http://server:8080/servlet/Hello?name=Inigo+Montoya. Przerwa pomiędzy imieniem a nazwiskiem jest wyjątkowo kodowana przez przeglądarkę jako znak „+”, ponieważ URL nie może zawierać spacji. Obiekt apletu: HttpServletRequest umożliwia dostęp do danych formularzowych w paśmie zapytań. Na przykładzie 2.2 została ukazana zmodyfikowana wersja naszego apletu „Hello”, która używa swojego obiektu zlecenia do odczytywania parametru name. Przykład 2.2. Aplet, który „wie” kogo pozdrawia import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Hello extends HttpServlet
{
public void doGet (HttpServletRequest req, HttpServletResponse res) throwsServletException, IOException { res.setContentType ("text/html"); PrintWriter out = res.getWriter ( ); String name = req.getParameter ("imię i nazwisko"); out.println(""); out.println("<TITLE>Hello, " + name + ""); out.println("Hello, " + name + ""); out.println(", a inna na <SERVLET>. Drugą ważną sprawą przy pisaniu w XML-u jest to, że rozpoczynając danym oznaczeniem musimy nim także zakończyć. Tak np. jeżeli rozpoczniemy znacznikiem rozpoczynającym <servlet> to musimy zakończyć znacznikiem końcowym . Dla ułatwienia pusty znacznik składniowy <servlet> może zostać użyty jako substytut oznaczenia jednoczesnego rozpoczęcia i zakończenia, pary znaczników <servlet>. Trzecia zasada jest taka, że elementy zagnieżdżone nie mogą się na siebie nakładać. Tak więc właściwym będzie układ data, układem niewłaściwym natomiast będzie data. Po czwarte, wszystkie wartości atrybutów muszą być „wzięte” w cudzysłów (pojedynczy lub podwójny). Tak więc właściwy będzie zapis <servlet id="O"/> a niewłaściwy <servlet id=O/>. Dokumenty (zwane well-formed) spełniające powyższe zasady, będą całkowicie przetwarzane przez automatyczne programy narzędziowe. Poza tymi zasadami, istnieją także sposoby szczegółowego określania struktury znaczników w pliku XML. Specyfikacja tego typu jest określana jako Dokumentacja typu dokumentu (DTD). DTD szczegółowo precyzuje jakie znaczniki mogą znaleźć się w pliku zgodności XML, jaki rodzaj danych mają zawierać te znaczniki oraz to gdzie w hierarchii mają (lub muszą) znajdować się te znaczniki. Każdy plik XML może być zadeklarowany do pewnego DTD. Pliki które całkowicie dostosowują się do DTD, do których są zadeklarowane nazywamy valid. XML jest
a
XML miał być uprzednio nazwany MAGMA. Więcej na stronie na stronie http://www.xml.com/axml/notes/TheCorrectTitle.html.
używany w apletach jako format pamięci przy konfiguracji plików. XML może być również używany przez aplety przy tworzeniu treści , jak to zostało opisane w rozdziale 17 „XMLC” Więcej informacji o XML-u można znaleźć na stronie http://www.w3.org/XML/ oraz w książce Bretta McLaughlin’a „Java and XML” (wydawnictwo O’Relly). !!!!!!!!!!!!!!!!koniec ramki Deskryptory wdrożenia z prostym sposobem obsługi wielu użytkowników na tym samym serwerze są również oferowane przez firmy zajmujące się obsługują sieci WWW. Użytkownicy mogą uzyskać kontrole nad swoimi domenami — mogą oni nadzorować rejestrację apletu, odwzorowanie URL-u, rodzaj MIME oraz poziom zabezpieczenia strony — bez potrzeby ogólnego dostępu do serwera.
Deskryptor wdrożenia Prosty deskryptor wdrożenia został pokazany na przykładzie 2. Żeby sprawić, by plik ten opisywał domyślną aplikacją WWW serwera „Tomcat” musimy go umieścić w server_root/webapps/ROOT/WEB-INF/web.xml. Przykład 2.5. Prosty deskryptor wdrożenia
<web-app> <servlet> <servlet-name> hi <servlet-class> HelloWorld
Pierwszy wiersz oznacza, iż jest to plik XML 1.0 zawierający znaki ze standardu ISO-8859-1 (Latin-1) charset. Drugi wiersz określa DTD dla pliku, pozwalając tym samym programowi bibliotecznemu, czytającemu plik na jego weryfikację (czy jest valid) oraz sprawdzenie czy odpowiada zasadom DTD. Wszystkie pliki deskryptora wdrożenia zaczynają się opisanymi dwoma wierszami lub bardzo podobnymi do nich. Pozostała część tekstu — pomiędzy <web-app> a dostarcza serwerowi informacji o tej aplikacji WWW. Ten prosty przykład rejestruje nasz aplet „Hello World” przed nazwą hi (otaczający biały obszar jest wycinany). Zarejestrowana nazwa jest tu umieszczana pomiędzy znacznikami <servlet-class>. Znacznik <servlet> „spina” znaczniki <servlet-name> oraz <servlet-class>. Składnia deskryptora wdrożenia XML wydaje się być lepiej zoptymalizowana dla odczytu automatycznego, raczej niż dla bezpośredniego (przez człowieka).
Dlatego też większość producentów oferuje środki graficzne pomocne w procesie tworzenia web.xml. Na rynku dostępnych jest również wiele edytorów XML, które można wykorzystać przy tworzeniu XML-u. !!!!!!!!!!!!!!!!!!!!początek ramki
Uwaga na kolejność znaczników Nie wszyscy uświadamiają sobie fakt, iż znaczniki w web.xml są uwarunkowane co do kolejności. Dla przykładu, żeby wszystko działało poprawnie znacznik <servlet-name> musi znajdować się przed znacznikiem <servlet-class>. Taka jest ich kolejność, w której zostały zdeklarowane w DTD. Analizatory sprawdzające poprawność składni wprowadzają taką kolejność oraz deklarują dokument jako nieprawidłowy (invalid) jeżeli kolejność nie jest zachowana. Niektóre serwery, nawet te bez analizatorów poprawności składni, mogą być po prostu przygotowane na przyjęcie takiej kolejności, mogą więc być zdezorientowane w przypadku otrzymania innej. Dla pewności należy upewnić się, ze wszystkie znaczniki <web-app> zostały uporządkowane we właściwej kolejności. Niektóre znaczniki mogą, lecz nie muszą być obecne, lecz te, które zostały przez nas zapisane muszą być we właściwym porządku. Na szczęście istnieją narzędzia, które upraszczają to zadanie (więcej informacji znajduje się w dodatku C — pod DTD). !!!!!!!!!!koniec ramki Po rejestracji, oraz po ponownym uruchomieniu serwera możemy wejść do apletu „Hello World” pod URL-em: http://server:8080/servlet/hi. Może zastanawiać fakt dlaczego ktoś miałby zadawać sobie trud rejestracji apletu pod specjalna nazwą. Odpowiedź jest taka, że procedura ta pozwala na „zapamiętanie” przez serwer szczegółów związanych z apletem i traktować go w szczególny sposób. Jednym z przykładów takiego traktowania jest możliwość ustalenia wzorców URL, które wywołują zarejestrowany aplet. Klient może postrzegać URL będący przedmiotem zlecenia , tak jak każdy inny, jednakże serwer jest w stanie stwierdzić , że zlecenie jest zgodne z odwzorowaniem modelu (wzoru) i dzięki temu może być obsłużone przez odpowiedni aplet. Możemy dla przykładu sprawić, że http://server:8080/hello.html wywoła aplet „HelloWorld”. Użycie odwzorowań apletu w ten sposób, pomaga ukryć , że strona używa apletów. Użycie to również pozwala apletowi na płynne zastąpienie istniejącej pod jakimkolwiek URL-em adresem strony, tak więc wszystkie zakładki oraz łączniki (linki) do strony nadal będą działać. Wzory URL konfigurowane są przy użyciu deskryptora wdrożenia, tak jak zostało to zaprezentowane na przykładzie 2.6. Przykład 2.6. Dołączanie odwzorowania apletu
<web-app> <servlet> <servlet-name>
hi <servlet-class> HelloWorld <servlet –mapping> <servlet –name> hi <servlet –name> /hello.html
Powyższy deskryptor wdrożenia dodaje zapis <servlet-mapping> informując tym samym serwer, że aplet hi powinien obsłużyć wszystkie URL-e zgodne ze wzorem dostępu do ścieżki głównej /hello.html. Jeżeli aplikacja WWW zostanie przekształcona do głównej ścieżki „/” pozwoli to apletowi „Hello World” obsłużyć zlecenie na http://server:8080/hello.html. Jeżeli zamiast powyższego aplikacja WWW zostanie przekształcona do ścieżki dostępu przedrostka /greeting: aplet „Hello” obsłuży zlecenia złożone na http://server:8080/greeting/hello.html. Różne zasady odwzorowania URL-u mogą zostać wyszczególnione w deskryptorze wdrożenia. Istnieją cztery rodzaje (style) odwzorowania, wyszukiwane w następującej kolejności: •
Odwzorowania jawne, takie jak /hello.html lub /images/chart.gif, które nie zawierają symboli wieloznacznych. Ten styl odwzorowania jest pomocny w zastępowaniu istniejącej strony.
•
Odwzorowania ścieżki dostępu przedrostka takie jak /lite/*, /dbfile/* lub /catalog/item/* Odwzorowania te zaczynają się od a /, a kończą na a/* i obsługują wszystkie zlecenia rozpoczynające się takimi przedrostkami (nie licząc kontekstu ścieżki dostępu). Ten styl odwzorowania pozwala apletowi na kształtowanie całej hierarchii sieciowej. Dla przykładu, aplet obsługujący /dbfile/* może serwować pliki z bazy danych, podczas gdy aplet obsługujący /lite/* może przesyłać pliki z systemu plików automatycznie skompresowanych.
•
Odwzorowania rozszerzenia takie jak *.wm lub .jsp. Takie odwzorowania zaczynają się od a* i obsługują wszystkie zlecenia kończące się tym przedrostkiem. To odwzorowanie pozwala apletowi na operowanie na wszystkich plikach określonego rozszerzenia. Dla przykładu aplet może zostać przypisany do obsługi plików kończących się na *.jsp celem obsługi stron JavaServer (jest to de facto odwzorowanie niejawne będące domeną specyfikacji apletu).
•
Odwzorowanie domyślne /. Odwzorowanie to określa serwer domyślny dla aplikacji WWW — jeżeli inne nie będą pasowały. Jest ono identyczne z ograniczonym odwzorowaniem ścieżki dostępu przedrostka (/*), wyjątek stanowi fakt iż odwzorowanie to jest brane pod uwagę po odwzorowaniach rozszerzenia. Odwzorowanie to daje kontrolę nad tym, w jaki sposób są wysyłane są podstawowe pliki — znacząca możliwość, która jednak powinna być wykorzystywana z rozwagą. W sytuacji kolizji odwzorowań dokładne (ścisłe) dopasowania mają pierwszeństwo przed dopasowaniami przedrostków, a dopasowanie ścieżki dostępu przedrostka przed dopasowaniami rozszerzenia. Odwzorowanie domyślne jest wywoływane tylko wtedy, kiedy nie występują żadne inne dopasowania. Dopasowania o dłuższym ciągu znaków w grupie traktowane są priorytetowo przed dopasowaniami o krótszym ciągu.
Deskryptor „snippet” zaprezentowany na przykładzie 2.7 prezentuje różne odwzorowania, które mogą być użyte do wchodzenia do apletu „Hello World”. Przykład 2.7. Sposoby powiedzenia „Hello”
Aplet „Hello World” może być wywołany poprzez użycie jednego z odwzorowań znajdujących sie na poniższej liście: /servlet/HelloWorld /servlet/hi /hello.html /well.hello /fancy/meeting/you/here.hello /hello/to/you
Bardziej praktyczne odwzorowania zostaną zaprezentowane w dalszych rozdziałach niniejszej książki.
Przejdźmy dalej Duża ilość informacji o apletach, aplikacjach WWW oraz plikach konfiguracyjnych XML zwartych w niniejszym wprowadzeniu powinna być wystarczająca do uzyskania pojęcia jak tworzyć proste aplety, instalować je na serwerach oraz informować serwery o ścieżkach, dla których chcemy wykonywać wspomniane aplety. Możliwości apletów znacznie przekraczają wyświetlanie „Hello World” czy pozdrawianie użytkowników ich imionami i nazwiskami — właśnie o tym będzie traktować dalsza część książki.
W tym rozdziale: •
Alternatywa apletu
•
Odnawianie (powtórne ładowanie) apletu
•
Inicjalizacja i usuwanie
•
Model trój-wątkowy (Single-Thread Model)
•
Przetwarzanie drugoplanowe
•
Ładowanie i uruchamianie
•
Buforowanie podręczne po stronie klienta
•
Buforowanie podręczne po stronie serwera
Rozdział 3.
Czas istnienia (cykl życia) apletu Czas istnienia (cykl życia) apletu jest jednym z bardziej interesujących aspektów apletów. Czas istnienia jest hybrydą czasów istnienia używanych w środkach programowania CGI oraz środkach programowania niskiego poziomu WAI/NSAPI i ISAPI, tak jak zostało to omówione w rozdziale1 „Wprowadzenie”.
Alternatywa apletu Czas istnienia (cykl życia) apletów pozwala ich pojemnikom na odniesienie się zarówno do wydajności, jak i do problemów związanych z CGI oraz do problemów dotyczących bezpieczeństwa nisko-poziomowych środków programowania serwerów API. Pojemniki apletów uruchamiają zwykle aplety wszystkie razem, w jednej maszynie wirtualnej Javy (JVM). Dzięki umiejscowieniu wszystkich apletów w tej samej JVM mogą one skutecznie wymieniać dane między sobą, jednak co się tyczy ich danych „prywatnych” — język Java nie daje możliwości wglądu jednemu apletowi w dane znajdujące się na drugim. Aplety mogą istnieć w JVM-ie pomiędzy zleceniami — jako kopie obiektów. Dzięki temu zajęte jest mniej pamięci niż w przypadku pełnej procedury, a aplety są nadal w stanie utrzymać odniesienia do zewnętrznych zasobów. Cykl życia apletów nie jest wielkością stałą. Jedyną rzeczą niezmienną i konieczną w tym cyklu jest to, iż pojemnik apletu musi przestrzegać następnych zasad: 1.
Stworzyć oraz uruchomić aplet
2.
Obsłużyć wywołania usługi od klientów
3.
Usunąć aplet a następnie go przywrócić
Jest rzeczą całkowicie naturalną w przypadku apletów, iż są one ładowane, tworzone konkretyzowane w swojej własnej maszynie wirtualnej Javy — tylko po to aby być usuniętymi i odtworzonymi nie obsłużywszy żadnych zleceń od klientów lub po obsłużeniu tylko jednego takiego zlecenia. Jednakże aplety zachowujące się w taki sposób nie utrzymają się długo na rynku. W tym rozdziale omówimy najbardziej popularne oraz czułe realizacje czasów istnienia apletów HTTP.
Pojedyncza maszyna wirtualna Javy Większość pojemników apletowych wdraża wszystkie aplety do jednej JVM w celu maksymalizacji zdolności apletów do wymiany informacji (wyjątkiem są tutaj pojemniki wyższej klasy, które realizują rozproszone wywołanie apletu na wielu serwerach wewnętrznych, tak jak zostało to omówione w rozdziale 12 „Serwery Przedsiębiorstw oraz J2EE”. Wykonania wyżej wspomnianej pojedynczej maszyny wirtualnej Javy mogą by różne na różnych serwerach: •
Na serwerze napisanym w Javie, takim jak np. „Apache Tomcat”, sam serwer może wywoływać w JVM-ie wraz ze swoimi apletami.
•
Na pojedynczo przetwarzającym, wielo-wątkowym serwerze WWW, zapisanym w innym języku, wirtualna maszyna Javy może zostać zawarta w procedurze serwera. JVM jako część procedury serwera zwiększa wydajność ponieważ aplet staje się w pewnym sensie, kolejnym rozszerzeniem serwera API niskiego poziomu. Serwer taki może wywołać aplet z nieskomplikowanym połączeniem kontekstu, może również dostarczyć informacje o zleceniach poprzez wywołania metod bezpośrednich.
•
Wieloprocedurowy serwer WWW (który uruchamia kilka procedur, aby obsłużyć zlecenia) właściwie nie może zawrzeć JVM w swojej procedurze ponieważ takiej nie posiada. Ten typ serwerów zwykle uruchamia zewnętrzny JVM, którego procedury może współdzielić. Taki sposób oznacza, iż każde wejście do apletu wiązać się będzie ze skomplikowanym połączeniem kontekstu przypominającym FastCGI. Jednakże wszystkie aplety będą nadal dzieliły tą samą zewnętrzną procedurę.
Na szczęście, z perspektywy apletów (a tym samym z naszej — jako ich twórców) wdrażanie serwerów nie ma większego znaczenia ponieważ zachowują się one zawsze w te sam sposób.
Trwałość kopii Tak jak to zostało opisane wcześniej, aplety istnieją pomiędzy zleceniami jako kopie obiektów. Inaczej mówiąc w czasie ładowania kodu dla apletu, serwer tworzy pojedynczą kopię. Ta pojedyncza kopia obsługuje wszystkie zlecenia, utworzone z apletu. Poprawia to wydajność w trzy następujące sposoby: •
zajmowana powierzchnia pamięci jest mała;
•
pozwala to wyeliminować obciążenie tworzenia obiektu (w przeciwnym wypadku konieczne byłoby utworzenie nowego obiektu apletu) aplet może być już ładowany w maszynie wirtualnej, kiedy zlecenie dopiero wchodzi, pozwalając mu na rozpoczęcie wywoływania natychmiast;
•
umożliwia trwanie — aplet może mieć wszystko, czego może potrzebować podczas obsługi zlecenia, już załadowane np. połączenie z bazą danych może zostać ustanowione raz i używane wielokrotnie; z takiego połączenia może korzystać wiele serwerów. Kolejnym przykładem może tutaj być aplet koszyka zakupów, który ładuje do pamięci listę cen wraz z informacją o ostatnio połączonych klientach. Niektóre serwery w sytuacji, kiedy otrzymują to samo zlecenie po raz drugi umieszczają całe strony w schowku, celem zaoszczędzenia czasu.
Aplety nie tylko trwają pomiędzy zleceniami, lecz także wykonują wszystkie wątki stworzone przez siebie. Taka sytuacja nie jest może zbyt korzystna w przypadku apletu „run-of-the-mill”, jednakże daje interesujące możliwości. Rozważmy sytuację, w której podrzędny wątek przeprowadza pewne kalkulacje, podczas, gdy inne wyświetlają ostatnie rezultaty. Podobnie jest w przypadku apletu animacyjnego, w którym jeden wątek zamienia obraz, a inny nanosi kolory.
Liczniki W celu przedstawienia cyklu życia (czasu istnienia apletu) posłużymy się prostym przykładem. Przykład 3.1 ukazuje serwer, który zlicza i wyświetla liczbę połączeń się z nim. Dla uproszczenia wynik przedstawiany jest jako zwykły tekst (kod dla wszystkich przykładów dostępny jest w internecie — patrz Wstęp) Przykład 3.1. Przykładowy prosty licznik import java.io.*; import javax.servlet.*; import javax.servlet.http.*;
public class SimpleCounter int
extends HttpServlet
{
count = 0;
public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(„text / zwykły”); PrintWriter out = res.getWriter(); count++; out.println ("Od załadowania z apletem łączono się" + count + " razy."); } }
Kod jest prosty — po prostu wyświetla oraz zwiększa kopię zmiennej zwanej count, jednakże dobrze ukazuje „potęgę” trwałości. Kiedy serwet ładuje ten aplet tworzy pojedynczą kopię celem obsłużenia wszystkich zleceń, złożonych na ten aplet, dlatego właśnie kod bywa taki prosty. Takie same kopie zmiennych występują pomiędzy wywołaniami, oraz w przypadku wszystkich wywołań.
Liczniki zsynchronizowane Z punktu widzenia projektantów apletów każdy klient, to kolejny wątek, który wywołuje aplet poprzez metody takie jak: service(), doGet (), doPost(), tak jak to pokazuje przykład 3.1*.
Rysunek 3.1. Wiele wątków — jedna kopia apletu
*
To, iż jedna kopia apletu może obsłużyć wiele zleceń w tym samym czasie może wydawać się dziwne, dzieje się tak prawdopodobnie dlatego, że kiedy obrazujemy program uruchamiający zwykle obserwujemy jak kopie obiektów wykonują zadanie wywołując nawzajem swoje metody. Mimo, że przedstawiony model działa w prostych przypadkach nie jest on dokładnym przedstawieniem rzeczywistości. Prawdziwa sytuacja wygląda tak, że wszystkie zadania wykonują wątki. Kopie obiektów nie są niczym więcej jak tylko strukturami danych, którymi operują wątki. Dlatego możliwa jest sytuacja, w której dwa działające wątki używają w tym samym czasie tego samego obiektu.
Jeżeli nasze aplety odczytują tylko zlecenia, piszą w odpowiedziach i zapisują informacje w lokalnych zmiennych, (czyli w zmiennych określonych w metodzie) nie musimy obawiać się interakcji pomiędzy wątkami. Jeżeli informacje zostają zapisane w zmiennych nielokalnych (czyli w zmiennych określonych w klasie, lecz poza szczególną metodą) musimy być wtedy świadomi, iż każdy z wątków klienckich może operować tymi zmiennymi apletu. Bez odpowiednich środków ostrożności sytuacja taka może spowodować zniszczenie danych oraz sprzeczności. I tak np. jeżeli aplet SimpleCounter założy fałszywie, że przyrost na liczniku oraz wyprowadzenie są przeprowadzanie niepodzielnie (bezpośrednio jeden po drugim, nieprzerwanie), to jeżeli dwa zlecenia zostaną złożone do SimpleCounter prawie w tym samym czasie, możliwe jest wtedy, że każdy z nich wskaże tą samą wartość dla count. Jak? Wyobraźmy sobie, że jeden wątek zwiększa wartość dla count i zaraz po tym, zanim jeszcze pierwszy watek wypisze wynik count, drugi wątek również zwiększa wartość. W takim przypadku, każdy z wątków wskaże tą samą wartość, po efektywnym zwiększeniu jej o 2أ. Dyrektywa wykonania wygląda mniej więcej w ten sposób: count++
// Wątek 1
count++
// Wątek 2
out.println
// Wątek 3
out.println
// Wątek 4
W tym przypadku ryzyko sprzeczności nie stanowi poważnego zagrożenia, jednakże wiele innych apletów zagrożonych jest poważniejszymi błędami. W celu zapobieżenia temu typowi błędów oraz sprzecznościom, które im towarzyszą, możemy dodać jeden lub więcej synchronicznych bloków do kodu. Jest gwarancja, że wszystko, co znajduje się w bloku synchronicznym lub w metodzie synchronicznej nie będzie wywoływane przez inny wątek. Zanim jakikolwiek z wątków rozpocznie wywoływanie kodu synchronicznego musi otrzymać monitor (zamek) na określoną kopie obiektu. Jeżeli monitor ma już inny wątek np. z powodu tego, że wywołuje on ten sam blok synchroniczny, lub inny tym samym monitorem, wtedy pierwszy wątek musi zaczekać. Działa to na zasadzie łazienki na stacji benzynowej, zamykanej na klucz (zawieszany zwykle na dużej, drewnianej desce), którym w naszym przypadku będzie monitor. Wszystko to dzieje się dzięki samemu językowi tak więc obsługa jest łatwa. Synchronizacja jednakże powinna być używana tylko w ostateczności. W przypadku niektórych platform sprzętowych otrzymanie monitora za każdym razem, kiedy wchodzimy do kodu synchronicznego wymaga wiele wysiłku, a co ważniejsze w czasie, kiedy jeden wątek wywołuje kod synchroniczny, pozostałe mogą być blokowane do zwolnienia monitora.
أ
Ciekawostka: jeżeli wartość count byłaby zamiast 32-bitowego int, 64-bitowym long, teoretyczna możliwa byłaby sytuacja, że przyrost będzie dokonany tylko w połowie, do czasu, gdy przerwie mu inny wątek. Dzieje się tak dlatego, ponieważ w Jawie używana jest 32=bitowa kaskada
Dla SimpleCounter istnieją cztery sposoby rozwiązywania potencjalnych problemów. Po pierwsze możemy dodać hasło zsynchronizowane z sygnaturą doGet(): public synchronized void doGet (HttpServletRequest req, HttpServletResponse res)
Taka sytuacja gwarantuje zgodność synchronizacji całej metody, używa się w tym celu kopii apletu, jako monitora. Nie jest to w rzeczywistości najlepsza metoda, ponieważ oznacza to, iż aplet może w tym samym czasie obsłużyć tylko jedno zlecenie GET. Drugim sposobem jest zsynchronizowanie tylko dwóch wierszy, które chcemy wywołać niepodzielnie. PrintWriter out = res.getWriter(); synchronized(this) { count++; out.println ("Od załadowania z apletem łączono się" + count + " razy."); }
Powyższa technika działa lepiej, ponieważ ogranicza czas, który aplet spędza w swoim zsynchronizowanym bloku, osiągając ten sam cel zgodności w wyniku liczenia. Prawdą jest, iż technika ta nie różni się specjalnie od pierwszego sposobu. Trzecim sposobem poradzenia sobie z potencjalnymi problemami jest utworzenie synchronicznego bloku, który wykonywał będzie wszystko, co musi być wykonane szeregowo, a następnie wykorzystanie poza blokiem synchronicznym. W przypadku naszego apletu, liczącego możemy zwiększyć wartość liczoną (count) w bloku synchronicznym, zapisać zwiększoną wartość do lokalnej zmiennej (zmiennej określonej wewnątrz metody), a następnie wyświetlić wartość lokalnej zmiennej poza blokiem synchronicznym: PrintWriter out = res.getWriter(); int local_count; synchronized(this) { local_count= ++count; } out.println ("Od załadowania z apletem łączono się" + localcount + " razy.");
Powyższa zmienna zawęża blok synchroniczny do najmniejszych, możliwych rozmiarów zachowując przy tym zgodność liczenia. Celem zastosowania czwartej, ostatniej z metod musimy zadecydować, czy chcemy ponieść konsekwencje zignorowania wyników synchronizacji. Czasem bywa i tak, że konsekwencje te są całkiem znośne. Dla przykładu, zignorowanie synchronizacji może oznaczać, że klienci otrzymają wynik trochę niedokładny. Trzeba przyznać, iż to rzeczywiście nie jest wielki problem. Jeżeli jednak oczekiwano by od apletu liczb dokładnych, wtedy sprawa wyglądałaby trochę gorzej. Mimo, iż nie jest to opcja możliwa do zastosowania na omawianym przykładzie, to na innych apletach możliwa jest zamiana kopii zmiennych na zmienne lokalne. Zmienne lokalne są niedostępne dla innych wątków i tym samym nie muszą być dokładnie strzeżone przed zniszczeniem. Jednocześnie zmienne lokalne nie istnieją pomiędzy zleceniami, tak więc nie możemy ich użyć do utrzymywania stałego stanu naszego licznika.
Liczniki całościowe Model „jeden egzemplarz na jeden aplet” jest sprawą do omówienia ogólnego. Prawda jest taka, że każda zarejestrowana nazwa (lecz nie każde URL-owe dopasowanie do wzorca) dla apletu jest związana z jedną kopią apletu. Nazwa używana przy wchodzeniu do apletu określa, która kopia obsłuży zlecenie. Taka sytuacja wydaje się być sensowna, ponieważ klient powinien kojarzyć odmienne nazywanie apletów z ich niezależnym działaniem. Osobne kopie są ponadto wymogiem dla apletów zgodnych z parametrami inicjalizacji, tak jak to zostało omówione dalej w tym rozdziale. Nasz przykładowy SimpleCounter posługuje się kopią liczenia zmiennej przy zliczaniu liczby połączeń z nim wykonanych. Jeżeli byłaby potrzeba liczenia wszystkich kopii (a tym samym wszystkich zarejestrowanych nazw) możliwe jest użycie klasy zmiennej statycznej. Zmienne takie są wspólne dla wszystkich kopii klasy. Przykład 3.2 ukazuje liczbę wejść na aplet, liczbę kopii utworzonych przez serwer (na jedną nazwę) oraz całkowitą liczbę połączeń z tymi kopiami. Przykład 3.2. Licznik całościowy import import import import
java.io.*; java.util.*; javax.servlet.*; javax.servlet.http.*;
public class HolisticCounter
extends HttpServlet
{
static int Classcount = 0; // dotyczy wszystkich kopii int count = 0; // oddzielnie dla każdego apletu static Hashtable instances = new Hashtable(); // również dotyczy wszystkich kopii public
void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType ("text / zwykły"); PrintWriter out = res.getWriter(); count++; out.println ("Od załadowania z apletem łączono się" + count + " razy.");
// utrzymuj ścieżkę liczenia poprzez wstawienie odwołania do niej // kopia w tablicy przemieszczania. Powtarzające się hasła są // ignorowane // Metoda size()odsyła liczbę kopii pojedynczych, umieszczonych w pamięci instances.put(this, this); out.println ("Aktualnie jest" + instances.size() + "razy"); classCount++ out.println ("Licząc wszystkie kpoie, z apletem tym " + "łączono "łączono się" + classCount + "razy") }
}
Przedstawiony licznik całościowy — Holistic Counter, śledzi liczbę połączeń własnych przy pomocy zmiennej kopii count, liczbę połączeń wspólnych za pomocą zmiennej klasy oraz liczbę kopii za pomocą tablicy asocjacyjnej — instances (kolejny wspólny element, który musi być zmienną klasy). Widok przykładu ukazuje rysunku 3.2.
Rysunek 3.2. Widok licznika całościowego
Odnawianie (powtórne ładowanie) apletu Jeśli ktoś próbował używać umówionych liczników, we własnym zakresie być może zauważył, iż z każdą kolejną rekompilacją liczenie zaczyna się automatycznie od 1. Wbrew pozorom to nie defekt, tylko właściwość. Większość serwerów odnawia (powtórnie ładuje) aplety, po tym jak zmieniają się ich pliki klasy (pod domyślnym katalogiem apletów WEB-INF/classes). Jest to procedura wykonywana na bieżąco, która znacznie przyśpiesza cyklu testu rozbudowy oraz pozwala na przedłużenie czasu sprawnego działania serwera. Odnawianie apletu może wydawać się proste, jednak wymaga dużego nakładu pracy. Obiekty ClassLoader zaprojektowane są do jednokrotnego załadowania klasy. Aby obejść to ograniczenie i wielokrotnie ładować aplety, serwery używają własnych programów ładujących, które ładują aplety ze specjalnych katalogów, takich jak WEB-INF/classes. Kiedy serwer wysyła zlecenie do apletu najpierw sprawdza, czy plik klasy apletu zmienił się na dysku. Jeżeli okaże się, że tak wtedy serwer nie będzie już używał programu ładującego starej wersji pliku tylko utworzy nowa kopię własnego programu ładującego klasy — celem załadowania nowej wersji. Niektóre serwery poprawiają wydajność poprzez sprawdzanie znaczników modyfikacji czasu tylko co jakiś czas lub na wyraźne żądanie administratora. W wersjach Interfejsów API sprzed wersji 2.2, chwyt z programem ładującym klasy skutkował tym, że inne aplety ładowane były przez odmienne programy ładujące — co skutkowało czasem zgłoszeniem ClassCastException jako wyjątku, kiedy aplety wymieniały informacje (ponieważ klasa załadowana przez jeden program ładujący nie jest tym samym, co klasa ładowana przez inny, nawet jeżeli dane dotyczące klasy są identyczne). Na początku Interfejsu API 2.2 jest gwarancja, że problemy z ClassCastException nie pojawi się dla apletów w tym samym kontekście. Tak więc obecnie większość wdrożeń ładuje każdy
kontekst aplikacji WWW w jednym programie ładującym klasy, oraz używa nowego programu ładującego do załadowania całego kontekstu, jeżeli jakikolwiek aplet w kontekście ulegnie zmianie. Skoro więc wszystkim apletom oraz klasom wspomagającym w kontekście zawsze odpowiada ten sam program ładujący, nie należy się więc obawiać żadnych nieoczekiwanych ClassCastException podczas uruchamiania. Powtórne ładowanie całego kontekstu powoduje mały spadek wydajności, który jednakże występuje tylko podczas tworzenia. Powtórne ładowanie (odnawianie) klasy nie jest przeprowadzane tylko wtedy, kiedy zmianie ulega klasa wspomagająca. Celem większej efektywności określenia, czy jest konieczne odnawianie kontekstu, serwery sprawdzają tylko znaczniki czasu apletów klasy. Klasy wspomagające w WEBINF/classes mogą być także powtórnie załadowane, kiedy kontekst jest odnowiony, lecz jeżeli klasa wspomagająca jest jedyną klasą do zmiany, serwer tego prawdopodobnie nie zauważy. Odnowienie apletu nie jest także wykonywane dla wszystkich klas (apletu lub innych) znajdujących się w ścieżce klasy serwerów. Klasy takie ładowane są przez rdzenny (pierwotny) program ładujący, a nie własny, konieczny do powtórnego załadowania. Klasy te są również ładowane jednorazowo i przechowywane w pamięci nawet wtedy, gdy ich pliki ulegają zmianie. Jeżeli chodzi o klasy globalne (takie jak klasy użyteczności com.oreilly.servlet) to najlepiej jest umieścić je gdzieś na ścieżce klasy, gdzie unikną odnowienia. Przyśpiesza to proces powtórnego ładowania oraz pozwala apletom w innych kontekstach wspólnie używać tych obiektów bez ClassCastException.
Metody „Init” i „Destroy” Tak jak zwykłe aplety, aplety wykonywane na serwerach mogą określać metody init() i destroy(). Serwer wywołuje metodę init() po skonstruowaniu kopii apletu, jednak zanim jeszcze aplet obsłuży jakiekolwiek zlecenie. Serwer wywołuje metodę destroy() po wyłączeniu apletu i zakończeniu wszystkich zleceń lub po przekroczeniu ich limitu czasowego*. W zależności od rodzaju serwera oraz konfiguracji aplikacji WWW, metoda init() może zostać wywołana w poniższych momentach: •
podczas uruchamiania apletu
•
podczas pierwszego łączenia się z apletem, przed wywołaniem metody service()
•
na żądania administratora serwera
W każdym przypadku metoda init() zostanie wywołana i zakończona ani zanim jeszcze aplet obsłuży swoje pierwsze zlecenie.
*
Specyfikacji projektów mającego ukazać się na rynku apletu API 2.3 (Servlet API 2.3), zakładają, że dodane zostaną metody cyklu życia (czasu istnienia), które umożliwią apletom oczekiwanie na sygnały, kiedy kontekst lub sesja są tworzone lub zakańczane oraz podczas wiązania i rozwiązywania atrybutu z kontekstem lub sesją.
Metoda init() jest zwykle wykorzystywana do inicjalizacji apletu — ładowania obiektów używanych przez aplet w procesie obsługi zleceń. W czasie wykorzystywania metody init() aplet może „chcieć” odczytać swoje parametry inicjalizacji (init). Parametry te są dostarczane samemu apletowi i nie są w jakikolwiek sposób związane z jednym zleceniem. Mogą one określać takie wartości początkowe jak: odkąd licznik powinien zacząć liczyć lub wartości domyślne takie jak np. szablon, który powinien zostać użyty w przypadku nie określenia tego w zleceniu. * Specyfikacji projektów mającego ukazać się na rynku interfejsu API 2.3 (Servlet API 2.3), zakładają, że dodane zostaną metody cyklu życia (czasu istnienia), które umożliwią apletom oczekiwanie na sygnały, kiedy kontekst lub sesja są tworzone lub zakańczane oraz podczas wiązania i rozwiązywania atrybutu z kontekstem lub sesją. Parametry początkowe dla apletu można znaleźć w deskryptorze wdrożenia, niektóre serwery mają graficzne interfejsy mogące zmodyfikować ten plik (patrz przykład 3.3). Przykład 3.3. Ustalanie wartości parametrów w deskryptorze rozmieszczenia <web-app> <servlet> <servlet-name> counter <servlet-class> InitCounter <param-name> initial <param-value> 1000 <description> The initial value for the counter
Wielokrotne elementy mogą zostać umieszczone w znaczniku <servlet>. Znacznik <descriptor> jest opcjonalny, pierwotnie miał on być przeznaczony do graficznych programów narzędziowych. Pełną definicję typu dokumentu dla pliku web.xml można znaleźć w dodatku F „Kodowania”. Podczas stosowania metody destroy() aplet powinien zwolnić wszystkie zasoby, które wcześniej pozyskał i które nie będą odzyskiwane. Metoda destroy() daje również apletowi możliwość wypisania jego nie zapisanych informacji ze schowka lub innych trwałych informacji, które powinny zostać odczytane podczas najbliższego wywołania metody init().
Licznik z metodą Init Parametry początkowe mają wiele zastosowań. Zasadniczo jednak określają początkowe lub domyślne wartości dla zmiennych apletu lub „mówią” apletowi jak w określony sposób dostosować jego zachowanie. Na przykładzie 3.4 nasz SimpleCounter został rozszerzony celem odczytania parametru początkowego (zwanego initial), który przechowuje wartość początkową dla naszego licznika. Poprzez ustawianie początkowego stanu licznika na wysokie wartości możemy sprawić, że nasza strona będzie wydawała się bardziej popularna niż w rzeczywistości. Przykład 3.4. Licznik odczytujący parametry początkowe import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class InitCounter extends HttpServlet
{
int count; public void init() throws ServletException { String initial getInitParameter("początkowy"); try { count = Integer.parseInt(initial); } catch (numberFormatException e) { count = 0; } } public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType ("text/zwykły"); PrintWriter out = res.getWriter(); count++ out.println ("Od załadowania (oraz z możliwą inicjalizacją"); out.println ("parametr figurujący w ),z apletem tym łączono się"); out.println (count + "razy"); } }
Metoda init() wykorzystuje metodę getinitParameter() w celu uzyskania wartości dla parametru początkowego zwanego initial. Metoda ta przyjmuje tą nazwę jako String i oddaje wartość również jako String. Nie ma możliwości uzyskania wartości jako innego typu. Dlatego aplet ten przekształca wartość String na wartość int lub w razie problemów zamienia na wartość 0. Należy pamiętać, że jeżeli chcemy wypróbować ten przykład może się okazać konieczne powtórne „zastartowanie” serwera celem wprowadzenia zmian w web.xml, oraz odniesienie się do apletu, używając zarejestrowanej nazwy. !!!!!!!!! początek ramki
Co się stało z super.init(config)? W Interfejsie API 2.0, aplet wdrażając metodę init() musiał wdrożyć jej formularz, który przejmował parametr ServletConfig, musiał on również wywołać super.init(config): public void init(ServletConfig config) throws ServletException { super.init(config); //od inicjalizacji następuje }
Parametr ServletConfig dostarczał informacje o konfiruracji do apletu, a wywołanie super.init(config) przekazywało obiekt konfiguracyjny do nadklasy GenericServlet gdzie było zapisywane dla użytku apletu. Specyficznie, klasa GenericServlet używała przekazany parametr config celem wdrożenia samego interfejsu ServletConfig (przekazując wszystkie wywołania do delegowanej konfiguracji pozwalając tym samym apletowi na wywołanie metody ServletConfig na siebie — dla wygody). Powyższa operacja była bardzo zawiła w Interfejsie API 2.1, została jednak uproszczona do tego stopnia, że obecnie wystarczy tylko jak aplet wdroży wersję bez-argumentową init(), a obsługa ServletConfig i GenericServlet będzie zrealizowana na dalszym planie. Poza scenami, klasa GenericServlet współpracuje z bez-argumentową metodą init, z kodem przypominającym poniższy: public class GenericServlet implements Servlet, ServletConfig { ServletConfig_config = null; public void init(ServletConfig config) throws ServletException { __config = config; log("init zwany"); init(); } public void init() throws ServletException { public String getInitParameter(String name) { return_config.getInitParameter(name); } // itd. ... } -—kontynuacja–
Zwróćmy uwagę, iż serwer wywołuje w czasie inicjalizacji metodę apletu init(ServletConfig config). Zmiana w 2.1 dotyczyła tego, iż obecnie GenericServlet przekazuje to wywołanie do bez-argumentowego init(), którą to metodą można zignorować nie martwiąc się o config. Co się tyczy zgodności z poprzednimi wersjami należy nadal ignorować init(ServletConfig config) i wywoływać super.init(config). W przeciwnym wypadku może być tak, iż nie będziemy mogli wywoływać metody bez-argumentowej init(). Niektórzy z programistów uważają, iż dobrze jest wywołać najpierw super.destroy() podczas gdy wdrażamy destroy() powoduje to, że GenericServlet wdraża destroy(), która to metoda „pisze” wiadomość do rejestru zdarzeń, że aplet jest niszczony. !!!!!!!!! koniec ramki
Licznik z metodami Init i Destroy Do tego momentu przykłady liczników demonstrowały jak stan apletu utrzymuje się pomiędzy połączeniami. To jednak rozwiązuje problem tylko częściowo. Za każdym razem, kiedy serwer jest wyłączany lub aplet odnawiany, liczenie zaczyna się od nowa. Rzeczą naprawdę potrzebna jest trwanie licznika niezależnie od ładowań, licznik który nie zaczyna ciągle od początku. To zadanie mogą wykonać metody init() i destroy(). Przykład 3.5 poszerza jeszcze bardziej przykład initCounter, dodając apletowi możliwość zachowania swojego stanu podczas destroy() oraz podczas powtórnego ładowania stron w init(). Dla uproszczenia przyjmijmy, że aplet ten nie jest zarejestrowany i dostępny tylko pod http://server:port/servlet/InitDestroyCountery. Gdyby ten aplet był zarejestrowany pod różnymi nazwami, musiałby zachowywać oddzielny stan dla każdej z nazw. Przykład 3.5. Licznik stale działający import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class InitDestroyCounter extends HttpServlet
{
int count; public void init() throws ServletException { //Spróbuj załadować liczenie początkowe z naszego zachowanego stałego stanu FileReader fileReader = null; BufferedReader bufferedReader = null; try { fileReader = new fileReader ("InitDestroyCounter.initial"); bufferedReader = new BufferedReader(fileReader); String initial = bufferedReader.readLine(); count = Integer.parseInt(initial); return; } catch (FileNotFoundException ignored) {} // nie ma stanu zapisanego catch (IOException ignored) {} // problem podczas czytania catch (NumberFormatException ignored) {} //zniekszta•• stan zapisany finally { // Nie zapomnij zamkn•• plik try { if(bufferedReader ! = null) { bufferedReader.close() } } catch (IOException ignored) {} } // W razie braku powodzenia ze stanem zapisanym, // sprawd• dla parametru init String initial = getInitParameter("pocz•tkowy"); try { count = Integer.parseInt(initial); return; }
catch (NumberFormatException ignored) {} //zero lub liczba nie ca•kowita // Domy•lne dla pocz•tkowego stanu licznika "0" count = 0; } public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType ("text/zwykły"); PrintWriter count++ out.println
out
= res.getWriter();
("Od początku z apletem łączono się" + count + " razy.");
} publi void destroy() { super.destroy(); //całkowicie opcjonalne saveState(); } publi void saveState() { // Spróbuj zapisać wartość dodaną FileWriter ( = null; PrintWriter printWriter = null; try { fileWriter = new FileWriter("InitDestroyCounter.initial"); printWriter = new PrintWriter(fileWriter); printWriter.println(count); return; } catch (IOException e) { // problem podczas pisania // Zarejestruj wyjątek. Patrz rozdział 5. } finally { // Nie zapomnij zamknąć plik if (printWriter ! = null) { printWriter.close(); } } } }
Za każdym razem, kiedy aplet jest usuwany, stan jego jest zachowywany w pliku o nazwie InitDestroyCounter.initial. Jeżeli nie ma dostarczonej ścieżki dostępu, plik jest zapisywany w procedurze serweru bieżącego katalogu, zwykle jest to katalog auto-startowy. Sposoby alternatywnej lokalizacji opisano w rozdziale 4 „Odzyskiwanie informacji”. Plik ten również zawiera liczbę całkowitą, zapisana jako ciąg znaków, reprezentujący ostatnie liczenie. Za każdym ładowaniem serwera jest próba odczytu z pliku, zachowanego liczenia. Jeżeli z jakiegoś powodu próba odczytu nie powiedzie się (jak ma to miejsce podczas pierwszego uruchomienia apletu — ponieważ plik jeszcze wtedy nie istnieje), aplet sprawdza, czy parametr początkowy określa liczenie początkowe. Jeżeli i to nie da efektu zaczyna od zera. Podczas stosowania metody init() zalecana jest najwyższa ostrożność.
Aplety mogą zachowywać swój stan na wiele różnych sposobów. Niektóre z nich mogą posłużyć się w tym celu formatem użytkowym pliku, tak jak to zostało tutaj zrobione przez nas. Inne serwery zapisują swój stan jak serializowane obiekty Java lub „umieszczają” go w bazie danych. Niektóre serwery nawet wykorzystują technikę journaling, powszechnie stosowaną przy bazach danych oraz przy kopiach zapasowych taśm, gdzie pełny stan apletu jest zapisywany rzadko, podczas gdy plik dziennika wprowadza do pamięci przyrostowe aktualizacje w trakcie zmian. To, którą metodę użyje aplet zależy od sytuacji. Powinniśmy być zawsze świadomi tego, iż zapisywany stan nie podlega żadnym zmianom na drugim planie. Teraz może nasuwać się pytanie: co się stanie, jeżeli aplet ulegnie awarii? Odpowiedź brzmi: metoda destroy() nie zostanie wywołanaأ. Nie jest to jednakże problem dla metod destroy(), które muszą tylko zwolnić zasoby; przeładowany serwer równie dobrze się do tego nadaje i (czasem nawet lepiej). Jednakże sytuacja taka jest problemem dla apletu, który musi zapisywać swój stan w swojej metodzie destroy(). Ratunkiem dla tych apletów jest częstsze zapisywanie swojego stanu. Aplet może „zdecydować się” na zapisanie swojego stanu po obsłudze każdego ze zleceń tak jak powinien to zrobić aplet „chess server” (serwer szachowy), tak że nawet kiedy serwer jest ponownie uruchamiany, gra może zostać wznowiona z ostatnią sytuacją na szachownicy. Inne aplety mogą potrzebować zapisać strony tylko po zmianie jakiejś ważnej wartości — aplet „shopping cart” (lista zakupów) musi zapisać swój stan tylko wtedy, gdy klient doda lub usunie pozycję z listy. I w końcu niektóre aplety mogą tracić niektóre ze swoich ostatnich zmian stanu. Takie aplety mogą zapisywać stan po pewnej określonej liczbie zleceń. Dla przykładu, w naszym przykładzie InitDestoyCounter, wystarczającym powinno być zapisywanie stanu co dziesięć połączeń. Celem wdrożenia powyższego można dodać prosty wiersz na końcu doGet(): if (count % 10 == 0)
saveState();
Można zapytać czy jest to istotna zmiana. Wydaje się, że tak, biorąc pod uwagę zagadnienia związane z synchronizacją. Stworzyliśmy możliwość utraty danych (jeśli saveState() zostanie uruchomionym przez dwa wątki, w tym samym czasie) oraz ryzyko, że saveState() nie będzie w ogóle wywołane i jeżeli liczenie zostanie zwiększone przez kilka wątków z rzędu przed sprawdzeniem. Załóżmy, że taka możliwość nie istniała kiedy saveState() było wywoływane tylko z metody destroy() ponieważ metoda destroy() jest wywoływana tylko raz na jedną kopię apletu. Jednakże teraz, kiedy saveState() jest wywoływana w metodzie do Get() musimy ponownie się nad tym zastanowić. Jeżeli zdarzyłoby się kiedyś, że aplet ten byłby odwiedzany tak często, iż byłoby więcej niż 10 niezależnie wywołujących wątków, jest prawdopodobne, że dwa aplety (10 osobnych zleceń) będą w saveState() w tym samym czasie. Może to spowodować zniszczenie pliku z danymi. Może to również doprowadzić do jednoczesnego zwiększenia liczenia przez dwa watki, zanim któryś „zorientuje się”, iż minął czas wywoływania saveState(). Rozwiązanie jest proste: przemieśćmy kontrolę liczenia do bloku zsynchronizowanego, tam gdzie liczenie jest zwiększane: int local_count ; synchronizeed(this) { local_count = ++count ; if (count % 10 == 0) saveState();
أ Jeżeli nie mamy pecha i nasz serwer nie będzie miał awarii podczas stosowania metody destroy(). W przeciwnym wypadku możemy zostać z częściowo zapisanym plikiem stanu — pozostałościami napisanymi na górze naszego poprzedniego stanu. Celem osiągnięcia całkowitego bezpieczeństwa aplet zapisuje swój stan w pliku roboczym, kopiując go następnie na górze oficjalnego pliku stanu w jednej komendzie.
} out.println
("Od załadowania z apletem łączono się" + count + " razy.");
Wniosek z powyższych rozważań jest jeden: bądźmy przezorni i chrońmy kod apletu od problemów związanym z wielowątkowym dostępem.
Model jedno-wątkowy (Single-Thread Model) Mimo, iż normalna sytuacja to jedna kopia apletu na jedną zarejestrowaną nazwę apletu, to możliwa jest również pula kopii utworzonych dla każdej z nazw apletu, której każda kopia obsługuje zlecenia. Aplety sygnalizują taka chęć poprzez wdrożenie interfejsu javax.servlet.SingleThreadModel. Jest to prosty interfejs „tag”, który nie określa żadnych metod, ani zmiennych, służy tylko do oznaczenia apletu, jako „wyrażającego chęć” zmiany stanu istnienia. Serwer, który ładuje aplet SingleThreadModel (Model jedno-wątkowy) musi gwarantować, zgodnie z dokumentacją InterfejsuAPI, że żadne dwa wątki nie będą wywoływały konkurencyjnie w metodzie apletu „service”. W celu spełnienia powyższego warunku każdy wątek używa wolnej kopii apletu z puli, tak jak na rycinie 3.3 dzięki temu każdy aplet wdrażający SingleThreadModel może zostać uznany jako bezpieczny co do wątku oraz nie wymagający synchronizacji dostępu do jego zmiennych kopii. Niektóre serwery dopuszczają konfigurację wielu kopii na pulę, inne nie. Niektóre z kolei serwery używają pul tylko z jedną kopią powodując zachowanie identyczne z metodą zsynchronizowaną service().
Rysunek 3.3. Model jedno-wątkowy Czas istnienia SingleThreadModel (Modelu jedno-wątkowego) nie ma zastosowania dla liczników lub innych aplikacji apletu, które wymagają obsługi centralnego stanu. Czas istnienia może mieć pewne zastosowanie, jednak tylko w unikaniu synchronizacji, ciągle obsługując sprawnie zlecenie. Dla przykładu aplety, które łączą się z bazami danych muszą czasem wykonać kilka poleceń bazy danych, niepodzielnie jako część pojedynczej obsługi transakcji. Każda transakcja bazy danych wymaga wydzielonego obiektu połączenia bazy danych, dlatego więc aplet musi jakoś
zagwarantować, że żadne dwa wątki nie będą próbowały „wchodzić” na to samo połączenie w tym samym czasie. Można tego dokonać poprzez użycie synchronizacji, pozwalając apletowi na obsługę tylko jednego zlecenia w jednym momencie. Poprzez wdrożenie SingleThreadModel oraz poprzez fakt, iż jest tylko jedno „połączenie” kopii zmiennej, aplet może w prosty sposób obsługiwać konkurencyjne (jednoczesne) zlecenia ponieważ każda kopia będzie miała swoje połączenie. Zarys kodu pokazano na przykładzie 3.6. Przykład 3.6. Obsługa połączeń bazy danych przy użyciu Modelu jedno-wątkowego import import import import import
java.io.*; java.sql.*; java.util.*; javax.servlet.*; javax.servlet.http.*;
public class SingleThreadConnection extends HttpServlet implements SingleThreadModel { Connection con = null; // po••czenie z baz• danych, jedno na jedn• kopi• puli public void init() throws ServletException { // Ustanów połączenie dla tej kopii try { con = esablishConnection (); con.AutoComit(false); } catch (SQLException e) { throw new ServletException(e.getMessage()); } } public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType ("text/zwykły"); PrintWriter out = res.getWriter(); try { // Użyj połączenia utworzonego specjalnie dla tej kopii Statement stmt = con.createStatement(); // Aktualizuj bazę danych jakimikolwiek sposobami // Zatwierdź obsługę żądania com.commit(); } catch (SQLException e ) { try { con.rollback(); } catch (SQLException ignored) { } } } public void destroy() { if (con ! = null) { try { con.close(); } catch (SQLException ignored) { } } } private Connection establishConnection() throws SQLException { // Nie wdwrożone. Patrz rozdział 9.
} }
W rzeczywistości SingleThreadModel nie jest najlepszym rozwiązaniem dla takiej jak ta aplikacji.O wiele lepszym rozwiązaniem dla apletu byłoby użycie wydzielonego obiektu ConnectionPool przechowywanego jako kopia lub zmienna klasy, z którego mógłby on „zaewidencjonować” oraz „wyewidencjonować” połączenia. Połączenie wyewidencjonowane może być przechowywane jako lokalna zmienna, zapewniająca wydzielony dostęp. Zewnętrzna pula zapewnia apletowi więcej kontroli nad zarządzaniem połączeniami. Pula może również zweryfikować poprawność każdego połączenia, może ona być także skonfigurowana w taki sposób, że będzie zawsze tworzyła pewną minimalną liczbę połączeń, lecz nigdy większą niż określona liczba maksymalna. Stosując metodę SingleThreadModel, mógłby utworzyć znacznie więcej kopii (a tym samym połączeń) niż baza danych może obsłużyć. Na ten moment należy więc unikać stosowania metody SingleThreadModel. Większość innych apletów mogłaby być lepiej wdrażana przy użyciu synchronizacji oraz puli zasobów zewnętrznych. Prawdą jest, iż interfejs daje pewien stopień kontroli programistom, nie zaznajomionym z programowaniem wielo-wątkowym; jednakże podczas gdy SingleThreadModel czyni sam aplet bezpiecznym co do wątku, to interfejs nie czyni tego z systemem. Interfejs nie zapobiega problemom związanym z synchronizacją, które wynikają z jednoczesnego dostępu apletów do wspólnych zasobów takich jak np. zmienne statyczne czy obiekty poza zasięgiem apletu. Problemy związane z wątkami będą się pojawiały zawsze kiedy pracujemy w systemie wielo-wątkowym, z lub bez SingleThreadModel.
Przetwarzanie drugoplanowe Aplety potrafią więcej niż tylko po prostu trwać pomiędzy wejściami na nie. Potrafią także wtedy wywoływać. Każdy wątek uruchomiony przez aplet może kontynuować wywoływanie nawet po wysłaniu odpowiedzi. Możliwość ta najlepiej sprawdza się przy dłuższych zadaniach, których wyniki przyrostowe są udostępniane wielu klientom. Wątki drugoplanowe, uruchomione w init() wykonują pracę w sposób ciągły podczas gdy wątki obsługujące zlecenia wyświetlają stan bieżący za pomocą doGet(). Jest to technika podobna to tej używanej w apletach animacyjnych, gdzie jeden wątek dokonuje zmian na rysunku, a drugi nanosi kolory. Przykład 3.7 ukazuje aplet wyszukujący liczb pierwszych, większych od kwadryliona. Tak wielka liczba wybierana jest celowo, żeby uczynić liczenie powolnym tak, aby zademonstrować efekty buforujące (które będą nam potrzebne przy omawianiu dalszej części materiału). Algorytm używany przez aplet jest bardzo prosty: aplet selekcjonuje wszystkie liczby nieparzyste i następnie próbuje podzielić je przez liczby nieparzyste całkowite z przedziału od 3 do ich pierwiastka kwadratowego. Jeżeli dana liczba, nie jest podzielna bez reszty przez żadną tych liczb, wtedy jest ona uznawana za liczbę pierwszą.*
*
Można zapytać dlaczego sprawdzane są tylko czynniki mniejsze od pierwiastka kwadratowego. Odpowiedź jest prosta: ponieważ jeżeli liczba miałaby dzielniki wśród liczb większych od tego pierwiastka, to musiałaby je także mieć wśród liczb od niego mniejszych.
Przykład 3.7. W poszukiwaniu liczb pierwszych import import import import
java.io.*; java.util.*; javax.servlet.*; javax.servlet.http.*;
public class PrimeSearcher extends HttpServlet implements Runnable { long lastprime = 0; // ostatnia znaleziona liczba pierwsza Datelastprimeodified = new Date(); // kiedy zosta•a znaleziona Thread searcher; // drugoplanowy w•tek szukaj•cy public void init() throws ServletException { searcher = new Thread(this); searcher.setPriority(Thread.MIN_PRIORITY); // b•d• dobrym obywatelem searcher.start(); } public void run() { //
QTTTBBBMMMTTTOOO
long candidate = 1000000000000001L; // kwadrilion jeden // Rozpocznij szukanie p•tlowe liczb pierwszych while (true) { // szukaj ca•y czas if (isPrime(candidate)) { lastprime = candidate; // nowa liczba pierwsza lastprimeodified = new Date(); // nowy "czas" liczby pierwszej" } candidate += 2;
// liczby parzyste nie s• liczbami pierwszymi
// Pomi•dzy potencjalnymi liczbami pierwszymi rób przerw• 0.2 sekundy // Kolejny sposób aby by• dobrym w zasobach systemu try { searcher.sleep(200); } catch (InterrruptedException ignored) {} } } private static boolean isPrime(long candidate) { // Spróbuj podzieli• podzieli• t• liczb• przez wszystkie liczby // nieparzyste z przedzia•u od 3 do jej pierwiastka kwadratowego long sqrt (long) Match.sqrt (candidate); for (long i = 3; i <sqrt; i += 2) { if (candidate % i == 0) return false; // znajd• czynnik } public void doGet(HttpServletRequest
req, HttpServletResponse
res) throws ServletException, IOException { res.setContentType ("text/zwykły"); PrintWriter out = res.getWriter(); if (lastprime == 0) { out.println ("Nadal szukam liczb pierwszych..."); } else{ out.println("Ostatnia liczba pierwsza została znaleziona " + lastprime); out.println("w" + lastprimeModified); } } public void destroy() searcher.stop(); }
}
Wątek wyszukujący rozpoczyna wyszukiwanie w metodzie init(). Ostatnia liczba przez niego znaleziona zostaje zapisana w lastprime, a czas, w którym to się stało w lastprimeModified. Za każdym razem kiedy klient łączy się z apletem, metoda doGet() informuje go jaka największa liczba pierwsza została znaleziona do tej pory oraz o czasie, w którym to się stało. Wątek niezależnie przetwarza połączenia klientów; nawet kiedy żaden klient nie jest połączony, wątek nadal kontynuuje poszukiwania liczb pierwszych. Jeżeli kilku klientów połączy się w tym samym czasie z apletem, aktualny stan wyświetlony dla nich wszystkich będzie jednakowy. Zwróćmy uwagę na fakt, iż metoda destroy() zatrzymuje wątek wyszukujący. Jest to bardzo istotne ponieważ jeżeli aplet nie zatrzyma swoich drugoplanowych wątków, będą one działały tak długo jak długo będzie istniała maszyna wirtualna. Nawet jeżeli aplet zostanie powtórnie załadowany (odnowiony) (jawnie bądź z powodu zmiany pliku klasy) jego wątki nie przestaną działać. Zamiast tego jest prawdopodobne, że nowy aplet utworzy kopie dodatkowe wątków drugoplanowych.
Uruchamianie i rozruch Aby sprawić, że PrimeSearcher zacznie szukać liczb pierwszych tak szybko jak to możliwe, możemy skonfigurować aplikację WWW apletu w taki sposób, że będzie ładowała aplet przy startowaniu serweru. Dokonuje się tego poprzez dodanie znacznika do hasła <servlet>, deskryptora wdrożenia, tak jak na przykładzie 3.8. Przykład 3.8. Ładowanie apletu przy rozruchu <web-app> <servlet> <servlet-name> ps <servlet-class> PrimeSearcher
Powyższe komendy „mówią serwerowi”, żeby utworzył kopię PrimeSearcher pod rejestrowaną nazwą ps oraz żeby zainicjował aplet podczas sekwencji uruchamiania serwera. Z apletem można połączyć się wtedy na URL-u /servlet/ps. Zwróćmy uwagę, iż kopia apletu obsługująca URL /servlet/PrintWriter meSearcher nie jest ładowana przy rozruchu.
Na przykładzie 3.8 znacznik jest pusty. Znacznik może również zawierać dodatnią liczbę całkowitą, oznaczającą kolejność, w której aplet powinien być załadowany w odniesieniu do innych apletów kontekstu. Aplety z niższymi liczbami ładowane są przed tymi z liczbami większymi. Aplety z wartościami ujemnymi lub nie-całkowitymi mogą być ładowane w każdym momencie sekwencji uruchamiania, dokładna kolejność zależna jest wtedy od serwera. Dla przykładu, web.xml ukazany na przykładzie 3.9 gwarantuje, że first będzie załadowany przed second, podczas gdy anytime może zostać załadowany w każdym momencie rozruchu serwera. Przykład 3.9. Pokaż możliwości apletu <web-app> <servlet> <servlet-name> first <servlet-class> First 10 <servlet> <servlet-name> second <servlet-class> Second 20 <servlet> <servlet –name> anytime <servlet-class> Anytime
Buforowanie podręczne po stronie klienta Do tej pory nauczyliśmy się, że aplety obsługują zlecenia GET przy pomocy metody doGet(). I jest to niemal prawda. Cała prawda, jednakże jest taka, że nie do każdego zlecenia konieczne jest wywoływanie metody doGet(). Dla przykładu, przeglądarka WWW, która stale łączy się z PrimeSearcher będzie musiała wywołać doGet() tylko po tym jak wątek wyszukujący znajdzie nową liczbę pierwszą. Do tego czasu wszystkie wywołania doGet() generują po prostu stronę, którą użytkownik już oglądał, stronę prawdopodobnie przechowywaną w pamięci podręcznej przeglądarki. To co jest na ten moment najbardziej potrzebne, to sposób w jaki aplet mógłby
informować o zmianach w jego wydruku wyjściowym. I tutaj właśnie pomocne będzie omówienie metody getlastModified(). Większość serwerów zawiera, jako część swojej odpowiedzi, nagłówek Last-Modified, wtedy kiedy odsyła dokument. Przykład wartości nagłówka Last-Modified mógłby wyglądać w następujący sposób: Tue, 06-May-98 15:41:02 GMT
Powyższy nagłówek informuje klienta o tym, kiedy ostatnio została zmieniona strona. Informacja sama w sobie nie jest specjalnie wartościowa, jednak zyskuje na wartości w momencie kiedy przeglądarka powtórnie ładuje stronę. Większość przeglądarek WWW, podczas odnawiania strony, zawiera w swoich zleceniach nagłówek If-Modified-Since, którego struktura jest identyczna z nagłówkiem LastModified: Tue, 06-May-98 15:41:02 GMT
Nagłówek ten informuje serwer o czasie, w którym nagłówek Last-Modified strony, był po raz ostatni ładowany przez przeglądarkę. Serwer może odczytać ten nagłówek oraz stwierdzić czy plik był zmieniany od określonego czasu. Jeżeli okaże się, że plik został zmieniony, serwer musi przesłać nową treść. Jeżeli okażę się, że plik nie uległ zmianie, wtedy serwer może wysłać przeglądarce krótką odpowiedź, informującą ją o tym oraz o tym, że wystarczy powtórnie wyświetlić wersję dokumentu schowaną w schowku (ta odpowiedź to: kod stanu 304 Not Modified). Technika ta działa najlepiej w przypadku stron statycznych: serwer może użyć systemu plików w celu sprawdzenia kiedy określony plik został zmodyfikowany po raz ostatni. Jednakże dla treści tworzonej dynamicznie, takiej jaka jest odsyłana przez aplety, serwer potrzebuje dodatkowej pomocy. Jednak wszystko co może zrobić to odtwarzać je bezpiecznie zakładając jednocześnie, że zawartość ulega zmianie z każdym połączeniem, eliminując w ten sposób konieczność użycia nagłówków Last-Modified oraz If-Modified-Since. Dodatkową pomocą aplet może służyć poprzez implementację (wdrożenie) metody getLastModified(). Aplet wdroży tą metodę, aby przesłać dane dotyczące czasu, w którym po
raz ostatni zmienił swój wydruk wyjściowy. Serwery wywołują tą metodę dwa razy, pierwszy raz kiedy odsyłają odpowiedzi (w celu wstawienia nagłówka odpowiedzi Last-Modified). Drugi raz, podczas obsługi zleceń GET, które zawierają nagłówek If-Modified-Since, dzięki temu serwer może odpowiedzieć w sposób inteligentny. Jeżeli czas odesłania przez getLastModified() jest taki sam, bądź wcześniejszy niż czas nadesłania nagłówka IfModified-Since, wtedy serwer przesyła kod stanu Not Modified. W przeciwnym wypadku serwer wywołuje metodę doGet() oraz odsyła wydruk wyjściowy apletu*. Niektórym apletom może sprawiać trudność ustalenie czasu ostatniej modyfikacji. Dlatego w takich sytuacjach najlepszym wyjściem jest użycie zachowania domyślnego „play-it-safe”. Większość apletów jednakże doskonale daje sobie z tym radę. Rozważmy przypadek apletu
*
Aplet może wstawić bezpośrednio swój nagłówek LastModified, w doGet(), przy użyciu technik omówionych w rozdziale 5.(„Przesyłanie informacji HTML”). Jednakże do czasu kiedy nagłówek zostanie wstawiony w doGet() jest już zbyt późno by zdecydować o tym czy wywoływać doGet() czy nie.
„bulletin board” („elektroniczny biuletyn informacyjny”), aplet taki może zarejestrować i odesłać kiedy, po raz ostatni treść biuletynu została zmieniona. Nawet gdy ten sam aplet obsługuje kilka biuletynów, nadal może przesyłać różne czasy modyfikacji, odpowiednie dla parametrów podanych w zleceniu. Oto metoda get-Last-Modified dla naszego przykładu PrimeSearcher, która przesyła czas znalezienia ostatniej liczby pierwszej: public long getLastModified(HttpServletRequest req) { return lastprimeModified.getTime() /1000 * 1000; }
Zwróćmy uwagę, iż metoda ta przesyła wartość długą, która przedstawia czas jako liczbę milisekund, która upłynęła od 1 stycznia 1970 roku GMT. Takiej samej reprezentacji używa Java w celu przechowywania wartości czasowych. Dzięki temu aplet używa metody getTime() do wczytania LastprimeModified() jako long. Aplet zanim odeśle tą wartość czasową, zaokrągla ją do najbliższej sekundy, dzieląc ją przez tysiąc a następnie mnożąc przez tysiąc. Wszystkie czasy odesłane przez getLastModified() powinny być zaokrąglone w ten sposób. Powodem tego jest fakt, że nagłówki Last-Modified oraz If-Modified-Since są przypisane do najbliższej sekundy. Jeżeli get-Last-Modified odeśle ten sam czas lecz z wyższą rozdzielczością, czas ten może błędnie wydawać się parę milisekund późniejszy niż podany przez If-Modified-Since. Załóżmy dla przykładu, że PrimeSearcher znalazł liczbę pierwszą dokładnie 869.127.442.359 milisekund od wyż. wsp. daty. Fakt ten przekazywany jest przeglądarce lecz tylko do najbliższej sekundy: Thu, 17 – Jul – 97
09:17:22 GMT
Teraz załóżmy znowu., że użytkownik powtórnie ładuje stronę i, że przeglądarka podaje serwerowi poprzez nagłówek If-Modified-Since, czas który uważa za czas ostatniej modyfikacji: Thu, 17-Jul-97
09:17:22 GMT
Niektóre serwery przyjmują ten czas, przeliczają go na dokładnie 869 127 442 000 milisekundy, uznają iż jest on 359 milisekund wcześniejszy od odesłanego przez getLastModified(), a następnie fałszywie zakładają, że treść apletu uległa zmianie. Dlatego właśnie, żeby zachować bezpieczeństwo („to play it safe”), getLastModified() powinna zawsze zaokrąglać do najbliższego tysiąca milisekund. Obiekt HttpServletRequest jest przekazywany do getLastModified() w razie jakby aplet potrzebował oprzeć swoje rezultaty na informacjach specyficznych dla określonego zlecenia. Standardowy aplet elektronicznego biuletynu informacyjnego może to wykorzystać np. do określenia który biuletyn jest przedmiotem zlecenia.
Buforowanie zewnętrzne po stronie serwera Metoda getLastModified() może, przy odrobinie pomysłowości, być pomocna w zarządzaniu pamięcią podręczną wydruku zewnętrznego apletu. Aplety stosujące taki chwyt mogą mieć swój wydruk zewnętrzny przechwycony i umieszczony w pamięci podręcznej na stronie serwera, a następnie odesłany do klientów jak to ma miejsce przy metodzie getLastModified(). Taka procedura może znacznie przyspieszyć tworzenie strony apletu, szczególnie w przypadku apletów, którym zajmuje dużo czasu tworzenie wydruku wyjściowego, zmieniającego się rzadko, takich jak np. aplety wyświetlające dane z bazy danych.
Celem wdrożenia buforowania zewnętrznego po stronie serwera, aplet musi: •
Rozszerzyć com.oreilly.servlet.CacheHttpServlet zamiast HttpServlet
•
Wdrożyć metodę getLastModified (HttpServletRequest)
Przykład 3.10 ukazuje aplet korzystający z CacheHttpServlet. Jest to księga gości apletu, która wyświetla przedłożone przez użytkowników komentarze. Aplet przechowuje komentarze użytkowników w pamięci jako obiekty Vector of GuestbookEntry.W rozdziale 9 „Dołączalność bazy danych” poznamy wersję tego apletu działającą poza bazą danych. W celu stymulacji czytania z wolnej bazy danych, pętla wyświetlacza ma pół-sekundowe opóźnienie na hasło. Im dłuższa lista haseł tym wolniejsza wizualizacja strony. Jednakże z powodu tego, że aplet rozszerza CacheHttpServlet, wizualizacja musi mieć miejsce tylko podczas pierwszego zlecenia GET, po dodaniu nowego komentarza. Wszystkie późniejsze zlecenia GET wysyłają odpowiedź z pamięci podręcznej. Przykładowy wydruk wyjściowy został pokazany na rycinie 3.4. Przykład 3.10. Lista gości używająca apletu „CacheHttpServlet” import import import import import
java.io.*; java.util.*; javax.servlet.*; javax.servlet.http.*; com.oreilly.servlet.CacheHttpServlet;
public class Guestbok
extends HttpServlet {
private Vector entries = new Vector(); // Lista hase• u•ytkownika private long lastModified = 0; // Czas, w którym zosta•o // dodane ostatnie has•o // Wy•wietl aktualne has•a, nast•pnie popro• out.println nowe has•o public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType ("text / zwyk•y"); PrintWriter out = res.getWriter(); printHeader(out); printForm(out); printMassages(out); printFooter(out); } // Dodaj nowe hasło, następnie odeślij z powrotem do doGet() public void doPost (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { handleForm(req, res); doGet(req, res); } private void printHeader(PrintWriter out) throws ServletException { out.println ("<TITLE>
"); out.println ("
"); e-mail>
"); out.println ("); out.println (""); } private void printMessages(PrintWriter out) throws ServletException { String name, email, comment; Enumeration e = entries.elements(); while (e.hasMoreElements()) { GuestbookEntry = (GuestbookEntry) e.nextElement(); name = entry.name; if(name == null) name = "Nieznany użytkownik"; email = entry.email; if(name == null) email = "Nieznany e-mail"; comment = entry.comment; if (comment = null) comment = "Bez komentarza"; out.println ("UploadTest") ; out.println("") ; out.println("UploadTest") ; // Wydrukuj parametry, które otrzymaliśmy out.println("Params:") ; out.println("") ; Enumeration params = multi. getParameterNames (); while (params. hasMoreElements ()) { String name = (String)params.nextElement(); String value = multi.getParameter (name); out.println(name + " = " + value); } out.println("") ; // Pokaż pliki, które otrzymaliśmy out.println("Files:") ; out.println("") ; Enumeration files = multi. getFileNames (); while (files.hasMoreElements()) { String name = (String)files.nextElement(); String filename = multi.getFilesystemName (name) ; String type = multi.getContentType (name); File f = multi.getFile(name) ; out.println("name: " + name); out.println("filename: " + filename); out.println("type: " + type); if (f != null) { out.println( "length: " + f.length()); } out.println() ; } out.println("") ; } catch (Exception e) { out.println("") ; e.printStackTrace(out) ; out.println("") ; } out.println( "") ; } }
Aplet przekazuje swój obiekt zlecenia do konstruktora MultipartRequest razem z katalogiem względnym do katalogu macierzystego serwera, gdzie będą zapisane ładowane pliki (ponieważ obszerne pliki mogą nie zostać umieszczone w pamięci) oraz z maksymalnym rozmiarem POST 5 MB. Aplet używa następnie MultipartRequest do przechodzenia kolejno przez parametry, które zostały przesłane. Zwróćmy uwagę, iż MultipartRequest API dla manipulowania parametrami zgadza się z ServletRequest. I wreszcie aplet wykorzystuje swojego MultipartRequest do przechodzenia przez przesłane pliki. Dla każdego pliku uzyskiwana jest nazwa pliku (zgodnie z formularzem), nazwa systemu plików (określona przez użytkownika) oraz typ treści. Aplet uzyskuje również odwołanie File, którego używa w celu wyświetlenia zapisywanego pliku. W razie jakichkolwiek problemów aplet powiadamia użytkownika o sytuacji wyjątkowej. Na przykładzie 4.22 zaprezentowano kod dla MultipartRequest. Na przykładzie tym będziemy mogli zaobserwować klasę wykorzystującą MultipartRequest com.oreilly.servlet.multipart.MultipartParser „w tle”, w celu wykonania parsowania zlecenia. Klasa MultipartParser zapewnia dostęp do poziomu podstawowego ładowania poprzez „przechodzenie” przez zlecenie kawałek po kawałku. Pozwala to, na przykład na bezpośrednie ładowanie plików do bazy danych lub na sprawdzenie czy plik przekazuje określone kryteria przed zapisaniem. Kod oraz dokumentacja klasy
MultipartParser można znaleźć na stronie http://www.servlets.com (podziękowania od autorów dla Geoffa Souter'a za wykonanie pracy niezbędnej do stworzenia parsera).
Należy mieć świadomość tego, iż wielu producentów serwerów nie testuje należycie swoich wyrobów pod kątem ładowania plików, nie jest rzeczą niezwykłą w przypadku tej klasy, fakt iż serwery bywają obarczone błędami. Jeżeli spotkamy się z problemami podczas korzystania tej klasy, powinniśmy spróbować innego serwera (jak np. „Tomcat’a”) w celu ustalenia czy rzeczywiście są one związane z serwerem — jeżeli okaże się, że to prawda należy skontaktować się z producentem serwera. Przykład 4.22. Klasa MultipartRequest package com.oreilly.servlet ; import import import import
java.io.* ; java.uti1.*; javax.servlet.* ; javax.servlet.http. *;
import import import import
com.oreilly.servlet.multipart.MultipartParser ; com.oreilly.servlet.multipart.Part; com.oreilly.servlet.multipart.FilePart ; com.oreilly.servlet multipart.ParamPart ;
// Klasa użytkowa do obsługimultipart/form-data requests. public class MultipartRequest { private static final int DEFAULT_MAX_POST_SIZE=1024 * 1024; //1 Meg private Hashtable parameters = new Hashtable(); // nazwa – Wektor – // wartości private Hashtable files = new Hashtable(); // nazwa – // UploadedFile public MultipartRequest (HttpServletRequest request, String saveDirectory) throws IOException { this(request, saveDirectory/ DEFAULT_MAX_POST_SIZE) ; } public MultipartRequest (HttpServletRequest request, String saveDirectory, int maxPostSize) throws IOException { // Wartości kontroli poprawności if (request == null) throw new IllegalArgumentException ("zlecenie nie może mieć wartości zero"); if (saveDirectory == null) throw new IllegalArgumentException ("saveDirectory nie może mieć wartości zero"); if (maxPostSize <TITLE>SessionTimer") ; out.println("Session Timer") ; // Wyświetl poprzedni termin ważności out.println("Poprzedni termin ważności to " + session. getMaxInactiveInterval ()) ; out.println("
") ; // Ustaw nowy termin ważności session.setMaxInactiveInterval(2*60*60); // dwie godziny // Wyświetl nowy termin ważności out.println("Nowo ustalony termin ważności to " + session.getMaxInactiveInterval()) ; out.println("") ; } }
Wybieranie właściwego terminu ważności Tak więc znamy, na ten moment, kilka sposobów na kontrolowanie terminu ważności sesji, lecz jaka wartość limitu czasu jest najlepsza? Odpowiedź brzmi: to (oczywiście) zależy. Pierwszą sprawą, którą trzeba zapamiętać, jest fakt iż wartość limitu czasu sesji nie determinuje jak długo sesja będzie trwała. Limit ten determinuje tylko jak długo będzie czekał z unieważnieniem sesji pomiędzy zleceniami. Sesje z półgodzinnym limitem czasu mogłaby trwać godzinami. Określenie prawidłowego okresu bez-aktywności musi być kompromisem pomiędzy wygodą użytkownika, jego bezpieczeństwem a jego skalownością. Dłuższe terminy ważności dają użytkownikowi większą wygodę, ponieważ może on robić dłuższe przerwy pomiędzy zleceniami, uzyskując czas aby np. wykonać telefon lub aby sprawdzić pocztę elektroniczną — bez utraty stanu. Krótsze limity czasu zwiększają bezpieczeństwo użytkownika, ponieważ ograniczają czas wrażliwości (jeżeli użytkownik np. zapomniał się wylogować) jednocześnie zwiększają skalowalność serwera, jako że serwer może wtedy uwolnić obiekty w sesji dużo wcześniej. Na wstępie zadajmy sobie pytanie jaki jest maksymalny czas oczekiwania naszego użytkownika pomiędzy zleceniami? Zwykle odpowiedź brzmi: pół godziny. Rozważmy sobie tą odpowiedź i poznajmy parę niezmiennych zasad: •
Bezpieczne aplikacje WWW, takie jak bankowość „on line”, powinny mieć krótsze niż zwykłe limity czasu aby umożliwić serwerowi odzyskanie lub „wypuszczenie” artykułów tak szybko jak to możliwe.
•
Sesje nie przechowujące „kosztownych” artykułów, mogą mieć dłuższe niż zwykłe terminy ważności.
•
Sesje przechowujące zawartości koszyków zakupów (kosztowne lub nie) powinny mieć dłuższe limity czasu, ponieważ może się zdarzyć, że użytkownicy nie będą pamiętali zawartości swych koszyków zakupów, co, w przypadku wczesnego unieważnienia, oznaczałoby dla nas koszty finansowe!
•
Sesje przechowujące w pamięci podręcznej informacje bazy danych powinny mieć, w przypadku gdy pamięć podręczna ma dużą objętość, krótsze terminy ważności, jednak terminy te powinny być dłuższe dla tych sesji jeżeli połączenie z bazą danych jest wyjątkowo wolne.
•
Jeżeli wymagamy od naszych użytkowników, aby wylogowywali się kiedy kończą pracę, domyślny limit czasu może być ustawiony na dłuższy.
Pamiętajmy również, iż termin ważności nie musi być taki sam dla każdego użytkownika. W celu ustawienia własnego limitu czasu, opartego na preferencjach użytkownika lub nawet w celu jego zmiany w czasie trwania sesji — np. aby uczynić limit czasu krótszym po przechowywaniu „kosztownego” artykułu, możemy posłużyć się metodą setMaxInactiveInterval().
Metody czasu trwania Istnieje wiele dodatkowych metod związanych z obsługą czasu trwania sesji: public boolean HttpSession.isNew()
Metoda ta odsyła informację odnośnie tego czy sesja jest nowa czy nie. Sesja jest uznawana za nową, jeżeli została utworzona przez serwer, lecz klient nie potwierdził jeszcze połączenia z nią. public void HttpSession.invalidate()
Metoda ta powoduje, iż sesja jest natychmiast unieważniana. Wszystkie obiekty przechowywane w sesji zostają „rozwiązane”. Metodę tą wywołuje się w celu wdrożenia wylogowania. public long HttpSession.getCreationTime()
Metoda ta odsyła czas, w którym sesja została utworzona, jako wartość long reprezentującą liczbę milisekund, która upłynęła od północy, 1 stycznia, 1970, czasu GMT. public long HttpSession.getLastAccessedTime()
Metoda ta odsyła czas, w którym klient przesłał ostatnie zlecenie związane z sesją, jako wartość long reprezentującą liczbę milisekund, która upłynęła od północy, 1 stycznia, 1970 roku. Bieżące zlecenie nie jest uznawane jako ostatnie. Każda z metod może zgłosić wyjątek java.lang.IllegalStateException w przypadku gdy sesja, do której uzyskiwany jest dostęp jest nieważna.
„Ręczne” unieważnianie starej sesji Aby zademonstrować działanie omawianych metod, na przykładzie 7.7 został zaprezentowany aplet „ręcznie” unieważniający sesję — jeżeli istnieje ona dłużej niż jeden dzień lub jeżeli nie była aktywna przez okres dłuższy niż jedną godzinę. Przykład 7.7. Unieważnianie starej sesji import import import import public
java. io. *; java.uti1.*; javax.servlet.*; javax.servlet.http.*; class ManualInvalidate extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html") ; PrintWriter out = res.getWriter() ; // Pobierz bieżący obiekt sesji, w razie konieczności utwórz go HttpSession session = reg.getSession() ; Unieważnij sesję, jeżeli istnieje dłużej niż jeden dzień lub nie była // aktywna dłużej niż jedną godzinę. if (! session. isNew()) { // pomiń nowe sesje Date dayAgo = new Date(System.currentTimeMillis() - 24*60*60*1000); Date hourAgo = new Date (System.currentTiineMillis () 60*60*1000); Date created = new Date (session.getCreationTime ()); Date accessed = new Date (session.getLastAccessedTtme ()); if (created.before(dayAgo) || accessed.before(hourAgo)) { session.invalidate() ; session = reg.getSession() ; // pobierz nową sesję } } // Kontynuuj przetwarzanie } }
Zasada działania sesji Tak więc zastanówmy się jak serwer WWW wdraża śledzenie sesji? Kiedy użytkownik po raz pierwszy uruchamia aplikację WWW, jest mu przypisywany nowy obiekt sesji HttpSession oraz niepowtarzalna identyfikacja sesji (ID sesji). ID sesji identyfikuje użytkownika i jest wykorzystywana do dopasowania użytkownika z obiektem HttpSession w kolejnych zleceniach. Niektóre serwery wykorzystują pojedynczą identyfikację sesji dla całego serwera, z każdą aplikacją WWW odwzorowującą to ID do innej kopii HttpSession. Z kolei inne serwery przypisują jedną identyfikację sesji do jednej aplikacji WWW, jako dodatkowe zabezpieczenie przed złośliwymi aplikacjami WWW, pomagającymi intruzowi w „zdepersonalizowaniu” nas.
W tle, ID sesji jest zwykle zapisywane po stronie klienta w cookie zwanym JSESSIONID. Dla klientów, którzy nie obsługują „Ciasteczek”, identyfikacja sesji może zostać przesłana jako część przepisanego URL-u, zakodowanego przy użyciu parametru ścieżki jsessionid, np. http://www.servlets.com/catalog/servlet/ItemDisplay;jsession=123?item=156592391X. Inne implementacje, takie jak wykorzystanie SSL — protokołu bezpiecznej transmisji danych sesji, są również możliwe. Aplet może poznać ID sesji za pomocą metody getId(): public String HttpSession.getId()
Metoda ta odsyła niepowtarzalny identyfikator String, przypisany do sesji. Dla przykładu, ID serwera „Tomcat” mogłoby wyglądać w następujący sposób awj4gyhsn2. Metoda zgłasza wyjątek IllegalStateException w przypadku gdy sesja nie jest ważna. Pamiętajmy, iż identyfikacja sesji powinna być traktowana jako tajna informacja serwera. Zwracajmy uwagę na to, co robimy z tą wartością.
Wycofanie Obiektu HttpSessionContext W Interfejsie API 2.0 istniał obiekt HttpSessionContext, który był wykorzystywany do „tropienia” aktualnych sesji (oraz odpowiadającym im ID sesji), wykonywanych przez serwer. Prawie zawsze klasa była wykorzystywana do „debagowania” i sprawdzania jakie sesje jeszcze istnieją. Klasa ciągle jeszcze istnieje — dla zachowania kompatybilności binarnej, jednak począwszy od wersji 2.1 Interfejsu API, jest wycofana i określana jako pusta. Powodem jest fakt, iż identyfikacje sesji muszą być pilnie strzeżone, więc nie powinny być przechowywane w łatwo dostępnej, pojedynczej lokalizacji, zwłaszcza jeżeli istnienie takiej lokalizacji nie daje żadnych znaczących korzyści, poza „debagowaniem”.
Apletowe śledzenie sesji Niemal każdy serwer obsługujący aplety wdraża śledzenie sesji oparte na „Ciasteczkach”, gdzie identyfikacja sesji jest zapisywana po stronie klienta w trwałym cookie. Serwer odczytuje ID sesji z cookie JSESSIONID, a następnie determinuje który obiekt sesji uczynić dostępnym podczas każdego zlecenia. Dla klientów apletu taka sytuacja może przedstawiać problem. Większość środowisk apletowych wdraża HttpURLConnection w taki sposób, że kiedy aplet tworzy połączenie HTTP, środowisko automatycznie dodaje zawierające cookies przeglądarki, do zlecenia. Pozwala to apletowi na uczestniczenie w tej samej, co inne zlecenia przeglądarki, sesji. Problemem jest jednak, iż inne środowiska apletowe, takie jak starsze wersje środowiska „Java Plug-In enviroment” nie są zintegrowane z przeglądarką i dlatego zlecenia apletów jawią się jako oddzielone od normalnej sesji przeglądarki. Rozwiązanie dla sytuacji, w której aplety muszą działać w podobnych środowiskach jest przesyłane identyfikacji sesji do apletu oraz pozwalanie apletowi na przekazanie
tego ID z powrotem do serwera, jako sztucznie utworzone cookie JSESSIONID. W rozdziale 10 „Komunikacja aplet zwykły — aplet tworzony na serwerze” została zamieszczona klasa HttpMessage — aby pomagać w tego typu sytuacjach. Aplet może otrzymać identyfikację sesji jako zwykły parametr apletu (dodany dynamicznie do strony HTML zawierającej aplet).
Awaryjne zmiany trybu pracy — „nie-ciasteczkowe” Specyfikacja apletu mówi, iż serwery WWW muszą obsługiwać śledzenie sesji również dla przeglądarek, które nie akceptują cookies, których tak wiele wykorzystuje przepisywanie URL-u jako awaryjną zmianę trybu pracy. Wymaga to dodatkowej pomocy ze strony apletów, które generują strony zawierające URL-e. W celu obsłużenia śledzenia sesji poprzez przepisywanie URL-u, aplet musi przepisać każdy lokalny URL, zanim odeśle go klientowi. Interfejs API zawiera dwie metody, aby wykonać to zadanie: encode() oraz encodeRedirectURL(): public String HttpServlrtresponse.encodeURL(String url)
Metoda ta koduje (przepisuje) określony URL aby dołączyć ID sesji, a następnie odsyła nowy URL lub, jeżeli kodowanie nie jest potrzebne albo nie obsługiwane, pozostawia URL niezmienionym. Zasady decydujące o tym kiedy i jak ma być zakodowany URL są domeną serwera. Wszystkie URL-e wychodzące z serwera, powinny być uruchamiane poprzez tą właśnie metodę. Zwróćmy uwagę, iż metoda encodeURL() mogłaby być bardziej precyzyjnie nazwana rewriteURL() — aby nie myliła się z procesem kodowania URL-u, który koduje specjalne znaki w strumieniu URL-u. public String HttpServlrtresponse.encodeRedirectURL(String url)
Metoda ta koduje (przepisuje) określony URL aby dołączyć identyfikację sesji, a następnie odsyła nowy URL lub, jeżeli kodowanie nie jest potrzebne albo nie obsługiwane — pozostawia URL niezmienionym. Zasady decydujące o tym kiedy i jak ma być zakodowany URL są domeną serwera. Metoda ta może używać odmiennych zasad niż metoda encodeURL(). Wszystkie URL-e przekazane do metody HttpServletResponse — sendDirect(), powinny być uruchamiane poprzez tą metodę. Poniższy fragment kodu ukazuje aplet piszący łącznik do samego siebie, który jest kodowany tak, aby zawierał bieżącą identyfikację sesji: out.println("Click here"); out.println("aby powtórnie załadować tą stronę.");
Na serwerach, które nie obsługują przepisywania URL-u lub mają wyłączoną tą funkcję, końcowy URL pozostaje bez zmian. Poniżej przedstawiamy fragment kodu, na którym jest zaprezentowany aplet przekierowujący użytkownika do URL-u zakodowanego tak, aby zawierał ID sesji:
res.sendRedirect(res.encodeRedirectURL("/servlet/NewServlet")) ;
Aplet jest w stanie wykryć czy identyfikacja sesji, wykorzystywana do zidentyfikowania aktualnego obiektu HttpSession, pochodzi od cookie czy od zakodowanego URL-u i wykorzystującego metody isRequestedSessionIdFromCookie() isRequestedSessionIdFromURL(): public boolean HttpServletRequest.isRequestedSessionIdFromCookie() public boolean HttpServletRequest.isRequestedSessionIdFromURL()
Określenie czy identyfikacja sesji pochodzi z innego źródła, takiego jak sesja SSL, nie jest aktualnie możliwe. ID sesji będące przedmiotem zlecenia może nie pokrywać się z identyfikacją sesji, odesłaną przez metodę getSession(), tak jak ma to miejsce kiedy ID sesji jest nieważne. Aplet może jednak ustalić czy identyfikacja sesji będąca przedmiotem zlecenia jest ważna, przy pomocy metody isRequestedSessionIdValid(): public boolean HttpServletRequest.isRequestedSessionIdValid()
Miejmy również świadomość, iż kiedy używamy śledzenia sesji opartego na przepisywaniu URLu, wielokrotne okna przeglądarki mogą należeć do różnych sesji lub do tej samej sesji, w zależności od tego w jaki sposób okna te zostały utworzone oraz czy tworzący je łącznik miał zastosowane przepisywanie URL-u.
Aplet „SessionSnoop” Zaprezentowany na przykładzie 7.8 aplet SessionSnoop wykorzystuje większość opisanych w rozdziale metod, aby „podsłuchać” informację o aktualnej sesji. Rysunek 7.3 prezentuje przykładowy wydruk wyjściowy apletu. Przykład 7.8. „Podsłuchiwanie” informacji sesji import import import import
java.io.*; java.util.*; javax.servlet.*; javax.servlet.http.*;
public class SessionSnoop extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html");
PrintWriter out = res.getWriter() ; // Pobierz aktualny obiekt sesji, w razie konieczności utwórz go HttpSession session = req.getSession(); // Zwiększ o jeden liczbę wejść na tą stronę. Wartość jest zapisana // w sesji klienta pod nazwą "snoop.count". Integer count = (Integer)session.getAttribute("snoop.count"); if (count == null) count = new Integer(1); else count = new Integer(count.intValue() + 1) ; session.setAttribute("snoop.count", count) ; out.println("<TITLE>SessionSnoop"); out.println("Session Snoop"); // Wyświetl ilość wejść na tą stronę out.println("Odwiedziłeś już tą " + count + ((count.intValue() == 1) ? " raz." : " razy.")); out.println("
") ; out.println("Oto twoje zachowane dane sesji:"); Enumeration enum = session.getAttributeNames(); while (enum.hasMoreElements()) { name = (String) enum.nextElement(); intln (name + ": " + session.getAttribute (name) + ""); } out.println("Oto parę ważniejszych stanów twojej sesji:"); out.println("ID sesji: " + session.getid() + " (keep it secret )
"); out.println("Now sesja: " + session.isNew() + "
"); out.println("Termin ważności: " + session.getMaxInactiveInterval()); out.println("(" + session.getMaxInactiveInterval() / 60 + " minut) < / Ix>
") ; out.println("Czas utworzenia: " + session. getCreationTime() ); out. println ("(" + new Date (session.getCreationTime ()) + ")>
") ; out.println("Ostatni czas wejścia: " + session.getLastAccessedTime()); out.println("(" + new Date(session.getLastAccessedTime()) + ")
") ; out.println("ID sesji będące przedmiotem zlecenia z URL: " + req. isRequestedSessionIdFromURL () + "
") ; out.println("ID sesji, będące przedmiotem zlecenia jest ważne: " + req.isRequestedSessionIdValid() + "
"); out.println("Test URL Rewriting"); out.println("Kliknij here") ; out.println("Aby sprawdzić czy śledzenie sesji działa poprzez przepisywanie"); out.println("URL-u, nawet wtedy gdy cookies nie są obsługiwane.");
}
out.println("") ; }
Aplet rozpoczyna tym samym kodem, który został zamieszczony w przykładzie 7.4, w aplecie SessionTracker. Następnie przechodzi do dalszego wyświetlania bieżącego ID sesji, tego czy jest to sesja nowa, wartości limitu czasu, czasu utworzenia sesji oraz czas ostatniego połączenia z sesją. Później aplet wyświetla czy identyfikacja sesji (jeżeli jest taka), będąca przedmiotem zlecenia, pochodzi od cookie czy od URL-u oraz czy to ID jest ważne. W końcu aplet drukuje zakodowany URL, który może zostać wykorzystany do powtórnego załadowania tej strony, w celu sprawdzenia czy przepisywanie URL-u działa nawet wtedy kiedy cookies nie są obsługiwane.
Zdarzenia wiążące sesję Niektóre obiekty mogą „chcieć” przeprowadzić operację zarówno wtedy, kiedy są związane z sesją, jak i wtedy kiedy nie są z nią związane. Dla przykładu, połączenie z bazą danych może rozpoczynać transakcję (obsługę zlecenia) w stanie związania z sesją, a kończyć kiedy nie są z nią związane. Wszystkie obiekty, które wdrażają interfejs javax.servlet.http.HttpSessionBindingListner, są powiadamiane o związaniu z sesją oraz o tym, że nie są z nią związane. Interfejs deklaruje dwie metody: valueBound() oraz valueUnbound(), które muszą zostać wdrożone: public void HttpSessionBindingListener.valueBound( HttpSessionBindingEvent event) public void HttpSessionBindingListener.valueUnbound ( HttpSessionBindingEvent event)
Metoda valueBound() jest wywoływana, kiedy odbiornik jest związany z sesją, a metoda valueUnbound() kiedy odbiornik nie jest z nią związany — poprzez usunięcie, zastąpienie albo poprzez unieważnienie sesji. Argument javax.servlet.http.HttpSessionBindingEvent zapewnia dostęp do nazwy pod którą obiekt jest wiązany (lub odwiązywany od niej) z metodą getName(): public String HttpSessionBindingEvent.getName()
Rysunek 7.3. Przykładowy wydruk wyjściowy apletu „SessionSnoop” Obiekt HttpSessionBindingEvent zapewnia również dostęp do obiektu, do którego jest wiązany (lub od którego jest „odwiązywany”) odbiornik, wykorzystując w tym celu metodę getSession(): public HttpSession HttpSessionBindingEvent.getSession()
Na
przykładzie 7.9 zaprezentowano użycie HttpSessionBindingListner oraz HttpSessionBindingEvent, z odbiornikiem rejestrującym w czasie związania jak i w czasie rozwiązania z sesją.
Przykład 7.9. Zdarzenia wiążące śledzenie sesji import import import import
java.io.*; java.util.*; javax.servlet.* ; javax.servlet.http.*;
public class SessionBindings extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IQException { res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter(); // Pobierz aktualny obiekt sesji. w razie potrzeby utwórz go HttpSession session = reg.getSession(); // Dodaj CustomBindingListener session.setAttribute("bindings.listener", new CustomBindingListener(getServletContext())); out.println("Ta strona jest celowo pozostawiona pustą"); ) } class CustomBindingListener inplements HttpSessionBindingListener { // Zapisz ServletContext dla użytku jego metody log () ServletContext context; public CustomBindingListener(ServletContext context) { this.context = context; } public void valueBound(HttpSessionBindingEvent event) { context.log("[" + new Dated + "] BOUND as " + event.getName() + " to " + event. getSession () . getid ()) ; } public void valueUnbound(HttpSessionBindingEvent event) { context.log("[" + new Date() + "] UNBOUND as " + event.getName () + " from " + event. getSession().getId() ); } }
Za każdym razem kiedy obiekt CustomBindingListner jest wiązany z sesją, jego metoda valueBound(), jest wywoływana i zdarzenie jest rejestrowane. Za każdym razem kiedy obiekt ten jest „odwiązywany” od sesji, wywoływana jest jego metoda valueUnbound(), tak że zdarzenie jest również rejestrowane. Możemy śledzić sekwencję zdarzeń poprzez obserwowanie dziennika zdarzeń serwera. Załóżmy, iż aplet ten jest wywołany jeden raz, powtórnie ładowany 30 sekund później i następnie nie wywoływany co najmniej przez następne pół godziny. Dziennik zdarzeń mógłby wyglądać w następujący sposób:
[Tue Sep 27 22:46:48 PST 2000] BOUND as bindings.listener to awj4qyhsn2 [Tue Sep 27 22:47:18 PST 2000] UNBOUND as bindings.listener from awj4qyhsn2 [Tue Sep 27 22:47:18 PST 2000] BOUND as bindings.listener to awj4qyhsn2 [Tue Sep 27 23:17:18 PST 2000] UNBOUND as bindings.listener from awj4qyhsn2
Pierwsza pozycja występuje podczas zlecenia pierwszej strony, kiedy odbiornik jest związany z nową sesją. Pozycje: druga oraz trzecia występują podczas ponownego ładowania, ponieważ odbiornik jest „odwiązywany” i powtórnie wiązany z sesją, w czasie tego samego wywołania setAttribute(). Czwarta pozycja ma miejsce pół godziny później, kiedy limit czasu sesji kończy się, a ona sama jest unieważniana.
Robienie zakupów przy użyciu śledzenia sesji Zakończmy ten rozdział spojrzeniem na to, jak zadziwiająco prostym może stać się nasz aplet „shopping cart viewer, przy zastosowaniu śledzenia sesji. Na przykładzie 7.10 zaprezentowano aplet zapisujący każdy artykuł z koszyka, w sesji użytkownika, pod nazwą cart.items. Zwróćmy uwagę, iż URL-e, znajdujące się na stronie, zostały przepisane tak, aby obsługiwać klientów z zablokowanymi „Ciasteczkami”. Przykład 7.10. Wykorzystanie Śledzenia Sesji API import java.io.*; import javax.servlet.* ; import javax.servlet.http.*; public class ShoppingCartViewerSession extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res. setContentType ("tekst/html") ; PrintWriter out = res.getWriter () ; // Pobierz obiekt bieżącej sesji. HttpSession session = req.getSession(); // Artykuły z koszyka są utrzymywane w obiekcie sesji. String [] items = (String []) session. getAttribute ("cart. items") ; out.println("<TITLE>SessionTracker"); out.println("Session Tracking Demo");
// Drukuj aktualne artykuły z koszyka. out.println("Aktualnie masz w swoim koszyku następuje artykuły:
"); if (items == null) { out.println("None") ; } else ( out.println("
") ; for (int i = 0; i < items.length; i++) { out.println("- " + items[i]); } out.println("
") ; } // Spytaj czy chcą dodać jeszcze do koszyka jakieś artykuły, czy też // chcą zakończyć. out.println("
Kody ucieczkowe HTML — filtrowanie wprowadzonego przez użytkownika pola komentarza w celu usunięcia specjalnych znaków, które mogłyby przypadkowo lub rozmyślnie sprawić kłopoty.
•
Lokalizacja — filtrowanie daty poprzez pewną zlokalizowaną logikę wyświetlania.
•
Przepisywanie URL-i — filtrowanie URL-i w celu dodania śledzącej sesji wartości jsessionid.
•
Wyświetlanie wartości null — filtrowanie wyświetlania zmiennych o wartości null w celu wyświetlenia komunikatu o błędzie lub ukrytego zignorowania zmiennej.
Filtry niestandardowe Filtry niestandardowe wykonują konkretne działania. Na przykład, zmienna klient może zostać przefiltrowana przez szablon klienta zaprojektowany w celu wyświetlania zmiennej i jej właściwości w oparciu o zewnętrzny plik szablonu. Szablon klienta może nawet przefiltrować adres klienta poprzez zewnętrzny szablon adresu. Pozwala to na ponowne stosowanie i łatwe dostosowywanie logiki wyświetlania. Filtry mogą być także łączone w łańcuchy. Pozwoliłoby to na przykład na zarówno lokalizację jak i wyłączenie zmiennej, określoną wcześniej wartością, jeżeli zmienna posiadałaby wartość null. Możliwe zastosowania filtrów znajdują się ciągle jedynie w wyobraźni ludzi. Interesujące może okazać się śledzenie, które zastosowania filtrów WebMacro staną się popularne.
W niniejszym rozdziale: •
Elementy strony jako obiekty
•
Wyświetlanie zbioru wyników
Rozdział 16.
Element Construction Set Pakiet Element Construction Set (ECS) prezentuje całkowicie inne podejście do tworzenia zawartości niż JSP, Tea i WebMacro. ECS odchodzi daleko od tekstu HTML i traktuje HTML jako jedynie kolejny zestaw obiektów Javy. Strona WWW w ECS jest zarządzana jak obiekt, który może zawierać inne obiekty HTML (takie jak listy i tabele), które mogą zawierać jeszcze więcej obiektów HTML (takich jak elementy listy i komórki tabeli). Ten model „obiektowego tworzenia HTML” okazuje się być bardzo potężny, ale również skoncentrowany na programiście. Stephan Nagy i Jon Stevens stworzyli ECS i udostępnili go jako Open Source jako część Java Apache Project, oczywiście w licencji Apache. Biblioteka został utworzona według produktu htmlKona firmy WebLogic, produktu, który stracił wsparcie po wykupieniu WebLogic przez BEA Systems. W niniejszym rozdziale opisywany jest ECS w wersji 1.3.3, dostępny pod adresem http://jakarta.apche.org/ecs.
Elementy strony jako obiekty ECS zawiera klasy dla wszystkich konstrukcji HTML 4.0. Przykład 16.1 przedstawia sposób tworzenia prostej strony HTML przy pomocy ECS. Przykład 16.1. Strona jako zbiór obiektów import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*;
import org.apache.ecs.*; import org.apache.ecs.html.*;
public class ECSWitaj extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException {
odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();
Document dok = new Document(); dok.appendTitle("Test ECS"); dok.appendBody(new Big("Witaj!")) .appendBody(new P()) .appendBody("Aktualny czas to " + new Date()); dok.output(wyj); } }
Proszę zauważyć, że wszystkie znaczniki HTML zostały zastąpione obiektami. Powyższy serwlet tworzy nowy obiekt Document przedstawiający stronę WWW, która zostanie zwrócona. Następnie dodaje do strony tytuł „Test ECS” i dodaje do jej głównej części duży napis „Witaj!”, przerwę akapitową oraz wyświetla aktualny czas. Na końcu wyświetla stronę w jej PrintWriter. W ten sposób działa obiektowe generowanie HTML — pobranie obiektu Document, dodanie do niego obiektów składowych i wysłanie go do klienta. Aby uruchomić powyższy serwlet należy zainstalować ECS przez umieszczenie pliku JAR ECS w ścieżce klas serwera (lub w katalogu WEB-INF/lib aplikacji WWW). Dla ECS 1.3.3 plik JAR nosi nazwę ecs-1.3.3.jar. Konieczne może się okazać ponowne uruchomienie serwera w celu odnalezienia nowego pliku JAR. Po zainstalowaniu ECS można wywołać serwlet w zwykły sposób, a wygeneruje on wynik podobny do poniższego: Test ECSWitaj!
Aktualny czas to FRI OCT 25 23:17:37 GMT 2001
Domyślnie wszystkie dane wyświetlane przez ECS pojawiają się w jednej linii bez wcięć i powrotów karetki. Przyśpiesza to transfer podczas komunikacji z przeglądarką klienta. Aby otrzymać zawartość łatwiejszą do odczytania należy dokonać edycji pliku ecs.properties, który jest dołączony do dystrybucji oraz zmienić wartość pretty_print na true. Następuje teraz trudniejsza część: należy upewnić się, że plik ecs.properties zostanie odnaleziony w ścieżce serwera przed plikiem JAR ECS, tak aby wyedytowany plik powodował pominięcie pliku ecs.properties zawartego w pliku JAR. Plik ecs.properties jest poszukiwany jako org/apache/ecs/ecs.properties, tak więc plik nie może zostać umieszczony bezpośrednio w ścieżce klas, ale w podkatalogu org/apache/ecs katalogu wewnątrz ścieżki klas (na przykład WEBINF/classes/org/apache/ecs/ecs.properties). Serwlet importuje dwa pakiety ECS — org.apache.ecs zawierający podstawowe klasy ECS i org.apache.ecs.html zawierający klasy specyficzne dla HTML. (Istnieją inne pakiety odpowiedzialne za XML, WML i RTF.)1 Pakiet org.apache.ecs.html zawiera prawie sto klas reprezentujących wszystkie elementy HTML 4.0. Większość klas HTML nosi nazwy odpowiadające nazwom znaczników HTML — Big, Small, P, Table, TR, TD, TH, H1, H2, H3, Frame, A, Head, Body i tak dalej. Każda klasa HTML posiada metody służące do konfiguracji elementu. Na przykład klasa TD posiada metodę setBackground(String url), która ustawia tło tej komórki tabeli. Klasa Body również posiada podobną metodę służącą do ustawiania tła dla całej strony. Żaden inny element ECS nie posiada metody setBackground(), ponieważ żaden inny element nie posiada możliwości ustawienia swojego tła, co pozwala ECS na upewnienie się, że programowo utworzone elementy zawsze zawierają prawidłowo utworzony HTML.
1
Osoby zainteresowane programowym tworzeniem XML przy pomocy Javy powinny się raczej skupić na wykorzystaniu JDOM (http://jdom.org), ponieważ JDOM jest lepiej zintegrowany z technologiami XML.
Aby dodać elementy głównej części, serwlet wykorzystuje łączenie metod w łańcuchy, w których kilka metod jest wywoływanych na tym samym obiekcie. W ECS można dotrzeć wiele takich konstrukcji. Na przykład, w celu utworzenia tabeli: Table tab = new Table() .setCellPadding(0) .setCellSpacing(0);
Puste miejsca nie mają znaczenia. Powyższy kod jest równy następującemu: Table tab = new Table().setCellPadding(0).setCellSpacing(0);
Powyższy łańcuch jest możliwy do utworzenia, ponieważ każda metoda set i append zwraca odwołanie do obiektu, na którym została wywołana — odwołanie to jest wykorzystywane do wywołania następnej metody. Sztuczka ta często okazuje się przydatna przy korzystaniu z ECS.
Wyświetlanie zbioru wyników Wykorzystanie ECS do pełnego tworzenia strony wypadło z łask po ulepszeniu opartych na serwletach technologii szablonów. Po prostu dynamiczne tworzenie czegoś, co w większości jest statyczną zawartością strony zajmuje zbyt wiele czasu. Jednak ECS ciągle posiada swoje miejsce. ECS sprawdza się w przypadku tych części strony, które są wyjątkowo dynamiczne, w których do określenia zawartości do utworzenia konieczna jest pełna moc Javy. Jak powiedział Jon Stevens, jeden z jego twórców, „należy wykorzystywać ECS wszędzie tam, gdzie w innym przypadku wystąpiłoby wyj.println().” Na przykład, proszę wyobrazić sobie aplikację WWW pozwalającą klientom na wykonywanie ad hoc zapytań w bazie danych. Na przykład mógł zostać zaimplementowany system śledzenia błędów i potrzebny jest serwlet dający zaawansowanym użytkownikom możliwość wykonywania swoich własnych zapytań w bazie danych (przy pomocy połączenia z uprawnieniami tylko-do-odczytu). ECS sprawdza się w tworzeniu strony wyświetlającej wyniki z baz danych, programowo tworząc tabelę dostosowaną do danych. Przykład 16.2 przedstawia prosty element przeglądający ResultSet. Przypomina on klasę HtmlSQLWynik przedstawioną w rozdziale 9, „Łączność z bazą danych”, która wykorzystywała wyj.println(). Zastosowanie ECS zamiast wyj.println() pozwala na uproszczenie i większe możliwości dostosowania kodu. Przykład 16.1. Ulepszona tabela ZbiorWynik import java.io.*; import java.sql.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*;
import org.apache.ecs.*; import org.apache.ecs.html.*;
public class ProstaTabelaResultSet extends Table {
public ProstaTabelaResultSet(ResultSet rs) throws SQLException { setBorder(1);
ResultSetMetaData rsmd = rs.getMetaData(); int colCount = rsmd.getColumnCount();
TR rzad = new TR();
for (int i = 1; i Przeglądanie XMLC
Lokalizacje klienta
W rzeczywistości powyższa lista byłaby częścią dużo większej strony, ale dla XMLC nie jest to w ogóle interesujące. Strona zostaje skompilowana przy pomocy kompilatora XMLC, z opcją –delete-class makieta w celu automatycznego usunięcia w fazie kompilacji elementów oznaczonych jako makieta: % xmlc –class Przegl –keep –delete-class makieta –methods przegl.html public org.w3c.dom.html.HTMLLIElement getElementLokal();
Proszę zauważyć, że do dokumentu została dodana tylko jedna metoda dostępu — getElementLokal(), która zwraca element oznaczony jako lokal w obiekcie HTMLLIElement. Klasa manipulacyjna dynamicznie zmieniająca powyższy dokument jest przedstawiona w przykładzie 17.5. Przykład 17.5. Klasa manipulacyjna dla listy języków import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*;
import org.w3c.dom.*; import org.w3c.dom.html.*;
import org.enhydra.xml.io.DOMFormatter;
public class PrzeglManipulacja extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();
// Pobranie do wyświetlenia pewnych danych dynamicznych Enumeration lokale = zad.getLocales();
// Utworzenie drzewa DOM Przegl przegl = new Przegl();
// Pobranie pierwszego, „prototypowego” elementu listy // Reszta została usunięta podczas kompilacji xmlc HTMLLIElement element = przegl.getElementLokal();
// Pobranie przodka prototypu w celu umożliwienia zarządzania potomkami Node przodek = element.getParentNode();
// Pętla nad lokalizacjami i dodanie węzła dla każdej while (lokale.hasMoreElements()) { Locale lok = (Locale)lokale.nextElement(); HTMLLIElement nowyElement = (HTMLLIElement) element.cloneNode(true); Text tekst = przegl.createTextNode(lok.toString()); nowyElement.replaceChild(tekst, newItem.getLastChild()); przodek.insertBefore(nowyElement, null); }
// Usunięcie elementu prototypowego przodek.removeChild(element);
// Wyświetlenie dokumentu DOMFormatter formatuj = new DOMFormatter();
// może być poprawiony
formatuj.write(przegl, out); } }
Interesującą częścią powyższego serwletu jest pętla while. Dla każdej lokalizacji serwlet klonuje element prototypowy, zmienia tekst przechowywany przez klon i wstawia klon na końcu listy. Po pętli while serwlet usuwa element prototypowy, pozostawiając listę pełną klonów zawierającą prawdziwe dane. Wynik przedstawiono na rysunku 17.13.
3
Proszę pamiętać o interakcjach w ścieżce klas podczas uruchamiania powyższego przykładu. Wykorzystywane są biblioteki potrzebne ZMLC, JDOM i przypuszczalnie samemu serwerowi. Usatysfakcjonowanie wymagań biblioteki wersji XML dla wszystkich trzech składników bez konfliktów może wymagać sporych umiejętności w czarnej magii i nie zawsze jest możliwe.
Rysunek 17.1. Skróty lokalizacji — angielski (USA), wietnamski i tajski W powyższym przykładzie można dostrzec, że jedną z trudności XMLC jest obsługa nieintuicyjnego modelu obiektów DOM. Ze stosowaniem DOM związane są pewne sztuczki. Na przykład, można dostrzec, że zawartość String może zostać utworzona jedynie przy pomocy metody fabryki createTextNode() wywoływanej na dokumencie, do którego ma zostać dodany tekst, a kiedy zawartość jest już gotowa, model dodawania tekstu do dokumentu wymaga dodania go jako węzła potomnego swojego elementu i usunięcia istniejącego potomka. Rozważa się możliwość dołączenia dużo łatwiejszego w obsłudze modelu obiektów JDOM do przyszłych wersji XMLC. Poza tym, pakiety narzędziowe dla XMLC przejmują DOM i odchodzą od niektórych z bardziej nieprzyjemnych jego wymagań.
Aplikacja „Narzędzia” W celu zakończenia dyskusji nad XMLC opisany zostanie sposób tworzenia przy pomocy XMLC aplikacji „Narzędzia” wykorzystanej wcześniej do zademonstrowania Tea i WebMacro. Na początku opisany zostanie szablon HTML przedstawiający wspólny projekt graficzny witryny, ale nie posiadający żadnej zawartości odwołującej się do listy narzędzi. Jest on przedstawiony w przykładzie 17.6. Przykład 17.6 Plik szablonu aplikacji „Narzędzia” <TITLE>Lista narzędzi
Strona główna Hosting Mechanizmy |
<SPAN ID="tytul">Lista narzędzi |
<SPAN ID="tytul2">Lista narzędzi do tworzenia zawartości
<SPAN ID="opis"> Bez narzędzi, ludzie nie są niczym więcej niż zwierzętami. I to dość słabymi. Poniżej przedstawiono listę opartych na serwletach narzędzi do tworzenia zawartości, które można wykorzystać w celu wzmocnienia się." <SPAN ID="nazwaNarz">Jakaś nazwa narzędzia <SPAN ID="stanNarz">(Nowość!) http://narzedzia.com
<SPAN ID="komentarzNarz"> Tutaj komentarz na temat narzędzia. Inna nazwa narzędzia (Uaktualnienie!) http://narzedzia.com
Tu komentarz na temat tego narzędzia.
|
| Strona Główna Hosting Mechanizmy |
Powyższy plik wygląda bardzo podobnie do szablonu, ale wartości obszarów zablokowanych zostały wymienione na realistyczne i wypełniono obszar zawartości pewnymi prototypowymi rekordami. Oto sposób wykorzystania tego pliku: programowo pobrane zostaną jego kluczowe elementy (tytuły, opis i prototypowy rekord), po czym zostaną one skopiowane i umieszczone w pliku szablonu w celu utworzenia ostatecznej wersji strony. Po co wykorzystywać dwa pliki? Czy nie jest możliwe po prostu bezpośrednie zmodyfikowanie pliku widoknarz.html? Jest to możliwe, ale szablon wykorzystywany jest po to, by w przypadku przyszłego uaktualnienia nagłówka, paska bocznego lub stopki konieczne było uaktualnienie jedynie pliku szablon.html i zmiana ta została uwidoczniona na wszystkich stronach. Innymi słowy, szablon narzuca ogólny wygląd strony. Plik widoknarz.html jest wykorzystywany jedynie w kluczowych częściach. Pliki HTML są poddawane kompilacji XMLC przy pomocy poniższych poleceń. Mogą wystąpić ostrzeżenia, ponieważ HTML w nich zawarty nie jest prawidłowym XML: % xmlc –class Szablon –keep –methods szablon.html % xmlc –class WidokNarz –keep –methods widoknarz.html
Następnie można utworzyć serwlet który działa jako klasa manipulacyjna. Jest on przedstawiony w przykładzie 17.8. Przykład 17.8.
Klasa manipulacyjna aplikacji „Narzędzia” import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*;
import org.w3c.dom.*; import org.w3c.dom.html.*;
import org.enhydra.xml.io.DOMFormatter;
public class WidokNarzSerwlet extends HttpServlet {
private Narzedzie[] narzedzia;
public void init() throws ServletException { // Załadowanie danych narzędzi w init w celu uproszczenia String plikNarz = getInitParameter("plikNarz"); // z web.xml if (plikNarz == null) { throw new ServletException( "Plik danych narzędzi musi być określony jako parametr inicjacji plikNarz "); } log("Ładowanie narzędzi z " + plikNarz); try { narzedzia = Narzedzie.ladujNarzedzia(plikNarz); if (narzedzia.length == 0) { log("Nie odnaleziono narzędzi w " + plikNarz); } else { log(narzedzia.length + " narzędzi znaleziono w " + plikNarz); } } catch (Exception w) { throw new ServletException(w); } }
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();
// Utworzenie drzewa DOM dla pełnego dokumentu Szablon szablon = new Szablon();
// Utworzenie drzewa DOM przechowującego wewnętrzną zawartość
WidokNarz widoknarz = new widokNarz();
// Pobranie prototypowego rekordu narzędzia HTMLDivElement rekord = widoknarz.getElementRekord();
// Pobranie odwołania do punktu wstawiania
dla listy narzędzi
HTMLDivElement punktWstaw = szablon.getElementZawartosc(); Node przodekWstaw = punktWstaw.getParentNode();
// Ustawienie tytułów i opisu // Usunięcie danych z pliku widoknarz.html String tytul = ((Text)widoknarz.getElementTytul().getFirstChild()).getData(); String ttul2 = ((Text)widoknarz.getElementTytul2().getFirstChild()).getData(); String opis = ((Text)widoknarz.getElementOpis().getFirstChild()).getData(); szablon.setTitle(tytul);
// tytuł strony
szablon.setTextTytul(tytul); // element oznaczony „tytul” template.setTextTytul2(tytul2); template.setTextOpis(opis);
// element oznaczony „tytul2”
// element oznaczony „opis”
// Pętla nad narzędziami, dodanie nowego rekordu dla każdego for (int i = 0; i < narzedzia.length; i++) { Narzedzie narzedzie = narzedzia[i];
widoknarz.setTextNazwaNarz(narzedzie.nazwa); widoknarz.setTextToolKomentarz(narzedzie.komentarz);
if (narzedzie.czyNowy(45)) { widoknarz.setTextToolStan(" (Nowość!) "); } else if (narzedzie.czyUaktualniony(45)) { widoknarz.setTextToolStan(" (Uaktualnienie!) "); } else { widoknarz.setTextToolStan(""); }
HTMLAnchorElement lacze = toolview.getElementLaczeNarz(); lacze.setHref(narzedzie.domURL); Text laczeTekst = widoknarz.createTextNode(narzedzie.domURL); lacze.replaceChild(laczeTekst, lacze.getLastChild());
// importNode() to DOM Level 2 przodekWstaw.insertBefore(szablon.importNode(rekord, true), null); }
// Usunięcie obszaru zablokowanego
przodekWstaw.removeChild(punktWstaw);
// Wyświetlenie dokumentu DOMFormatter formatuj = new DOMFormatter();
// może być poprawiony
formatuj.write(szablon, out); } }
Metoda init() serwletu pobiera dane narzędzi z pliku określonego w parametrze inicjacji plikNarz, przy pomocy klasy Narzedzie z rozdziału 14, „Szkielet Tea”. Ciekawe działanie następuje w metodzie doGet(). Tworzone są egzemplarze dokumentów Szablon i WidokNarz. Następnie odnajdywany jest prototypowy rekord narzędzia w WidokNarz oraz punkt wstawiania dla rekordów narzędzie w Szablon. Następnie odczytywane są tytuły i opis z dokumentu WidokNarz, po czym wartości te zostają skopiowane do Szablon. W pętli for dokonywana jest obsługa zadania tworzenia listy rekordów narzędzi. Dla każdego narzędzia do prototypowego rekordu przypisane zostają odpowiednie wartości — na początku nazwa i komentarz, po czym łącze. Po zmodyfikowaniu rekordu zostaje on dodany do szablonu w punkcie wstawiania. W DOM węzły przypisane są do dokumentu, który je utworzył, tak więc zastosowana zostaje metoda importNode() w celu udostępnienia rekordu przesuwanego z dokumentu WidokNarz do Szablon. Metoda importNode() wykonuje głębokie kopiowanie (ponieważ przekazano jej jako drugi argument true), tak więc każda iteracja pętli for dodaje inna kopię zawartości rekordu. Po zakończeniu pętli for punkt wstawiania zostaje usunięty z szablonu i dokument jest wyświetlany klientowi. Podczas uruchamiania powyższego serwletu należy pamiętać, że metoda importNode() jest nowością w DOM Level 2, tak więc, aby mógł on zostać uruchomiony, serwer musi posiadać ścieżkę klas zawierającą DOM Level 2 przed wszystkimi klasami DOM Level 1. Archiwum xmlc.jar zawiera DOM Level 2, tak więc można pomyśleć, że nie ma żadnego problemu, ale niektóre serwery (włączając w to Tomcat 3.2) posiadają w swojej domyślnej ścieżce implementację DOM Level 1 pomagającą w odczytywaniu plików web.xml. Aby powyższy serwlet mógł pracować na takich serwerach konieczna jest edycja ścieżki klas serwera w celu upewnienia się, że plik XMLC xmlc.jar znajduje się przed własnymi bibliotekami XML serwera. (Tomcat 3.2 automatycznie ładuje pliki JAR w porządku alfabetycznym, tak więc konieczna może okazać się zmiana nazw plików JAR.) Jednym z problemów związanych z XMLC jest przedstawiony przez powyższy przykład fakt, że warunkowo dołączane bloki tekstu, których zawartość może się zmieniać, podobnie jak uwagi (Nowość! i Uaktualnienie!) mogą sprawiać trudności przy dołączaniu do strony, ponieważ szablon może zadeklarować tylko jeden możliwy blok tekstu do dołączenia. W powyższym przykładzie oczywiste jest, że kod Javy wie więcej niż powinien na temat tworzenia uwag. Miłą własnością XMLC jest automatyczne umieszczanie kodów ucieczkowych przed znakami specjalnymi takimi jak i & podczas wyświetlania ich do klienta, ponieważ wtedy narzędzie dokonujące wyświetlenia może bez problemu rozpoznać, co jest strukturą, a co zawartością. Ta wiedza o strukturze dokumentu umożliwia również mechanizmowi formatującemu na wykonywanie zaawansowanych zadań takich jak modyfikacja wszystkich łączy w celu zakodowania identyfikatorów sesji, chociaż ta konkretna własność nie została jeszcze wprowadzona. Strona wygenerowana przez powyższy serwlet wygląda identycznie jak strona utworzona przez przykłady Tea, albo WebMacro.
W niniejszym rozdziale: •
Wykorzystywanie JavaServer Pages
•
Zasady działania
•
Wyrażenia i deklaracje
•
Instrukcje
•
JSP i JavaBeans
•
Dołączenia i przekazania
•
Aplikacja „Narzędzia”
•
Biblioteki własnych znaczników
Rozdział 18.
JavaServer Pages JavaServer Pages, znana powszechnie jako JSP, to technologia utworzona przez Sun Microsystems, blisko związana z serwletami. JSP była jedną z pierwszych prób utworzenia ogólno dostępnego systemu tworzenia zawartości opartego na serwletach. Osoby od dawna zajmujące się serwletami mogą pamiętać, że JSP została przedstawiona po raz pierwszy na wiosnę 1998 — na tyle wcześnie, by pierwsze wydanie niniejszej książki mogło zawierać krótki samouczek testowej wersji 0.91 JSP. Oczywiście JSP zmieniła się bardzo od tego czasu. W tym rozdziale opisana zostanie JSP 1.1, opierająca się na Servlet API 2.2. Podobnie jak w przypadku serwletów, Sun udostępnia specyfikację JSP (utworzoną przez grupę ekspertów składającą się z niezależnych producentów i osób), po czym inni producenci konkurują w swoich implementacjach tego standardu. Różnica pomiędzy JSP i innymi technologiami opartymi bezpośrednio na serwletach polega na tym, że JSP jest specyfikacją, nie produktem i wymaga wsparcia ze strony serwera. Większość producentów kontenerów serwletów zapewnia tę obsługę, między innymi Tomcat, w którym mechanizm JSP nosi nazwę Jasper. JSP jest podstawowym składnikiem Java 2, Enterprise Edition (J2EE). Jednym z głoszonych celów JSP (cytat ze specyfikacji) jest „umożliwienie oddzielenia zawartości dynamicznej i statycznej”. Innym celem jest „umożliwienie tworzenia stron WWW, które zawierają elementy dynamiczne w łatwy sposób, ale z maksymalną mocą i elastycznością”. JSP spełnia dobrze oba te zadania. Jednak, zgodnie z kwestią „maksymalnej mocy” przy tworzeniu JSP, autor strony JSP zawsze posiada całkowitą kontrolę nad systemem, i w związku z tym można powiedzieć, że JSP umożliwia oddzielenie zawartości od grafiki, ale nie wymusza jej ani nie narzuca, jak to się dzieje w przypadku jej alternatyw. Jest to zjawisko podobne do tego, że C++ umożliwia projektowanie obiektowe, ale nie promuje tego doskonałego sposobu tak mocno, jak to się dzieje w przypadku Javy.
JSP jest również technologią bardzo elastyczną, i istnieje wiele sposobów jej wykorzystania. Jednym z nich jest „skrótowy” sposób tworzenia serwletów — programista serwletów, dobrze znający Javę, może umieścić kod Javy bezpośrednio na stronie JSP, zamiast pisać kompletny serwlet — co daje ułatwienia takie jak wyeliminowanie wywołań wyj.println(), umożliwienie bezpośredniego wskazywania na pliki JSP i wykorzystanie własności autokompilacji JSP, Jednak poprzez tworzenie strony JSP zamiast serwletu programista traci pełny kontakt z kodem oraz możliwość kontroli prawdziwego środowiska uruchomieniowego (np. rozszerzanie com.oreilly.servlet.CacheHttpServlet lub tworzenie wyniku binarnego (obrazka)). Z tego powodu najlepiej jest pozostawić prawdziwe kodowanie zwykłym serwletom, a strony JSP wykorzystać przede wszystkim do tworzenia logiki prezentacyjnej. Nawet podczas zastosowania stron JSP jedynie do logiki prezentacyjnej, można je wykorzystać na wiele sposobów. Jednym z nich jest użycie komponentów JavaBeans osadzonych na stronie. Innym tworzenie własnych znaczników wyglądających jak HTML, ale będących tak naprawdę punktami zaczepienia do wspomagającego kodu Javy. Jeszcze innym jest wysyłanie wszystkich żądań do serwletu, który wykonuje logikę biznesową, po czym przesyła żądanie do JSP tworzącej stronę przy pomocy mechanizmu RequestDispatcher. Technika ta jest często nazywana architekturą Model 2, która to nazwa pochodzi od specyfikacji JSP 0.92. Istnieją również inne mechanizmy noszące dziwne nazwy takie jak Model 1½, Model 2½, czy Model 2+1. Niemożliwe jest wskazanie najlepszej techniki stosowania JSP. W niniejszym rozdziale opisane zostaną różne sposoby wykorzystania JSP, począwszy od „skrótu do serwletu” a skończywszy na własnych znacznikach. Opis będzie krótki, ale powinien dostarczyć podstaw do porównania JSP z jej alternatywami. Nie zostaną opisane wszystkie opcje architektury, a zamiast tego nacisk zostanie położony na szczegóły techniczne. Opcje architektury i większa ilość informacji na temat JavaServer Pages jest dostępna na stronie głównej JSP pod adresem http://java.sun.com/products/jsp („ściąga” składni jest dostępna pod adresem http://java.sun.com/products/jsp/syntax.pdf) oraz w książce „JavaServer Pages” autorstwa Hansa Bergstena (O'Reilly).
Wykorzystywanie JavaServer Pages Najbardziej podstawową funkcją JSP jest umożliwienie bezpośredniego umieszczenia kodu serwletu w statycznym pliku HTML1. Każdy blok kodu serwletu (nazywany skryptletem) jest ograniczany otwierającym znacznikiem . Wykorzystywanie skryptletu ułatwia kilka predefiniowanych zmiennych. Sześć najpopularniejszych to: HttpServletRequest request Żądanie serwletu. HttpServletResponse response Odpowiedź serwletu. javax.servlet.jsp.JspWriter out Urządzenie wyświetlające, stosowane podobnie jak PrintWriter, posiadające jednak inną charakterystykę buforowania. HttpSession session Sesja użytkownika. ServletContext application Aplikacja WWW. javax.servlet.jsp.PageContext pageContext Obiekt wykorzystywany przede wszystkim do rozdzielania implementacji serwera, ale często wykorzystywany bezpośrednio do współdzielenia zmiennych pomiędzy stronami JSP i wspomagającymi elementami i znacznikami. 1
Przed rozpoczęciem opisu warto zauważyć, że umieszczanie kodu Javy na stronie JSP jest uważane za działanie w złym stylu. Uważane za lepsze zaawansowane zastosowania JSP zostaną opisane w dalszej części rozdziału.
Można dostrzec, że klasy JSP umieszczone są w pakiecie javax.servlet.jsp. Przykład 18.1 przedstawia prostą stronę JSP wyświetlającą spersonalizowane „Witaj” przy pomocy predefiniowanych zmiennych request i out. Jeżeli posiada się serwer obsługujący JavaServer Pages i pragnie się przetestować tę stronę, należy umieścić plik w podkatalogu katalogu macierzystego dokumentów serwera i zapamiętać ją z rozszerzeniem .jsp. Zakładając, że strona została zapamiętana jako witaj1.jsp, dostęp do niej można uzyskać pod URL-em http://serwer:port/witaj1.jsp lub, jeżeli plik zostanie umieszczony w ścieżce kontekstowej jsp, URL będzie wynosił http://serwer:port/jsp/witaj1.jsp. Przykład 18.1. Powitanie przy pomocy JSP <TITLE>Witaj
Przykładowy wynik uruchomienia powyższego programu przedstawiony jest na rysunku 18.1. Rysunek 18.1. Powitanie przy pomocy JavaServer Pages
Zasady działania Jak działa JSP? Poza obszarem widoczności serwer automatycznie tworzy, kompiluje, ładuje i uruchamia specjalny serwlet tworzący zawartość strony, jak przedstawiono na rysunku 18.2. Można myśleć o tym specjalnym serwlecie jak o serwlecie roboczym, działającym w tle. Statyczne części strony HTML są tworzone przez serwlet roboczy przy pomocy ekwiwalentów wywołań wyj.println(), podczas gdy części
dynamiczne są dołączane bezpośrednio. Na przykład, serwlet przedstawiony w przykładzie 18.2 mógłby być serwletem roboczym dla witaj1.jsp działającym na serwerze Tomcat2.
Rysunek 18.2. Generowanie stron JavaServer Pages Przykład 18.2. Generowany automatycznie serwlet roboczy dla witaj1.jsp import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; import javax.servlet.jsp.tagext.*; import java.beans.*; import java.io.*; import java.util.*; import org.apache.jasper.runtime.*; import org.apache.jasper.*;
public class _0002fwitaj_00031_0002ejspwitaj1_jsp_0 extends HttpJspBase {
static { } public _0002fwitaj_00031_0002ejspwitaj1_jsp_0( ) { }
2
Osoby zainteresowane zobaczeniem prawdziwego kodu źródłowego serwletu dla strony JSP, w większości przypadków mogą odnaleźć go w tymczasowym katalogu określonym w atrybucie kontekstu javax.servlet.context.tempdir (proszę zobaczyć rozdział 4, „Pobieranie informacji”. Kiedy odnajdzie się prawdziwe źródło serwletu można zobaczyć, że jest ono o wiele bardziej skomplikowane niż to przedstawione w tym miejscu.
private static boolean _jspx_inited = false;
public final void _jspx_init() throws JasperException { }
public void _jspService(HttpServletRequest request, HttpServletResponse
response)
throws IOException, ServletException {
JspFactory _jspxFactory = null; PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; String
_value = null;
try {
if (_jspx_inited == false) { _jspx_init(); _jspx_inited = true; } _jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html;charset=ISO-8859-1"); pageContext = _jspxFactory.getPageContext(this, request, response, "", true, 8192, true);
application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut();
// HTML // begin [file="C:\\ witaj1.jsp";from=(0,0);to=(4,0)] out.write("\r\n<TITLE>Witaj\r\n\r\n\r\n"); // end // begin [file="C:\\ witaj1.jsp";from=(4,2);to=(11,0)]
if (request.getParameter("nazwa") == null) { out.println("Witaj œwiecie"); } else { out.println("Witaj, " + request.getParameter("nazwa")); } // end // HTML // begin [file="C:\\ witaj1.jsp";from=(11,2);to=(14,0)]
out.write("\r\n\r\n\r\n"); // end
} catch (Exception ex) { if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (pageContext != null) pageContext.handlePageException(ex); } finally { if (out != null) out.flush(); if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext); } } }
Kiedy wyświetla się stronę JSP po raz pierwszy można zauważyć, że jej wywołanie zabiera pewną ilość czasu. Jest to czas potrzebny serwerowi na stworzenie i skompilowanie serwletu roboczego, co zostało nazwane karą pierwszej osoby. Następne żądania powinny następować ze zwykłą szybkością, ponieważ serwlet może ponownie wykorzystać serwlet w pamięci. Jedynym wyjątkiem jest zmiana pliku .jsp, co serwer rozpoznaje podczas wykonywania następnego żądania tej strony, po czym rekompiluje serwlet działający w tle. Jeżeli kiedykolwiek wystąpi błąd kompilacji, można spodziewać się, że serwer zakomunikuje go w pewien sposób, zazwyczaj poprzez stronę zwracaną klientowi i/lub dziennik zdarzeń serwera.
Wyrażenia i deklaracje Oprócz skryptletów, JavaServer Pages pozwala na umieszczanie kodu na stronie przy pomocy wyrażeń i deklaracji. Wyrażenie JSP rozpoczyna się znacznikiem . Każde wyrażenie Javy pomiędzy tymi dwoma znacznikami jest oceniane, jego wynik konwertowany do łańcucha String, a tekst dołączany bezpośrednio do strony. Technika ta eliminuje nieporządek wywołania wyj.println(). Na przykład, dołącza wartość zmiennej buu. Deklaracja rozpoczyna się od . Pomiędzy tymi znacznikami można umieścić dowolny kod serwletu, który powinien pozostać poza metodą usługową serwletu. Zadeklarować można zmienne statyczne lub egzemplarzowe, albo też zdefiniować nowe metody. Przykład 18-3 przedstawia stronę JSP, która wykorzystuje deklarację do zdefiniowania metody pobierzNazwa() oraz wyrażenie ją wyświetlające. Komentarz na początku pliku pokazuje, że komentarze JSP oznaczane są znacznikami . Przykład 18.3. Powitanie przy pomocy deklaracji JSP <TITLE>Witaj Witaj,
Powyższy kod JSP zachowuje się identycznie jak witaj1.jsp. Specjalne metody jspInit() i jspDestroy() mogą zostać zaimplementowane wewnątrz deklaracji. Są one wywoływane przez metody init() i destroy() serwletu działającego w tle i dają stronie JSP możliwość zadeklarowania kodu, który powinien zostać wykonany podczas inicjacji i niszczenia. Strona JSP nie może omijać standardowych metod init() i destroy(), ponieważ metody te są ostatecznie deklarowane przez serwlet roboczy.
Instrukcje Instrukcja JSP pozwala stronie JSP na kontrolowanie pewnych aspektów jej serwletu roboczego. Instrukcje mogą zostać wykorzystane do nakazania serwletowi roboczemu ustawienia jego typu zawartości, importowania pakietu, kontroli buforowania wyświetlania i zarządzania sesją, rozszerzania różnych superklas oraz specjalnej obsługi błędów. Instrukcja może nawet określać wykorzystanie języka skryptowego innego niż Java. Składnia instrukcji musi zawierać jej nazwę oraz parę nazwa-wartość atrybutu, a całość musi być zwarta pomiędzy znacznikami . Cudzysłowy zamykające wartość atrybutu są obowiązkowe:
Instrukcja page pozwala JSP na kontrolowanie generowanego serwletu poprzez ustawienie specjalnych atrybutów wymienionych poniżej: contentType Określa typ zawartości generowanej strony. Na przykład:
Domyślnym typem zawartości jest text/plain; charset=8859_1. import Określa listę klas i pakietów, które tworzony serwlet powinien importować. Większa ilość klas może zostać określona w formie listy rozdzielonej przecinkami. Na przykład:
Domyślna, ukryta i zawsze dostępna lista klas to java.lang.*,javax.servlet.*,javax.servlet.http.*,javax.servlet.jsp.*. buffer Określa minimalną wymaganą wielkość bufora odpowiedzi w kilobajtach, podobnie do metody serwletu setBufferSize(). Wartość powinna zostać zapisana w formie ##kb. Specjalna wartość none wskazuje, że zawartość powinna zostać przekazana bezpośrednio do odpowiedniego PrintWriter w ServletResponse (który może, ale nie musi przekazać zawartość klientowi). Na przykład:
Domyślna wartość wynosi 8kb. autoFlush Określa, czy bufor powinien być opróżniany, kiedy jest pełny, czy zamiast tego powinien zostać wywołany wyjątek IOException. Wartość true wskazuje na opróżnianie, false na wyjątek. Na przykład:
Wartość domyślna wynosi true. session Wskazuje, że strona pragnie posiadać dostęp do sesji użytkownika. Wartość true umieszcza w zakresie zmienną session i może ustawić cookie klienta zarządzające sesją. Na przykład:
Wartość domyślna wynosi true. errorPage Określa stronę, którą należy wyświetlić, kiedy na stronie wystąpi Throwable, które nie zostanie przechwycone przed dotarciem do serwera. Okazuje się to przydatne, ponieważ nie jest trudno wykonać operacje try i catch na blokach podczas tworzenia stron JSP. Na przykład:
Domyślne zachowanie zależy od implementacji. Ścieżka jest względna wobec kontekstu, tak więc nie trzeba się przejmować dodawaniem właściwej ścieżki kontekstu. Celem może być JSP, ale nie jest to konieczne. Jeżeli cel jest serwletem, może on pobrać Throwable jako atrybut kontekstu javax.servlet.jsp.jspException. isErrorPage Wskazuje, że strona została zaprojektowana jako cel errorPage. Jeżeli wartość wynosi true, strona może uzyskać dostęp do ukrytej zmiennej o nazwie exception w celu odczytania Throwable. language Określa język skryptowy wykorzystany w częściach strony zawierających kod. Zastosowany język musi na tyle dobrze współpracować z Javą, aby uwidocznić potrzebne obiekty Javy środowisku skryptu. Na przykład:
Domyślna wartość wynosi java, jest to jedyny język rekomendowany przez specyfikację.
Wykorzystanie instrukcji Przykład 18.4 przedstawia stronę JSP noszącą nazwę bladRob.jsp, która wykorzystuje kilka instrukcji. Po pierwsze ustawia atrybut session dyrektywy page na false, ponieważ strona nie wykorzystuje obiektu sesji i w związku z tym nie istnieje potrzeba, by serwer tworzył sesję. Następnie ustawia atrybut errorPage na /bladBierz.jsp, tak więc jeżeli na stronie wydarzy się niewyłapany wyjątek, strona bladBierz.jsp będzie obsługiwać wyświetlanie komunikatu o błędzie. Główna część strony jest prosta. Wyzwala ona wyjątek w celu wywołania zachowania errorPage. (Powód wykorzystania sprawdzenia if zostanie opisany później.) Przykład 18.4. Zły chłopiec
0) { throw new Exception("uups"); } %>
Strona bladBierz.jsp jest przedstawiona w przykładzie 18.5. Ustawia ona instrukcję strony isErrorPage na true w celu wskazania, że ta strona obsługuje błędy, oraz wykorzystuje atrybut import w celu zaimportowania klas com.oreilly.servlet. JSP wykorzystuje skryptlet do ustawienia kodu stanu odpowiedzi na 500, po czym tworzy zawartość strony przy pomocy nazwy klasy wyjątku, jego ścieżkę oraz wiadomość sugerującą kontakt z administratorem WWW w celu zgłoszenia problemu. Przykład 18.5. A oto i problem...
<TITLE>Błąd:
Wystąpił błąd podczas tworzenia oryginalnego WWW
Proszę skontaktować się z webmaster@ w celu zgłoszenia błędu.
Podczas każdego żądania bladRob.jsp wywoływany jest wyjątek, kontrola jest przekazywana do bladBierz.jsp, który wyświetla elegancką stronę informującą o błędzie. Proszę zobaczyć rysunek 18.3.
Rysunek 18.3. Własna strona błędów
Tak więc dlaczego w bladRob.jsp występuje wywołanie System.currentTime Millis()? Bierze się to z faktu, że strony JSP muszą przechowywać wszystkie puste miejsca z tekstu dokumentu. Pozwala to na wykorzystanie JSP do tworzenia nie tylko HTML, ale także XML, w którym puste miejsca mogą być niezwykle ważne. Niestety zachowanie wszystkich pustych miejsc oznacza, że prosty skryptlet:
musi wygenerować kod w celu wyświetlenia nowej linii po wywołaniu błędu — kod, który nigdy nie zostanie osiągnięty! Kompilacja tego skryptu na Tomcat-cie, generuje następujący komunikat o błędzie. org.apache.jasper.JasperException: unable to compile class for JSPC: \engines\jakarta-tomcat\work\localhost_8080%2Fjsp\ -0001fbladRob_0002ejspbladRob-jsp-3.java:75: Statement not reached. out.write("\r\n"); ^
Poprzez dodanie sprawdzenia, czy System.currentTimeMillis() jest większe niż zero, kompilator przyjmuje, że istnieje możliwość, że wyjątek nie zostanie wywołany i w związku z tym nie wystąpi błąd kompilacji. Podobne problemy mogą pojawiać się w przypadku wyrażeń return i throw wewnątrz skryptletów. Tak naprawdę istnieje wiele sztuczek powiązanych z wykorzystaniem skryptletów w JSP. Jeżeli przypadkowo napisze się skryptlet zamiast wyrażenia (poprzez pominięcie znaku równości), zadeklaruje statyczną zmienną wewnątrz skryptletu (gdzie zmienne te nie są dozwolone), zapomni przecinka (nie są konieczne w wyrażeniach, ale konieczne w skryptletach) lub napisze się cokolwiek nie będącego idealnym kodem Javy, przypuszczalnie otrzyma się dziwny komunikat o błędzie, ponieważ kompilator działa na wygenerowanym kodzie Javy, nie na pliku JSP. Aby zademonstrować problem, proszę wyobrazić sobie, że w pliku bladBierz.jsp zostało zastąpione przez . Tomcat wygeneruje wtedy następujący błąd: org.apache.jasper.JasperException: unable to compile class for JSPC: \engines\jakarta-tomcat\work\localhost_8080%2Fjsp\ -0001fbladRob_0002ejspbladRob-jsp-3.java:91: Class name not found. name ^
Usunięcie błędu podobnego do przedstawionego powyżej często wymaga od programisty przyjrzenia się generowanemu kodowi w celu rekonstrukcji powodu błędu.
Unikanie kodu Javy na stronach JSP Skryptlet, wyrażenia i deklaracje umożliwiają umieszczanie kodu Javy na stronie JSP. Są to narzędzia o dużej mocy, jednak ich wykorzystywanie nie jest polecane. Nawet w rękach doświadczonego programisty umieszczanie kodu na stronie jest programowaniem „nieświeżym”. Tworzy się kod Javy, ale nie bezpośrednio. W przypadku wystąpienia błędu ciężko jest go zdiagnozować z powodu dodatkowej niejasności. Sytuacja staje się jeszcze gorsza, kiedy mechanizm ten wykorzystuje nowicjusz. Osoby nie będące programistami muszą nauczyć się Javy chociaż w pewnym stopniu, a Java nie jest zaprojektowana jako język skryptowy, nie jest też przeznaczona dla takich osób. Język taki jak JavaScipt — prostszy mniej ścisły i lepiej znany przez projektantów WWW — mógłby być lepszym rozwiązaniem, ale obsługa JavaScriptu nie jest czynnością standardową i w związku z tym jej implementacja nie jest popularna. Ostatnią kwestią jest projekt. Jeżeli projektanci i programiści pracują nad tym samym plikiem, tworzy się wąskie gardło i dodatkowa możliwość wystąpienia błędu. Zawartość i grafika powinny być od siebie oddzielone. Aby to umożliwić i usunąć kod Javy ze strony, Java pozwala na zastosowanie JavaBeans i bibliotek własnych znaczników. Poniżej opisane zostaną JavaBeans.
JSP i JavaBeans Jedną z najbardziej interesujących i potężnych metod wykorzystania JavaServer Pages jest współpraca z komponentami JavaBeans. JavaBeans to klasy Javy zaprojektowane do wielokrotnego wykorzystania, a nazwy ich metod i zmiennych tworzone są według pewnej konwencji, która daje im dodatkowe możliwości. Mogą one być osadzane bezpośrednio na stronie Javy przy pomocy znacznika <jsp:useBean>, czyli działania nazywanego przez JSP akcją. Komponent JavaBean może wykonywać dobrze zdefiniowane zadanie (zapytanie bazy danych, łączenie się z serwerem poczty, utrzymywanie informacji na temat klienta itp.), którego wynik jest dostępny stronie JSP poprzez proste metody dostępu. Większa ilość informacji na temat JavaBeans znajduje się pod adresem http://java.sun.com/bean i w książce „Developing JavaBeans” autorstwa Roberta Englandera (O'Reilly). Różnica pomiędzy komponentem JavaBeans osadzonym na stronie JSP i każdą inną dodatkową klasą wykorzystywaną przez wygenerowany serwlet jest taka, że serwer WWW może traktować JavaBeans osadzone na stronie w specjalny sposób. Na przykład, serwer może automatycznie skonfigurować komponent (zwłaszcza zmienne egzemplarza) przy pomocy wartości parametrów w żądaniu klienta. Innymi słowy, jeżeli żądanie zawiera parametr nazwa, a serwer odkryje poprzez introspekcję (technikę, w której metody i zmienne klasy Javy mogą zostać określone programowo w czasie uruchomienia), że komponent posiada parametr nazwa i metodę ustawNazwe(String nazwa), serwer może automatycznie wywołać pobierzNazwe() z wartością parametru nazwa. Nie jest konieczne wykorzystanie metody getParameter(). Serwer może także automatycznie zarządzać zakresem komponentu. Komponent może zostać przypisany do konkretnej strony (gdzie jest wykorzystany raz i zniszczony), konkretnego żądania (podobne do zakresu strony poza tym, że komponent może przetrwać wewnętrzne wywołania dołączenia i przekazania), sesji klienta (w którym komponent jest automatycznie udostępniany, kiedy tan sam klient połączy się ponownie) lub aplikacji (w którym to zakresie ten sam komponent jest udostępniany całej aplikacji WWW).
Osadzanie komponentu Bean Komponenty są osadzane na stronie JSP przy pomocy akcji <jsp:useBean>. Posiada ona następującą składnię (zależną od wielkości liter, cudzysłowy obowiązkowe): <jsp:useBean> id="nazwa" scope="page|request|session|application" type "nazwaTypu">
Ustawione mogą być następujące atrybuty akcji <jsp:useBean>:
id Określa nazwę komponentu. Jest to klucz pod którym komponent jest przechowywany, jeżeli jego zakres rozciąga się poza stronę. Jeżeli egzemplarz komponentu zapamiętany pod podaną nazwą już istnieje w zakresie strony, egzemplarz ten zostaje wykorzystany na stronie. W pozostałych przypadkach tworzony jest nowy komponent. Na przykład: id="preferencjeUzyt"
scope Określa zakres widoczności komponentu. Wartość musi wynosi page, request, session lub application. Jeżeli page, zmienna jest tworzona jako zmienna egzemplarza. Jeżeli request, zmienna jest przechowywana jako atrybut żądania; jeżeli session, komponent przechowywany jest w sesji użytkownika, a jeżeli application, w kontekście serwletu. Na przykład: scope="session"
Domyślną wartością jest page. class Określa nazwę klasy komponentu. Jest ona wykorzystywana przy pierwszym tworzeniu komponentu. Nazwa klasy musi być podana w pełnej formie. Na przykład:
Atrybut class nie jest potrzebny, jeżeli komponent istnieje już w aktualnym zakresie, ale ciągle musi wystąpić atrybut type pozwalający na stworzeni obiektu odpowiedniego typu. Jeżeli wystąpi problem ze stworzeniem danej klasy, strona JSP zgłasza wyjątek InstatiationException. type Określa typ komponentu, który powinien zostać pobrany z systemu, wykorzystywany do utworzenia obiektu po pobraniu z żądania, sesji lub kontekstu. Wartość typu powinna zostać podana w pełnej formie superklasy lub interfejsu dla klasy aktualnej. Na przykład: type="com.firma.sledzenie.PreferencjeUzyt"
Jeżeli typ nie zostanie określony, domyślna wartość jest równa atrybutowi class. Jeżeli typ nie pasuje do prawdziwego typu obiektu, strona JSP zgłasza wyjątek ClassCastException. beanName Atrybut class może zostać zastąpiony atrybutem beanName. Różnica między nimi jest taka, że beanName wykorzystuje Beans.instatiate() do utworzenia egzemplarza komponentu, który poszukuje zserializowanej wersji komponentu (pliku .ser) przed utworzeniem egzemplarza od zera. Pozwala to na wykorzystanie komponentów prekonfigurowanych. Proszę przejrzeć dokumentację Javadoc dla Beans.instatiate() w celu uzyskania większej ilości informacji. Na przykład: BeanName="com.firma.sledzenie.PreferencjeUzytImpl"
Główna część elementu <jsp:useBean> — wszystkie informacje pomiędzy znacznikiem otwierającym <jsp:useBean> a zamykającym — jest interpretowane po utworzeniu komponentu. Jeżeli komponent nie zostanie utworzony (ponieważ w danym zakresie odnaleziono istniejący egzemplarz), wtedy część ta jest ignorowana. Na przykład: <jsp:useBean id="preferencje" class=com.firma.sledzenie.PreferencjeUzytImpl"> Jeżeli ten napis jest widoczny, to utworzono nowy komponent! <jsp:useBean>
Jeżeli nie jest konieczne umieszczenie głównej części, może zostać wykorzystany skrót XML dla pustego znacznika />: <jsp:useBean id="preferencje" class=com.firma.sledzenie.PreferencjeUzytImpl" />
Kontrola parametrów komponentu Akcja <jsp:setProperty> umożliwia parametrom żądania automatyczne (poprzez introspekcję) ustawianie właściwości komponentów osadzonych na stronie. Daje to komponentom automatyczny dostęp do parametrów żądania bez konieczności wywoływania getParameter(). Własność ta może zostać wykorzystana na kilka sposobów. Po pierwsze: <jsp:setProperty name="nazwaKomponentu" property="*" />
Powyższa linia określa, że dowolny parametr żądania o tej samej nazwie i typie, co własność komponentu, powinien zostać wykorzystany do ustawienia tej własności komponentu. Na przykład, jeżeli element posiada metodę ustawWielkosc(int wielkosc) oraz żądanie posiada parametr wielkosc o wartości 12, serwer automatycznie wywoła komponent.ustawWielkosc(12) na początku obsługi żądania. Jeżeli wartość parametru nie może zostać przekonwertowana na właściwy typ, parametr jest ignorowany. (Proszę zauważyć, że pusta wartość parametru łańcuchowego jest traktowana tak, jakby parametr nie istniał, i pusta wartość łańcucha nie zostanie przypisana własności String). Proszę zauważyć, że akcja wykorzystuje skrót XML dla pustego znacznika. Jest to bardzo ważne. Specyfikacja JSP wymaga od wszystkich akcji JSP by posiadały one właściwą formę XML, nawet kiedy są umieszczone w plikach HTML. Poniżej przedstawiono drugi sposób wykorzystania <jsp:setProperty>: <jsp:setProperty name="nazwaKomponentu" property="nazwaWlasnosci" />
Powyższa linia nakazuje ustawienie danej własności, jeżeli istnieje parametr żądania o tej samej nazwie i typie. Na przykład, aby ustawić własność wielkosc, a nie żadną inną, należy wykorzystać następującą akcję: <jsp:setProperty name="preferencje" property="wielkosc" />
Oto trzeci sposób wykorzystania <jsp:setProperty>: <jsp:setProperty name="nazwaKomponentu" property="nazwaWlasnosci" param="nazwaParam" />
Powyższa linia nakazuje ustawienie danej własności, jeżeli występuje parametr żądania o danej nazwie i tym samym typie. Pozwala to parametrowi o pewnej nazwie na ustawianie własności o nazwie innej. Na przykład, aby ustawić własność wielkosc przy pomocy parametru wielkoscCzcionki: <jsp:setProperty name="preferencje" property="wielkosc" param="wielkoscCzcionki" />
Poniżej przedstawiono ostatni sposób wykorzystania: <jsp:setProperty name="nazwaKomponentu" property="nazwaWlasnosci" value="stala" />
Powyższa linia kodu nakazuje nadanie danej własności danej wartości, która zostanie przekonwertowana do właściwego typu, jeżeli okaże się to konieczne. Na przykład, aby ustawić domyślną wielkość na 18 z pominięciem parametru: <jsp:setProperty name="preferencje" property="wielkosc" value="18" /> <jsp:setProperty name="preferencje" property="wielkosc" param="wielkoscCzcionki" />
Zaawansowani użytkownicy powinni wiedzieć, że atrybut value nie musi być stałą; może on zostać określony poprzez wyrażenie, przy pomocy mechanizmu nazywanego wyrażeniem atrybutu czasu uruchomienia. Na przykład, aby upewnić się, że wielkość nigdy nie spadnie poniżej 6: <jsp:setProperty name="preferencje" property="wielkosc" param="wielkoscCzcionki"/> <jsp:setProperty name="preferencje" property="wielkosc" value="" />
Natomiast akcja <jsp:getProperty> dostarcza mechanizmu służącego do odczytywania wartości własności bez konieczności wykorzystywania kodu Javy na stronie. Jego zastosowanie jest podobne do <jsp:setProperty>: <jsp:getProperty name="nazwaKomponentu" property="nazwaWlasnosci" />
Powyższa linia nakazuje dołączenie w jej miejscu wartości danej własności do danego komponentu. Jest ona dłuższa niż wyrażenie, ale pozwala na uniknięcie umieszczania kodu Javy na stronie. Wybór pomiędzy <jsp:getProperty> a wyrażeniem jest kwestią osobistego gustu. Proszę zauważyć, że <jsp:getProperty>, bezpośrednio osadza wartość własności, tak więc jeżeli zawiera ona znaki traktowane przez HTML jako specjalne, może to powodować problemy. Projekt Apache Struts pozwala na rozwiązanie tego problemu przy pomocy własnego znacznika <property>, co zostanie opisane w dalszej części tego rozdziału.
Powitania przy pomocy komponentu Przykład 18.6 przedstawia zastosowanie komponentu JavaBeans na stronie JSP. Strona ta wyświetla „Witaj” przy pomocy WitajKomp. Przykład 18.6. Powitanie przy pomocy JavaBean
<jsp:useBean id="witaj" > <jsp:setProperty name="witaj" property="*" />
<TITLE>Witaj Witaj, <jsp:getProperty name="witaj" property="nazwa" />
Jak można dostrzec, wykorzystanie komponentu JavaBeans na stronie JavaServer Pages może zredukować ilość kodu umieszczoną na stronie. Klasa WitajKomp zawiera logikę określającą nazwę użytkownika, a strona JSP działa jedynie jako szablon. Kod WitajKomp jest przedstawiony w przykładzie 18.7. Plik jego klasy powinien zostać umieszczony w standardowym katalogu klas wspierających (WEB-INF/classes). Przykład 18.7. Klasa WitajKomp public class WitajKomp { private String nazwa = "świecie";
public void ustawNazwe(String nazwa) { this.nazwa = nazwa; }
public String pobierzNazwe() { return nazwa; } }
Jest to chyba najprostszy możliwy komponent. Posiada pojedynczą własność nazwa ustawianą przy pomocy ustawNazwe(), a odczytywaną przy pomocy pobierzNazwe(). Domyślną wartością nazwa jest świecie, ale kiedy nadchodzi żądanie zawierające parametr nazwa, własność jest automatycznie ustawiana przez serwer przy pomocy wywołania ustawNazwe(). W celu przetestowana mechanizmu, proszę obejrzeć stronę http://serwer:port/witaj3.jsp. Powinien pojawić się wynik podobny do przedstawionego na rysunku 18.4.
Rysunek 18.4. Powitanie przy pomocy JavaServer Pages we współpracy z komponentem JavaBeans
Istnieje jedna kwestia, na którą należy zwrócić uwagę: na niektórych serwerach (włączając w to Tomcat 3.2), jeżeli istnieje komponent o zakresie session lub application, a implementacja klasy komponentu zostanie zmieniona, w późniejszych żądaniach może pojawić się ClassCastException. Wyjątek ten pojawia się, ponieważ kod generowanego serwletu musi wywołać egzemplarz komponentu, który pobierany jest z sesji lub aplikacji, a typ starego komponentu przechowywany w sesji lub aplikacji nie pasuje do spodziewanego typu nowego komponentu. Najprostszym rozwiązaniem jest ponowne uruchomienie serwera.
Dołączenia i przekazania Poprzez połączenie instrukcji i akcji, JSP umożliwia obsługę dołączeń i przekazań. Istnieją dwa typy dołączeń i jedno przekazań. Pierwszym dołączeniem jest instrukcja include:
Powyższe dołączenie następuje w czasie tłumaczenia, co oznacza, że następuje podczas tworzenia serwletu działającego w tle. Cała zawartość zewnętrznego pliku jest dołączana tak, jak gdyby była bezpośrednio umieszczona na stronie JSP. Programiści C mogą dostrzec jej bliskie podobieństwo do #include. Ścieżka do pliku jest zakotwiczona w katalogu macierzystym kontekstu (tak więc /indeks.jsp odnosi się do index.jsp aktualnej aplikacji WWW), a ścieżka nie może sięgać poza katalog macierzysty kontekstu (proszę nie próbować ../../../innyKontekst). Zawartość wewnątrz pliku jest zazwyczaj fragmentem strony, nie całą stroną, tak więc można pomyśleć o zastosowaniu rozszerzenia pliku .inc lub .jin w celu wskazania tego faktu. Poniższa strona JSP wykorzystuje kilka instrukcji dołączenia w celu utworzenia strony ze zbioru elementów (elementy nie są pokazane): Witaj,
Skąd pochodzi zmienna nazwa? Jest ona tworzona przez plik obliczNazwe.inc. Ponieważ zawartość dołączanego pliku jest dołączana bezpośrednio, nie występuje oddzielenie zakresu zmiennej. Drugim typem dołączenia jest akcja <jsp:include>: <jsp:include page="sciezkaDoZasobuDynamicznego" flush="true" />
Powyższe dołączenie następuje w czasie żądania. Nie jest to surowe dołączenie takie, jak w instrukcji include; zamiast tego serwer uruchamia określony zasób dynamiczny i dołącza jego wynik do zawartości wysyłanej do klienta. Niewidocznie akcja <jsp:include> generuje kod, który wywołuje metodę include() RequestDispatcher, tak więc do <jsp:include> stosują się te same zasady wykonania, co do include() — dołączana strona musi być dynamiczna, nie może ustawiać kodu stanu, nie może ustawiać żadnych nagłówków, a ścieżka strony jest zakotwiczona w katalogu macierzystym kontekstu3. Wymagany jest atrybut flush, który w JSP 1.1 musi być ustawiony na true. Wskazuje to na konieczność opróżnienia bufora
3
Istnieje jedna drobna różnica pomiędzy metodą include() i akcją <jsp:include> — metoda include() wymaga, żeby ścieżka do strony rozpoczynała się od /. Akcja <jsp:include> dodatkowo zezwala na względne ścieżki takie, jak ../indeks.jsp, tak długo, jak ścieżka nie wychodzi poza aktualny kontekst.
odpowiedzi przed dołączeniem. Zważywszy na możliwości Servlet API 2.2, wartość false nie może być obsługiwana; spodziewane jest, że JSP 1.2 utworzone na podstawie Servlet API 2.3 będzie zezwalać na wartość false. Jako przykład, poniższa akcja <jsp:include> dołącza zawartość generowaną przez wywołanie strony powitanie.jsp. Strona powitanie.jsp może wyglądać podobnie do witaj1.jsp poza tym, że pożądane może być usunięcie otaczającego kodu HTML sprawiającego, że witaj1.jsp jest kompletną stroną HTML. <jsp:include page="/powitanie.jsp" />
Akcja <jsp:include> może opcjonalnie zawierać w swojej głównej części dowolną ilość znaczników <jsp:param>. Znaczniki te dodają parametry do dołączanego żądania. Na przykład, poniższa akcja przekazuje powitaniu domyślną nazwę: <jsp:include page="/powitanie.jsp"> <jsp:param name="domyslnaNazwa" value="Nowy użytkownik" />
JSP może także przekazywać żądanie przy pomocy akcji <jsp:forward>: <jsp:forward page="sciezkaDoZasobuDynamicznego" />
Przekazanie powoduje przejęcie kontroli obsługi żądania przez konkretny zasób. Tak jak w przypadku akcji <jsp:include> jest ono wykonywane w czasie żądania i jest oparte na metodzie forward() RequestDispatcher. Stosowane są zasady wykorzystania forward() — w czasie przekazania cały zbuforowany wynik zostaje wyczyszczony, a jeżeli stało się to wcześniej, system zgłasza wyjątek. Poniższa akcja przekazuje żądanie specjalnej stronie, jeżeli użytkownik nie jest zalogowany: <jsp:forward page="/logowanie.jsp" />
Akcja <jsp:forward> również przyjmuje znaczniki <jsp:param>.
Aplikacja „Narzędzia” W tym momencie, znając już zasady dołączeń i osadzania JavaBeans na stronach JSP, można wykorzystać te własności do stworzenia aplikacji wyświetlającej narzędzia. Na stronie JSP zostanie osadzony komponent dający stronie dostęp do informacji na temat narzędzi i pozwalający na traktowanie strony jako przeglądarki tych danych. Przykład 18.8 przedstawia stronę JSP o nazwie widoknarz.jsp. Przykład 18.8. Aplikacja przeglądająca narzędzia przy pomocy JSP
Bez narzędzi, ludzie nie są niczym więcej niż zwierzętami. " + I to dość słabymi. Poniżej przedstawiono listę opartych na " + serwletach narzędzi do tworzenia zawartości, które można " + wykorzystać w celu wzmocnienia się.";
%>
<jsp:useBean id="narzedzia" scope="request"/>
<struts:iterate id="narzedzie" collection="">
<struts:property name="narzedzie" property="nazwa" />
(Nowość!) (Uaktualnienie!)
<struts:property name="narzedzie" property="domURL"/>
<struts:property name="narzedzie" property="komentarz" />
Powyższa strona po pierwsze wykorzystuje instrukcję taglib do pobrania biblioteki własnych znaczników Struts. Nie wolno zapomnieć o tym kroku! Jeżeli krok ten zostanie pominięty, nie będzie wyświetlony bezpośredni komunikat o błędzie, ale strona nie będzie działać. Powodem tego jest fakt, że bez instrukcji taglib znaczniki <struts:iterate> i <struts:property> serwer traktuje jak wszystkie inne znaczniki HTML i w związku z tym jako zawartość do wyświetlenia, a nie logikę strony do wykonania! Główna część strony wykorzystuje znacznik <struts:iterate> w celu wykonania pętli nad tablicą narzędzi oraz znacznik <struts:property> w celu wyświetlenia nazwy, URL-a i komentarza dla każdego narzędzia. Jeżeli komentarze mogą zawierać kod HTML, właściwym znacznikiem byłby <jsp:getProperty> przeciwdziałający przefiltrowaniu zawartości HTML. Wykorzystanie popularnych znaczników Apache Struts usuwa dużą ilość kodu ze strony HTML, pozostaje jednak kilka skryptletów. Usunięcie wszystkich skryptletów wiązałoby się z koniecznością utworzenia własnego znacznika wykonującego porównanie znaczników czasu. Niestety, z powodu skomplikowania procesu tworzenia własnych znaczników, samouczek nie został umieszczony w tej książce. Osoby zainteresowane tworzeniem własnych znaczników powinny przejrzeć książkę „JavaServer Pages” autorstwa Hansa Bergstena (O'Reilly).
W niniejszym rozdziale: •
Analiza parametrów
•
Wysyłanie poczty elektronicznej
•
Stosowanie wyrażeń regularnych
•
Uruchamianie programów
•
Stosowanie rdzennych metod
•
Występowanie jako klient RMI
•
Usuwanie błędów
•
Poprawa wydajności
Rozdział 19.
Informacje dodatkowe W każdym domu znajduje się szuflada na szpargały — szuflada załadowana do pełna rzeczami, które nie do końca pasują do żadnej zorganizowanej szuflady, ale nie mogą też zostać wyrzucone, ponieważ kiedy są potrzebne, to są naprawdę potrzebne. Niniejszy rozdział pełni funkcję takiej szuflady. Zawiera spory zestaw przydatnych przykładów serwletów i porad, które nie pasują do żadnego innego miejsca. Zawarto tu serwlety, które analizują parametry, wysyłają pocztę elektroniczną, uruchamiają programy, wykorzystują mechanizmy wyrażeń regularnych i rdzenne metody oraz działają jako klienty RMI. Rozdział ten zawiera również demonstrację technik usuwania błędów, a także pewne sugestie dotyczące poprawy wydajności serwletów.
Analiza parametrów Osoby, które próbowały tworzyć własne serwlety podczas lektury niniejszej książki przypuszczalnie zauważyły, że pobieranie i analiza parametrów żądania może być dość nieprzyjemnym zajęciem, zwłaszcza jeżeli parametry te muszą zostać przekonwertowane do formatu innego niż String. Na przykład, konieczne jest odczytanie parametru count i zmiany jego wartości na format int. Poza tym, obsługa warunków błędów powinna być wykonywana przez wywołanie obslugaBezCount(), jeżeli count nie jest podany, a obslugaZlyCount(), jeżeli count nie może zostać przekształcony na liczbę całkowitą. Wykonanie tego działania przy pomocy standardowego Servlet API wymaga następującego kodu: int count;
String param = zad.getParameter("count"); if (param=null || param.length() == 0) { obslugaBezCount(); } else { try { count = Integer.parseInt(param); } catch (NumberFormatException w) { obslugaZlyCount(); } }
Czy to wygląda jak porządny kod? Nie jest zbyt piękne, prawda? Lepszym rozwiązaniem jest przekazanie odpowiedzialności za pobieranie i analizę parametrów klasie narzędziowej. Klasa com.oreilly.servlet.ParameterParser jest właśnie taką klasą. Przy pomocy ParameterParser możliwe jest przepisanie powyższego kodu w bardziej elegancki sposób: int count;
ParameterParser analiza = new ParameterParser(zad); try { count = analiza.getIntParameter("count"); } catch (NumberFormatException w) { obslugaZlyCount(); } catch (ParameterNotFoundException w) { obslugaBezCount(); }
Analizująca parametry metoda getIntParameter() zwraca wartość określonego parametru jako int. Zgłasza wyjątek NumberFormatException, jeżeli parametr nie może zostać przekonwertowany na int oraz ParameterNotFoundException, jeżeli parametr nie jest częścią żądania. Zgłasza również ParameterNotFoundException, jeżeli wartość parametru to pusty łańcuch. Często dzieje się tak w przypadku wysyłanych formularzy, gdy nie wpisano żadnych wiadomości do pól tekstowych, co we wszystkich przypadkach powinno być traktowane w ten sam sposób, co brakujący parametr. Jeżeli wystarczy, aby w przypadku problemów z parametrem serwlet wykorzystywał domyślną wartość, co jest częstą praktyką, kod może zostać uproszczony w jeszcze większym stopniu: ParameterParser analiza = new ParameterParser(zad); int count = analiza.getIntParameter("count", 0);
Powyższa druga wersja getIntParameter() pobiera domyślną wartość 0, która jest zwracana zamiast zgłoszenia błędu. Istnieje również możliwość sprawdzenia, czy w żądaniu brakuje jakichkolwiek parametrów: ParameterParser analiza = new ParameterParser(zad); String[] wymagane = { "nazwa1", "nazwa2", "konto" }; String[] brakujace = analiza.getMissingParameters(wymagane);
Powyższa metoda zwraca null, jeżeli nie brakuje żadnego parametru. ParameterParser obsługuje także internacjonalizację poprzez metodę setCharacterEncoding(). Określa ona kodowanie, które powinno zostać wykorzystane podczas interpretacji wartości parametrów. Wartość może pochodzić z cookie użytkownika, ukrytego pola formularza lub sesji użytkownika:
ParameterParser analiza = new ParameterParser(zad); analiza.setCharacterEncoding("Shift_JIS"); String wartoscJaponia = analiza.getStringParameter("nazwaLatin");
Wewnętrznie ParameterParser wykorzystuje sztuczkę getBytes() przedstawioną w rozdziale 13, „Internacjonalizacja” do obsługi konwersji. Nazwy parametrów muszą być ciągle podane w kodowaniu Latin1, ponieważ mechanizm poszukiwania wykorzystuje jeszcze nie zinternalizowane metody Servlet API getParameter() i getParameterValues().
Kod ParameterParser Klasa ParameterParser zawiera ponad tuzin metod, które zwracają parametry żądania — dwie dla każdego rdzennego typu Javy. Posiada również dwie metody getStringParameter() w przypadku, gdyby konieczne było pobranie parametru w jego surowym formacie String. Kod ParameterParser jest przedstawiony w przykładzie 19.1. Wyjątek ParameterNotFoundExceptoion znajduje się w przykładzie 19.2. Przykład 19.1. Klasa ParameterParser package com.oreilly.servlet;
import java.io.*; import java.util.*; import javax.servlet.*;
public class ParameterParser {
private ServletRequest req; private String encoding;
public ParameterParser(ServletRequest req) { this.req = req; }
public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { // Sprawdzenie prawidłowości kodowania new String("".getBytes("8859_1"), encoding); // Jeżeli tutaj, to prawidłowe, więc jego ustawienie this.encoding = encoding; }
public String getStringParameter(String name) throws ParameterNotFoundException { String[] values = req.getParameterValues(name); if (values == null) { throw new ParameterNotFoundException(name + " not found"); }
else if (values[0].length() == 0) { throw new ParameterNotFoundException(name + " was empty"); } else { if (encoding == null) { return values[0]; } else { try { return new String(values[0].getBytes("8859_1"), encoding); } catch (UnsupportedEncodingException e) { return values[0];
// should never happen
} } } }
public String getStringParameter(String name, String def) { try { return getStringParameter(name); } catch (Exception e) { return def; } }
public boolean getBooleanParameter(String name) throws ParameterNotFoundException, NumberFormatException { String value = getStringParameter(name).toLowerCase(); if ((value.equalsIgnoreCase("true")) || (value.equalsIgnoreCase("on")) || (value.equalsIgnoreCase("yes"))) { return true; } else if ((value.equalsIgnoreCase("false")) || (value.equalsIgnoreCase("off")) || (value.equalsIgnoreCase("no"))) { return false; } else { throw new NumberFormatException("Parameter " + name + " value " + value + " is not a boolean"); } }
public boolean getBooleanParameter(String name, boolean def) { try { return getBooleanParameter(name); } catch (Exception e) { return def; } }
public byte getByteParameter(String name) throws ParameterNotFoundException, NumberFormatException { return Byte.parseByte(getStringParameter(name)); }
public byte getByteParameter(String name, byte def) { try { return getByteParameter(name); } catch (Exception e) { return def; } }
public char getCharParameter(String name) throws ParameterNotFoundException { String param = getStringParameter(name); if (param.length() == 0) throw new ParameterNotFoundException(name + " is empty string"); else return (param.charAt(0)); }
public char getCharParameter(String name, char def) { try { return getCharParameter(name); } catch (Exception e) { return def; } }
public double getDoubleParameter(String name) throws ParameterNotFoundException, NumberFormatException { return new Double(getStringParameter(name)).doubleValue(); }
public double getDoubleParameter(String name, double def) { try { return getDoubleParameter(name); } catch (Exception e) { return def; } }
public float getFloatParameter(String name) throws ParameterNotFoundException, NumberFormatException { return new Float(getStringParameter(name)).floatValue(); }
public float getFloatParameter(String name, float def) { try { return getFloatParameter(name); } catch (Exception e) { return def; } }
public int getIntParameter(String name) throws ParameterNotFoundException, NumberFormatException { return Integer.parseInt(getStringParameter(name)); }
public int getIntParameter(String name, int def) { try { return getIntParameter(name); } catch (Exception e) { return def; } }
public long getLongParameter(String name) throws ParameterNotFoundException, NumberFormatException { return Long.parseLong(getStringParameter(name)); }
public long getLongParameter(String name, long def) { try { return getLongParameter(name); } catch (Exception e) { return def; } }
public short getShortParameter(String name) throws ParameterNotFoundException, NumberFormatException { return Short.parseShort(getStringParameter(name)); }
public short getShortParameter(String name, short def) { try { return getShortParameter(name); } catch (Exception e) { return def; } }
public String[] getMissingParameters(String[] required) { Vector missing = new Vector(); for (int i = 0; i < required.length; i++) { String val = getStringParameter(required[i], null); if (val == null) { missing.addElement(required[i]); } } if (missing.size() == 0) { return null; } else { String[] ret = new String[missing.size()]; missing.copyInto(ret); return ret;
} } }
Przykład 19.2. Klasa ParameterNotFoundException package com.oreilly.servlet;
public class ParameterNotFoundException extends Exception {
public ParameterNotFoundException() { super(); }
public ParameterNotFoundException(String s) { super(s); } }
Wysyłanie poczty elektronicznej Czasami wysłanie przez serwlet wiadomości jest konieczne, a czasami jest to po prostu ułatwienie. Na przykład, proszę sobie wyobrazić serwlet otrzymujący dane z formularza użytkownika, który umieszcza tam swoje komentarze. Serwlet ten może chcieć przesłać dane formularza na listę dystrybucyjną zainteresowanych stron. Albo proszę sobie wyobrazić serwlet, który napotyka niespodziewany problem i może wysłać administratorowi pocztą stronę prosząc o pomoc. Serwlet może wykorzystać do wysłania poczty elektronicznej jedną z czterech metod: •
Może sam zarządzać szczegółami — nawiązać połączenie przez zwykły port z serwerem poczty i wysyłając wiadomość poprzez protokół pocztowy niskiego poziomu, zazwyczaj tak zwany Simple Mail Transfer Protocol (Prosty Protokół Transferu Poczty — SMTP).
•
Może uruchomić z wiersza poleceń zewnętrzny program pocztowy, jeżeli system serwera posiada taki program.
•
Może wykorzystać interfejs JavaMail, zaprojektowany do pomocy w skomplikowanej obsłudze, tworzeniu i przetwarzaniu poczty (proszę zobaczyć http://java.sun.com/products/javamail).
•
Może wykorzystać jedną z wielu dostępnych bezpłatnie klas pocztowych, które zmieniają szczegóły wysyłania poczty na proste i łatwe do wywołania metody.
W przypadku nieskomplikowanego wysyłania poczty poleca się ostatni sposób ze względu na jego prostotę. Dla bardziej skomplikowanych zastosowań rekomendowany jest JavaMail — zwłaszcza w przypadku serwletów działających pod kontrolą serwera J2EE, w którym na pewno dostępne są dwa pliki JAR wymagane przez JavaMail.
Stosowanie klasy MailMessage Dla celów tego przykładu zademonstrowany zostanie serwlet wykorzystujący klasę com.oreilly.servlet.MailMessage, która nie zostanie tu przedstawiona, ale jest dostępna pod adresem http://www.servlets.com. Została ona utworzona na podstawie klasy sun.net.smtp.SmtpClient zawartej w JDK firmy Sun, ale unika politycznego problemu wykorzystywania „niewspieranej i będącej celem zmian” klasy sun.*. Posiada ona również kilka przyjemnych usprawnień funkcjonalności. Jej stosowanie jest proste:
1.
Wywołanie MailMessage wiad = new MailMessage(). Opcjonalnie przekazanie konstruktorowi nazwy komputera, który należy wykorzystać jako serwer poczty i który zastępuje domyślny localhost. Większość komputerów pracujących pod Uniksem może działać jako serwer poczty SMTP.
2.
Wywołanie wiad.from(adresOd), określające adres nadawcy. Adres nie musi być prawidłowy.
3.
Wywołanie wiad.to(adresDo), określające adres odbiorcy. Metoda ta może zostać wywołana kilka razy, jeżeli istnieją dodatkowi odbiorcy. Występują również metody cc() i bcc().
4.
Wywołanie wiad.setSubject(temat) w celu określenia tematu. Technicznie nie jest to wymagane, ale umieszczenie tematu zawsze jest dobrym pomysłem.
5.
Wywołanie wiad.setHeader(nazwa, wartosc) jeżeli powinny zostać dołączone dodatkowe nagłówki. Nagłówki To: (Do), CC: (DW) i Subject: (Temat) nie muszą być ustawiane, ponieważ są one automatycznie określane przez metody to(), cc() i setSubject(). (Metoda bcc() oczywiście nie wysyła żadnych nagłówków.) Metodę setHeader() można wykorzystać do nadpisania wartości domyślnych. Nazwy i wartości nagłówków powinny być podporządkowane zasadom podanym w dokumencie RFC 822, dostępnym pod adresem http://www.ietf.org/rfc/rfc0822.txt.
6.
Wywołanie PrintStream wiad = wiad.getPrintStream() w celu pobrania potoku wynikowego wiadomości.
7.
Wysłanie zawartości wiadomości pocztowej do PrintStream.
8.
Wywołanie wiad.sendAndClose() w celu wysłania wiadomości i zamknięcia połączenia z serwerem.
Wysyłanie pocztą danych formularza Przykład 19.3 przedstawia serwlet wysyłający dane, które otrzymuje, pocztą na listę dystrybucyjną. Proszę zauważyć bardzo szerokie wykorzystanie klasy ParametrParser. Przykład 19.3. Wysyłanie wiadomości z serwletu. import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*;
import com.oreilly.servlet.MailMessage; import com.oreilly.servlet.ParameterParser; import com.oreilly.servlet.ServletUtils;
public class SerwletPoczta extends HttpServlet {
static final String FROM = "SerwletPoczta"; static final String TO = "
[email protected]";
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter();
ParameterParser analiza = new ParameterParser(zadanie); String from = analiza.getStringParameter("from", FROM); String to = analiza.getStringParameter("to", TO);
try { MailMessage wiad = new MailMessage();
// przyjęcie, że localhost
wiad.from(from); wiad.to(to); wiad.setSubject("Komentarz klienta");
PrintStream glowny = wiad.getPrintStream();
Enumeration enum = zad.getParameterNames(); while (enum.hasMoreElements()) { String name = (String)enum.nextElement(); if (nazwa.equals("to") || nazwa.equals("from")) continue; //Opuszczenie góra/dół String wartosc = analizator.getStringParameter(nazwa, null); glowny.println(nazwa + " = " + wartosc); }
glowny glowny.tytul2(); glowny.println("---"); body.println("Wysłany przez " + HttpUtils.getRequestURL(zad));
wiad.sendAndClose();
wyj.println("Dziękujemy za komentarz..."); } catch (IOException w) { wyj.println("Wystąpił problem z obsługą komentarza..."); log("Wystąpił problem z wysyłaniem wiadomości", e); } } }
Serwlet po pierwsze określa adresy „Od” i „Do” wiadomości. Domyślne wartości są ustawione w zmiennych FROM i TO, chociaż wysyłany formularz może zawierać (najprawdopodobniej ukryte) pola określające alternatywy adresów Od i Do. Serwlet następnie rozpoczyna tworzenie wiadomości pocztowej SMTP. Łączy się z lokalnym komputerem i adresuje wiadomość. Następnie ustawia temat i wstawia dane formularza do zawartości, ignorując zmienne FROM i TO. Na końcu wysyła wiadomość i dziękuje użytkownikowi za komentarz. Jeżeli wystąpi problem, informuje o tym użytkownika i zapisuje wyjątek w dzienniku zdarzeń. Klasa MailMessage aktualnie nie obsługuje załączników, ale ich obsługa może zostać w prosty sposób dodana. W przypadku zastosowań bardziej zaawansowanych, dobrą alternatywą jest JavaMail.
Stosowanie wyrażeń regularnych Ten podrozdział przeznaczony jest szczególnie dla osób znających oparte na CGI skrypty Perla i przyzwyczajonych do możliwości wyrażeń regularnych w Perlu. W tym rozdziale zostaną przedstawione zasady
korzystania z wyrażeń regularnych w Javie. Osobom nie znającym tematu należy się wyjaśnienie, że wyrażenia regularne są mechanizmem umożliwiającym bardzo zaawansowaną manipulację łańcuchami przy minimalnej ilości kodu. Cała moc wyrażeń regularnych jest doskonale przedstawiona w książce „Mastering Reular Expressions” autorstwa Jefreya E. F. Friedla (O'Reilly). Pomimo dużej ilości klas i możliwości dodawanych przez firmę Sun do JDK przez wiele lat, ciągle brakuje w nim wyrażeń regularnych. Nie należy się tym jednak martwić. Podobnie jak w przypadku większości funkcji Javy, jeżeli nie dostarcza ich Sun, można je uzyskać za rozsądną cenę od innego niezależnego producenta, a jeżeli jest to coś tak przydatnego, jak wyrażenia regularne, istnieje duża szansa, że dostępna jest odpowiednia biblioteka Open Source. Tak jest również w tym przypadku — istnieje mechanizm wyrażeń regularnych Open Source dostępny jako część Apache Jakarta Project, stworzony początkowo przez Jonathana Locke'a i dostępny w licencji Apache. Licencja Apache jest dużo bardziej liberalna niż Ogólna Licencja Publiczna GNU (GPL), ponieważ pozwala programistom na wykorzystanie mechanizmu wyrażeń regularnych przy tworzeniu nowych produktów bez konieczności udostępniania tych produktów jako Open Source.
Odnajdywanie łącz przy pomocy wyrażeń regularnych W celu zademonstrowania zastosowań wyrażeń regularnych, wykorzystany zostanie mechanizm wyrażeń regularnych Apache w celu utworzenia serwletu, który pobiera i wyświetla listę wszystkich łącz HTML odnalezionych na stronie WWW. Jego kod jest przedstawiony w przykładzie 19.4. Przykład 19.4. Wyszukiwanie wszystkich łącz import java.io.*; import java.net.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*;
import com.oreilly.servlet.*;
import org.apache.regexp.*;
public class Lacza extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();
// URL do przetworzenia pobierany jest jako dodatkowa część ścieżki // np. http://localhost:8080/servlet/Lacza/http://www.servlets.com/ String url = zad.getPathInfo(); if (url == null || url.length() == 0) { odp.sendError(odp.SC_BAD_REQUEST, "Proszę przekazać URL do odczytania jako dodatkową część ścieżki "); return; } url = url.substring(1);
String strona = null;
// odcięcie wiodącego '/'
try { // Żądanie strony HttpMessage wiad = new HttpMessage(new URL(url)); BufferedReader in = new BufferedReader(new InputStreamReader(wiad.sendGetMessage()));
// Przekształcenie całej odpowiedzi do String StringBuffer buf = new StringBuffer(10240); char[] znaki = new char[10240]; int znakiOdczyt = 0; while ((znakiOdczyt = in.read(znaki, 0, znaki.length)) != -1) { buf.append(znaki, 0, znakiOdczyt); } strona = buf.toString(); } catch (IOException w) { odp.sendError(odp.SC_NOT_FOUND), "Niemożliwe odczytanie " + url + ":
" + ServletUtils.getStackTraceAsString(w)); return; }
wyj.println("<TITLE>Pobieranie łączy");
try { // Konieczne określenie żeby względne łącza działały właściwie // Jeżeli strona już go posiada, można go użyć RE wr = new RE("]*>", RE.MATCH_CASEINDEPENDENT); boolean maBase = wr.match(strona);
if (maBase) { // Wykorzystanie istniejącego wyj.println(wr.getParen(0)); } else { // Obliczenie BASE z URL-a, wykorzystanie wszystkiego do ostatniego '/' wr = new RE("http://.*/", RE.MATCH_CASEINDEPENDENT); boolean odczytBase = wr.match(url); if (odczytBase) { // Sukces, wyświetlenie odczytanego BASE wyj.println(""); } else { // Brak wiodącego ukośnika, dodanie go wyj.println(""); }
}
wyj.println("");
wyj.println("Łącza na " + url + "" + " są następujące:
"); wyj.println("
");
String szukaj = " Tomcat v3.2 ...
Wykorzystanie niezależnego narzędzia Niezależne narzędzia dają nowe możliwości i łatwość wykorzystania zadaniu usuwania błędów. IBM AlphaWorks produkuje program o nazwie Distributed Application Tester (DAT), który przegląda żądania HTTP i HTTPS, umożliwiając przeglądanie i zapisywanie obu stron ruchu klient-serwer. DAT zawiera możliwość
wykonywania testów funkcjonalności wydajności aplikacji WWW poprzez automatyczne generowanie żądań i przeglądanie odpowiedzi. Program jest utworzony całkowicie w Javie, ale jest udostępniany z programem instalacyjnym działającym jedynie pod Windows. Jego jedyną licencją jest bezpłatny 90-dniowy czas testowania, ponieważ oprogramowanie to występuje w wersji „alpha”, a co dziwne w wersji tej jest od stycznia 199. DAT jest dostępny pod adresem http://www.alphaworks.ibm.com. Firma Allaire, twórca popularnej „wtyczki” serwletów Jrun (po wykupieniu Live Software), posiada mało znane narzędzie służące do usuwania błędów z serwletów o nazwie ServletDebugger. Narzędzie to jest zaprojektowane programowego wspomagania testów i usuwania błędów z serwletów. ServletDebugger nie wymaga wykorzystania serwera WWW lub przeglądarki do wykonania żądania. Zamiast tego, wykorzystuje się zbiór klas do stworzenia niewielkiej klasy-końcówki, która przygotowuje i wykonuje żądanie serwletu. Końcówka określa wszystko — parametry inicjacji serwletu, nagłówki HTTP żądania oraz parametry żądania. ServletDebugger jest stosunkowo prosty i dobrze przystosowany do zautomatyzowanego testowania. Jego największą wadą jest konieczność wykonania sporej ilości pracy w celu właściwego przygotowania realistycznego żądania. ServletDebugger można odnaleźć w cenniku Allaire pod adresem http://www.allaire.com3.
Ostatnie wskazówki Jeżeli wszystkie powyższe porady nie pomogły w odnalezieniu i usunięciu błędu, proszę obejrzeć poniższe ostateczne wskazówki dotyczące suwania błędów z serwletów: •
Proszę wykorzystać System.getProperty("java.class.path") przy pomocy serwletu w celu uzyskania pomocy w rozwiązywaniu problemów związanych ze ścieżką klas. Ponieważ serwlety są często uruchamiane na serwerach WWW z osadzonymi wirtualnymi maszynami Javy, trudne może być dokładne określenie ścieżki klas poszukiwanej przez JVM. Określić to może właściwość java.class.path.
•
Proszę pamiętać, że klasy odnalezione w bezpośredniej ścieżce klas serwera (katalog_macierzysty/classes) przypuszczalnie nie są przeładowywane podobnie jak, w większości serwerów niezwiązanych z serwletami, klasy wspierające w katalogu klas aplikacji WWW (WEBINF/classes). Zazwyczaj przeładowywane są jedynie klasy serwletów w katalogu klas aplikacji WWW.
•
Należy zażądać od przeglądarki pokazania surowej zawartości wyświetlanej strony. Może to pomóc w zidentyfikowaniu problemów z formatowaniem. Zazwyczaj jest to opcja w menu Widok.
•
Należy upewnić się, że przeglądarka nie przechowuje w pamięci podręcznej wyników poprzedniego żądania poprzez wymuszenie pełnego przeładowania strony. W przeglądarce Netscape Navigator, należy zastosować Shift-Reload; W Internet Explorer należy zastosować Shift-Refresh.
•
Jeżeli pomija się wersję init(), która pobiera ServletConfig należy upewnić się, że pomijająca metoda od razu wywołuje super.init(config).
Poprawa wydajności Serwlety poprawiające wydajność wymagają nieco innego podejścia niż aplikacje lub aplety Javy wykonujące to samo działanie. Powodem tego jest fakt, że JVM uruchamiająca serwlety zazwyczaj obsługuje kilkadziesiąt, jeżeli nie kilkaset wątków, z których każdy uruchamia serwlet. Te współistniejące serwlety muszą dzielić się zasobami JVM w sposób inny, niż zwykłe aplikacje. Tradycyjne sztuczki poprawiające wydajność oczywiście działają, ale posiadają mniejszy wpływ, kiedy zostają wykorzystane w systemie wielowątkowym. Poniżej przedstawiono niektóre sztuczki najczęściej wykorzystywane przez programistów serwletów.
3
Nie byłoby zaskakujące, jeżeli w najbliższym czasie firma Allaire zrzuciłaby obsługę ServletDebugger. Jeżeli się to zdarzy, a nawet jeżeli się nie wydarzy, można poszukać wersji Open Source.
Tworzyć, ale nie przesadzać Należy unikać niepotrzebnego tworzenia obiektów. To zawsze była dobra rada — tworzenie niepotrzebnych obiektów marnuje pamięć i dużą ilość czasu. W przypadku serwletów to jest rada jeszcze lepsza. Tradycyjnie wiele JVM wykorzystywało globalną stertę obiektów, która musi zostać przypisana do każdej nowej alokacji pamięci. Kiedy serwlet tworzy nowy obiekt lub alokuje dodatkową pamięć, działania tego nie może wykonać żaden inny serwlet.
Nie łączyć Należy unikać łączenia kilku łańcuchów. Zamiast StringBuffer poleca się zastosowanie metody append(). To również zawsze była dobra rada, ale w przypadku serwletów szczególnie pociągające jest napisanie kodu przygotowującego łańcuch do późniejszego wyświetlania w następujący sposób: String wyswietl; wyswietl += "<TITLE>"; wyswietl += "Witaj, " + uzyt; wyswietl += "";
Chociaż powyższy kod wygląda miło i sprawnie, w trakcie uruchomienia działa, jak gdyby wyglądał mnie więcej tak jak poniżej, z nowymi StringBuffer i String tworzonymi w każdej linii: String wyswietl; wyswietl = new StringBuffer().append("<TITLE>").toString();; wyswietl = new StringBuffer(wyswietl).append("Witaj, ").toString(); wyswietl = new StringBuffer(wyswietl).append(uzyt).toString(); wyswietl = new StringBuffer(wyswietl).append("").toString();
Kiedy wydajność jest ważną kwestią, należy przepisać oryginalny kod tak, aby wyglądał jak poniższy, tak aby utworzone zostały tylko pojedyncze StringBuffer i String: StringBuffer buf = new StrngBuffer(); buf.append("<TITLE>"); buf.append("Witaj, "").append(uzyt); buf.append(""); wyswietl = buf.toString();
Proszę zauważyć, że jeszcze bardziej wydajne jest zastosowanie tablicy bajtów.
Ograniczać synchronizację Należy synchronizować bloki, kiedy jest to konieczne, ale nic poza tym. Każdy zsynchronizowany blok w serwlecie wydłuża czas odpowiedzi serwletu. Ponieważ ten sam egzemplarz serwletu może obsługiwać wiele współbieżnych żądań, musi, oczywiście, zająć się ochroną swojej klasy i zmiennych egzemplarza przy pomocy zsynchronizowanych bloków. Jednak przez cały czas jeden wątek żądania znajduje się w zsynchronizowanym bloku i żaden inny wątek nie może zostać do bloku wprowadzony. W związku z tym najlepiej jest uczynić te bloki jak najmniejszymi. Należy także przyjrzeć się najgorszemu z możliwych wyników sporu między wątkami. Jeżeli najgorszy przypadek jest znośny (jak w przykładzie licznika w rozdziale 3, „Okres trwałości serwletów”), można rozważyc całkowite usuniecie bloków synchronizacji. Można również rozważyć zastosowanie interfejsu znaczników SingleThreadModel, w którym serwer zarządza pulą egzemplarzy serwletów, aby zagwarantować, że każdy egzemplarz jest wykorzystywany przez co najwyżej jeden wątek w jednym czasie. Serwlety będące implementacją SingleThreadModel nie muszą synchronizować dostępu do swoich zmiennych egzemplarza. W końcu należy również pamiętać, że java.util.Vector i java.util.Hashtable są zawsze wewnętrznie zsynchronizowane, podczas gdy równoważne im java.util.ArrayList i java.util.HashMap, wprowadzone w JDK 1.2, nie są zsynchronizowane, jeżeli nie nastąpi odpowiednie
żądanie. Tak wiec jeżeli dany Vector lub Hashtable nie potrzebuje synchronizacji, można go zastąpić ArrayList lub HashMap.
Buforować dane wprowadzane i wyświetlane Należy buforować dane wprowadzane i wyświetlane, wszystkie pliki magazynowe, wszystkie potoki pobrane z bazy danych itd. To prawie zawsze podnosi wydajność, ale poprawa ta może być szczególnie widoczna w przypadku serwletów. Jej powód jest taki, że odczyt i zapis jednego elementu za jednym razem może spowolnić cały serwer w związku z koniecznością dokonywania częstych przełączeń kontekstu. Na szczęście ogólnie buforowanie podczas zapisu do PrintWriter lub ServletOutputStream lub podczas odczytywania z BufferedReader lub ServletInputStream nie jest konieczne. Większość implementacji serwerów sama buforuje te potoki.
Spróbować wykorzystania OutputStream W przypadku stron WWW wykorzystujących kodowanie znaków Latin-1, technicznie możliwe jest wykorzystanie PrintWriter lub ServletOutputStream. Rekomendowanym podejściem jest wykorzystanie PrintWriter, ponieważ obsługuje on internacjonalizację, ale na niektórych serwerach wykorzystanie ServletOutputStream powoduje zauważalny wzrost wydajności, a ServletOutputStream posiada także ułatwiające pracę metody print() i println() będące dziedzictwem Servlet API 1.0, w którym nie występowała opcja PrintWriter. Proszę jednak uważać. W przypadku wielu serwerów zależność jest odwrotna i to PrintWriter powoduje wyższą wydajność. Osoby, które nie są pewne swojej platformy programistycznej i nie przeprowadzały porównywalnych prób czasowych powinny trzymać się PrintWriter.
Wykorzystać narzędzie profilujące Dostępnych jest kilka narzędzi profilujących Javy które mogą wspomóc w odnalezieniu wąskich gardeł w kodzie. Większość problemów z wydajnością w Javie działającej po stronie serwera jest powodowana nie przez język czy JVM, ale kilka wąskich gardeł. Sztuką jest odnalezienie tych wąskich gardeł. Narzędzia analizujące pracują w tle, obserwując obsługę żądań przez serwer i zwracając dokładne podsumowanie spędzonego czasu, a także opis alokacji pamięci. Dwa popularne narzędzia to OptimizeIT! Firmy Intuitive Systems (http://www.optimizeit.com) i Jprobe formy Sitraka, dawniej KL Group (http://sitraka.com/jprobe). Duża ilość JVM może przyjmować również znaczniki z linii poleceń (-prof w JDK 1.1 i Xrunhproof() w JDK 1.2). Aby uruchomić obciążony serwer, można wykorzystać narzędzie takie jak Apache JMeter (http://java.apache.org).
W niniejszym rozdziale: •
Zmiany w Servlet API 2.3
•
Konkluzja
Rozdział 20.
Zmiany w Servlet API 2.3 Krótko przed przekazaniem niniejszej książki do druku firma Sun Microsystems opublikowała Proposed Final Draft (Proponowaną Ostateczną Próbę) specyfikacji Servlet API 2.31. Nie jest to ostateczna wersja specyfikacji; Proposed Final Draft to krok do formalnej Final Release (Wersji Ostatecznej), tak więc szczegóły techniczne ciągle mogą ulec zmianie. Jednak zmiany te nie powinny być znaczące — tak naprawdę producenci serwerów już rozpoczęli implementację nowych funkcji. W niniejszym rozdziale opisane zostaną w skrócie wszystkie zmiany, jakie zaszły pomiędzy API 2.2 i 2.3. Wyjaśnione także zostaną powody zmian i przedstawione zostaną sposoby tworzenia serwletów przy pomocy nowych funkcji2.
Zmiany w Servlet API 2.3 Servlet API 2.3 właściwie pozostawia nietknięte jądro serwletów, co oznacza, że serwlety osiągnęły wysoki poziom dojrzałości. Większość działań związana była z dodaniem nowych funkcji poza jądrem. Zmiany te to między innymi: •
Serwlety wymagają teraz JDK 1.2 lub późniejszego.
•
Utworzony został (nareszcie) mechanizm filtrów.
•
Dodane zostały nowe zdarzenia okresu trwałości aplikacji.
•
Dodana została nowa obsługa internacjonalizacji.
•
Sformalizowana została technika wyrażania zależności pomiędzy plikami JAR.
1
Chociaż specyfikacja ta została opublikowana przez firmę Sun, Servlet API 2.3. został tak naprawdę utworzony przez wiele osób i przedsiębiorstw działających jako grupa ekspertów JSR-053, zgodnie z procesem Java Community Process (JCP) 2.0. Ta grupa ekspertów działała pod przewodnictwem Danny'ego Cowarda z Sun Microsystems.
2
Niniejszy materiał pojawił się po raz pierwszy w artykule „Servlet 2.3: New Features Exposed” autorstwa Jasona Huntera, opublikowanym przez JavaWorld (http://www.javaworld.com), własność ITworld.com, Inc., styczeń 2001. Przedruk za pozwoleniem. (Proszę zobaczyć także http://www.javaworld.com/j2-01-2001/jw-0126-servletapi.html).
•
Wyjaśnione zostały zasady ładowania klas.
•
Dodane zostały nowe atrybuty błędów i bezpieczeństwa.
•
Opuszczona została klasa HttpUtils.
•
Dodane zostały różne nowe pomocne metody.
•
Rozszerzone i wyjaśnione zostało kilka zachowań DTD.
Wykonane zostały także inne wyjaśnienia, ale skupiają się one głównie na producentach serwerów, nie ogólnie na programistach serwletów (poza faktem, że programiści ujrzą poprawioną przenośność), tak więc te szczegóły zostaną tu ominięte. Przed rozpoczęciem przyglądania się zmianom należy zaznaczyć, że wersja 2.3 została udostępniona jedynie jako specyfikacja próbna. Większość opisanych tu funkcji nie będzie działać na wszystkich serwerach. Aby przetestować te funkcje, poleca się pobranie oficjalnego wzorcowego serwera, Apache Tomcat 4.0. Jest to Open Source i może być pobrany za darmo. Tomcat 4.0 jest aktualnie dostępny w wersji beta; obsługa Servlet API 2.3 staje się coraz lepsza, ale ciągle jest niekompletna. Proszę przeczytać plik NEW_SPECS.txt dołączony do Tomcata 4.0 w celu poznania jego poziomu wsparcia dla wszystkich funkcji nowej specyfikacji.
Serwlety w J2SE i J2EE Jedną z pierwszych rzeczy, jakie zazwyczaj zauważa się na temat Servlet API 2.3 jest fakt, że serwlety zależą teraz od platformy Java 2, Standard Edition (znanej także jako J2SE 1.2 lub JDK 1.2). Ta mała, ale ważna zmiana oznacza, że w serwletach można teraz wykorzystywać funkcje J2SE 1.2 z gwarancją, że serwlety te będą działać na wszystkich kontenerach. Poprzednio funkcje J2SE mogły być wykorzystywane, ale ich obsługa przez serwery nie była obowiązkowa. Servlet API2.3 ma stać się częścią platformy Java 2, Enterprise Edition 1.3 (J2EE 1.3). Poprzednia wersja, Servlet API 2.2 była częścią J2EE 1.2. Jedyną zauważalną różnicą jest fakt dodania kilku stosunkowo obskurnych znaczników deskryptora związanych z J2EE w deskryptorze web.xml — obsługującego „obiekty administrowane”, takie jak te wymagane przez Java Messaging System (JMS), pozwalający na współdzielony lub wyłączny dostęp do odwołania do zasobów oraz określający identyfikator bezpieczeństwa dla wywołującego EJB. Większość autorów serwletów nie powinna przejmować się tymi znacznikami J2EE; pełny ich opis dostępny jest w specyfikacji J2EE 1.3.
Filtry Najbardziej znaczącą częścią API 2.3 jest dodanie filtrów. Filtry są obiektami potrafiącymi zmieniać kształt żądania lub modyfikować odpowiedź. Proszę zauważyć, że nie są to serwlety; tak naprawdę nie tworzą one odpowiedzi. Przetwarzają one wstępnie żądanie zanim dotrze ono do serwletu, oraz przetwarzają odpowiedź opuszczającą serwlet. W skrócie, filtry są dojrzałą wersją starego pomysłu „łańcuchów serwletów”. Filtr potrafi: •
Przechwycić wywołanie serwletu przed jego wywołaniem.
•
Sprawdzić żądanie przed wywołaniem serwletu.
•
Zmodyfikować nagłówki i dane żądania poprzez dostarczenie własnej wersji obiektu żądania, który zostaje dołączony do prawdziwego żądania.
•
Zmodyfikować nagłówki i dane odpowiedzi poprzez dostarczenie własnej wersji obiektu odpowiedzi, który zostaje dołączony do prawdziwej odpowiedzi.
•
Przechwycić wywołanie serwletu po jego wywołaniu.
Filtr może być skonfigurowany tak, by działał jako serwlet lub grupa serwletów; ten serwlet lub grupa może zostać przefiltrowana przez dowolną ilość filtrów. Niektóre praktyczne idee filtrów to między innymi filtry uwierzytelniania, filtry logowania i nadzoru, filtry konwersji obrazków, filtry kompresji danych, filtry kodowania, filtry dzielenia na elementy, filtry potrafiące wywołać zdarzenia dostępu do zasobów, filtry XSLT przetwarzające zawartość XML lub filtry łańcuchowe typu MIME (podobne do łańcuchów serwletów). Filtr jest implementacją javax.servlet.filter i definiuje trzy metody tej klasy:
void setFilterConfig(FilterConfig konfig) Ta metoda ustawia obiekt konfiguracyjny filtra. FilterConfig getFilterConfig() Ta metoda zwraca obiekt konfiguracyjny filtra. void doFilter(ServletRequest zad, ServletResponse odp, FilterChain lancuch) Ta metoda wykonuje właściwą pracę filtra. Serwer wywołuje jeden raz setFilterConfig() w celu przygotowania filtru do działania, następnie wywołuje doFilter() dowolną ilość razy dla różnych obiektów. Interfejs FilterConfig posiada metody pobierające nazwę filtra, jego parametry inicjacji oraz aktywny kontekst filtra. Serwer może również przekazać setFilterConfig() wartość null aby wskazać, że filtr zostaje wyłączony.
UWAGA!!!! Przewiduje się, że w ostatecznej wersji 2.3 metoda getFilterConfig() zostanie usunięta, a metoda setFilterConfig(FilterConfig konfig)zostanie zastąpiona przez init(FilterConfig) i destroy().
Każdy filtr pobiera aktualne żądanie i odpowiedź w swojej metodzie doFilter(), a także FilterChain zawierający filtry, które muszą stale być przetwarzane. W metodzie doFilter() filtr może wykonać dowolne działanie żądania i odpowiedzi. (Na przykład może zbierać dane przez wywoływanie ich metod, dołączać informacje nadające im nowe zachowanie). Filtr następnie wywołuje lancuch.doFilter() w celu przekazania kontroli następnemu filtrowi. Kiedy wywołanie to wraca, filtr może, na końcu swojej własnej metody doFilter(), wykonać dodatkowe działania na odpowiedzi; na przykład może zapisać informacje na temat odpowiedzi w dzienniku. Jeżeli filtr chce całkowicie zatrzymać przetwarzanie żądania i zyskać pełną kontrolę nad odpowiedzią, może specjalnie nie wywoływać następnego filtra. Filtr może zmieniać obiekty żądania i odpowiedzi w celu dostarczenia własnego zachowania, zmiany konkretnej metody wywołują implementację wpływającą na późniejsze działania obsługujące żądania. Serwlet API 2.3 dostarcza nowych klas HttpServletRequestWrapper i HttpServletResponseWrapper w tym pomagających. Posiadają one domyślną implementację wszystkich metod żądań i odpowiedzi oraz domyślnie przekazują wywołania do oryginalnych żądań i odpowiedzi. Poniżej przedstawiony jest kod prostego filtra logowania, który zapamiętuje czas trwania wszystkich żądań: import javax.servlet.*; import javax.servlet.http.*;
public class FiltrDziennik implements Filter {
FilterConfig konfig;
public void setFilterConfig(FilterConfig konfig) { this.konfig = konfig; }
public FilterConfig getFilterConfig() { return konfig; }
public void doFilter(ServletRequest zad, ServletResponse odp, FilterChain lancuch) {
ServletContext kontekst = getFilterConfig().getServletContext(); long przed = System.currentTimeMillis(); lancuch.doFilter(zad, odp); // nie jest potrzebny żaden parametr łańcucha long po = System.currentTimeMillis(); kontekst.log("Żądanie" + zad.getRequestURI() + ": " + (przed-po)); } }
Kiedy serwer wywołuje setFilterConfig(), filtr zapamiętuje w swojej zmiennej konfig odwołanie do konfiguracji filtra, które później zostanie wykorzystane w metodzie doFilter() w celu pobrania ServletContext. Logika doFilter() jest prosta — obliczenie czasu obsługi żądania i zapamiętanie czasu po zakończeniu przetwarzania. Aby wykorzystać powyższy filtr, trzeba wcześniej zadeklarować go w deskryptorze DTD przy pomocy znacznika , jak przedstawiono poniżej: dziennik FiltrDziennik
Powyższy fragment kodu przekazuje serwerowi informację, że filtr o nazwie dziennik jest zaimplementowany w klasie FiltrDziennik. Można przypisać zarejestrowany filtr do konkretnych wzorów URL-i lub nazw serwletów przy pomocy znacznika : log /*
Powyższy fragment konfiguruje filtr tak, aby był stosowany do wszystkich żądań serwera (statycznych lub dynamicznych), co jest działaniem odpowiednim dla filtra zapisującego informacje w dzienniku. Jeżeli nastąpi połączenie, dziennik mógłby wyglądać następująco: Żądanie /indeks.jsp: 10
Zdarzenia okresu trwałości Drugą bardzo poważną zmianą w Servlet API 2.3 jest dodanie zdarzeń okresu trwałości aplikacji, które umożliwiają powiadomienie obiektów „nasłuchujących", kiedy inicjowane bądź niszczone są konteksty serwletów i sesje, a także kiedy dodawane lub usuwane z kontekstu lub sesji są atrybuty. Okres trwałości serwletu jest podobny do zdarzeń Swing. Każdy obiekt nasłuchujący zainteresowany obserwacją okresu trwałości ServletContext może zaimplementować interfejs ServletContextListener. Interfejs ten posiada dwie metody: void contextInitialized(ServletContextEvent z) Wywoływana z aplikacji WWW, kiedy jest ona po raz pierwszy gotowa o przetwarzania żądań (tzn. podczas uruchomienia serwera lub dodania albo przeładowania kontekstu). Żądania nie są obsługiwane, dopóki metoda nie zwróci swoich wartości. void contextDestroyed(ServletContextEvent z)
Wywoływana z aplikacji WWW, która powinna zostać zakończona (tzn. podczas wyłączania serwera lub usuwania albo przeładowania kontekstu). Obsługa żądań zostaje zatrzymana przed wywołaniem tej metody. Klasa ServletContextEvent przekazywana obu metodom zawiera jedynie metodę pobierzZrodloKontekst(), która zwraca kontekst, który jest inicjowany lub niszczony. Obiekt nasłuchujący zainteresowany obserwacją okresem trwałości atrybutu ServletContext może zaimplementować interfejs ServletContextAttributesListener, który posiada trzy metody: void attributeAdded(ServletContextAttributeEvent z) Wywoływana, kiedy atrybut zostaje dodany do kontekstu serwletu. void attributeRemoved(ServletContextAttributeEvent z) Wywoływana, kiedy atrybut zostaje usunięty z kontekstu serwletu. void attributeReplaced(ServletContextAttributeEvent z) Wywoływana, kiedy atrybut w kontekście serwletu zostaje wymieniony na inny. Klasa ServletContextAttributeEvent jest rozszerzeniem ServletContextEvent i dodaje metody getName() i getValue(), tak więc obiekt nasłuchujący może uzyskać informacje na temat zmieniających się atrybutów. Jest to przydatna własność, ponieważ aplikacje WWW które muszą zsynchronizować stan aplikacji (atrybuty kontekstu) z czymś w rodzaju bazy danych mogą to teraz uczynić w jednym miejscu. Model obiektu nasłuchującego sesji jest podobny do modelu obiekt nasłuchującego okresu trwałości. W modelu sesji występuje interfejs HttpSessionListener posiadający dwie metody: void SessionCreated(HttpSessionEvent z) Wywoływana podczas tworzenia sesji. void SessionDestroyed(HttpSessionEvent z) Wywoływana podczas zniszczenia (unieważnienia) sesji. Powyższe metody przyjmują egzemplarz HttpSessionEvent z metodą dostępu getSession() w celu zwrócenia sesji, która jest tworzona bądź niszczona. Metody te mogą być zastosowane podczas implementacji interfejsu administratora, który śledzi wszystkich aktywnych użytkowników w aplikacji WWW. Model sesji posiada również interfejs HttpSessionAttributesListener posiadający trzy metody. Metody te informują obiekt nasłuchujący, kiedy zmieniają się atrybuty i mogłyby być zastosowane, na przykład przez aplikację synchronizującą dane profilu przechowywane w sesji do bazy danych: void attributeAdded(HttpSessionBindingEvent z) Wywoływana, kiedy atrybut zostaje dodany do sesji. void attributeRemoved(HttpSessionBindingEvent z) Wywoływana, kiedy atrybut zostaje usunięty z sesji. void attributeReplaced(HttpSessionBindingEvent z) Wywoływana, kiedy atrybut w sesji zostaje wymieniony na inny. Jak można się było spodziewać, klasa HttpSessionBindingEvent jest rozszerzeniem HttpSessionEvent i dodaje metody getName() i getValue(). Jedyna dość niezwykła własność jest taka, że klasa zdarzeń nosi nazwę HttpSessionBindingEvent, a nie HttpSessionAttributeEvent. Dzieje się tak z powodu tradycji — API posiadał już klasę HttpSessionBindingEvent, tak więc została ona wykorzystana ponownie. Ten nieco mylący aspekt API może zostać poprawiony w ostatecznej wersji. Możliwym praktycznym zastosowaniem dla zdarzeń okresu trwałości jest współdzielone połączenie z bazą danych zarządzane przez obiekt nasłuchujący kontekstu. Obiekt ten deklarowany jest w pliku web.xml, w następujący sposób: <listener> <listener-class>
com.firma.MojZarzadcaPolaczen
Serwer tworzy egzemplarz klasy nasłuchującej służącej do otrzymywania zdarzeń i wykorzystuje introspekcje w celu określenia, jaki interfejs (lub interfejsy) nasłuchu powinien zostać wykorzystany przez klasę. Należy pamiętać, że ponieważ mechanizm nasłuchujący jest konfigurowany w deskryptorze, można dodawać nowe mechanizmy bez konieczności zmiany kodu. Sam obiekt nasłuchujący mógłby wyglądać następująco: import javax.servlet.*; import javax.servlet.http.*;
public class MojZarzadcaPolaczen implements ServletContextListener {
public void contextInitialized(ServletContextEvent z) { Connection pol =
// utworzenie połączenia
z.getServletContext().setAttribute("pol", pol); }
public void contextDestroyed(ServletContextEvent z) { Connection pol = (Connection) z.getServletContext().getAttribute("pol"); try { pol.close(); } catch (SQLException ignored) { } // zamknięcie połączenia } }
Powyższy obiekt nasłuchujący zapewnia dostępność połączenia z bazą danych w każdym nowym kontekście serwletu oraz zamknięcie wszystkich połączeń po zamknięciu kontekstu. Interfejs HttpSessionActivationListener, kolejny nowy interfejs nasłuchu w API 2.3 jest zaprojektowany do obsługi sesji wędrujących z jednego serwera do drugiego. Obiekt nasłuchujący będący implementacją HttpSessionActivationListener jest powiadamiany o gotowości każdej sesji do wędrówki oraz kiedy sesja gotowa jest do aktywacji na drugim komputerze. Metody te dają aplikacji szansę przesuwania danych niemożliwych do zserializowania pomiędzy wirtualnymi maszynami Javy, lub doklejania i odklejania obiektów zserializowanych pomiędzy pewnego rodzaju modelem obiektów przed lub po migracji. Interfejs ten posiada dwie metody: void sessionWillPassivate(HttpSessionEvent z) Sesja ma zamiar dokonać wędrówki. Sesja jest już niedostępna kiedy następuje to wywołanie. void sessionDidActivate(HttpSessionEvent z) Sesja została aktywowana. Sesja nie jest jeszcze w użytku, kiedy następuje to wywołanie. Taki obiekt nasłuchujący jest rejestrowany tak, jak inne. Jednak różnica pomiędzy nimi jest taka, że wywołania migracji i aktywacji najprawdopodobniej będą występować na dwóch różnych serwerach!
Wybranie kodowania znaków Servlet API 2.3 dostarcza tak bardzo potrzebnej obsługi formularzy wysyłanych w różnych językach. Nowa metoda, zadanie.setCharacterEncoding(String kodowanie) pozwala na poinformowanie serwera o kodowaniu znaków żądania. Kodowanie znaków (albo po prostu kodowanie) to sposób odwzorowania bajtów na znaki. Serwer może wykorzystać określone kodowanie, aby prawidłowo zanalizować parametry i dane POST. Domyślnie serwer analizuje parametry przy pomocy popularnego kodowania Latin-1 (ISO 8859-1) Niestety jest ono odpowiednie tylko dla języków zachodnioeuropejskich. Kiedy przeglądarka wykorzystuje inne kodowanie,
powinna wysyłać informacje o kodowaniu w nagłówku żądania Content-Type, ale prawie żadna przeglądarka nie stosuje się do tej zasady. Metoda setCharacterEncoding() pozwala serwletowi na poinformowanie serwera, które kodowanie jest wykorzystywane (zazwyczaj jest to kodowanie strony, która zawiera formularz); serwer zajmuje się resztą. Na przykład, serwlet pobierający japońskie parametry z formularza zakodowanego przy pomocy Shift_JIS powinien odczytywać parametry w następujący sposób: // ustawienie kodowania na Shift_JISD zad.setCharacterEncoding("Shift_JIS");
// Odczytanie parametru przy pomocy tego kodowania String nazwa = zad.getParameter("nazwa");
Proszę pamiętać o ustawieniu kodowania przed wywołaniem getParameter() lub getReader(). Wywołanie setCharacterEncoding() może zgłosić wyjątek java.io.UnsupportedEncodingException, jeżeli dane kodowanie nie jest obsługiwane. Funkcjonalność ta jest również dostępna użytkownikom API 2.2 i wcześniejszych, jako część klasy com.oreilly.servlet.ParameterParser.
Zależności plików JAR Plik WAR (plik Web Application Archive — Archiwum Aplikacji WWW, dodane w Servlet API 2.2) często wymaga istnienia i prawidłowego działania na serwerze innych bibliotek JAR. Na przykład, aplikacja WWW wykorzystująca klasę ParameterParser wymaga w ścieżce klas serwera pliku cos.jar. Aplikacja WWW wykorzystująca WebMacro wymaga webmacro.jar. Przed powstaniem API 2.3, zależności te musiały być udokumentowane (ale czy ktoś tak naprawdę czyta dokumentację!), lub każda aplikacja WWW musiała zawierać wszystkie wymagane pliki JAR w swoim własnym katalogu WEB-INF/lib. Servlet API 2.3 pozwala na zadeklarowanie zależności JAR w WAR przy pomocy pozycji META-INF/MANIFEST.MF archiwum WAR. Jest to standardowy sposób deklarowania zależności plików JAR, ale od API 2.3 pliki WAR muszą oficjalnie obsługiwać ten mechanizm. Jeżeli zależność nie może zostać wypełniona, serwer może delikatnie odrzucić aplikację WWW w trakcie tworzenia zamiast wywoływać obskurne komunikaty o błędach w trakcie uruchomienia. Mechanizm ten pozwala na wysoki stopień ziarnistości. Na przykład można wyrazić zależność w konkretnej wersji opcjonalnego pakietu, a serwer musi ją odnaleźć przy pomocy algorytmu wyszukiwania.
Mechanizmy ładowania klas Nastąpiła tu mała zmiana, posiadająca jednak ogromne znaczenie — w API 2.3, kontener serwletów (czyli serwer) musi blokować możliwość dostrzeżenia klas implementacji serwera przez klasy aplikacji WWW. Innymi słowy, mechanizmy ładowania klas powinny być od siebie oddzielone. Nie brzmi to jak wielka zmiana, ale eliminuje możliwość kolizji pomiędzy klasami aplikacji WWW i klasami serwera. Stały się one poważnym problemem z powodu konfliktów analizatora XML. Każdy serwer potrzebuje analizatora XML w celu zinterpretowania plików web.xml, a obecnie wiele aplikacji WWW wykorzystuje analizator XML do obsługi odczytu, manipulacji i zapisu danych XML. Jeżeli analizatory obsługiwały różne wersje DOM lub SAX, powodowało to nienaprawialny konflikt. Oddzielenie zakresów klas elegancko ten problem rozwiązuje.
Nowe atrybuty błędów Poprzednia wersja interfejsu, Servlet API 2.2 wprowadziła kilka atrybutów żądania, które mogą być wykorzystywane przez serwlety i strony JSP pełniące funkcję celu zasady <error-page>. Wcześniej w niniejszej książce opisano, że zasady <error-page> pozwalają na skonfigurowanie aplikacji WWW tak, aby konkretne kody stanu lub typy wyjątków powodowały wyświetlenie konkretnych stron: <web-app>
Serwlet umieszczony w zasady <error-page> mógł otrzymywać następujące trzy atrybuty: javax.servlet.error.status_code Liczba Integer przekazująca kod stanu błędu, jeżeli taki istnieje. javax.servlet.error.exception_type Egzemplarz Class wskazujący na typ wyjątku, który spowodował błąd, jeżeli taki istnieje. javax.servlet.error.message Łańcuch String przekazujący wiadomość o błędzie, przekazywany do konstruktora wyjątku. Przy pomocy powyższych atrybutów serwlet mógł wygenerować stronę błędu dostosowaną do błędu, jak przedstawiono poniżej: import java.io.*; import javax.servlet.*; import javax.servlet.http.*;
public class WyswietlBlad extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();
String kod = null, wiadomosc = null, typ = null, uri = null; Object kodObi, wiadomoscObi, typObi;
// Odczytanie trzech możliwych atrybutów błędów. Niektóre mogą mieć wartość null kodObi = zad.getAttribute("javax.servlet.error.status_code"); wiadomoscObi = req.getAttribute("javax.servlet.error.message"); typObi = req.getAttribute("javax.servlet.error.exception_type");
// Konwersja atrybutów na wartości łańcuchowe // Dlatego, że niektóre stare typy serwerów zwracają typy String // a nowe typy Integer, String i Class
if (kodObi != null) kod = kodObi.toString(); if (wiadomoscObi != null) wiadomosc = wiadomoscObi.toString(); if (typObi != null) typ = typObi.toString();
// Powód błędu to kod stanu lub typ wyjątku String powod = (kod != null ? kod : typ);
wyj.println(""); wyj.println("<TITLE>" + powod + ": " + wiadomosc + ""); wyj.println(""); wyj.println("" + powod + ""); wyj.println("" + wiadomosc + ""); wyj.println(""); } wyj.println(""); wyj.println(""); wyj.println("Błąd przy dostępie do " + zad.getRequestURI() + ""); wyj.println(""); } }
Ale co by się stało, gdyby strona błędu mogła zawierać ścieżkę stosu wyjątku lub URI serwletu, który naprawdę spowodował problem, (ponieważ nie zawsze jest to początkowo zażądany URI)? W API 2.2 nie było to możliwe. W API 2.3 informacje te są dostępne poprzez dwa nowe atrybuty: javax.servlet.error.exception Obiekt Throwable, zawierający właściwy zgłoszony wyjątek. javax.servlet.error.request_uri Łańcuch String przekazujący URI zasobu sprawiającego problem. Atrybuty te pozwalają na dołączenie do strony błędu ścieżki stosu wyjątku oraz URI zasobu sprawiającego problem. Serwlet przedstawiony poniżej został ponownie napisany tak, aby wykorzystywał nowe atrybuty. Proszę zauważyć, że elegancko przerywa działanie, jeżeli one nie istnieją. Dzieje się tak w celu zachowania wstecznej kompatybilności. import java.io.*; import javax.servlet.*; import javax.servlet.http.*;
public class WyswietlBlad extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();
String kod = null, wiadomosc = null, typ = null, uri = null; Object kodObi, wiadomoscObi, typObi; Throwable throwable;
// Odczytanie trzech możliwych atrybutów błędów. Niektóre mogą mieć wartość null kodObi = zad.getAttribute("javax.servlet.error.status_code"); wiadomoscObi = req.getAttribute("javax.servlet.error.message"); typObi = req.getAttribute("javax.servlet.error.exception_type"); throwable = (Throwable) zad.getAttribute("javax.servlet.error.exception"); uri = (String) zad.getAttribute("javax.servlet.error.request_uri");
if (uri == null) { uri = zad.getRequestURI(); // gdyby nie podano URI }
// Konwersja atrybutów na wartości łańcuchowe if (kodObi != null) kod = kodObi.toString(); if (wiadomoscObi != null) wiadomosc = wiadomoscObi.toString(); if (typObi != null) typ = typObi.toString();
// Powód błędu to kod stanu lub typ wyjątku String powod = (kod != null ? kod : typ);
wyj.println(""); wyj.println("<TITLE>" + powod + ": " + wiadomosc + ""); wyj.println(""); wyj.println("" + powod + ""); wyj.println("" + wiadomosc + ""); wyj.println(""); if (throwable != null) { throwable.printStackTrace(wyj); } wyj.println(""); wyj.println(""); wyj.println("Błąd przy dostępie do " + uri + ""); wyj.println(""); } }
Nowe atrybuty bezpieczeństwa Servlet API 2.3 dodaje również dwa nowe atrybuty żądania, które mogą pomóc serwletowi w podejmowaniu dobrze umotywowanych decyzji dotyczących obsługi bezpiecznych połączeń HTTPS. Do żądań wykonanych przy pomocy HTTPS serwer dołączy następujące nowe atrybuty żądania: javax.servlet.request.cipher_suite Łańcuch String reprezentujący typ szyfrowania stosowany przez HTTPS, jeżeli taki występuje. javax.servlet.request.key_size Liczba Integer reprezentująca wielkość algorytmu w bitach, jeżeli taka występuje. Serwlet może wykorzystać powyższe atrybuty do programowej decyzji, czy połączenie jest na tyle bezpieczne, że można z niego skorzystać. Aplikacja może odrzucić połączenia o niewielkiej liczbie bitów, lub algorytmy niegodne zaufania. Na przykład, serwlet mógłby wykorzystać poniższą metodę w celu upewnienia się, że jego połączenie wykorzystuje klucz przynajmniej 128-bitowy: public boolean czyPonad128(HttpServletRequest zad) { Integer wielkosc = (Integer) zad.getAttribute("javax.servlet.request.key_size");
if (wielkosc == null || wielkosc.intValue() < 128) { return false; } else { return true; } }
UWAGA!!!! Nazwy atrybutów w wersji próbnej wykorzystują myślniki zamiast kresek dolnych. Jednak zostaną one zmienione w wersji ostatecznej, jak przedstawiono powyżej, w celu uzyskania większej spójności z nazwami istniejących atrybutów.
Niewielkie poprawki W Servlet API 2.3 znajdzie się także pewna ilość drobnych zmian. Po pierwsze, metoda getAuthType(), która zwraca typu uwierzytelnienia wykorzystywanego do identyfikacji klienta została zdefiniowana tak, aby zwracać jedną z czterech nowych stałych typu String klasy HttpServletRequest — BASIC_AUTH, DIGEST_AUTH, CLIENT_AUTH i FORM_AUTH. Umożliwia to wykorzystanie uproszczonego kodu: if (zad.getAuthType() == zad.BASIC_AUTH) { //obsługa uwierzytelniania podstawowego }
Oczywiście cztery stałe posiadają ciągle tradycyjne wartości String, tak więc poniższy kod z API 2.2 również działa, ale nie jest ani tak szybki, ani elegancki. Proszę zwrócić uwagę na odwrócone sprawdzenia equals() w celu uniknięcia wyjątku NullPointerException, jeżeli getAuthType() zwróci null: if ("BASIC".equals(zad.getAuthType())) { //obsługa uwierzytelniania podstawowego }
Inną zmianą w API 2.3 jest opuszczenie HttpUtils. Klasa HttpUtils była zawsze uważana za zbiór różnych statycznych metod — wywołań, które mogły być czasami użyteczne, ale równie dobrze mogły się znaleźć w innych miejscach. Klasa ta zawierała metody służące do zrekonstruowania oryginalnego URL-a z
obiektu żądania i do przeformatowania danych parametrów do tablicy asocjacyjnej. API 2.3 przesuwa tę funkcjonalność do obiektu żądania, co jest miejscem bardziej odpowiednim i opuszcza HttpUtils. Nowe metody obiektu żądania wyglądają następująco: StringBuffer zad.getRequestURL() Zwraca StringBuffer zawierający URL oryginalnego żądania, odtworzony na podstawie informacji żądania. java.util.Map zad.getParameterMap() Zwraca niemodyfikowalne odwzorowanie Map parametrów żądania. Nazwy parametrów pełnią funkcję kluczy, a wartości parametrów funkcję wartości odwzorowania. Nie zapadła decyzja co do obsługi parametrów o wielu wartościach; najprawdopodobniej wszystkie wartości zostaną zwrócone jako String[]. Metody te wykorzystują nową metodę setCharacterEncoding() do obsługi konwersji znaków. API 2.3 dodaje również dwie nowe metody do ServletContext, które pozwalają na odczytanie nazwy kontekstu i wyświetlenia wszystkich przechowywanych przez niego zasobów: String kontekst.getServletContextName() Zwraca nazwę kontekstu zadeklarowaną w pliku web.xml. java.util.Set kontekst.getResourcePaths() Zwraca ścieżki do wszystkich zasobów dostępnych w kontekście, jako niemodyfikowalny zbiór obiektów String. Każdy String posiada wiodący ukośnik (/) i powinien być uważany za względny do katalogu macierzystego kontekstu. Pojawiła się także nowa metoda obiektu odpowiedzi zwiększająca kontrolę programisty nad buforem odpowiedzi. API 2.2 wprowadził metodę odp.reset() pozwalającą na usunięcie odpowiedzi i wyczyszczenie jej głównej części, nagłówków i kodu stanu. API 2.3 dodaje odp.resetBuffer(), który czyści jedynie główną część odpowiedzi: void resetBuffer() Czyści bufor odpowiedzi, ale pozostawia nagłówki i kod stanu. Jeżeli odpowiedź został już wysłana, zgłasza wyjątek IllegalStateException. I wreszcie, po długich naradach grupy ekspertów, Servlet API 2.3 wyjaśnia raz na zawsze, co dokładnie dzieje się podczas wywołania odp.sendRedirect("indeks.html") w przypadku serwletu nie działającego w kontekście macierzystym serwera. Niejasność wynikała z tego, że Servlet API 2.2 wymaga, aby niekompletna ścieżka taka jak "/indeks.html" została przetłumaczona przez kontener serwletów na ścieżkę kompletną, nie określa jednak zasad obsługi ścieżek kontekstów. Jeżeli serwlet wykonujący wywołanie znajduje się w kontekście o ścieżce "/sciezkakontekstu", czy URI przekierowania powinien być określony względem katalogu macierzystego kontenera (http://serwer:port/indeks.html), czy katalogu macierzystego kontekstu (http://serwer:port/sciezkakontekstu/indeks.html)? W celu zapewnienia maksymalnej przenośności zdefiniowanie zachowania jest konieczne. Po długiej debacie eksperci wybrali tłumaczenie ścieżek względem katalogu macierzystego serwera. Osoby pragnące tłumaczenia względem kontekstu powinny dodać na początku URI wynik wywołania getContextPath().
Wyjaśnienia deskryptora DTD Servlet API 2.3 rozwiewa kilka niejasności związanych z zachowaniem deskryptora web.xml. Obowiązkowe stało się przycięcie wartości tekstowych w pliku web.xml przed jego zastosowaniem. (W standardowym nie sprawdzanym XML wszystkie miejsce są ogólnie rzecz biorąc zachowywane.) Zasada ta zapewnia, że się poniższe pozycje będą traktowane identycznie: <servlet-name>witaj
i <servlet-name> witaj
Servlet API 2.3 zezwala również na wykorzystanie zasady , tak więc specjalna wartość „*” może zostać wykorzystana jako wieloznacznik zezwalający na wszystkie role. Pozwala to na utworzenie zasady podobnej do poniższej, która pozwala na wejście wszystkim użytkownikom, jeżeli zostali oni poprawnie rozpoznani jako posiadający dowolną rolę w aplikacji WWW: *
Ostatnią zmianą jest dopuszczenie nazwy roli zadeklarowanej przez zasadę <security-role> jako parametru metody isUserInRole(). Na przykład, proszę spojrzeć na następujący fragment pozycji web.xml: <servlet> <servlet-name> sekret <servlet-class> PrzegladPensji <security-role-ref> men
1 Oryginalny komentarz do tego znacznika błędnie deklaruje, że znacznik „zawiera oczekiwany typ klasy Javy EJB, do którego występuje odwołanie”.
<env-entry-name> Zestawienie
Opis Element <env-entry-name> określa nazwę pozycji środowiskowej aplikacji służącą do poszukiwań. <env-entry> <description>Wysyłanie kodu PIN pocztą <env-entry-name>pocztaPIN <env-entry-value>false <env-entry-type>java.lang.Boolean
<env-entry-value> Zestawienie
Opis Element <env-entry-value> zawiera domyślną wartość pozycji środowiskowej aplikacji. <env-entry> <description>Wysyłanie kodu PIN pocztą <env-entry-name>pocztaPIN <env-entry-value>false <env-entry-type>java.lang.Boolean
<session-timeout> Zestawienie
Opis Element <session-timeout> definiuje domyślny czas przedawnienia sesji dla wszystkich sesji utworzonych w aplikacji WWW. Dany czas przedawnienia musi być określony w pełnych minutach. <session-config> <session-timeout>60
<small-icon> Zestawienie
Opis Element <small-icon> określa położenie wewnątrz aplikacji WWW pliku zawierającego mały (16 x 16 pikseli) obrazek ikony. Narzędzia przyjmują formaty przynajmniej GIF i JPEG. <small-icon>/obrazki/maly.gif /obrazki/duzy.gif
Zestawienie
Opis Element jest wykorzystywany w celu opisania biblioteki znaczników JSP. /WEB-INF/struts.tld /WEB-INF/struts.tld
Zestawienie
Opis Element zawiera umiejscowienie (jako zasobu względnego do katalogu macierzystego aplikacji WWW) pliku biblioteki znaczników (TLD). /WEB-INF/struts.tld /WEB-INF/struts.tld
Zestawienie
Opis Element opisuje URI względny do położenia dokumentu web.xml, który identyfikuje bibliotekę znaczników wykorzystaną w aplikacji WWW. /WEB-INF/struts.tld
/WEB-INF/struts.tld
Zestawienie
Opis Element określa sposób komunikacji pomiędzy klientem i serwerem jako NONE, INTEGRAL lub CONFIDENTIAL. NONE oznacza, że aplikacja nie potrzebuje żadnych gwarancji transportu. Wartość INTEGRAL oznacza, że dane przesyłane pomiędzy klientem i serwerem powinny być wysyłane w ten sposób, aby nie mogły zostać zmienione podczas transportu. CONFIDENTIAL oznacza, że aplikacja wymaga przesyłania danych w ten sposób, aby zawartość transmisji nie mogła zostać zaobserwowana przez żaden inny program lub egzemplarz programu. W większości przypadków obecność znacznika INTEGRAL lub CONFIDENTIAL oznacza konieczność wykorzystania SSL. <user-data-constraint> CONFIDENTIAL
Zestawienie
Opis Element zawiera wzór URL-a będącego odwzorowaniem serwletu. Wzór ten musi być zgodny z zasadami wymienionymi w części 10 specyfikacji Servlet API, wyjaśnionymi w rozdziale 2, „Podstawy serwletów HTTP”. <servlet-mapping> <servlet-name>witaj /witaj.html
<user-data-constraint> Zestawienie
Opis Element <user-data-constraint> jest wykorzystywany w celu wskazania sposobu ochrony danych przesyłanych pomiędzy klientem i kontenerem. <security-constraint> <web-resource-collection> ...
<user-data-constraint> CONFIDENTIAL
<web-app > Zestawienie
Opis Element <web-app> jest podstawowym elementem deskryptora aplikacji WWW.
<web-app> <servlet> <servlet-name>witaj <servlet-class>WitajSwiecie
<web-resource-collection> Zestawienie
Opis Element <web-resource-collection> jest wykorzystywany w celu zidentyfikowania podzbioru zasobów i metod HTTP tych zasobów, do których odnoszą się ograniczenia dostępu. Jeżeli nie zostały określone żadne metody HTTP, ograniczenia dostępu odnoszą się do wszystkich metod HTTP. <security-constraint> <web-resource-collection> <web-resource-name>TajnaOchrona /servlet/SerwerPlac
/servlet/sekret GET POST
<web-resource-name> Zestawienie
Opis <web-resource-name> określa nazwę zbioru zasobów WWW. <web-resource-collection> <web-resource-name>TajnaOchrona /servlet/SerwerPlac /servlet/sekret GET POST
<welcome-file> Zestawienie
Opis Element <welcome-file> zawiera nazwę pliku, który powinien zostać wykorzystany jako domyślny plik powitalny. <welcome-file-list> <welcome-file>indeks.html <welcome-file>indeks.htm
<welcome-file-list> Zestawienie
Opis <welcome-file-list> określa listę plików, które powinien wyszukać kontenener, kiedy przeglądarka zażąda katalogu, a nie strony WWW lub serwletu. <welcome-file-list>
<welcome-file>indeks.html <welcome-file>indeks.htm
Dodatek D.
Kody stanu HTTP Kody stanu HTTP są pogrupowane w sposób przedstawiony w tabeli D.1. Tabela D.1. Grupy kodów stanu HTTP Zakres kodów
Znaczenie odpowiedzi
100-199
Informacyjne
200-299
Żądanie klienta pomyślne
300-399
Żądanie klienta przekierowane, konieczne następne działanie
400-499
Żądanie klienta niekompletne
500-599
Błąd serwera
Tabela D.2 wylicza stałe kodów stanu zdefiniowane w interfejsie HttpServletResponse i wykorzystywane jako parametry metod setStatus() i sendError(). Numer wersji w ostatniej kolumnie odnosi się do wersji protokołu HTTP, która pierwsza zdefiniowała dany kod stanu. Servlet API 2.0 dodał stałe dla kodów stanu HTTP w wersji 1.1, jak opisano w proponowanym standardzie RFC 2068. Servlet API 2.2 dodał kody stanu 416 i 417, jak opisano w próbnym standardzie RFC 2616. Proszę zauważyć, że kody stanu HTTP/1.1 wymagają przeglądarki zgodnej z HTTP/1.1. Większa ilość informacji na temat HTTP jest dostępna w książce „Web Client Programming” autorstwa Clintona Wonga (O'Reilly). Najnowsza specyfikacja HTTP/1.1 jest dostępna w dokumencie RFC 2616 pod adresem http://www.ietf.org/rfc/rfc2616.txt. Tabela D.2. Stałe kodów stanu HTTP Stała
Kod
Domyślna wiadomość
Znaczenie
Wersja HTTP
SC_CONTINUE
100
Continue
Serwer otrzymał początkową część żądania, klient może kontynuować z dalszymi częściami.
1.1
SC_SWITCHING_PROTOCOLS
101
Switching Protocols
Serwer chce wypełnić żądanie klienta zmiany protokołu na określony w nagłówku żądania Upgrade.
1.1
Działanie to może zawierać przełączenie na nowszą wersję HTTP lub własny synchroniczny kanał wideo. SC_OK.
201
OK
Żądanie klienta zakończyło się 1.0 sukcesem i odpowiedź serwera zawiera żądane dane. Jest to domyślny kod stanu.
SC_CREATED
201
Created
Na serwerze został utworzony zasób, 1.0 przypuszczalnie w odpowiedzi na żądanie klienta. Główna część odpowiedzi powinna zawierać URL (e), pod którym można ten nowy zasób odnaleźć, z najbardziej specyficznym URL-em zawartym w nagłówku Location. Jeżeli zasób nie może zostać utworzony natychmiast, powinien zamienne zostać zwrócony kod stanu SC_ACCEPTED.
SC_ACCEPTED
202
Accepted
Żądanie zostało przyjęte do przetwarzania, ale nie zostało jeszcze ukończone. Serwer powinien opisać aktualny stan żądania w głównej części odpowiedzi. Serwer nie jest zobligowany do działania na ani kończenia wykonywania żądania.
SC_NON_AUTHORITATIVE_INF 203 ORMATION
NonAuthoritative Information
Nagłówki odpowiedzi HTTP nadeszły 1.1 z lokalnego lub niezależnego źródła, a nie z oryginalnego serwera. Zwykłe serwlety nie mają powodów, by wykorzystywać ten kod stanu
SC_NO_CONTENT
204
No Content
Żądanie powiodło się, ale nie istnieje główna część nowej odpowiedzi. Przeglądarki przyjmujące ten kod powinny zachować swój aktualny widok dokumentów. Kod ten jest przydatny serwletowi, kiedy otrzymuje on dane z formularza, ale chce, aby przeglądarka zatrzymała się na formularzu, w ten sposób unikając komunikatu o błędzie „Dokument nie zawiera żadnych danych”.
SC_RESET_CONTENT
205
Reset Content Żądanie powiodło się i przeglądarka powinno wyczyścić (ponownie pobrać) aktualnie przeglądany dokument. Kod ten jest przydatny serwletowi, który pobiera dane z formularza i chce, aby został on wyświetlony w czystej formie.
1.1
SC_PARTIAL_CONTENT
206
Partial Content
Serwer ukończył częściowe żądanie GET i zwrócił część dokumentu określoną w nagłówku Range klienta.
1.1
SC_MULTIPLE_CHOICES
300
Multiple Choices
Żądany URL odnosi się do więcej niż jednego zasobu. Na przykład, URL może odnosić się do dokumentu
1.1
1.0
1.0
przetłumaczonego na wiele języków. Główna część odpowiedzi powinna wyjaśniać klientowi opcje w formie odpowiedniej do typu zawartości żądania. Serwer może zasugerować wybór przy pomocy nagłówka Location. SC_MOVED_PERMANENTLY
301
Moved Permanently
Żądany zasób został na stałe przeniesiony do nowej lokacji. Przyszłe odwołania powinny wykorzystywać w żądaniach nowy URL. Nowe umiejscowienie jest podane w nagłówku Location. Większość przeglądarek automatycznie przekierowuje do nowej lokacji.
1.0
SC_MOVED_TEMPORARILY
302
Moved Temporarily
Żądany zasób został czasowo przeniesiony do innej lokacji, ale przyszłe żądania powinny dalej wykorzystywać oryginalny URI w celu uzyskania dostępu do zasobu. Nowe umiejscowienie jest podane w nagłówku Location. Większość przeglądarek automatycznie przekierowuje do nowej lokacji.
1.0
SC_SEE_OTHER
303
See Other
1.1 Żądany zasób przetworzył żądanie, ale klient powinien pobrać swoją odpowiedź poprzez wykonanie GET na URL-u określonym w nagłówku Location. Kod ten jest przydatny serwletowi, który chce otrzymywać dane POST, po czym przekierowywać klienta do innego zasobu w celu wygenerowania odpowiedzi.
SC_NOT_MODIFIED
304
1.0 Not Modified Żądany dokument nie został zmieniony od daty wymienionej w nagłówku żądania If-ModifiedSince. Zwykłe serwlety nie powinny być zmuszone do korzystania z tego kodu stanu. Zamiast tego implementują one getLastModified().
SC_USE_PROXY
305
Use Proxy
Dostęp do żądanego zasobu musi być uzyskiwany poprzez serwer proxy podany w nagłówku Location.
1.1
SC_BAD_REQUEST
400
Bad Request
Serwer nie mógł zrozumieć żądania, prawdopodobnie z powodu błędu składni.
1.0
SC_UNAUTHORIZED
401
Unauthorized Żądaniu brakuje właściwego uwierzytelnienia. Wykorzystywany w połączeniu z nagłówkami WWWAuthenticate i Authorization.
SC_PAYMENT_REQUIRED
402
Payment
1.0
Zarezerwowany do przyszłego użytku. 1.1 Istnieją propozycje, by
Required
wykorzystywać ten kod w połączeniu z nagłówkiem Charge-To, ale nie stał się on jeszcze standardem w czasie oddawania niniejszej książki do druku.
SC_FORBIDDEN
403
Forbidden
Żądanie zostało zrozumiane, ale serwer nie chce go wypełnić. Serwer może wyjaśnić powody swojego oporu w głównej części odpowiedzi.
1.0
SC_NOT_FOUND
404
Not Found
Żądany zasób nie mógł zostać odnaleziony, lub jest niedostępny.
1.0
SC_METHOD_NOT_ALLOWED
405
Method Not Allowed
Metoda wykorzystywana przez klienta 1.1 nie jest obsługiwana w tym URL-u. Metody obsługiwane muszą zostać wymienione w nagłówku odpowiedzi Accept.
SC_NOT_ACCEPTABLE
406
Not Acceptable
Żądany zasób istnieje, ale w formacie nie przyjmowanym przez klienta (jak wskazano w nagłówku (ach) żądania Accept.
1.1
SC_PROXY_AUTHENTICATION_ 407 REQUIRED
Proxy Serwer proxy musi dokonać Authenticatio uwierzytelnienia, zanim umożliwi n Required przejście dalej. Wykorzystywany z nagłówkiem ProxyAuthenticate. Zwykłe serwlety nie powinny być zmuszone do korzystania z tego kodu stanu.
SC_REQUEST_TIMEOUT
408
Request Timeout
Klient nie dokończył swojego żądania 1.1 w czasie, w którym serwer był skłonny go słuchać.
SC_CONFLICT
409
Conflict
Żądanie nie mogło zostać wypełnione, 1.0 ponieważ nastąpił konflikt z innym żądaniem lub konfiguracją serwera. Kod ten występuje najczęściej w przypadku żądań HTTP PUT. W których plik jest poddawany kontroli oraz w przypadku konfliktów nowych wersji z poprzednimi. Serwer może wysłać opis konfliktu w głównej części odpowiedzi
SC_GONE
410
Gone
Zasób nie jest już dostępny na danym serwerze, a żaden alternatywny adres nie jest znany. Kod ten powinien być wykorzystywany jedynie w przypadku, kiedy zasób został trwale usunięty. Zwykłe serwlety nie powinny być zmuszone do korzystania z tego kodu stanu.
1.1
SC_LENGTH_REQUIRED
411
Length Required
Serwer nie przyjmie żądania bez nagłówka Content-Length.
1.1
SC_PRECONDITION_FAILED
412
Precondition Failed
Wynik wstępnego warunku określonego w jednym lub więcej nagłówku If... wynosi false.
1.1
1.1
SC_REQUEST_ENTITY_TOO_LA 413 RGE
Request Entity Too Large
Serwer nie przetworzy żądania, ponieważ zawartość żądania jest zbyt duża. Jeżeli ograniczenie to jest tymczasowe, serwer może dołączyć nagłówek Retry-After.
1.1
SC_REQUEST_URI_TOO_LONG
414
Request-URI Too Long
Serwer nie przetworzy żądania, ponieważ URI żądania jest dłuższe, niż może przyjąć. Może to nastąpić, kiedy klient przypadkowo przekonwertował żądanie POST na GET. Zwykłe serwlety nie powinny być zmuszone do korzystania z tego kodu stanu.
1.1
SC_UNSUPPORTED_MEDIA_TYP 415 E
Unsupported Media Type
Serwer nie przetworzy żądania, ponieważ jego główna część posiada format nieobsługiwany przez żądany zasób.
1.1
SC_REQUESTED_RANGE_NOT_S 416 ATISFYABLE
Requested Range Not Satisfiable
Zakres zawartości żądanej przez klienta poprzez nagłówek Range koliduje z wielkością żądanego zasobu
1.1 (RFC 2616)
SC_EXPECTATION_FAILED
417
Expectation Failed
Oczekiwanie podane przez klienta poprzez nagłówek Expect nie mogło zostać spełnione.
1.1 (RFC 2616)
SC_INTERNAL_SERVER_ERROR 500
Internal Server Error
Wewnątrz serwera nastąpił 1.0 niespodziewany błąd, który przeszkodziła w wypełnieniu żądania.
SC_NOT_IMPLEMENTED
501
Not Implemented
Serwer nie obsługuje funkcjonalności potrzebnej do wypełnienia żądania.
SC_BAD_GATEWAY
502
Bad Gateway Serwer działający jako brama lub proxy nie otrzymał prawidłowej odpowiedzi od głównego serwera.
1.0
SC_SERVICE_UNAVAILABLE
503
Service Unavailable
Usługa (serwer) jest tymczasowo niedostępna, ale powinna zostać przywrócona w przyszłości. Jeżeli serwer wie, kiedy będzie ponownie dostępny, może dołączyć nagłówek Retry-After.
1.0
SC_GATEWAY_TIMEOUT
504
Gateway Timeout
Serwer działający jako brama lub proxy nie otrzymał prawidłowej odpowiedzi od głównego serwera w czasie oczekiwania.
1.1
HTTP Version Not Supported
Serwer nie obsługuje wersji protokołu 1.1 HTTP wykorzystanej w żądaniu. Główna część odpowiedzi powinna określać protokoły obsługiwane przez serwer. Zwykłe serwlety nie powinny być zmuszone do korzystania z tego kodu stanu.
SC_HTTP_VERSION_NOT_SUPP 505 ORTED
1.0
Dodatek E.
Encje znakowe Poniższa tablica wymienia różne kody ucieczkowe Unicode, encje nazwane i numeryczne HTML dla wszystkich możliwych do wyświetlenia znaków ISO-8859 (Latin-1). Encje nazwane i numeryczne mogą zostać wykorzystane na stronach HTML; są one konwertowane na symbole przez przeglądarki WWW. Kody ucieczkowe Unicode mogą zostać wykorzystane w kodzie serwletów; są one interpretowane przez kompilator Javy. Na przykład, znak funta (£) może zostać osadzony na stronie HTML jako £ lub £. Może również zostać osadzony bezpośrednio w kodzie Javy jako \u00A3. Proszę zauważyć, że nie każdy znak HTML jest uniwersalnie obsługiwany. Kolumna Obsługa pokazuje poziom obsługi znaku. Wartość S oznacza, że numeryczne i nazwane wartości encji dla danego symbolu są częścią standardu HTML. P wskazuje, że wartości encji są proponowanym standardem — nie są częścią standardu HTML, ale większości przypadków są powszechnie obsługiwane. N w tej kolumnie oznacza, że wartości encji nie są standardem i w związku z tym ich obsługa jest ograniczona. W przypadku tych symboli najlepiej jest zastosować kody ucieczkowe Unicode. Kod ucieczkowy Unicode
Encja numeryczna
Encja nazwana
Symbol
Opis
Obsługa
\u0009
\t
Tabulator poziomy
S
\u000A
\n
Koniec linii
S
\u000D
\r
Powrót karetki
S
\u0020
Spacja
S
\u0021
!
!
Wykrzyknik
S
\u0022
"
"
Cudzysłów
S
\u0023
#
#
Płotek
S
\u0024
$
$
Znak dolara
S
\u0025
%
%
Znak procent
S
\u0026
&
&
Znak łączący
S
\u0027
'
'
Apostrof
S
"
&
\u0028
(
(
Lewy nawias
S
\u0029
)
)
Prawy nawias
S
\u002A
*
*
Gwiazdka
S
\u002B
+
+
Znak dodawania
S
\u002C
,
,
Przecinek
S
\u002D
-
-
Myślnik
S
\u002E
.
.
Kropka
S
\u002F
/
/
Ukośnik
S
\u0030-
0-
0-9
Cyfry 0-9
S
\u0039
9
\u003A
:
:
Dwukropek
S
\u003B
;
;
Średnik
S
\u003C
Znak większości
S
\u003F
?
?
Znak zapytania
S
\u0040
@
@
„Małpa”
S
\u0041-
A-
A-Z
Litery A-Z
S
\u005A
Z
\u005B
[
[
Lewy nawias kwadratowy
S
\u005C
\
\
Lewy ukośnik
S
\u005D
]
]
Prawy nawias kwadratowy
S
\u005E
^
^
Karetka
S
\u005F
_
_
Kreska dolna
S
\u0060
`
`
Akcent grave
S
\u0061-
a-
a-z
Litery a-z
S
\u007A
z
\u007B
{
{
Lewy nawias klamrowy
S
\u007C
|
|
Kreska pionowa
S
\u007D
}
}
Prawy nawias klamrowy
S
\u007E
~
~
Tylda
S
\u0082
‚
Dolny lewy nawias pojedynczy N
\u0083
f
Floren
N
\u0084
„
Lewy dolny nawias podwójny
N
\u0085
…
Trzykropek
N
\u0086
†
Sztylet
N
\u0087
‡
Podwójny sztylet
N
\u0088
ˆ
circumflex
N
\u0089
‰
Promil
N
< >
\u008A
Š
Duże S, caron
N
\u008B
‹
Znak mniejszości
N
\u008C
Œ
Duże OE, ligatura
N
\u0091
‘
Lewy pojedynczy cudzysłów
N
\u0092
’
Prawy pojedynczy cudzysłów
N
\u0093
“
Lewy podwójny cudzysłów
N
\u0094
”
Prawy podwójny cudzysłów
N
\u0095
•
Pocisk
N
\u0096
–
Krótki myślnik
N
\u0097
—
Długi myślnik
N
\u0098
~
Tylda
N
\u0099
™
Znak towarowy
N
\u009A
š
Małe s, caron
N
\u009B
›
Znak większości
N
\u009C
œ
Małe oe, ligatura
N
\u009F
Ÿ
Duże Y, umlaut
N
\u00A0
Spacja niełamiąca
P
\u00A1
¡
¡
¡
Odwrócony wykrzyknik
P
\u00A2
¢
¢
¢
Znak centa
P
\u00A3
£
£
£
Znak funta
P
\u00A4
¤
¤
¤
Ogólny znak waluty
P
\u00A5
¥
¥
¥
Znak jena
P
\u00A6
¦
¦
¦
Złamana pionowa kreska
P
\u00A7
§
§
§
Paragraf
P
\u00A8
¨
¨
¨
Umlaut
P
\u00A9
©
©
©
Prawa autorskie
P
\u00AA
ª
ª
ª
Żeński liczebnik porządkowy
P
\u00AB
«
«
«
Lewy cudzysłów kątowy
P
\u00AC
¬
¬
¬
Znak zaprzeczenia
P
\u00AD
-
„Miękki” myślnik
P
\u00AE
®
®
®
Zarejestrowany znak towarowy P
\u00AF
¯
¯
¯
Akcent macron
P
\u00B0
°
°
°
Znak stopnia
P
\u00B1
±
±
±
Plus lub minus
P
\u00B2
²
²
²
Indeks górny 2
P
\u00B3
³
³
³
Indeks górny 3
P
\u00B4
´
´
´
Akcent acute
P
\u00B5
µ
µ
µ
Znak mikro (greckie mi)
P
\u00B6
¶
¶
¶
Znak akapitu
P
\u00B7
·
·
·
Kropka centralna
P
\u00B8
¸
¸
¸
Cedilla
P
\u00B9
¹
¹
¹
Indeks górny 1
P
\u00BA
º
º
º
Męski liczebnik porządkowy
P
\u00BB
»
»
»
Prawy cudzysłów kątowy
P
\u00BC
¼
¼
¼
Ułamek jedna czwarta
P
\u00BD
½
½
½
Ułamek jedna druga
P
\u00BE
¾
¾
¾
Ułamek trzy czwarte
P
\u00BF
¿
¿
¿
Odwrócony znak zapytania
P
\u00C0
À
À
À
Duże A, akcent grave
S
\u00C1
Á
Á
Á
Duże A, akcent acute
S
\u00C2
Â
Â
Â
Duże A, akcent circumflex
S
\u00C3
Ã
Ã
Ă
Duże A, tylda
S
\u00C4
Ä
Ä
Ä
Duże A, umlaut
S
\u00C5
Å
Å
Å
Duże A, okrąg
S
\u00C6
Æ
&Aelig;
Æ
Duże A, ligatura
S
\u00C7
Ç
Ç
Ç
Duże C, cedilla
S
\u00C8
È
È
È
Duże E, akcent grave
S
\u00C9
É
É
É
Duże E, akcent acute
S
\u00CA
Ê
Ê
Ê
Duże E, akcent circumflex
S
\u00CB
Ë
Ë
Ë
Duże E, umlaut
S
\u00CC
Ì
Ì
Ì
Duże I, akcent grave
S
\u00CD
Í
Í
Í
Duże I, akcent acute
S
\u00CE
Î
Î
Î
Duże I, akcent circumflex
S
\u00CF
Ï
Ï
Ï
Duże I, umlaut
S
\u00D0
Ð
Ð
Ð
Duże eth, islandzki
S
\u00D1
Ñ
Ñ
Ñ
Duże N, tylda
S
\u00D2
Ò
Ò
Ò
Duże O, akcent grave
S
\u00D3
Ó
Ó
Ó
Duże O, akcent acute
S
\u00D4
Ô
Ô
Ô
Duże O, akcent circumflex
S
\u00D5
Õ
Õ
Õ
Duże O, tylda
S
\u00D6
Ö
Ö
Ö
Duże O, umlaut
S
\u00D7
×
×
×
Znak mnożenia
P
\u00D8
Ø
Ø
Ø
Duże O, ukośnik
S
\u00D9
Ù
Ù
Ù
Duże U, akcent grave
S
\u00DA
Ú
Ú
Ú
Duże U, akcent acute
S
\u00DB
Û
Û
Û
Duże U, akcent circumflex
S
\u00DC
Ü
Ü
Ü
Duże U, umlaut
S
\u00DD
Ý
Ý
Ý
Duże Y, akcent acute
S
\u00DE
Þ
Þ
Þ
Duże thorn, islandzki
S
\u00DF
ß
ß
ß
Małe sz, ligatura, niemiecki
S
\u00E0
à
à
à
Małe a, akcent grave
S
\u00E1
á
á
á
Małe a, akcent acute
S
\u00E2
â
â
â
Małe a, akcent circumflex
S
\u00E3
ã
ã
ã
Małe a, tylda
S
\u00E4
ä
ä
ä
Małe a, umlaut
S
\u00E5
å
å
å
Małe a, okrąg
S
\u00E6
æ
æ
æ
Małe a, ligatura
S
\u00E7
ç
ç
ç
Małe c, cedilla
S
\u00E8
è
è
è
Małe e, akcent grave
S
\u00E9
é
é
é
Małe e, akcent acute
S
\u00EA
ê
ê
ê
Małe e, akcent circumflex
S
\u00EB
ë
ë
ë
Małe e, umlaut
S
\u00EC
ì
ì
ì
Małe i, akcent grave
S
\u00ED
í
í
í
Małe i, akcent acute
S
\u00EE
î
î
î
Małe i, akcent circumflex
S
\u00EF
ï
ï
ï
Małe i, umlaut
S
\u00F0
ð
ð
ð
Małe eth, islandzki
S
\u00F1
ñ
ñ
ñ
Małe n, tylda
S
\u00F2
ò
ò
ò
Małe o, akcent grave
S
\u00F3
ó
ó
ó
Małe o, akcent acute
S
\u00F4
ô
ô
ô
Małe o, akcent circumflex
S
\u00F5
õ
õ
õ
Małe o, tylda
S
\u00F6
ö
ö
ö
Małe o, umlaut
S
\u00F7
÷
÷
÷
Znak dzielenia
P
\u00F8
ø
ø
ø
Małe o, ukośnik
S
\u00F9
ù
ù
ù
Małe u, akcent grave
S
\u00FA
ú
ú
ú
Małe u, akcent acute
S
\u00FB
û
û
û
Małe u, akcent circumflex
S
\u00FC
ü
ü
ü
Małe u, umlaut
S
\u00FD
ý
ý
ý
Małe y, akcent acute
S-
\u00FE
þ
þ
þ
Małe thorn, islandzki
S
\u00FF
ÿ
ÿ
ÿ
Małe y, umlaut
S
Dodatek F.
Kodowania Poniższa tabela zawiera sugerowane kodowania dla dużej ilości języków. Kodowania są wykorzystywane przez serwlety generujące informacje w wielu językach; określają one, które kodowanie znaków powinien wykorzystać PrintWriter serwletów. Domyślnie PrintWriter wykorzystuje kodowanie ISO-8859-1 (Latin-1), właściwe dla większości języków zachodnioeuropejskich. Aby określić alternatywne kodowanie, wartość kodowania musi zostać przekazana metodzie setContentType() zanim serwlet pobierze swój PrintWriter, na przykład: odp.setContentType("text/html; charset=Shift_JIS"); // Kodowanie japońskie PrintWriter wyj = odp.getWriter; // Zapisuje japoński Shift_JIS
Kodowanie może być również ustawione pośrednio, przy pomocy metody setLocale(), na przykład: odp.setContentType("text/html"); odp.setLocale(new Locale("ja", "")); // Ustawia kodowanie na Shift_JIS PrintWriter wyj = odp.getWriter; // Zapisuje japoński Shift_JIS
Metoda setLocale() przypisuje odpowiedzi kodowanie zgodnie z poniższą tabelą. W przypadku, gdy możliwe jest więcej niż jedno kodowanie, wybierane jest kodowanie umieszczone w tabeli na pierwszej pozycji. Proszę zauważyć, że nie wszystkie przeglądarki WWW obsługują wszystkie kodowania, lub posiadają czcionki, dzięki którym istnieje możliwość wyświetlania wszystkich znaków, chociaż wszystkie klienty obsługują przynajmniej ISO-8859-1. Proszę także pamiętać, że kodowanie UTF-8 może reprezentować wszystkie znaki Unicode, a w związku z tym może być uznane za właściwe dla wszystkich języków. Język
Kod języka
Sugerowane kodowania
angielski
en
ISO-8859-1
Albański
sq
ISO-8859-2
Arabski
ar
ISO-8859-6
białoruski
be
ISO-8859-5
Bułgarski
bg
ISO-8859-5
chiński (tradycyjny / Tajwan)
zh (kraj TW)
Big5
chiński (uproszczony / kontynentalny
zh
GB2312
Chorwacki
hr
ISO-8859-2
czeski
cs
ISO-8859-2
duński
da
ISO-8859-1
holenderski
nl
ISO-8859-1
estoński
et
ISO-8859-1
fiński
fi
ISO-8859-1
francuski
fr
ISO-8859-1
grecki
el
ISO-8859-7
hebrajski
he (dawniej iw)
ISO-8859-8
hiszpański
es
ISO-8859-1
islandzki
is
ISO-8859-1
japoński
ja
Shift_JIS, ISO-2022-JP, EUC-JP1
kataloński (hiszpański)
ca
ISO-8859-1
koreański
ko
EUC-KR2
litewski
lt
ISO-8859-2
łotewski
lv
ISO-8859-2
macedoński
mk
ISO-8859-5
niemiecki
de
ISO-8859-1
polski
pl
ISO-8859-2
portugalski
pt
ISO-8859-1
rosyjski
ru
ISO-8859-5, KOI8-R
rumuński
ro
ISO-8859-2
serbski
sr
ISO-8859-5, KOI8-R
serbsko-chorwacki
sh
ISO-8859-5, ISO-8859-2, KOI8-R
słowacki
sk
ISO-8859-2
słoweński
sl
ISO-8859-2
szwedzki
sv
ISO-8859-1
turecki
tr
ISO-8859-9
ukraiński
uk
ISO-8859-5, KOI8-R
węgierski
hu
ISO-8859-2
włoski
it
ISO-8859-1
1
Obsługiwany po raz pierwszy w JDK 1.1.6. Poprzednie wersje JDK znają zestaw znaków EUC-JP pod nazwą EUCJIS, tak więc w celu zapewnienia przenośności można ustawić kodowanie na EUC_JP i samodzielnie skonstruować PrintWriter EUCJIS.
2
Obsługiwany po raz pierwszy w JDK 1.1.6. Poprzednie wersje JDK znają zestaw znaków EUC-KR pod nazwą KSC_5601, tak więc w celu zapewnienia przenośności można ustawić kodowanie na EUC_KR i samodzielnie skonstruować PrintWriter KSC_5601.