Java Servlet - Programowanie

489
Spis treści SPIS TREŚCI........................................................................................................................................................... 1 O AUTORACH....................................................................................................................................................... 9 WSTĘP................................................................................................................................................................... 10 SERVLET API 2.2................................................................................................................................................... 10 Servlet API 2.3................................................................................................................................................ 11 CZYTELNICY PIERWSZEGO WYDANIA............................................................................................................................ 11 CZYTELNICY........................................................................................................................................................... 12 Co należy wiedzieć.......................................................................................................................................... 12 PRZYKŁADY............................................................................................................................................................ 12 ORGANIZACJA..........................................................................................................................................................13 KONWENCJE WYKORZYSTYWANE W TEJ KSIĄŻCE........................................................................................................... 15 PROŚBA O KOMENTARZE............................................................................................................................................ 15 PODZIĘKOWANIA...................................................................................................................................................... 15 PODZIĘKOWANIA Z WYDANIA PIERWSZEGO.................................................................................................................... 16 ROZDZIAŁ 1. WPROWADZENIE.................................................................................................................... 18 HISTORIA APLIKACJI WWW..................................................................................................................................... 18 Common Gateway Interface........................................................................................................................... 18 Inne rozwiązania............................................................................................................................................. 20 Serwlety Javy.................................................................................................................................................. 21 OBSŁUGA SERWLETÓW.............................................................................................................................................. 22 Samodzielne kontenery serwletów.................................................................................................................. 22 Dołączane kontenery serwletów..................................................................................................................... 23 Osadzane kontenery serwletów....................................................................................................................... 24 Uwagi dodatkowe........................................................................................................................................... 24 POTĘGA SERWLETÓW................................................................................................................................................ 24 Przenośność.................................................................................................................................................... 24 Moc................................................................................................................................................................. 25 Wydajność i wytrzymałość.............................................................................................................................. 25 Bezpieczeństwo............................................................................................................................................... 25 Elegancja........................................................................................................................................................ 26 Integracja........................................................................................................................................................ 26 Rozszerzalność i elastyczność......................................................................................................................... 26 ROZDZIAŁ 2. APLETY HTTP —WPROWADZENIE................................................................................... 27 PODSTAWY HTTP.................................................................................................................................................. 27 Zlecenia, odpowiedzi, nagłówki...................................................................................................................... 27 Metody GET i POST....................................................................................................................................... 28 Pozostałe metody Http.................................................................................................................................... 29 INTERFEJS API (SERVLET API)................................................................................................................................ 29 TWORZENIE STRONY................................................................................................................................................. 30 Pisanie „Hello World”................................................................................................................................... 30 Uruchamianie „Hello World”........................................................................................................................ 31

Transcript of Java Servlet - Programowanie

Page 1: Java Servlet - Programowanie

Spis treści

SPIS TREŚCI...........................................................................................................................................................1

O AUTORACH....................................................................................................................................................... 9

WSTĘP...................................................................................................................................................................10

SERVLET API 2.2................................................................................................................................................... 10Servlet API 2.3................................................................................................................................................ 11

CZYTELNICY PIERWSZEGO WYDANIA............................................................................................................................11CZYTELNICY........................................................................................................................................................... 12

Co należy wiedzieć..........................................................................................................................................12PRZYKŁADY............................................................................................................................................................ 12ORGANIZACJA..........................................................................................................................................................13KONWENCJE WYKORZYSTYWANE W TEJ KSIĄŻCE........................................................................................................... 15PROŚBA O KOMENTARZE............................................................................................................................................15PODZIĘKOWANIA......................................................................................................................................................15PODZIĘKOWANIA Z WYDANIA PIERWSZEGO....................................................................................................................16

ROZDZIAŁ 1. WPROWADZENIE.................................................................................................................... 18

HISTORIA APLIKACJI WWW..................................................................................................................................... 18Common Gateway Interface........................................................................................................................... 18Inne rozwiązania.............................................................................................................................................20Serwlety Javy.................................................................................................................................................. 21

OBSŁUGA SERWLETÓW..............................................................................................................................................22Samodzielne kontenery serwletów.................................................................................................................. 22Dołączane kontenery serwletów..................................................................................................................... 23Osadzane kontenery serwletów.......................................................................................................................24Uwagi dodatkowe........................................................................................................................................... 24

POTĘGA SERWLETÓW................................................................................................................................................ 24Przenośność.................................................................................................................................................... 24Moc................................................................................................................................................................. 25Wydajność i wytrzymałość.............................................................................................................................. 25Bezpieczeństwo............................................................................................................................................... 25Elegancja........................................................................................................................................................ 26Integracja........................................................................................................................................................26Rozszerzalność i elastyczność......................................................................................................................... 26

ROZDZIAŁ 2. APLETY HTTP —WPROWADZENIE................................................................................... 27

PODSTAWY HTTP.................................................................................................................................................. 27Zlecenia, odpowiedzi, nagłówki......................................................................................................................27Metody GET i POST....................................................................................................................................... 28Pozostałe metody Http.................................................................................................................................... 29

INTERFEJS API (SERVLET API)................................................................................................................................ 29TWORZENIE STRONY................................................................................................................................................. 30

Pisanie „Hello World”................................................................................................................................... 30Uruchamianie „Hello World”........................................................................................................................ 31

Page 2: Java Servlet - Programowanie

Przetwarzanie danych formularzowych..........................................................................................................32Obsługa zleceń POST..................................................................................................................................... 34Obsługa zleceń HEAD.................................................................................................................................... 34

APLIKACJE WWW................................................................................................................................................. 35Katalog WEB-INF...........................................................................................................................................36Deskryptor wdrożenia.....................................................................................................................................37

PRZEJDŹMY DALEJ....................................................................................................................................................40

ROZDZIAŁ 3. CZAS ISTNIENIA (CYKL ŻYCIA) APLETU........................................................................ 41

ALTERNATYWA APLETU.............................................................................................................................................41Pojedyncza maszyna wirtualna Javy.............................................................................................................. 41Trwałość kopii.................................................................................................................................................42Liczniki............................................................................................................................................................42Liczniki zsynchronizowane..............................................................................................................................43Liczniki całościowe......................................................................................................................................... 45

ODNAWIANIE (POWTÓRNE ŁADOWANIE) APLETU............................................................................................................ 46METODY „INIT” I „DESTROY”...................................................................................................................................46

Licznik z metodą Init....................................................................................................................................... 47Licznik z metodami Init i Destroy................................................................................................................... 49Model jedno-wątkowy (Single-Thread Model)............................................................................................... 51

PRZETWARZANIE DRUGOPLANOWE...............................................................................................................................53URUCHAMIANIE I ROZRUCH........................................................................................................................................54BUFOROWANIE PODRĘCZNE PO STRONIE KLIENTA........................................................................................................... 55BUFOROWANIE ZEWNĘTRZNE PO STRONIE SERWERA........................................................................................................57

ROZDZIAŁ 4. ODCZYTYWANIE INFORMACJI.......................................................................................... 65

APLET....................................................................................................................................................................66Pobieranie parametru początkowego apletu.................................................................................................. 66Pobieranie nazw parametrów początkowych apletów....................................................................................67Pobieranie nazwy apletu.................................................................................................................................68

SERWER ................................................................................................................................................................ 68Pobieranie informacji o serwerze...................................................................................................................68Zapisywanie w pliku tymczasowym.................................................................................................................70Ograniczanie działania apletu do serwera.....................................................................................................71Pobieranie kontekstowego parametru początkowego.................................................................................... 72Ustalanie wersji apletu................................................................................................................................... 73

KLIENT.................................................................................................................................................................. 75Pobieranie informacji o komputerze klienta...................................................................................................75Ograniczanie dostępu..................................................................................................................................... 76Pobieranie informacji o użytkowniku............................................................................................................. 77Personalizowane powitanie............................................................................................................................ 78Zlecenie ..........................................................................................................................................................79Parametry zlecenia......................................................................................................................................... 79Generowanie Klucza Licencji......................................................................................................................... 81Informacje ścieżki........................................................................................................................................... 83Pobieranie typów MIME.................................................................................................................................86Podawanie plików...........................................................................................................................................87Czytanie z zasobów oddzielonych................................................................................................................... 88Podawanie zasobów........................................................................................................................................90Podawanie zasobów do ściągnięcia............................................................................................................... 92Określanie co było przedmiotem zlecenia...................................................................................................... 93Sposób złożenia zlecenia.................................................................................................................................94Nagłówki zleceniowe.......................................................................................................................................95Strumień wyjściowy.........................................................................................................................................98Ładowanie plików przy użyciu strumienia wejściowego................................................................................ 99Dodatkowe atrybuty......................................................................................................................................106

ROZDZIAŁ 5. PRZESYŁANIE INFORMACJI HTTP..................................................................................108

STRUKTURA ODPOWIEDZI.........................................................................................................................................108PRZESYŁANIE STANDARDOWEJ ODPOWIEDZI ............................................................................................................... 109KORZYSTANIE ZE POŁĄCZEŃ STAŁYCH....................................................................................................................... 110

Page 3: Java Servlet - Programowanie

BUFOROWANIE ODPOWIEDZI.....................................................................................................................................111Regulowanie bufora odpowiedzi...................................................................................................................112

KODY STATUSU..................................................................................................................................................... 114Ustanawianie kodu statusu........................................................................................................................... 114Ulepszanie apletu „ViewFile” przy pomocy kodów statusu.........................................................................115Nagłówki HTTP ........................................................................................................................................... 116

USTAWIANIE NAGŁÓWKÓW HTTP .......................................................................................................................... 117Przekierowywanie zlecenia...........................................................................................................................118Nadzorowanie łączników do innych stron.................................................................................................... 119Klienckie ściąganie z serwera.......................................................................................................................120

ROZWIĄZYWANIE PROBLEMÓW................................................................................................................................. 121Kody statusu..................................................................................................................................................122Konfiguracja stron błędu.............................................................................................................................. 123Rejestracja.................................................................................................................................................... 125Raportowanie................................................................................................................................................126Wyjątki.......................................................................................................................................................... 127Wyjątki apletowe „ServletException”.......................................................................................................... 127Wyjątki apletowe „UnavailableException”..................................................................................................128Konfiguracja Stron Wyjątków.......................................................................................................................128Jak rozpoznać, że nikt nie oczekuje na sygnały?.......................................................................................... 130

SZEŚĆ SPOSOBÓW WYCIĄGANIA KORZYŚCI Z APLETÓW ................................................................................................. 131

ROZDZIAŁ 6. PRZESYŁANIE TREŚCI MULTIMEDIALNEJ.................................................................. 134

WAP I WML..................................................................................................................................................... 134WML..............................................................................................................................................................136Symulatory urządzeń WAP............................................................................................................................137Podawanie treści WAP................................................................................................................................. 137Dynamiczna treść WAP.................................................................................................................................138Poznajmy WAP..............................................................................................................................................142

OBRAZKI.............................................................................................................................................................. 142Generowanie obrazków................................................................................................................................ 142Obrazek „Hello World”................................................................................................................................143Dynamicznie generowany schemat...............................................................................................................145Składanie obrazu...........................................................................................................................................147Rysowanie obrazków.....................................................................................................................................147Obrazki — efekty...........................................................................................................................................152

TREŚĆ SKOMPRESOWANA.........................................................................................................................................155SERWER CYKLICZNY............................................................................................................................................... 157

ROZDZIAŁ 7. ŚLEDZENIE SESJI ..................................................................................................................161

UWIERZYTELNIANIE UŻYTKOWNIKA........................................................................................................................... 162UKRYTE POLA DANYCH FORMULARZA........................................................................................................................163PRZEPISYWANIE URL-U......................................................................................................................................... 165TRWAŁE COOKIES...................................................................................................................................................167

Praca z cookies............................................................................................................................................. 167Robienie zakupów przy pomocy trwałych cookies........................................................................................169

API — ŚLEDZENIE SESJI.........................................................................................................................................171Podstawy śledzenia sesji............................................................................................................................... 171Wykorzystywanie śledzenia sesji — liczba wizyt...........................................................................................172Czas istnienia (cykl życia) sesji.....................................................................................................................173Ustawianie terminu ważności sesji............................................................................................................... 174Metody czasu trwania................................................................................................................................... 176Awaryjne zmiany trybu pracy — „nie-ciasteczkowe”.................................................................................. 178Robienie zakupów przy użyciu śledzenia sesji.............................................................................................. 182

ROZDZIAŁ 8. BEZPIECZEŃSTWO............................................................................................................... 184

Uwierzytelnienie poprzez HTTP................................................................................................................... 185KONFIGURACJA UWIERZYTELNIENIA HTTP................................................................................................................185

Uwierzytelnienie w oparciu o rolę................................................................................................................185Ograniczanie dostępu do serwletu................................................................................................................186

WYSZUKIWANIE INFORMACJI UWIERZYTELNIENIA......................................................................................................... 188

Page 4: Java Servlet - Programowanie

Uwierzytelnienie w oparciu o formularz.......................................................................................................189Uwierzytelnienie niestandardowe.................................................................................................................191Certyfikaty cyfrowe....................................................................................................................................... 195Protokół bezpiecznej transmisji danych (SSL)..............................................................................................197

ROZDZIAŁ 9. ŁĄCZNOŚĆ Z BAZĄ DANYCH.............................................................................................202

RELACYJNE BAZY DANYCH...................................................................................................................................... 203JDBC API..........................................................................................................................................................205

Sterowniki JDBC...........................................................................................................................................205Połączenie z bazą danych............................................................................................................................. 206Otwieranie połączenia z serwletu................................................................................................................. 207Wykonywanie zapytań SQL...........................................................................................................................208Obsługa wyjątków SQL.................................................................................................................................209Zestawy wyników w szczegółach...................................................................................................................210Obsługa pól mających wartość null..............................................................................................................211Aktualizacja baz danych............................................................................................................................... 212Użycie gotowych zapytań..............................................................................................................................213Ponowne użycie obiektów bazy danych........................................................................................................ 214Ponowne użycie połączeń do baz danych..................................................................................................... 214Ponowne użycie przygotowanych wyrażeń................................................................................................... 215

TRANSAKCJE......................................................................................................................................................... 215Użycie transakcji w JDBC............................................................................................................................ 216Optymalizacja transakcji.............................................................................................................................. 217Pule połączeń................................................................................................................................................217Serwlet księgi gości.......................................................................................................................................221

ZAAWANSOWANE TECHNIKI JDBC...........................................................................................................................225Przechowywane procedury........................................................................................................................... 225Pliki binarne i księgi, czyli bardzo duże obiekty...........................................................................................226

CO DALEJ?........................................................................................................................................................... 227

ROZDZIAŁ 10. KOMUNIKACJA APLET-SERWLET................................................................................ 229

OPCJE KOMUNIKACJI...............................................................................................................................................229Aplety godne i niegodne zaufania.................................................................................................................229Połączenia przez HTTP i zwykłe porty......................................................................................................... 230Serwlety i serializacja obiektu...................................................................................................................... 231JDBC, RMI i CORBA....................................................................................................................................232Podejście hybrydowe.................................................................................................................................... 233

SERWER GODZINY.................................................................................................................................................. 233Aplet.............................................................................................................................................................. 233Oparta na tekście komunikacja HTTP..........................................................................................................236Oparta na obiektach komunikacja HTTP..................................................................................................... 240Komunikacja przez port................................................................................................................................ 242Komunikacja RMI......................................................................................................................................... 248Wskazówka!!!!...............................................................................................................................................253W którym miejscu uruchomić rejestr?.......................................................................................................... 253

SERWER POGAWĘDEK..............................................................................................................................................255Projekt...........................................................................................................................................................256Serwlet...........................................................................................................................................................257Aplet HTTP................................................................................................................................................... 260Aplet łączący się przez port...........................................................................................................................262Aplet RMI......................................................................................................................................................263Dyspozytor.................................................................................................................................................... 266

ROZDZIAŁ 11. WSPÓŁPRACA SERWLETÓW...........................................................................................268

DZIELENIE INFORMACJI............................................................................................................................................268Współdzielenie przy pomocy ServletContext................................................................................................ 268Współdzielenie z innym ServletContext........................................................................................................ 270

DZIELENIE KONTROLI..............................................................................................................................................270Pobieranie dyspozytora żądań......................................................................................................................271Dyspozycja przekazaniem............................................................................................................................. 271Przekazanie czy przekierowanie................................................................................................................... 273

Page 5: Java Servlet - Programowanie

Dyspozycja dołączania................................................................................................................................. 274

ROZDZIAŁ 12. SERWLETY KORPORACYJNE I J2EE.............................................................................277

DYSTRYBUCJA ŁADUNKU.........................................................................................................................................277Jak być dystrybuowalnym............................................................................................................................. 278Wiele stylów dystrybucji................................................................................................................................279

INTEGRACJA Z J2EE.............................................................................................................................................. 279Podział pracy w J2EE...................................................................................................................................280Pozycje środowiskowe.................................................................................................................................. 280Odwołania do elementów EJB......................................................................................................................282Odwołania do zewnętrznych fabryk zasobów............................................................................................... 282Dystrybucja serwletów w środowisku J2EE................................................................................................. 283

ROZDZIAŁ 13. INTERNACJONALIZACJA................................................................................................. 284

JĘZYKI ZACHODNIOEUROPEJSKIE................................................................................................................................284Encje znakowe HTML................................................................................................................................... 285Kody ucieczkowe Unicode............................................................................................................................ 286

HOŁDOWANIE LOKALNYM ZWYCZAJOM...................................................................................................................... 287JĘZYKI SPOZA EUROPY ZACHODNIEJ......................................................................................................................... 288

Kodowanie.................................................................................................................................................... 288Tworzenie wyników zakodowanych.............................................................................................................. 289Odczyt i zapis wyników zakodowanych........................................................................................................ 290

WIĘKSZA ILOŚĆ JĘZYKÓW........................................................................................................................................291UCS-2 i UTF-8..............................................................................................................................................291Tworzenie UTF-8.......................................................................................................................................... 292

DYNAMICZNA NEGOCJACJA JĘZYKA........................................................................................................................... 293Preferencje językowe.................................................................................................................................... 294Preferencje kodowania................................................................................................................................. 294Pakiety zasobów............................................................................................................................................294Wyświetlanie odpowiednich informacji........................................................................................................ 295Klasa LocaleNegotiator................................................................................................................................296Lokalizacje dostarczane przez system...........................................................................................................299

FORMULARZE HTML............................................................................................................................................299Ukryte kodowanie......................................................................................................................................... 300

ROZDZIAŁ 14. SZKIELET TEA......................................................................................................................304

JĘZYK TEA........................................................................................................................................................... 304POCZĄTKI............................................................................................................................................................. 305INFORMACJA O ŻĄDANIU..........................................................................................................................................306

Sięganie głębiej.............................................................................................................................................307ADMINISTRACJA TEA..............................................................................................................................................308

Skompilowane szablony: szansa dla biznesu................................................................................................311ZASTOSOWANIA TEA.............................................................................................................................................. 312

Przetwarzanie tekstu..................................................................................................................................... 312Obsługa zawartości.......................................................................................................................................313Obsługa żądań/odpowiedzi...........................................................................................................................314Tworzenie aplikacji Tea................................................................................................................................315

APLIKACJA „NARZĘDZIA”....................................................................................................................................... 317OSTATNIE SŁOWO...................................................................................................................................................324

ROZDZIAŁ 15. WEBMACRO..........................................................................................................................325

SZKIELET WEBMACRO...........................................................................................................................................325Powitanie przy pomocy WebMacro.............................................................................................................. 326

INSTALACJA WEBMACRO....................................................................................................................................... 328Język szablonów WebMacro......................................................................................................................... 329Narzędzia kontekstu WebMacro................................................................................................................... 330

INSTRUKCJE WEBMACRO....................................................................................................................................... 332#if.................................................................................................................................................................. 332#set................................................................................................................................................................ 332#foreach........................................................................................................................................................ 332#parse............................................................................................................................................................333

Page 6: Java Servlet - Programowanie

#include.........................................................................................................................................................333#param.......................................................................................................................................................... 333#use............................................................................................................................................................... 333

SZABLONY WEBMACRO......................................................................................................................................... 334Gotowy do ponownego wykorzystania serwlet MacroPrzegl....................................................................... 335Przetwarzanie szablonów..............................................................................................................................337

APLIKACJA „NARZĘDZIA”....................................................................................................................................... 337FILTRY................................................................................................................................................................. 341

Filtry niestandardowe...................................................................................................................................341

ROZDZIAŁ 16. ELEMENT CONSTRUCTION SET.....................................................................................342

ELEMENTY STRONY JAKO OBIEKTY............................................................................................................................342WYŚWIETLANIE ZBIORU WYNIKÓW............................................................................................................................ 343

Dostosowywanie wyświetlania......................................................................................................................345

ROZDZIAŁ 17. XMLC.......................................................................................................................................351

PROSTA KOMPILACJA XML.................................................................................................................................... 351KLASA MANIPULACYJNA..........................................................................................................................................358

Modyfikacja listy...........................................................................................................................................359APLIKACJA „NARZĘDZIA”....................................................................................................................................... 361

ROZDZIAŁ 18. JAVASERVER PAGES..........................................................................................................367

WYKORZYSTYWANIE JAVASERVER PAGES................................................................................................................. 368ZASADY DZIAŁANIA................................................................................................................................................ 369WYRAŻENIA I DEKLARACJE......................................................................................................................................371INSTRUKCJE.......................................................................................................................................................... 371

Wykorzystanie instrukcji............................................................................................................................... 372Unikanie kodu Javy na stronach JSP........................................................................................................... 374

JSP I JAVABEANS................................................................................................................................................. 375Osadzanie komponentu Bean........................................................................................................................375Kontrola parametrów komponentu...............................................................................................................377Powitania przy pomocy komponentu............................................................................................................ 377

DOŁĄCZENIA I PRZEKAZANIA....................................................................................................................................379APLIKACJA „NARZĘDZIA”....................................................................................................................................... 380BIBLIOTEKI WŁASNYCH ZNACZNIKÓW.........................................................................................................................383

Stosowanie bibliotek własnych znaczników..................................................................................................383Aplikacja „Narzędzia” wykorzystująca bibliotekę własnych znaczników....................................................385

ROZDZIAŁ 19. INFORMACJE DODATKOWE........................................................................................... 387

ANALIZA PARAMETRÓW...........................................................................................................................................387Kod ParameterParser...................................................................................................................................388

WYSYŁANIE POCZTY ELEKTRONICZNEJ.......................................................................................................................391Stosowanie klasy MailMessage.....................................................................................................................391Wysyłanie pocztą danych formularza........................................................................................................... 392

STOSOWANIE WYRAŻEŃ REGULARNYCH......................................................................................................................392Odnajdywanie łącz przy pomocy wyrażeń regularnych............................................................................... 393

URUCHAMIANIE PROGRAMÓW...................................................................................................................................396Finger............................................................................................................................................................396Uruchamianie polecenia finger.................................................................................................................... 396Uruchamianie finger z argumentami............................................................................................................397Uruchamianie finger z przekierowywanym wynikiem.................................................................................. 398

STOSOWANIE RDZENNYCH METOD............................................................................................................................. 398WYSTĘPOWANIE JAKO KLIENT RMI..........................................................................................................................399USUWANIE BŁĘDÓW............................................................................................................................................... 400

Sprawdzenie dzienników zdarzeń..................................................................................................................400Wyświetlenie dodatkowych informacji..........................................................................................................401Stosowanie standardowego programu uruchomieniowego..........................................................................401Analiza żądania klienta.................................................................................................................................402Utworzenie własnego żądania klienta.......................................................................................................... 403Wykorzystanie niezależnego narzędzia......................................................................................................... 405Ostatnie wskazówki.......................................................................................................................................405

Page 7: Java Servlet - Programowanie

POPRAWA WYDAJNOŚCI........................................................................................................................................... 406Tworzyć, ale nie przesadzać......................................................................................................................... 406Nie łączyć...................................................................................................................................................... 406Ograniczać synchronizację...........................................................................................................................406Buforować dane wprowadzane i wyświetlane.............................................................................................. 407Spróbować wykorzystania OutputStream..................................................................................................... 407Wykorzystać narzędzie profilujące............................................................................................................... 407

ROZDZIAŁ 20. ZMIANY W SERVLET API 2.3............................................................................................408

ZMIANY W SERVLET API 2.3................................................................................................................................. 408Serwlety w J2SE i J2EE................................................................................................................................ 409Filtry............................................................................................................................................................. 409Zdarzenia okresu trwałości...........................................................................................................................411Wybranie kodowania znaków....................................................................................................................... 413Zależności plików JAR.................................................................................................................................. 413Mechanizmy ładowania klas.........................................................................................................................414Nowe atrybuty błędów.................................................................................................................................. 414Nowe atrybuty bezpieczeństwa..................................................................................................................... 416Niewielkie poprawki......................................................................................................................................417Wyjaśnienia deskryptora DTD......................................................................................................................418

KONKLUZJA.......................................................................................................................................................... 418

DODATEK A. KRÓTKI OPIS SERVLET API...............................................................................................419

GenericServlet...............................................................................................................................................419RequestDipatcher..........................................................................................................................................422Servlet........................................................................................................................................................... 423ServletConfig.................................................................................................................................................423ServletContext............................................................................................................................................... 424ServletException........................................................................................................................................... 428ServletInputStream........................................................................................................................................429ServletOutputStream.....................................................................................................................................429ServletRequest...............................................................................................................................................430ServletResponse............................................................................................................................................ 434SingleThreadModel.......................................................................................................................................436UnavailableException ..................................................................................................................................437

DODATEK B. KRÓTKI OPIS HTTP SERVLET API................................................................................... 439

Cookie........................................................................................................................................................... 440HttpServlet.................................................................................................................................................... 443HttpServletRequest........................................................................................................................................445HttpServletResponse..................................................................................................................................... 448HttpSession................................................................................................................................................... 452HttpSessionBindingEvent..............................................................................................................................454HttpSessionBindingListener..........................................................................................................................455HttpSessionContext.......................................................................................................................................456HttpUtils........................................................................................................................................................456

DODATEK C. KRÓTKI OPIS DESKRYPTORÓW DTD.............................................................................458

<auth-constraint>........................................................................................................................................ 462<auth-method>.............................................................................................................................................463<context-param>..........................................................................................................................................463<description>............................................................................................................................................... 463<display-name>............................................................................................................................................463<distributable>.............................................................................................................................................463<ejb-link>.....................................................................................................................................................464<ejb-ref>...................................................................................................................................................... 464<ejb-ref-name>............................................................................................................................................ 464<ejb-ref-type>.............................................................................................................................................. 465<env-entry>.................................................................................................................................................. 465<env-entry-name>........................................................................................................................................ 465<env-entry-type>.......................................................................................................................................... 465

Page 8: Java Servlet - Programowanie

<env-entry-value>........................................................................................................................................ 466<error-code>................................................................................................................................................466<error-page>................................................................................................................................................466<exception-type>..........................................................................................................................................466<extension>.................................................................................................................................................. 467<form-error-page>.......................................................................................................................................467<form-login-config>.....................................................................................................................................467<form-login-page>.......................................................................................................................................467<home>........................................................................................................................................................ 468<http-method>..............................................................................................................................................468<icon>.......................................................................................................................................................... 468<init-param>................................................................................................................................................ 468<jsp-file>...................................................................................................................................................... 469<large-icon>................................................................................................................................................ 469<load-on-startup>........................................................................................................................................469<location>.................................................................................................................................................... 469<login-config>............................................................................................................................................. 470<mime-mapping>......................................................................................................................................... 470<mime-type>................................................................................................................................................ 470<param-name>.............................................................................................................................................470<param-value>.............................................................................................................................................470<realm-name>..............................................................................................................................................471<remote>...................................................................................................................................................... 471<res-auth>....................................................................................................................................................471<res-ref-name>............................................................................................................................................ 471<res-type>.................................................................................................................................................... 472<resource-ref>............................................................................................................................................. 472<role-link>................................................................................................................................................... 472<role-name>.................................................................................................................................................472<security-constraint>...................................................................................................................................473<security-role>.............................................................................................................................................473<security-role-ref>.......................................................................................................................................473<servlet>.......................................................................................................................................................473<servlet-class>............................................................................................................................................. 474<servlet-mapping>....................................................................................................................................... 474<servlet-name>............................................................................................................................................ 474<session-config>.......................................................................................................................................... 474<session-timeout>........................................................................................................................................ 475<small-icon>................................................................................................................................................ 475<taglib>........................................................................................................................................................475<taglib-location>......................................................................................................................................... 475<taglib-uri>..................................................................................................................................................476<transport-guarantee>.................................................................................................................................476<url-pattern>................................................................................................................................................476<user-data-constraint>................................................................................................................................ 476<web-app >.................................................................................................................................................. 477<web-resource-collection>.......................................................................................................................... 477<web-resource-name>................................................................................................................................. 477<welcome-file>.............................................................................................................................................478<welcome-file-list>.......................................................................................................................................478

DODATEK D. KODY STANU HTTP.............................................................................................................. 479

DODATEK E. ENCJE ZNAKOWE..................................................................................................................484

DODATEK F. KODOWANIA...........................................................................................................................488

Page 9: Java Servlet - Programowanie

O Autorach

Jason Hunter jest starszym technologiem w firmie CollabNet (http://collab.net), firmie dostarczającej narzędzia iusługi dla współpracy Open Source. Oprócz bycia autorem książki „Java Servlet — programowanie” jest takżeredaktorem witryny Servlets.com, twórcą biblioteki com.oreilly.servlet, współpracownikiem projektu ApacheJakarta, 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 KomitetuWykonawczego JCP nadzorującego platformę Javy, jako reprezentant Apache Software Foundation. Piszerównież artykuły dla JavaWorld oraz przemawia na wielu konferencjach programistycznych i Open Source. Wostatnich 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 ikonsultacjach związanych z Javą i działał jako wynajęty ekspert dla wielu przedsiębiorstw włączając w to SunMicrosystems. Jeszcze wcześniej pracował w Silicon Graphics, gdzie był odpowiedzialny za tworzenie (iniszczenie) 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 technologiamiprogramowania 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 programieinformatycznym szpitala Children's Hospital w Bostonie, gdzie pomagał przy tworzeniu pierwszegoelektronicznego systemu zapisów medycznych opartego na sieci WWW i był związany z jednymi z pierwszychkorporacyjnych zastosowań języka Java. Był konsultantem projektów sieci Intranet w między innymi Children'sHospital w Massachusetts, General Hospital w Brigham, Women's Hospital, Boston Anesthesia EducationFoundation i Harvard Medical Center.

Will obecnie przewodzi zespołowi projektanckiemu w firmie Invantage, Inc. w Cambridge, Massachusetts, któratworzy oparte na Javie narzędzia intranetowe dla przemysłu farmaceutycznego. W wolnym czasie jest zapalonymamatorem fotografii, pisarzem i studentem ekonomii na Yale University.

Page 10: Java Servlet - Programowanie

Wstęp

Od czasu, kiedy napisane zostało pierwsze wydanie niniejszej książki, serwlety i platforma Javy działająca postronie 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ą obecnieobsługę serwletów jako standardową własność swojego oprogramowania. W specyfikacji Java 2, EnterpriseEdition (J2EE) serwlety istnieją jako podstawowy składnik, a niemożliwym jest obecnie znalezienie producentaserwerów aplikacji, którego produkt nie zawierałby skalowalnej implementacji serwletów. Jest to jednak więcejniż zjawisko napędzane przez producentów. Serwlety stały się podstawą dla JavaServer Pages (JSP) i innychszkieletó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 czasachpierwszego wydania. Interfejs serwletów (Servlet API) został poddany dwóm przeglądom, a trzeci jest w trakcieprzygotowań. 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łyzauważ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 ApacheSoftware Foundation (ASF), i można je aktualnie odnaleźć pod adresem http://jakarta.apache.org. Pakiety tedalej 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 traktowanyjako wzorcowa implementacja Servlet API, został również przeniesiony do ASF i udostępniony jako OpenSource pod nazwą Apache Tomcat. Od tego czasu Tomcat stał się jednym z najpopularniejszych kontenerówserwletó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ą doprogramowania 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 informacjezaawansowane — opisuje działania podejmowane najczęściej przy pomocy serwletów oraz najpopularniejszenarzędzia do tego służące. Można tam znaleźć wiele przykładów, kilka wskazówek i ostrzeżeń, a nawet opisykilku prawdziwych błędów, które umknęły uwagi korektorów technicznych.

Servlet API 2.2Niniejsze wydanie książki opisuje wersję 2.2 Servlet API, która osiągnęła stan „wersji publicznej” w sierpniu1999, a stan „wersji ostatecznej” w grudniu 1999. Wydanie pierwsze opisywało wersje 2.0. Zmiany pomiędzywersjami 2.0 i 2.2 są znaczne:

• Zostały wprowadzone zasady definiujące dystrybucje serwletów pomiędzy kilkoma serweramiwspierającymi.

Page 11: Java Servlet - Programowanie

• Serwlety korzystają aktualnie z dołączanych aplikacji WWW, które mogą być konfigurowane iwdraż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 uaktualnieniaAPI 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 — techniktworzenia prawdziwych dynamicznych witryn opartych na serwletach. W niniejszym wydaniu znajdują sięsamouczki pięciu najpopularniejszych technologii tworzenia zawartości opartej na serwletach, należących doOpen 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 wielubardzo 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 sieciWWW, wykorzystywany przez innowacyjne witryny takie jak customatix.com

• Element Construcion Set (ECS), utworzony przez Apache w celu obsługi najbardziej wymagającychpotrzeb programistycznych

Niniejsze drugie wydanie opisuje również WAP, Wireless Application Protocol (Protokół AplikacjiBezprzewodowych) oraz wyjaśnia, jak tworzyć oparte na serwletach aplikacje WWW dla urządzeńbezprzewodowych.

Servlet API 2.3W czasie pisania niniejszej książki, Servlet API 2.3 jest w trakcie tworzenia. Jednak nie został on jeszczeukończony. W związku z tym tekst niniejszego wydania zawiera w różnych miejscach krótkie uwagi na tematzmian spodziewanych w z Servlet API 2.3. Dodatkowo, ostatni rozdział książki zawiera dokładniejszy opispróbnej specyfikacji Servlet API 2.3, udostępnionej w październiku 2000, który pozwala na zapoznanie się znajnowszymi 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 wydaniaCzytelnicy książki „Java Servlet Programming, 1st ed.” zorientują się, że niniejsza książka została obszernieuaktualniona do Servlet API 2.2 i, gdzie to tylko możliwe, Servlet 2.3. Każdy rozdział został znaczącopoprawiony w porównaniu z pierwszym wydaniem, a także dodano sześć nowych rozdziałów opisującychtechniki tworzenia zawartości opartej na serwletach, jak również nowy rozdział siódmy, „Serwlety korporacyjne iJ2EE”, 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, polecasię czytelnikom pierwszego wydania przeczytanie każdego interesującego ich rozdziału oraz zwrócenie uwagi na

Page 12: Java Servlet - Programowanie

nowe mechanizmy, które pozwalają na wykonanie tradycyjnych zadań. Czytelnicy dysponujący ograniczonymczasem powinni przejrzeć listę najbardziej znaczących zmian w podrozdziale „Organizacja”.

CzytelnicyDla 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ścitworzący aplikacje dla serwerów J2EE mogą nauczyć się jak najlepiej zintegrować serwlety z innymipodobnymi technologiami.

• Programiści JSP — JavaServer Pages (JSP) tworzone są na podstawie serwletów. Wykorzystanie pełnejmocy 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. Serwletysą 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 zprzenośnością, bezpieczeństwem, wydajnością i/lub integracją z innymi źródłami danych. Serwletyprzewyż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ą znajtrudniejszych do określenia rzeczy jest docelowy czytelnik. Czy zna on Javę? Czy ma doświadczenie wprogramowaniu 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 dladrugich.

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 innychksiąż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 dobrzeznają 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 jedyniekrótko zapoznać się z rozdziałami na temat apletów i programowania Swing (graficznego), a skupić się na sieci iprogramowaniu wielowątkowym. Aby zacząć od razu naukę serwletów i uczyć się Javy w trakcie, polecane jestprzeczytanie niniejszej książki równocześnie z „Java in a Nutshell” autorstwa Davida Flanagana (O'Reilly) lubinnym podręcznikiem.

Niniejsza książka nie wymaga od czytelników doświadczenia w programowaniu WWW, HTTP i HTML. Niezawiera jednak pełnego wprowadzenia lub wyczerpującego opisu tych technologii. Opisane zostaną podstawypotrzebne do efektywnego programowania serwletów, a szczegóły (takie jak pełna lista znaczników HTML inagłówków HTTP 1.1) pozostawione zostaną innym źródłom.

PrzykładyW niniejszej książce znaleźć można ponad 100 przykładów serwletów. Ich kod jest całkowicie zawarty wewnątrztekstu, 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 ztych 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 trybiesamodzielnym, wirtualnej maszyny Javy (Java Virtual Machine — JVM) zawartej w Java Development KIT1.1.8 i 1.2.2, zarówno pod Windows jak i Uniksem. Kilka zaawansowanych przykładów wymaga własności,

Page 13: Java Servlet - Programowanie

których nie obsługuje Tomcat w trybie samodzielnym. W tym przypadku przykłady były testowane na różnychinnych serwerach, jak opisano w tekście. Serwer Apache Tomcat jest oficjalną wzorcową implementacją ServletAPI, 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 serwletyprzykładowe, mogą się także okazać przydatne przy tworzenie własnych. Klasy te zawarte są w pakieciecom.oreilly.servlet. Między innymi są to klasy pomagające serwletom w analizie parametrów, obsłudzewysył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 tezawiera również klasę wspomagającą komunikację apletów z serwletami. Od czasu pierwszego wydania dodanezostały nowe klasy pomagające serwletom w wysyłaniu wiadomości poczty elektronicznej, przechowywaniuodpowiedzi w pamięci podręcznej oraz automatycznym wykrywaniu obsługi Servlet API. Kod źródłowywiększości pakietu com.oreilly.servlet zawarty jest w tekście, a pełna, aktualna wersja jest dostępna wformie elektronicznej (razem z dokumentacją javadoc) pod adresem http://www.servlets.com.1

OrganizacjaNiniejsza 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. Wdrugim wydaniu dodane zostały dodatkowe informacje na temat serwerów.

• Rozdział 2, „Podstawy serwletów HTTP”. Zawiera krótkie wprowadzenie do HTTP i funkcji, jakiemogą pełnić serwlety HTTP. Przedstawia tworzenie prostej strony i wprowadza pojęcie dołączanejaplikacji 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 kwestiisynchronizacji w systemie wielowątkowym. Opisane są również stany trwałe. Drugie wydanie zawieranowe zasady kontekstowego przeładowywania i rejestracji serwletów, nowy podrozdział na tematpamięci podręcznej po stronie serwera oraz uwagę na temat super.init(config).

• Rozdział 4, „Pobieranie informacji”. Wprowadza najpopularniejsze metody wykorzystywane przezserwlety 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 opisujeustawianie informacji w deskryptorze, pobieranie nazwy serwletu, dostęp do katalogów tymczasowych,obsługę kontekstowych parametrów początkowych, określanie wersji Servlet API, przypisywanieodwzorowania 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 dziennikuzdarzeń serwera oraz wysyłania dostosowanych nagłówków HTML. Drugie wydanie zawiera nowy opisbuforowania odpowiedzi, bardzo przydatny przykład przekierowywania oraz nowe podrozdziały natemat konfiguracji stron zawierających błędy i obsługi błędów.

• Rozdział 6, „Wysyłanie zawartości multimedialnej”. Opisuje różne interesujące dane, które możezwracać serwlet — zawartość WAP/WML dla urządzeń bezprzewodowych, dynamicznie tworzoneobrazki, zawartość skompresowana oraz odpowiedzi wieloczęściowe. W drugim wydaniu dodano opisWAP/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 protokoleHTTP. Pierwsza część rozdziału opisuje tradycyjne techniki śledzenia sesji stosowane przezprogramistów CGI. Druga część opisuje sposoby zastosowania wbudowanej w Servlet API obsługi

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 wersjipróbnych różnych serwerów WWW i aplikacji. Zważywszy na nieustanny szybki postęp na rynku serwletów, dołączoneserwery stałyby się przestarzałe jeszcze przed wydrukowaniem książki. Te same wersje próbne dostępne są w sieci i polecasię pobranie ich własnoręcznie. Proszę pamiętać, że jeżeli zamierza się czytać niniejszą książkę offline, polecane jestpobranie kodu przykładów i serwera WWW Apache Tomcata, kiesy tylko będzie to możliwe. Łącza do pobrań umieszczonesą pod adresem http://www.servlets.com.

Page 14: Java Servlet - Programowanie

śledzenia sesji. Drugie wydanie zawiera zasady tworzenia sesji aplikacji WWW, materiał na tematnowych nazw metod sesji, dyskusję na temat zarządzania przekraczaniem czasu oraz śledzenie sesjioparte na apletach.

• Rozdział 8, „Bezpieczeństwo”. Wyjaśnia kwestie bezpieczeństwa związane z programowanierozproszonym. Opisuje sposoby korzystania ze standardowych funkcji serwletów związanych zzarządzaniem kontami użytkowników oraz sposoby tworzenia bardziej zaawansowanego systemu przypomocy dodatkowego uwierzytelniania i autoryzacji. Wyjaśni również rolę serwletów w bezpiecznejkomunikacji 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ładykonfiguracji 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órymusi porozumieć się z serwerem. Uaktualniony w drugim wydaniu.

• Rozdział 11, „Współpraca serwletów”. Opisuje powody komunikacji serwletów i sposoby ichwspółpracy przez dzielenie się informacjami lub wywoływanie sienie nawzajem. W drugim wydaniucałkowicie przeredagowany.

• Rozdział 12, „Serwlety korporacyjne i J2EE”. Opisuje zaawansowane własności serwletówwykorzystywane 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ądzaniukodowaniem i sposoby wykorzystywania nowych metod API w zarządzaniu wersjami lokalnymi.

• Rozdział 14, „Szkielet Tea”. Przedstawia szkielet Tea, elegancki, ale zarazem potężny mechanizmszablonów. Nowość w drugim wydaniu.

• Rozdział 15, „WebMacro”. Opisuje szkielet WebMacro, podobny do Tea lecz z kilkoma innymidecyzjami projektanckimi. Nowość w drugim wydaniu.

• Rozdział 16, „Element Construction Set”. Zawiera krótki opis ECS, obiektowego podejścia dotworzenia strony. Nowość w drugim wydaniu.

• Rozdział 17, „XMLC”. Przegląd XMLC, podejścia do tworzenia strony opartego na XML. Nowość wdrugim wydaniu.

• Rozdział 18, „JavaServer Pages”. Wyjaśnia JSP, standardową technologię firmy Sun, w której stronyWWW 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 analizatorparametrów zlokalizowanych, nową klasę poczty elektronicznej oraz uaktualniony podrozdział na tematwyrażeń regularnych, nowy podrozdział na temat dodatkowych narzędzi oraz dodatkowe podpowiedzina 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 pakieciejavax.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 pakieciejavax.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łemnemoniczne wykorzystywane przez serwlety.

• Dodatek E, „Encje znakowe”. Lista encji znakowych zdefiniowanych w HTML, a także równoważne donich wartości kodów ucieczkowych Uniksa.

Page 15: Java Servlet - Programowanie

• Dodatek F, „Kodowania”. Lista sugerowanych kodowań wykorzystywanych przez serwlety w celutworzenia 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 odpoczątku do końca zapewnia uniknięcie wszelkich niespodzianek, jako że starano się unikać odwołań dodalszych 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ącynieprawidłowo.

Konwencje wykorzystywane w tej książceKursywa 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-eCzcionka pogrubiona wykorzystywana jest do:

• Konkretnych klawiszy na klawiaturze

• Nazw przycisków interfejsu użytkownika i menuCzcionka o stałej szerokości wykorzystywana jest do:

• Wszystkich danych pojawiających się dokładanie w programie Javy, takich jak słowa kluczowe, typydanych, stałe, nazwy metod, zmienne, nazwy klas oraz nazwy interfejsów

• Wszystkich wydruków kodu Javy

• Dokumentów HTML, znaczników i atrybutówCzcionka o stałej szerokości z kursywą wykorzystywana jest do:

• Ogólnych obszarów zablokowanych wskazujących, że dany element jest zastępowany w programieprzez konkretną wartość.

Pogrubiona czcionka o stałej szerokości wykorzystywana jest do:

• Wpisów w wierszu poleceń

Prośba o komentarzeProsimy 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 miejscuniniejszej 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 stroniehttp://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łychwydań książki.

PodziękowaniaKiedy 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ą. Pierwszewydanie opisywało Servlet API 2.0, specyfikację tworzoną przez około dwa lata. Niniejsze drugie wydanieprzedstawia Servlet API 2.2 i 2.3, co daje mniej więcej dwa dodatkowe lata pracy projektantów. Tak więcjedynie z tej perspektywy można dostrzec, że jeżeli pierwsze wydanie zabrało mniej więcej rok aktywnegopisania, to drugie powinno zabrać mniej więcej tyle samo czasu. I rzeczywiście tak było — około 9 miesięcy.

Page 16: Java Servlet - Programowanie

Wiele osób pomogło mi w tworzeniu tej książki. Jestem im głęboko wdzięczny. Po pierwsze są to redaktorzytechniczni książki — James Duncan Davidson, przewodniczący specyfikacji Servlet API 2.1 i 2.2, oraz DannyCoward, 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 temattworzenia zawartości (i w wielu przypadkach tworzyli opisywaną technologię) — Reece Wilton i Brian O'Neilldla Tea, Justin Wells dla WebMacro, Jon Stevens dla ECS, Mark Diekhnas i Christian Cryder dla XMLC orazHans Bergsten i Craig McClanahan dla JSP.

Chciałbym również podziękować Bobowi Ecksteinowi, redaktorowi książki, którego ręczne notatki były zawszecelne, choć czasami niemożliwe do odcyfrowania. Bob przejął obowiązki redaktorskie od Pauli Ferguson, potym, 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ądarekwykorzystywane przy testowaniu przykładów; Magnusowi Stenmanowi z firmy Orion, który wyjaśnił miimplementację J2EE w serwerze Orion; Justynie Horwat, zwanej przez niektórych Boginią BibliotekiZnacznikó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órcyJDOM. 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 pierwszegoHistoria tej książki rozpoczęła się właściwie 20 marca 1997, w księgarni „Computer Literacy” w San Jose wKalifornii. 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ę wjego 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 ozdobionyzwierzę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 niezostał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żelicoś się nie udaje, poproś o pomoc”, tak więc po krótkich poszukiwaniach poprosiłem Williama Crawforda, którypracował w tym czasie nad książką „Java Enterprise in a Nutshell”, czy pomógłby mi w przyśpieszeniu pracy nadksiąż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 seriiJava, za ich starania o zapewnienie (i poprawę) jakości tej książki. Oraz Timowi O'Reilly za danie mi szansyspełnienia marzeń.

Dziękuję również moim menedżerom w firmie Silicon Graphics, Kathy Tansill i Waltowi Johnsonowi, zadostarczenie 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 JamesDuncan Davidson (Wyglądający niemal jak James Gosling), Jim Driscoll, Rob Clark i Dane Brownell.

Page 17: Java Servlet - Programowanie

Dziękuję również członkom listy dystrybucyjnej jserv-interest, których pytania i odpowiedzi ukształtowałyzawartość tej książki; Willowi Rameyowi, staremu przyjacielowi, który nie pozwolił, aby przyjaźń przesłoniłajego krytyczne oko; Mike'owi Engberowi, człowiekowi, do którego zwróciłem się po ucieczce z eleganckichmiejsc pracy i byłem gotowy na zaakceptowanie jego szalonych pomysłów; Dave'owi Vandergriftowi, pierwszejosobie, 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 JillSteinberg, redaktorom „JavaWorld”, dzięki którym napisałem mój pierwszy profesjonalny tekst; DougowiYoungowi, który dzielił się za mną technicznymi sztuczkami poznanymi przy pisaniu siedmiu własnych książektechnicznych oraz Shoji Kuwabara'rze, Mieko Aono, Song'owi Yung'owi, Matthew Kim'owi oraz AlexandrowiPashintsev'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, DaveMcMurdie i Rob Clark. Ciągle jestem w szoku, po tym jak dowiedziałem się, że jednemu recenzentowi zabrałotrzy 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 dnanauczenie mnie podstaw pisania. Dziękuję też Kristi Taylor, która sprawiła, że ta niewielka część czasu, która niebył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 (atakże pozostałej części zespołu, której wkład pozostaje nieoceniony), których pomoc i wsparcie sprawiła, żewszystko to stało się możliwe. A także Martinowi Streeterowi z firmy Invantage, Inc., za jego wsparcie w trakcietrwania tego projektu.

Bez Roba Leitha, Rogera Stacey i Freda Sterbeigha, przypuszczalnie ciągle trwałbym w stronie biernej. DaleDogherty zaoferował mi pieniądze w zamian za słowa, wydarzenie, którego ciągle nie potrafię pojąć. AndyKwak, Joel Pomerantz i Matthew Proto, wspaniali ludzie, zechcieli przeczytać próbne wydruki i słuchać skarg ogodzinie pierwszej w nocy.

I, oczywiście Mamie i Tacie za ich lata wsparcia, oraz mojej siostrze Faith za (zazwyczaj) wybaczanie mi byciadurniem.

William Crawford

Lipiec 1998

Page 18: Java Servlet - Programowanie

Rozdział 1. Wprowadzenie

Rozwój aplikacji Javy działających po stronie serwera — wszystko od działających samodzielnie serwletów dopełnej platformy Java 2, Enterprise Edition (J2EE) — był jednym z najbardziej ekscytujących trendów wprogramowaniu Javy. Język Java został utworzony pierwotnie w celu zastosowania w małych, osadzonychurzą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ł niestetypominię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żychaplikacji typu klient-serwer. Niezależna od platformy natura Javy jest niezwykle użyteczna dla organizacjiposiadających heterogeniczny zbiór serwerów pracujących pod różnymi odmianami systemów operacyjnychUNIX i Windows (oraz coraz bardziej Mac OS X). Nowoczesny, obiektowy i chroniący pamięć projekt Javypozwala 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łączanerozszerzenie serwera, które rozszerza jego funkcjonalność. Serwlety pozwalają programistom na rozszerzanie idostosowywanie każdego serwera WWW lub aplikacji z obsługą Javy do wcześniej nieznanego poziomuprzenośności, elastyczności i łatwości. Jednak przed przejściem do szczegółów, należy spojrzeć na sprawę zpewnej perspektywy.

Historia aplikacji WWWChociaż 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ówCGI. Kiedy wykorzystuje się serwlet do utworzenia dynamicznej zawartości strony WWW lub podniesienia winny sposób funkcjonalności serwera WWW, w efekcie tworzy się aplikację WWW. Podczas, gdy strona WWWwyświetla jedynie zawartość statyczną i pozwala użytkownikowi na nawigację poprzez tę zawartość, aplikacjaWWW dostarcza doświadczenia bardziej interaktywnego. Aplikacja WWW może być tak prosta jakwyszukiwanie słowa kluczowego w archiwum dokumentów lub tak złożona, jak sklep elektroniczny. AplikacjeWWW są umieszczane w Internecie oraz korporacyjnych sieciach intranet i extranet, w których posiadają onepotencjał 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 aplikacjiWWW.

Common Gateway InterfaceCommon Gateway Interface (Wspólny Interfejs Bramek), w skrócie CGI, był jednym z pierwszych praktycznychtechnik tworzenia zawartości dynamicznej. Przy pomocy CGI serwer WWW przekazuje konkretne żądania doprogramu 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 CGIszybko 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 ubocznymefektem ich początkowego przeznaczenia — zdefiniowania standardowej metody porozumiewania się serwerainformacji z aplikacjami zewnętrznymi. To źródło wyjaśnia, dlaczego CGI posiada przypuszczalnie najgorszy dowyobrażenia okres trwałości. Kiedy serwer otrzymuje żądanie, które uzyskuje dostęp do programu CGI, musi on

Page 19: Java Servlet - Programowanie

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 wygenerowaniaodpowiedzi. Tworzenie procesu dla każdego takiego żądania wymaga czasu i znaczących zasobów serwera, coogranicza liczbę żądań, które serwer może obsługiwać równocześnie. Rysunek 1.1 przedstawia okres trwałościCGI.

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 wzarządzaniu szczegółami interfejsu CGI. Tworzenie skryptu CGI w Perlu pozwala na uniezależnienie oplatformy, ale wymaga również uruchomienia osobnego interpretatora Perla dla każdego żądania, co zabierajeszcze więcej czasu i wymaga dodatkowych zasobów.

Innym często przeoczonym problemem z CGI jest niemożność interakcji programu CGI z serwerem WWW lubskorzystania z możliwości serwera po rozpoczęciu działania tego programu, ponieważ działa on jako osobnyproces. 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).

FastCGIFirma o nazwie OpenMarket stworzyła alternatywę dla standardu CGI o nazwie FastCGI. W większościaspektów, FastCGI działa podobnie do CGI — ważną różnicą jest tworzenie przez FastCGI jednego trwałegoprocesu dla każdego programu FastCGI, jak przedstawiono na rysunku 1.2. Eliminuje to konieczność tworzenianowego 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żewykonywać 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.) Innymproblemem 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 natemat FastCGI jest dostępna pod adresem http://www.fastcgi.com.

Page 20: Java Servlet - Programowanie

PerlExPerlEx, utworzony przez ActiveState, zwiększa wydajność skryptów CGI napisanych w Perlu pracujących naserwerach WWW pod Windows NT (Internet Information Server Microsoftu, Website Professional O'Reilly orazFastTrack 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_perlPrzy korzystaniu z serwera WWW Apache, inną opcją zwiększenia wydajności CGI jest wykorzystaniemod_perl. mod_perl jest modułem serwera Apache osadzającym kopię interpretatora Perla w plikuwykonywalnym Apache'a, dostarczającym pełnej funkcjonalności Perla wewnątrz Apache'a. Jego efektem jestprekompilowanie skryptów CGI przez serwer i wykonywanie ich bez rozdzielania, a związku z tym ich praca jestdużo szybsza i wydajniejsza. Jego wadą jest możliwość wykorzystania jedynie w serwerze Apache. Większailość informacji na temat mod_perl jest dostępna pod adresem http://perl.apache.org.

Inne rozwiązaniaCGI/Perl posiada zaletę bycia mniej lub bardziej niezależnym od platformy sposobem na tworzenie dynamicznejzawartości WWW. Inne dobrze znane technologie tworzenia aplikacji WWW takie jak ASP i JavaScriptdziałający po stronie serwera, są opatentowanymi rozwiązaniami pracującymi jedynie z określonymi serweramiWWW.

Interfejsy rozszerzeń serweraKilka 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. Przypomocy każdego z tych interfejsów, można utworzyć rozszerzenia serwera zwiększające lub zmieniające jegopodstawową funkcjonalność, pozwalającą mu na obsługę zadań wcześniej delegowanych do zewnętrznychprogramów CGI. Jak można dostrzec na rysunku 1.3, rozszerzenia serwera występują wewnątrz głównegoprocesu serwera WWW.

Rysunek 1.3. Okres trwałości rozszerzeń serwera

Ponieważ specyficzne dla serwera interfejsy wykorzystują połączony kod C lub C++, rozszerzenia serweradział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 iniezawodności — załamanie rozszerzenia może prowadzić do załamania całego serwera, złośliwe rozszerzeniemoż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 systemuoperacyjnego.

JavaScript działający po stronie serweraiPlanet/Netscape posiada również technikę skryptów działających po stronie serwera, nazywaną server-sideJavaScript, w skrócie SSJS. Podobnie jak ASP, SSJS pozwala na osadzanie fragmentów kodu w stronach HTMLw celu utworzenia dynamicznej zawartości WWW. Różnica jest taka, że SSJS wykorzystuje JavaScript jakojęzyk skryptowy. Z SSJS, strony są prekompilowane w celu poprawienia wydajności. Obsługa SSJS jest możliwajedynie w serwerach iPlanet/Netscape. Większa ilość informacji na temat programowania przy pomocy

Page 21: Java Servlet - Programowanie

JavaScript działającego po stronie serwera dostępna jest pod adresemhttp://developer.netscape.com/tech/javascript/ssjs/ssjs.html.

Active Server PagesMicrosoft posiada technikę tworzenia dynamicznej zawartości WWW o nazwie Active Server Pages (AktywneStrony 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 jestodczytywany 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ładnikomCOM.

Obsługa ASP jest wbudowana w Internet Information Server Microsoftu w wersji 3.0 i wyższych, dostępnymbezpłatnie pod adresem http://www.microsoft.com/iis. Obsługa innych serwerów WWW jest dostępna jakokomercyjny produkt firmy Chili!Soft pod adresem http://www.chilisoft.com. Proszę pamiętać, że strony ASPdział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 adresemhttp://www.microsoft.com/workshop/server/default.asp oraz http://www.activeserverpages.com/.

JavaServer PagesJavaServer Pages, często nazywana po prostu JSP, jest opartą na Javie alternatywą dla ASP, utworzoną iwystandaryzowaną przez firmę Sun. JSP wykorzystuje składnię podobną do ASP poza tym, że językiemskryptowym w tym przypadku jest Java. Odwrotnie niż ASP, JSP jest otwartym standardem implementowanymprzez wielu producentów na wszystkich platformach. JSP jest ścisłe związana z serwletami, ponieważ strona JSPjest przetwarzana do serwletu, co stanowi część jej wykonania. JSP jest bardziej szczegółowo opisana wdalszych częściach niniejszej książki. Większa ilość informacji na temat JSP jest dostępna pod adresemhttp://java.sun.com/products/jsp.

Serwlety JavyW tym miejscu pojawiają się serwlety Javy. Jak wspomniano wcześniej, serwlet jest ogólnym rozszerzeniemserwera — 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 dopoprzednio omawianego rozszerzenia serwera, poza tym, że działa on wewnątrz wirtualnej maszyny Javy (JavaVirtual 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 przezprzeglą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ówi/lub oddzielnych żądań, serwlety mogą być obsługiwane przez osobne wątki w tym samym procesie, z wielomaprocesami rozciągniętymi na klika serwerów wspierających. Oznacza to, że serwlety są również wydajne iskalowalne. Ponieważ serwlety działają z komunikacją dwustronną do serwera WWW, mogą bardzo ściślewspół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 przypadkuJavy, jak i pomiędzy serwerami WWW. Jak zostanie to opisane poniżej, większość głównych serwerów WWW

Page 22: Java Servlet - Programowanie

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ówPodobnie 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ścipodstawowych serwerów WWW2. Serwlety Javy zdefiniowane przez dział Java Software firmy SunMicrosystems (dawniej znany jako JavaSoft), stanowią Pakiet Opcjonalny (Optional Package) dla Javy (dawniejznany jako Rozszerzenie Standardowe — Standard Extension). Oznacza to, że serwlety zostały oficjalniepobłogosławione przez Sun'a i stanowią część języka Java, lecz nie są częścią podstawowego API Javy. Zamiasttego, 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 żadnegomechanizmu WWW. Pakiety javax.servlet i javax.servlet.http składają się na Servlet API.Najnowsza wersja tych klas jest dostępna do pobrania pod adresemhttp://java.sun.com/products/servlet/download.html3. Wszystkie serwery WWW obsługujące serwlety musząwykorzystywać te klasy wewnętrznie (chociaż mogą stosować alternatywną implementację), tak więc generalnieten 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 celukompilowania serwletów. Dodatkowo konieczny jest program uruchamiający serwlety (technicznie nazywanykontenerem 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ówSamodzielny 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 producenciserwerów generalnie obsługują jedynie JVM dostarczoną przez samych siebie. Serwery WWW dostarczającesamodzielnej obsługi to między innymi:

• 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 jakododatek 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 niniejszejksiążki serwlety są jedynymi pakietami java.* lub javax.* utrzymywanymi jako Open Source4.Proszę zobaczyć http://jakarta.apache.org.

• iPlanet Web Server Enterprise Edition Netscape'a (wersja 4.0 i późniejsze), przypuszczalnienajpopularniejszy 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.6zawierały wbudowaną obsługę serwletów, to jednak był to wczesny Servlet API 1.0 i zawierały onedużą liczbę błędów tak poważnych, że obsługa serwletów była praktycznie bezużyteczna. W celuwykorzystania serwletów z serwerami Netscape'a w wersji 3.x należy wykorzystać dołączany kontener.Proszę zobaczyć http://www.iplanet.com.

2 Proszę zauważyć, że niektórzy producenci serwerów WWW posiadają swoje własne implementacje Javy działającej postronie serwera, niektóre z nich noszą również nazwę serwletów. Są one generalnie niekompatybilne z serwletami Javyutworzonymi przez Sun'a. Większość z tych producentów konwertuje swoją obsługę Javy do standardowych serwletów lubwprowadzają obsługę standardowych serwletów równolegle, w celu zapewnienia wstecznej kompatybilności.3 W pewnym momencie planowano dołączenie tych klas do JDK 1.2. Później jednak zdecydowano na utrzymanie ichniezależności od JDK w celu ułatwienia dokonywania poprawek do Servlet API.4 Implementacja javax.servlet i javax.servlet.http w standardzie Open Source spowodowała naprawieniewielu błędów (na przykład, autor miał okazję poprawić obsługę warunkowego GET w HttpServlet) i kwestiiniekompatybilności. Istnieje nadzieja, że przykład ten wspomoże w udostępnieniu większej ilości oficjalnych pakietów Javyjako Open Source

Page 23: Java Servlet - Programowanie

• 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 listawł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 trybiesamodzielnym 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 dlazastosowań, 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ł pierwszymserwerem implementującym serwlety oraz działał jako efektywna wzorcowa implementacja dla ServletAPI 2.0. Jest on napisany całkowicie w Javie (poza dwoma bibliotekami kodu macierzystego, którepowię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.

Serwery aplikacji są rosnącym obszarem tworzenia. Serwer aplikacji oferuje obsługę po stronie serwera dlatworzenia 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 Javieserwerów aplikacji. Proszę zobaczyć http://www.beasys.com/products/weblogic.

• Orion Application Server, wysoko wydajny serwer o stosunkowo niskiej cenie, napisany całkowicie wJavie. 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 serweraSmalltalk. Proszę zobaczyć http://www.gemstone.com/products/j.

• Jrun Server Allaire (poprzednio Live Software), prosty kontener serwletów, który rozrósł się dozaawansowanego 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 skupieniasię na serwletach. Proszę zobaczyć http://www.silverstream.com.

Dołączane kontenery serwletówDołą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 innymiApache'a, FastTrack Server i Enterprise Server iPlanet, Internet Information Server i Personal Web Server

Page 24: Java Servlet - Programowanie

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 wszystkichpopularnych serwerach na wszystkich popularnych systemach operacyjnych. Zawiera bezpłatny programuruchomieniowy. Proszę zobaczyć http://www.servletexec.com.

• Jrun Allaire (dawniej Live Software), dostępny jako moduł rozszerzający do obsługi serwletów wewszystkich 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 TomcatServer (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ówtakich jak Apache, iPlanet/Netscape i IIS.

Osadzane kontenery serwletówOsadzany kontener jest ogólnie niewielką platformą programistyczną, która może być osadzana w innychaplikacjach. 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, tworzeniewiększości innych osadzanych serwerów zatrzymano.

• Nexus Web Server autorstwa Andersa Kristensena, dostępny bezpłatnie program uruchamiającyserwlety, implementujący większą część Servlet API, który może być w łatwy sposób dołączany doaplikacji Javy. Proszę zobaczyć http://www-uk.hpl.hp.com/people/ak/java/nexus/.

Uwagi dodatkowePrzed przejściem do następnej części należy zapamiętać, że nie wszystkie kontenery serwletów są tworzone wjednakowy sposób. Tak więc przed wybraniem kontenera serwletów (i prawdopodobnie serwera) przy pomocyktórego udostępniane będą serwlety, należy go wypróbować, prawie do granic możliwości. Sprawdzić listydystrybucyjne. Należy zawsze sprawdzać, czy serwlety zachowują się tak, jak we wzorcowej implementacjiTomcata. 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ć ozgodność z najsłabszą implementacją, tak więc należy pobrać kontener serwletów, który posiada wszystkiepożądane właściwości.

Kompletna, aktualna lista dostępnych kontenerów serwletów razem z ich obecnymi cenami jest dostępna podadresem http://www.servlets.com.

Potęga serwletówJak 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. Cosprawia, że serwlety są jednym z najlepszych sposobów programowania WWW? Zdaniem autorów posiadają onekilka 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 kolejnoomówiona.

PrzenośnośćPonieważ serwlety są pisane w Javie według dobrze zdefiniowanego i szeroko akceptowanego API, są one wduż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 wysokowydajnym serwerze Uniksowym z iPlanet/Netscape Application Server. Stosując serwlety można naprawdę„napisać raz, udostępniać wszędzie”.

Page 25: Java Servlet - Programowanie

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ójniezaimplementowanej części języka Java — Abstract Windowing Toolkit (AWT), który stanowi bazę graficznychinterfejsów Javy, takich jak Swing.

MocSerwlety 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órazawiera 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). Listastandardowych API dostępnych serwletom rośnie, czyniąc tworzenie aplikacji WWW szybszym, łatwiejszym ibardziej 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 ioparty 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 stronieserwera języków innych niż Java jest znacznie bardziej skomplikowane, jako że konieczne jest tworzenie swoichwł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 doobsł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 pojedynczyegzemplarz 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ństwoSerwlety 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 wto 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 jakointeger. Automatyczne zbieranie śmieci przez Javę i brak wskaźników oznaczają, że serwlety są generalniebezpieczne od problemów z zarządzaniem pamięcią, takich, jak uszkodzone wskaźniki, niewłaściwe odwołaniado 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ępuJavy. Jeżeli serwlet wykona dzielenie przez zero lub inne nieprawidłowe działanie, wyrzuca wyjątek, który możebyć bezpiecznie wychwycony i obsłużony przez serwer, który zapisze błąd w dzienniku zdarzeń i przeprosiużytkownika. Jeżeli podobny wyjątek napotkałoby rozszerzenie serwera oparte na C++, przypuszczalnienastąpiłoby załamanie serwera.

Serwer może chronić siebie w większym stopniu poprzez zastosowanie menedżera bezpieczeństwa lub kontroleradostępu Javy. Serwer może wykonywać swoje serwlety pod ochroną dokładnego kontrolera dostępu który, naprzykład wymusza politykę bezpieczeństwa zaprojektowaną do strzeżenia przed złośliwym lub źlezaprojektowanym serwletem dążącym do zniszczenia systemu plików serwera.

Page 26: Java Servlet - Programowanie

ElegancjaElegancja kodu serwletów jest uderzająca. Kod serwletów jest czysty, obiektowy, modularny i zadziwiającoprosty. Jednym z powodów tej prostoty jest sam Servlet API, który zawiera metody i klasy obsługujące wielerutynowych 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 popularnychzadań zostało pozostawione poza API, i w tych przypadkach autorzy próbowali to naprawić i tak powstał zbiórprzydatnych klas w pakiecie com.oreilly.servlet.

IntegracjaSerwlety są ściśle zintegrowane z serwerem. Ta integracja pozwala serwletowi na współpracę z serwerem wsposób niedostępny dla programów CGI. Na przykład, serwlet może wykorzystywać serwer w celuprzetłumaczenia ścieżek plików, dokonania logowania, sprawdzenia uwierzytelnienia oraz wykonaniaodwzorowania 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 zawieraklasy z wyspecjalizowaną obsługą serwletów HTTP. Lecz w późniejszym okresie może być ona rozszerzona izoptymalizowana dla innego typu serwletów, czy to produkcji Suna, czy innej firmy. Jest również możliwe, żejego 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ść przypomocy wyrażeń out.println(), lub generować skomplikowany zbiór stron przy pomocy mechanizmuszablonó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 celuutworzenia całkowicie nowych technologii takich jak JavaServer Pages. Nie wiadomo, do czego jeszcze zostanąwykorzystane.

Page 27: Java Servlet - Programowanie

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 zachowanieprzy użyciu XML-owego deskryptora rozmieszczenia.

W przeciwieństwie do pierwszej edycji niniejszej książki, ten rozdział obecnej nie opisuje opartych na apletachplików dołączanych serwera (SSI) lub wiązania łańcuchowego oraz filtrowania apletu. Mimo tego, iż techniki tebyły bardzo przydatne oraz zostały umieszczone w serwerze WWW (Java Web Server), nie zostały zatwierdzonew specyfikacji apletu, (która ukazała się po pierwszej edycji niniejszej pozycji). SSI zostały zastąpione przeznowe techniki tworzenia plików dołączanych programu. Wiązanie łańcuchowe apletu zostało uznane za zbytnieczytelne dla oficjalnego zatwierdzenia, mimo tego jest wielce prawdopodobne, że sama jego idea zostaniewykorzystana w Interfejsie API 2.3 (Servlet API 2.3) jako część oficjalnego mechanizmu ogólnego zastosowaniaprzed 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 orazinnych rozdziałach tej książki, zarówno w formie źródłowej jak i skompilowanej (jak zostało to opisane wprzedmowie). Jednakże, co się tyczy tego rozdziału, wydaje się, iż rzeczą najbardziej pomocną w nauce będziezapisanie 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 czasembywają skomplikowane, dlatego też rozdział ten ma na celu wprowadzenie w ogólne zasady ich działania izorientowania się w temacie. Czytelnik po lekturze niniejszej książki będzie w stanie samodzielnie tworzyćnajrozmaitsze aplety.

Podstawy HTTPZanim przejdziemy do omawiania prostych apletów HTTP, musimy sprawdzić znajomość podstaw działaniaprotokołu HTTP. Będąc doświadczonym w programowaniu w CGI (lub mając doświadczenie w tworzeniu stronWWW na serwerach) można z powodzeniem pominąć czytanie tego podrozdziału. W takim przypadkukorzystnym 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ół HTTPzostał szczegółowo omówiony w „Pocket Reference” Clintona Wong’a (wydawnictwo O’Reilly).

Zlecenia, odpowiedzi, nagłówkiHTTP jest prostym, międzynarodowym protokołem. Klient, np. przeglądarka WWW składa zlecenie, serwerWWW 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 zleceniajest składany. Pierwszy wiersz zlecenia określa adres dokumentu (URL) oraz używaną wersję protokółu HTTP.Oto przykład:

Page 28: Java Servlet - Programowanie

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łaniuzlecenia klient może przesłać informacje nagłówkową w celu dostarczenia serwerowi dodatkowych informacji ozleceniu, takich jak: jakie oprogramowanie jest używane przez klienta oraz jaka forma informacji potrzebna jestklientowi do jej zrozumienia. Takie informacje nie odnoszą się bezpośrednio do tego, co było przedmiotemzlecenia, jednakże mogą być wykorzystane przez serwer w tworzeniu odpowiedzi. Oto dwa przykładynagłó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ślarodzaj nośnika (MIME) najkorzystniejszy dla klienta (nagłówki zleceń zostaną omówione szerzej przyomawianiu 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żerównież przesłać inne dodatkowe dane, tak jak w przypadku metody POST, która zostanie zaraz omówiona. Jeżelizlecenie nie zawiera żadnych danych, to kończy się nie zapisanym wierszem. Po przesłaniu przez klientazlecenia, serwer przetwarza je i przesyła odpowiedź. Pierwszy wiersz odpowiedzi zawiera wiersz statusu orazjego 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 tenoznacza, że dokument nie został odnaleziony. W rozdziale 5 „Przesyłanie informacji HTML” zostały omówionenajczęściej spotykane kody statusu oraz w jaki sposób można je wykorzystać w apletach. Dodatek D „Kodystatusu HTTP” zawiera kompletną listę kodów statusu HTTP. Po przesłaniu wiersza statusu serwer przesyłanagłówki odpowiedzi, które są informacją dla klienta taką jak np.: jakiego oprogramowania używa serwer oraznośnika informacji użytego do zapisania odpowiedzi serwera. Oto przykład:

Date: Saturday, 23-May-00 03:25:12 GMTServer: Tomcat Web Server/3.2MIME-version:1.0Content-type: text/htmlContent-length: 1029Last-modified: Thursday, 7-May-00 12:15:35 GMT

Nagłówek Server dostarcza informacji o oprogramowaniu serwera, nagłówek Content-type określa rodzajrozszerzenia MIME odnośnie danych zawartych w odpowiedzi (nagłówki odpowiedzi zostaną omówione szerzejw rozdziale 5). Po nagłówkach serwer przesyła „czysty wiersz” celem zakończenia sekcji nagłówkowej. Jeżelizlecenie zostało wykonane, żądane dane są następnie przesyłane jako część odpowiedzi. W przeciwnymwypadku odpowiedź może zawierać informację tekstową dla osoby obsługującej przeglądarkę, która będziewyjaśniała dlaczego serwer nie mógł wykonać zlecenia.

Metody GET i POSTKlient łącząc się z serwerem może złożyć zlecenie w kilku różnych formach, zwanych metodami. Najczęściejuż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 tometoda 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 dlawykresów. Takie informacje są przesyłane jako ciąg znaków dołączonych do URL-u w formie znanej ciągiemzapytań. Ten sposób zamieszczania dodatkowych informacji w URL-u umożliwia przesłanie strony e-mailembądź utworzenie z niej zakładki. Ponieważ zlecenia GET nie są przeznaczone do przesyłania dużych partiiinformacji, 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ż niekiedymetody tej używa się do przesyłania większych partii informacji. Zlecenie złożone za pomocą metody POST

Page 29: Java Servlet - Programowanie

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 nieulega 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 — sytuacjataka wynika z odpowiedniego zaprojektowania — informacja jak np. numer naszej karty kredytowej, powinnabyć 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 wszystkiezgł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 celuuniknięcia problemów związanych z nadmiernie długimi URL-ami. Metoda GET jest również częstowykorzystywana do ładowania informacji przez proste formularze ponieważ, no cóż, po prostu da się to w tensposó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, zaktó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 przypadkuwystąpienia zmian na serwerze).

Pozostałe metody HttpPoza 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 rozmiarudokumentu, czasu modyfikacji lub ogólnej dostępności. Inne metody to PUT — do zamieszczania dokumentówbezpośrednio na serwerze, DELETE — wykorzystywana do ich usuwania stamtąd. Dwie ostatnie metody niewspółpracują ze wszystkimi serwerami z powodu skomplikowanych procedur. Metoda TRACE jest powszechnieużywana do usuwania błędów — umożliwia przesłanie klientowi dokładnej treści jego zlecenia. MetodaOPTIONS może być użyta do zapytania serwera, z którymi metodami współpracuje lub jak dotrzeć doposzczegó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żywasię do tworzenia apletów HTTP, lub innych rodzajów apletów odpowiednich dla tej materii. Aplety używają klasi interfejsów z dwóch pakietów: javax.servlet i javax.servlet.http. Pakiet javax.servlet zawieraklasy i współpracuje ze standardowymi protokołowo–niezależnymi apletami. Klasy te są rozszerzane przez klasyw pakiecie java.servlet.http w celu dodania funkcjonalności specyficznej dla HTTP. Pakiet najwyższejklasy nazywa się javax zamiast zwykłego java, aby zasygnalizować, iż Interfejs API jest pakietemdodatkowym (uprzednio zwanym Standardowym Rozszerzeniem). Każdy aplet musi wdrożyć interfejsjavax.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żnyprotokołowo powinien być podrzędny do GenericServlet, a aplet HTTP powinien być podrzędny w stosunkudo 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 naserwerze nie zawiera metody main(). Zamiast tego pewne metody apletów wywoływane są przez serwer wprocesie 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(). Metodaservice() akceptuje dwa parametry: obiekt zlecenia i obiekt odpowiedzi. Obiekt zlecenia informuje aplet ozleceniu, a obiekt odpowiedzi używany jest do wysyłania odpowiedzi. Rysunek 2.1. ukazuje jak standardowyaplet obsługujący zlecenie.

Page 30: Java Servlet - Programowanie

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, wzależności od tego, jaki jest typ zlecenia, które ma obsłużyć. Metoda service() HttpServlet obsługujeinstalację oraz transfer do wszystkich metod doXXX(), dlatego właśnie zwykle nie powinna być ignorowana. Narysunku 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 POSTAplet HTTP może zignorować odpowiednio metody doPut() i doDelete() celem obsłużenia zleceń PUT iDELETE. Jednakże aplety HTTP generalnie nie modyfikują metod doTrace() czy doOptions(). Dla nichprawie 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ń iodpowiedzi standardowych serwerów, natomiast klasy HttpServletRequest i HttpServletResponse wjavax.servlet.http umożliwiają dostęp do zleceń i odpowiedzi HTTP. Pakiet javax.servlet.httpzawiera również klasę HttpSession, która oferuje wbudowaną funkcjonalność śledzenia sesji oraz klasęCookie, która pozwala na szybkie instalowanie i przetwarzanie cookies.

Tworzenie stronyNajbardziej podstawowy typ apletu HTTP tworzy pełną stronę HTML. Taki aplet ma zwykle dostęp do takichsamych informacji, co przesyłane do skryptu CGI oraz do pewnej partii innych. Aplet, który tworzy stronyHTML może zostać użyty do wykonywania wszystkich zadań, tych które obecnie są wykonywane przy pomocyCGI, 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 ,zakaż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ł prekursoremję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.

Page 31: Java Servlet - Programowanie

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("<HTML>"); out.println("<HEAD><TITLE>Hello World</TITLE></HEAD>"); out.println ("<BODY>") ; out.println (<BIG>Hello World</BIG>"); out.println ("</BODY><HTML>"); }}

Powyższy aplet rozszerza klasę HttpServlet oraz ignoruje odziedziczoną z niej metodę doGet(). Kiedyserwer WWW otrzymuje zlecenie GET z tego apletu, każdorazowo wywołuje metodę doGet(), przekazując muobiekty HttpServletRequest oraz HttpServletResponse. Obiekt HttpServletRequest reprezentujezlecenie klienta. Obiekt ten daje apletowi dostęp do informacji o kliencie, parametrach zlecenia, nagłówkachHTTP przekazywanych razem ze zleceniem oraz inne. W rozdziale 4. omówione zostały wszystkie możliwościobiektu zlecenia. Dla tego przykładu możemy jednak je pominąć jako, ze niezależnie od typu zlecenia aplet tenwyświetla „Hello World”.

Obiekt HttpServletResponse reprezentuje odpowiedź apletu. Aplet może wykorzystać ten obiekt dodostarczenia danych klientowi. Mogą to być dane różnego typu, typ ten jednak powinien być określony jakoczęść odpowiedzi. Aplet może również użyć tego obiektu do ustalenia nagłówków odpowiedzi HTTP. Wrozdziałach 5. i 6 „Przesyłanie treści multimedialnej” zostało omówione wszystko co może zrobić aplet jakoczęść odpowiedzi.

Nasz aplet ustala najpierw za pomocą metody setContentType() typ zawartości swojej odpowiedzi dotext/html, standardowego typu zawartości MIME dla stron HTML. Następnie używa metody getWriter() wcelu odczytania PrintWriter, międzynarodowego odpowiednika PrintStream. PrintWriter przekształcakod 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 doklienta.

Uruchamianie „Hello World”Do tworzenia apletów potrzebne są dwie rzeczy: pliki klasy interfejsu API, które używane są w tłumaczeniuprogramu źródłowego na język wynikowy oraz pojemnik apletu np. serwer WWW, który używany jest z koleiprzy uruchamianiu apletów. Wszystkie popularne pojemniki apletów oferują pliki klasy Interfejsu API, więcmoż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łozamieszczonych w rozdziale 1 „Wprowadzenie”. Dokonując wyboru serwera należy pamiętać, że musi onwspół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. Aktualnalista 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 odrodzaju serwera. W przykładach zaprezentowanych w tej książce występuje serwer „Apache Tomcat 3.2”, serwerimplementacji 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 niniejszejksiążce dlatego właśnie omówione zostaną tylko ogólne zasady dotyczące pracy z serwerem. Poniższeomówienia powinny być także zgodne z innymi serwerami (niż „Tomcat”) jednakże nie można tegozagwarantować.

Jeżeli używamy serwera „Apache Tomcat”, to powinniśmy umieścić kod źródłowy dla apletu w kataloguserver_root/webapps/ROOT/WEB-INF/classes (gdzie server_root jest katalogiem , w którym zainstalowaliśmynasz serwer), jest to standardowa lokalizacja plików klasy apletu. Powód, dla którego aplety występują w tymkatalogu zostanie omówiony później w tym rozdziale.

Page 32: Java Servlet - Programowanie

Kiedy już mamy właściwie umiejscowiony kod źródłowy HelloWorld, musimy go skompilować. To zadaniemożemy wykonać przy pomocy standardowego kompilatora javac (lub naszego ulubionego środowiskagraficznego Javy). Należy się tylko upewnić, że mamy pakiety javax.servlet i javax.servlet.http wnaszej ścieżce klasy. Pracując z serwerem „Tomcat”, wystarczy tylko zamieścić server_root/lib/servlet.jar (lubprzyszły odpowiednik) gdzieś w naszej ścieżce klasy. Nazwa pliku oraz lokalizacja zależą od serwera, więc wrazie problemów trzeba zajrzeć do dokumentacji serwera, z którym mamy do czynienia. Jeżeli wyświetlonyzostanie 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 wWindows) znajdujący się w katalogu server_root/bin.

To powinno wystarczyć do uruchomienia naszego serwera, jeżeli pracujemy w Solairs lub w Windows. Wprzypadku 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 porcie8080.

Istnieje wiele sposobów dostępu do apletów. Dla przykładu możemy zrobić to przez wyraźne wprowadzenie doURL-u nazwy klasy apletu. Możemy wprowadzić URL do swojej ulubionejprzeglą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świetlonastrona 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 przezuż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 czasieadministrowania 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 formularzowychAplet „HelloWorld” nie jest zbyt skomplikowany, przejdźmy więc do rzeczy bardziej zaawansowanych. Tymrazem utworzymy aplet, który będzie pozdrawiał użytkownika jego imieniem (i nazwiskiem). Nie jest to rzeczspecjalnie skomplikowana, potrzebny jest nam najpierw formularz HTML, który spyta użytkownika o jego imię inazwisko. Następująca strona powinna być w tym celu wystarczająca:

<HTML><HEAD><TITLE>Introductions</TITLE></HEAD><BODY><FORM METHOD=GET ACTION="/servlet/Hello">Pozwól że spytam, jak się nazywasz?<INPUT TYPE=TEXT NAME="imię i nazwisko"><P><INPUT TYPE=SUBMIT></FORM></BODY></HTML>

Na rysunku 2.4 został ukazany sposób, w jaki strona ta zostanie wyświetlona na stronie użytkownika.

Page 33: Java Servlet - Programowanie

Rysunek 2.4. Formularz HTML

Formularz ten powinien znaleźć się w pliku HTML, w katalogu serwera document_root. Jest to miejsce, wktó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ęcjakiekolwiek 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 jestwyją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ń. Naprzykładzie 2.2 została ukazana zmodyfikowana wersja naszego apletu „Hello”, która używa swojego obiektuzlecenia 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("<HTML>"); out.println("<HEAD><TITLE>Hello, " + name + "</TITLE><HEAD>"); out.println("<BODY">); out.println ("HELLO, " + name); out.println (</BODY></HTML>"); } public String getServletInfo() { return "aplet, który wie jak nazywa się osoba, do której + "mówi hallo"; }}

Powyższy aplet jest niemal identyczny z apletem „HelloWorld”. Najważniejsza różnica jest taka, że terazwywoływane jest reg.getParameter("name") w celu ustalenia imienia i nazwiska użytkownika oraz, fakt,że następnie to imię i nazwisko jest wyświetlane ,zamiast nieprzyjemnie bezosobowego (nie mówiąc już, żenadmiernie obszernego pojęciowo) „World”. Metoda getParameter() daje apletowi dostęp do parametrów wjego paśmie zapytań. Wartość zdekodowana parametru jest następnie odsyłana, lub jeżeli parametr nie byłokreślony — null. Jeżeli parametr został przesłany lecz nie została podana jego wartość, tak jak w przypadkupustego pola formularzowego, getParameter() odsyła pusty wiersz. Aplet ten dołącza również metodęgetServletInfo. Aplet może zignorować tę metodę w celu odesłania informacji opisowych o nim samym,takich jak np. cel jego działania, jego autor, wersja i (lub) prawa autorskie.

Jest to podobna sytuacja do apletu getAppletInfo(). Metoda jest używana zasadniczo w celu umieszczaniawyjaśnień na administracyjnym programie serwisowym serwera WWW, dlatego nie będzie zamieszczana w

Page 34: Java Servlet - Programowanie

następnych przykładach jako że wprowadza tylko niepotrzebne zamieszanie w procesie uczenia.

Wygląd apletu jest mniej więcej taki, jak na rysunku 2.5.

Rysunek 2.5. Aplet „Hello” używający danych formularzowych

Obsługa zleceń POSTPo omówieniu dwóch apletów, które wdrażają metodę doGet()możemy przejść do przerobienia naszego apletu„Hello” w taki sposób, aby również obsługiwał zlecenia POST. Naszym zadaniem jest doprowadzenie do tego,aby aplet zachowywał się tak samo jak przy metodzie POST, jak to miało miejsce przy GET, możemy to uzyskaćwysyłając po prostu wszystkie zlecenia POST do metody doGet() za pomocą następującego kodu:

Public void do Post (HttpServletRequest reg, HttpServletResponse res) throws ServletException, IOException { doGet (reg, res);}

Teraz już aplet „Hello” jest w stanie obsłużyć przedłożenia formularzy, które używają metody POST:<FORM METHOD=POST ACTION="/servlet/Hello">

Ogólnie rzecz biorąc, najlepiej jest kiedy aplet wdraża albo metodę doGet() albo doPost().

Aplet wybierając jedną w wyżej wymienionych metod bierze pod uwagę jaki rodzaj zlecenia będzie musiałobsłużyć (tak, jak to zostało omówione wcześniej). Kody wprowadzane, potrzebne do wdrożenia obu metod sąniemal takie same. Różnica polega na tym, że metoda doPost() ma dodatkowo zdolność przyjmowania dużychilości danych wejściowych.

Zastanowimy się co by się stało, gdybyśmy połączyli się z apletem „Hello” złożywszy zlecenie POST, przedwdrożeniem doPost() — domyślne zachowanie apletu odziedziczone po HttpServlet dla obu metod doGet() i doPost(), to zawiadomienie klienta, iż żądany URL nie współpracuje z tą metodą.

Obsługa zleceń HEADOdrobina pomysłowości sprawia, że obsługa zleceń HEAD (przesyłanych przez klienta, który chce oglądać tylkonagłówki odpowiedzi) staje się dziecinnie prosta , zaraz wyjaśnimy na czym rzecz polega. W takiej sytuacji niepiszemy przede wszystkim metody doHead() (jakkolwiek aplet, który jest podklasą dla HttpServlet orazwdraża metodę doGet() automatycznie współpracuje ze zleceniami HEAD).

A oto jak się to odbywa: metoda service() HttpServlet identyfikuje zlecenie HEAD i traktuje jeodmiennie. Konstruuje mianowicie zmodyfikowany obiekt HttpServletResponse i następnie przekazuje gorazem z niezmienionym zleceniem do metody doGet(). Ta ostatnia metoda postępuje w sposób standardowy, ztą różnicą tylko, że nagłówki, które ustala przesyła klientowi. Obiekt specjalnej odpowiedzi skutecznie tłumiwszystkie treści wychodzące.

Mimo, iż taka strategia jest wygodna, możliwe jest również usprawnienie procesu poprzez wykrywanie zleceńHEAD w metodzie doGet(), dzięki temu możliwa jest wcześniejsza odpowiedź (bez niepotrzebnych cyklówpisania danych wychodzących, których nikt nie będzie czytał). Na przykładzie 2.3 został ukazany sposób , w jakiza pomocą metody getMethod() zlecenia, można wdrożyć wspomnianą strategię w naszym serwerze „Hello”.

Przykład 2.3. Aplet „Hello” zmodyfikowany w sposób umożliwiający szybkie odpowiadanie na zlecenia HEADimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class Hello extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse res)

Page 35: Java Servlet - Programowanie

throws ServletException, IOException {

// Wstaw nagłówek Typ Zawartości res.setContentType ("text/html"); // Odeślij bezzwłocznie jeżeli to jest HEAD if. (req.getMethod ( ).equals ("HEAD")) return; // W przeciwnym razie postępuj PrintWriter out = res.getWriter ( ); String name = req.getParameter ("imię i nazwisko"); out.println("<HTML>"); out.println("<HEAD><TITLE>Hello, " + name + "</TITLE><HEAD>"); out.println("<BODY">); out.println ("HELLO, " + name); out.println (</BODY></HTML>"); }}

Zwróćmy uwagę, iż nawet wtedy, kiedy obsługujemy zlecenia HEAD, wstawiamy nagłówek Content-Type.Nagłówki takie są odsyłane do klienta. Niektóre wartości nagłówków, takie jak np. Content-Lenght mogą niebyć dostępne do czasu wygenerowania całej odpowiedzi. Skuteczność takiego skrótu może być ograniczona ,należy więc być ostrożnym jeżeli chce się precyzyjne określać wartości nagłówków.

Jest rzeczą ważną ażeby zakończyć obsługę zlecenia instrukcją return. Nie należy wywoływać System.exit(), ponieważ grozi to wyjściem z serwera.

Aplikacje WWWAplikacje WWW (czasem w skrócie określana web app) to zbiór apletów, stron systemów Javy (JSPs),dokumentów HTML, obrazów, szablonów oraz innych zasobów sieci WWW, które zostały skonfigurowane wtaki sposób , aby mogły być łatwo zainstalowane na każdym serwerze WWW udostępniającym aplety. Dziękistandaryzacji umiejscowienia plików w aplikacji WWW oraz standaryzacji formatu konfiguracji pliku, aplikacjaWWW może być przesyłana z jednego serwera na drugi bez potrzeby dodatkowego zarządzania serwerem.Czasy, kiedy do zainstalowania komponentów sieciowych innej firmy potrzebne było mnóstwo stroniczawierających szczegółowe instrukcje, odmienne dla każdego typu serwera należą już do przeszłości.

Wszystkie pliki w server_root/webapps/ROOT należą do jednej aplikacji WWW (aplikacji głównej). Pliki te,celem uproszczenia instalacji, mogą zostać zebrane w jeden plik archiwalny i zainstalowane na innym serwerze— poprzez samo umieszczenie tego pliku w określonym katalogu. Pliki te mają rozszerzenie .war (skrót od: webapplication archive). Pliki WAR to właściwie pliki JAR (utworzone przy pomocy programu narzędziowego jar)zapisane w zastępczym rozszerzeniu. Zastosowanie formatu JAR pozwala na przechowywanie plików WAR wformie skompresowanej oraz opatrywanie ich zawartości podpisem elektronicznym. Zamiast rozszerzenia .warwybrano rozszerzenie .jar, żeby zasygnalizować użytkownikom oraz narzędziom, których używają, iż pliki tenależy traktować inaczej.

Struktura organizacji plików w aplikacji WWW została ściśle określona. Na przykładzie 2.4 został ukazanylisting plików.

Przykład 2.4. Struktura organizacji plików w aplikacji WWWindex.htmlfeedback.jspimages/banner.gifimages/jumping.gifWEB-INF/web.xmlWEB-INF/lib/bhawk4j.jarWEB-INF/classes/Myservlet.classWEB-INF/classes/com/mycorp/frontend/CorpServlet.classWEB-INF/classes/com/mycorp/frontend/SupportClass.class

Taka hierarchia może być utrzymana jako oddzielne pliki w katalogu jakiegoś serwera lub też pliki te mogązostać zapisane jako plik WAR. Przy Instalowaniu ta aplikacja WWW może zostać przekształcona zjakimkolwiek przedrostkiem pliku URI na serwerze. Aplikacja WWW obsługuje wtedy wszystkie zleceniamające taki przedrostek. Jeżeli na przykład poprzednia struktura plików została utworzona pod przedrostkiem /demo, serwer użyje tej aplikacji WWW w celu obsłużenia wszystkich zleceń zaczynających się od /demo.Odpowiedzią dla zlecenia na demo/index/.html będzie plik index.html z aplikacji WWW. Zlecenie na /demo/feedback.jsp lub /demo/images/banner.gif zostało by także obsłużone z aplikacji WWW.

Page 36: Java Servlet - Programowanie

Katalog WEB-INFKatalog WEB-INF jest wyjątkowy — pliki nie są przesyłane bezpośrednio do klienta, zamiast tego zawierają oneklasy Java oraz informacje o konfiguracji dla aplikacji WWW. Katalog ten zachowuje się jak katalog META-INFpliku JAR: zawiera meta-informacje o treściach archiwalnych.

Katalog WEB-INF/classes zawiera pliki klas dla apletów aplikacji WWW oraz klasy wspomagające. WEB-INF/lib zawiera klasy przechowywane jako pliki JAR. Dla ułatwienia programy ładujace klasy serwera majaautomatyczny wgląd do WEB–INF/classes oraz do WEB–INF/lib podczas ładowania klas, wiec nie trzebawykonywać dodatkowych czynności przy instalowaniu tej aplikacji.

Aplety w tej aplikacji WWW mogą zostać wywołane przy użyciu URI , takich jak np.

/demo/servlet/Myservlet oraz /demo/ servlet/com.mycorp.frontend.CorpServlet. Zwróćmy uwagę , iż każdezlecenie na tą aplikacje zaczyna sie od /demo nawet zlecenia na aplety. W przypadku serwera „Tomcat”server_root/webapps/ROOT jest kontekstem domyślnym przekształconym na główną ścieżkę dostępu nazwypliku. Oznacza to, iż dostęp do apletów znajdujących się w server_root/webapps/ROOT/WEB-INF/classesmożliwy jest tak, jak pokazywaliśmy wcześniej, przy użyciu przedrostka /servlet/HelloWorld. W serwerze„Tomcat” domyślne odwzorowanie kontekstowe może zostać zmienione, a nowe odwzorowania mogą zostaćdodane przy użyciu pliku konfiguracyjnego działającego na wszystkich serwerach o nazwieserwer_root/conf/server.xml. Na innych serwerach konfiguracja odwzorowania przebiega w inny sposób(szczegóły można znaleźć w dokumentacji serwera).

Plik web.xml, znajdujący się w katalogu WEB-INF zwany jest deskryptorem wdrożenia. Zawiera on informacje okonfiguracji aplikacji WWW, w której się znajduje. Jest to plik XML ze standaryzowanym DTD. DTD zawieraponad 50 znaczników, pozwalając tym samym na pełną kontrolę nad aplikacją WWW. Plik deskryptorawdrożenia kontroluje rejestrację apletu, odwzorowanie URL, przyjmuje pliki i typy MIME jak również funkcjezaawansowane, takie jak regulacja poziomu zabezpieczeń strony oraz wpływa na zachowanie się apletu wśrodowisku rozproszonym. Zawartość tego pliku będzie omówiona szerzej w następnych rozdziałach.Szczegółowo opisane DTD można znaleźć w dodatku C „Opis deskryptora wdrożenia DTD”.

Struktura pliku web.xml nie jest rzeczą istotną na ten moment. Ważny jest fakt, iż plik deskryptora wdrożeniapozwala na wyszczególnienie informacji konfiguracyjnych w sposób niezależny od serwera, upraszczając tymsamym znacznie proces wdrażania. Deskryptory wdrożenia nie tylko umożliwiają przenoszenie prostych apletówlecz również pozwalają ma międzyserwerowy transfer podsekcji naszych stron.

Popyt na pliki WAR będzie prawdopodobnie rozwijał się z biegiem czasu. Staną się one prawdopodobnieintegralna częścią sieci. Prawdopodobnie będzie je można również ładować i instalować i będą od razu gotowedo pracy — bez względu na to, jaki system operacyjny jest używany przez nas lub przez nasz serwer WWW.

!!!!!!!!!!początek ramki

XML i DTDXML to angielski skrót (Extensible Markup Language)a , który można przetłumaczyć jako rozszerzalny językoznaczania znaczników, oznaczający rozszerzenie HTML-a. Jest to uniwersalna składnia (syntaktyka) tworzeniastruktury danych, stworzona przez Konsorcjum „World Wide Web Consortium” (W3C), mająca swój początek w1996 roku. Od czasu swojej standaryzacji w 1998 roku podbija sieć w zawrotnym tempie.

Cechą wspólna dla XML i HTML-u jest to ,że zarówno XML, jak i HTML określają treść i „mark it up”używając znaczników zawartych w nawiasach ostrych jak np. <title> i </title>. Jednakże XML spełnianieco inną rolę niż HTML — załączniki w dokumentach XML nie określają sposobu wyświetlania tekstu, leczraczej wyjaśniają jego znaczenie. XML jest to „rozszerzalny” język, oznaczania znaczników, ponieważ noweznaczniki mogą być tworzone ze swoim własnym znaczeniem, właściwym dla tworzonego dokumentu. XMLdziała najlepiej w formacie pliku jednorodnego, ponieważ jest to standardowa, dobrze opisana i niezależna odplatformy technika stworzona dla danych hierarchicznych, istnieje także wiele programów narzędziowychwspomagających odczytywanie, tworzenie oraz operowanie plikami XML.

Zasady pisania w XML-u są bardziej restrykcyjne niż w przypadku HTML-u. Przede wszystkim znaczniki XMLreagują na wielkość liter i tak inna będzie reakcja na napis <servlet>, 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ć. Taknp. jeżeli rozpoczniemy znacznikiem rozpoczynającym <servlet> to musimy zakończyć znacznikiemkońcowym </servlet>. Dla ułatwienia pusty znacznik składniowy <servlet> może zostać użyty jako

Page 37: Java Servlet - Programowanie

substytut oznaczenia jednoczesnego rozpoczęcia i zakończenia, pary znaczników <servlet></servlet>.Trzecia zasada jest taka, że elementy zagnieżdżone nie mogą się na siebie nakładać. Tak więc właściwym będzieukład <outside><inside>data</inside></outside>, układem niewłaściwym natomiast będzie<outside><inside>data</outside></inside>. 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"/> aniewł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 precyzujejakie znaczniki mogą znaleźć się w pliku zgodności XML, jaki rodzaj danych mają zawierać te znaczniki oraz togdzie w hierarchii mają (lub muszą) znajdować się te znaczniki. Każdy plik XML może być zadeklarowany dopewnego DTD. Pliki które całkowicie dostosowują się do DTD, do których są zadeklarowane nazywamy valid.XML jest używany w apletach jako format pamięci przy konfiguracji plików. XML może być również używanyprzez aplety przy tworzeniu treści , jak to zostało opisane w rozdziale 17 „XMLC” Więcej informacji o XML-umoż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 swoimidomenami — mogą oni nadzorować rejestrację apletu, odwzorowanie URL-u, rodzaj MIME oraz poziomzabezpieczenia strony — bez potrzeby ogólnego dostępu do serwera.

Deskryptor wdrożeniaProsty 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<?xml wersja="1.0" kodowannie="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// DTD Web Application 2.2 // EN" "http: // java.sun.com/j2ee/dtds/web-app_2_2.dtd"> <web-app> <servlet> <servlet-name> hi </servlet-name> <servlet-class> HelloWorld </servlet-class> </servlet> </web-app>

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 najego weryfikację (czy jest valid) oraz sprawdzenie czy odpowiada zasadom DTD. Wszystkie pliki deskryptorawdrożenia zaczynają się opisanymi dwoma wierszami lub bardzo podobnymi do nich.

Pozostała część tekstu — pomiędzy <web-app> a </web-app> dostarcza serwerowi informacji o tej aplikacjiWWW. Ten prosty przykład rejestruje nasz aplet „Hello World” przed nazwą hi (otaczający biały obszar jestwycinany). Zarejestrowana nazwa jest tu umieszczana pomiędzy znacznikami <servlet-class>. Znacznik<servlet> „spina” znaczniki <servlet-name> oraz <servlet-class>. Składnia deskryptora wdrożeniaXML wydaje się być lepiej zoptymalizowana dla odczytu automatycznego, raczej niż dla bezpośredniego (przezczłowieka).

Dlatego też większość producentów oferuje środki graficzne pomocne w procesie tworzenia web.xml. Na rynkudostępnych jest również wiele edytorów XML, które można wykorzystać przy tworzeniu XML-u.

!!!!!!!!!!!!!!!!!!!!początek ramki

Page 38: Java Servlet - Programowanie

Uwaga na kolejność znacznikówNie 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ącepoprawność składni wprowadzają taką kolejność oraz deklarują dokument jako nieprawidłowy (invalid) jeżelikolejność nie jest zachowana. Niektóre serwery, nawet te bez analizatorów poprawności składni, mogą być poprostu przygotowane na przyjęcie takiej kolejności, mogą więc być zdezorientowane w przypadku otrzymaniainnej. Dla pewności należy upewnić się, ze wszystkie znaczniki <web-app> zostały uporządkowane wewłaściwej kolejności. Niektóre znaczniki mogą, lecz nie muszą być obecne, lecz te, które zostały przez naszapisane 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 apletupod specjalna nazwą. Odpowiedź jest taka, że procedura ta pozwala na „zapamiętanie” przez serwer szczegółówzwiązanych z apletem i traktować go w szczególny sposób. Jednym z przykładów takiego traktowania jestmożliwość ustalenia wzorców URL, które wywołują zarejestrowany aplet. Klient może postrzegać URL będącyprzedmiotem zlecenia , tak jak każdy inny, jednakże serwer jest w stanie stwierdzić , że zlecenie jest zgodne zodwzorowaniem modelu (wzoru) i dzięki temu może być obsłużone przez odpowiedni aplet. Możemy dlaprzykładu sprawić, że http://server:8080/hello.html wywoła aplet „HelloWorld”. Użycie odwzorowań apletu wten sposób, pomaga ukryć , że strona używa apletów. Użycie to również pozwala apletowi na płynne zastąpienieistniejącej pod jakimkolwiek URL-em adresem strony, tak więc wszystkie zakładki oraz łączniki (linki) do stronynadal będą działać. Wzory URL konfigurowane są przy użyciu deskryptora wdrożenia, tak jak zostało tozaprezentowane na przykładzie 2.6.

Przykład 2.6.

Dołączanie odwzorowania apletu<?xml version="1.0" kodowanie="ISO-8859-1"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// DTD Web Application 2.2 // EN" "http: // java.sun.com/j2ee/dtds/web-app_2_2.dtd"><web-app> <servlet> <servlet-name> hi </servlet-name> <servlet-class> HelloWorld </servlet-class> </servlet> <servlet –mapping> <servlet –name> hi <servlet –name> <url-pattern> /hello.html </url-pattern> </servlet-mapping> </web-app>

Powyższy deskryptor wdrożenia dodaje zapis <servlet-mapping> informując tym samym serwer, że aplet hipowinien obsłużyć wszystkie URL-e zgodne ze wzorem dostępu do ścieżki głównej /hello.html. Jeżeli aplikacjaWWW zostanie przekształcona do głównej ścieżki „/” pozwoli to apletowi „Hello World” obsłużyć zlecenie nahttp://server:8080/hello.html. Jeżeli zamiast powyższego aplikacja WWW zostanie przekształcona do ścieżkidostę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ą czteryrodzaje (style) odwzorowania, wyszukiwane w następującej kolejności:

Page 39: Java Servlet - Programowanie

• Odwzorowania jawne, takie jak /hello.html lub /images/chart.gif, które nie zawierają symboliwieloznacznych. 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 tezaczynają 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łejhierarchii sieciowej. Dla przykładu, aplet obsługujący /dbfile/* może serwować pliki z bazy danych, podczasgdy 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 nawszystkich plikach określonego rozszerzenia. Dla przykładu aplet może zostać przypisany do obsługi plikówkończących się na *.jsp celem obsługi stron JavaServer (jest to de facto odwzorowanie niejawne będącedomeną specyfikacji apletu).

• Odwzorowanie domyślne /. Odwzorowanie to określa serwer domyślny dla aplikacji WWW — jeżeli innenie 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ącamoż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żkidostępu przedrostka przed dopasowaniami rozszerzenia. Odwzorowanie domyślne jest wywoływane tylkowtedy, kiedy nie występują żadne inne dopasowania. Dopasowania o dłuższym ciągu znaków w grupietraktowane 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żytedo wchodzenia do apletu „Hello World”.

Przykład 2.7. Sposoby powiedzenia „Hello”<!--... --><servlet-mapping> <servlet-name> hi </servlet-name> <url-pattern> /hello.html </url-pattern></servlet-mapping><servlet-mapping> <servlet-name> hi</servlet-name><url-pattern> *.hello</url-pattern></servlet-mapping><servlet-mapping><servlet-name> hi</servlet-name><url-pattern> /hello/*<url-pattern><servlet-mapping><!--... -->

Aplet „Hello World” może być wywołany poprzez użycie jednego z odwzorowań znajdujących sie na poniższejliś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.

Page 40: Java Servlet - Programowanie

Przejdźmy dalejDuża ilość informacji o apletach, aplikacjach WWW oraz plikach konfiguracyjnych XML zwartych wniniejszym 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 ichimionami i nazwiskami — właśnie o tym będzie traktować dalsza część książki.

Page 41: Java Servlet - Programowanie

Rozdział 3. Czas istnienia (cykl

życia) apletu

Czas istnienia (cykl życia) apletu jest jednym z bardziej interesujących aspektów apletów. Czas istnienia jesthybrydą czasów istnienia używanych w środkach programowania CGI oraz środkach programowania niskiegopoziomu WAI/NSAPI i ISAPI, tak jak zostało to omówione w rozdziale1 „Wprowadzenie”.

Alternatywa apletuCzas istnienia (cykl życia) apletów pozwala ich pojemnikom na odniesienie się zarówno do wydajności, jak i doproblemów związanych z CGI oraz do problemów dotyczących bezpieczeństwa nisko-poziomowych środkówprogramowania serwerów API. Pojemniki apletów uruchamiają zwykle aplety wszystkie razem, w jednejmaszynie wirtualnej Javy (JVM). Dzięki umiejscowieniu wszystkich apletów w tej samej JVM mogą oneskutecznie wymieniać dane między sobą, jednak co się tyczy ich danych „prywatnych” — język Java nie dajemożliwości wglądu jednemu apletowi w dane znajdujące się na drugim. Aplety mogą istnieć w JVM-ie pomiędzyzleceniami — jako kopie obiektów. Dzięki temu zajęte jest mniej pamięci niż w przypadku pełnej procedury, aaplety 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 swojejwłasnej maszynie wirtualnej Javy — tylko po to aby być usuniętymi i odtworzonymi nie obsłużywszy żadnychzleceń od klientów lub po obsłużeniu tylko jednego takiego zlecenia. Jednakże aplety zachowujące się w takisposób nie utrzymają się długo na rynku. W tym rozdziale omówimy najbardziej popularne oraz czułe realizacjeczasów istnienia apletów HTTP.

Pojedyncza maszyna wirtualna JavyWiększość pojemników apletowych wdraża wszystkie aplety do jednej JVM w celu maksymalizacji zdolnościapletów do wymiany informacji (wyjątkiem są tutaj pojemniki wyższej klasy, które realizują rozproszonewywołanie apletu na wielu serwerach wewnętrznych, tak jak zostało to omówione w rozdziale 12 „SerweryPrzedsiębiorstw oraz J2EE”.

Wykonania wyżej wspomnianej pojedynczej maszyny wirtualnej Javy mogą by różne na różnych serwerach:

Page 42: Java Servlet - Programowanie

• Na serwerze napisanym w Javie, takim jak np. „Apache Tomcat”, sam serwer może wywoływać w JVM-iewraz ze swoimi apletami.

• Na pojedynczo przetwarzającym, wielo-wątkowym serwerze WWW, zapisanym w innym języku, wirtualnamaszyna Javy może zostać zawarta w procedurze serwera. JVM jako część procedury serwera zwiększawydajność ponieważ aplet staje się w pewnym sensie, kolejnym rozszerzeniem serwera API niskiegopoziomu. 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 niemoże zawrzeć JVM w swojej procedurze ponieważ takiej nie posiada. Ten typ serwerów zwykle uruchamiazewnętrzny JVM, którego procedury może współdzielić. Taki sposób oznacza, iż każde wejście do apletuwiązać się będzie ze skomplikowanym połączeniem kontekstu przypominającym FastCGI. Jednakżewszystkie 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 mawiększego znaczenia ponieważ zachowują się one zawsze w te sam sposób.

Trwałość kopiiTak jak to zostało opisane wcześniej, aplety istnieją pomiędzy zleceniami jako kopie obiektów. Inaczej mówiącw czasie ładowania kodu dla apletu, serwer tworzy pojedynczą kopię. Ta pojedyncza kopia obsługuje wszystkiezlecenia, 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łobyutworzenie nowego obiektu apletu) aplet może być już ładowany w maszynie wirtualnej, kiedy zleceniedopiero 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 takiegopołą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 wsytuacji, kiedy otrzymują to samo zlecenie po raz drugi umieszczają całe strony w schowku, celemzaoszczędzenia czasu.

Aplety nie tylko trwają pomiędzy zleceniami, lecz także wykonują wszystkie wątki stworzone przez siebie. Takasytuacja nie jest może zbyt korzystna w przypadku apletu „run-of-the-mill”, jednakże daje interesującemożliwości. Rozważmy sytuację, w której podrzędny wątek przeprowadza pewne kalkulacje, podczas, gdy innewyświetlają ostatnie rezultaty. Podobnie jest w przypadku apletu animacyjnego, w którym jeden wątek zamieniaobraz, a inny nanosi kolory.

LicznikiW celu przedstawienia cyklu życia (czasu istnienia apletu) posłużymy się prostym przykładem. Przykład 3.1ukazuje serwer, który zlicza i wyświetla liczbę połączeń się z nim. Dla uproszczenia wynik przedstawiany jestjako 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 extends HttpServlet { int 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ę" +

Page 43: Java Servlet - Programowanie

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ędzywywołaniami, oraz w przypadku wszystkich wywołań.

Liczniki zsynchronizowaneZ punktu widzenia projektantów apletów każdy klient, to kolejny wątek, który wywołuje aplet poprzez metodytakie jak: service(), doGet (), doPost(), tak jak to pokazuje przykład 3.1*.

Rysunek 3.1. Wiele wątków — jedna kopia apletu

Jeżeli nasze aplety odczytują tylko zlecenia, piszą w odpowiedziach i zapisują informacje w lokalnychzmiennych, (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, leczpoza szczególną metodą) musimy być wtedy świadomi, iż każdy z wątków klienckich może operować tymizmiennymi apletu. Bez odpowiednich środków ostrożności sytuacja taka może spowodować zniszczenie danychoraz sprzeczności. I tak np. jeżeli aplet SimpleCounter założy fałszywie, że przyrost na liczniku orazwyprowadzenie są przeprowadzanie niepodzielnie (bezpośrednio jeden po drugim, nieprzerwanie), to jeżeli dwazlecenia zostaną złożone do SimpleCounter prawie w tym samym czasie, możliwe jest wtedy, że każdy z nichwskaże tą samą wartość dla count. Jak? Wyobraźmy sobie, że jeden wątek zwiększa wartość dla count i zarazpo tym, zanim jeszcze pierwszy watek wypisze wynik count, drugi wątek również zwiększa wartość. W takimprzypadku, 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 1count++ // Wątek 2out.println // Wątek 3out.println // Wątek 4

*To, iż jedna kopia apletu może obsłużyć wiele zleceń w tym samym czasie może wydawać się dziwne, dzieje się takprawdopodobnie 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 ondokładnym przedstawieniem rzeczywistości. Prawdziwa sytuacja wygląda tak, że wszystkie zadania wykonują wątki. Kopieobiektów nie są niczym więcej jak tylko strukturami danych, którymi operują wątki. Dlatego możliwa jest sytuacja, w którejdwa działające wątki używają w tym samym czasie tego samego obiektu.Ciekawostka: jeżeli أ wartość count byłaby zamiast 32-bitowego int, 64-bitowym long, teoretyczna możliwa byłabysytuacja, ż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

Page 44: Java Servlet - Programowanie

W tym przypadku ryzyko sprzeczności nie stanowi poważnego zagrożenia, jednakże wiele innych apletówzagrożonych jest poważniejszymi błędami. W celu zapobieżenia temu typowi błędów oraz sprzecznościom, któreim 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 innywą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 tensam blok synchroniczny, lub inny tym samym monitorem, wtedy pierwszy wątek musi zaczekać. Działa to nazasadzie ł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ługajest łatwa. Synchronizacja jednakże powinna być używana tylko w ostateczności. W przypadku niektórychplatform sprzętowych otrzymanie monitora za każdym razem, kiedy wchodzimy do kodu synchronicznegowymaga wiele wysiłku, a co ważniejsze w czasie, kiedy jeden wątek wywołuje kod synchroniczny, pozostałemogą być blokowane do zwolnienia monitora.

Dla SimpleCounter istnieją cztery sposoby rozwiązywania potencjalnych problemów. Po pierwsze możemydodać 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, jakomonitora. Nie jest to w rzeczywistości najlepsza metoda, ponieważ oznacza to, iż aplet może w tym samymczasie 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 zsynchronizowanymbloku, osiągając ten sam cel zgodności w wyniku liczenia. Prawdą jest, iż technika ta nie różni się specjalnie odpierwszego sposobu.

Trzecim sposobem poradzenia sobie z potencjalnymi problemami jest utworzenie synchronicznego bloku, którywykonywał będzie wszystko, co musi być wykonane szeregowo, a następnie wykorzystanie poza blokiemsynchronicznym. W przypadku naszego apletu, liczącego możemy zwiększyć wartość liczoną (count) w blokusynchronicznym, zapisać zwiększoną wartość do lokalnej zmiennej (zmiennej określonej wewnątrz metody), anastę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 tymzgodność liczenia.

Celem zastosowania czwartej, ostatniej z metod musimy zadecydować, czy chcemy ponieść konsekwencjezignorowania wyników synchronizacji. Czasem bywa i tak, że konsekwencje te są całkiem znośne. Dlaprzykładu, zignorowanie synchronizacji może oznaczać, że klienci otrzymają wynik trochę niedokładny. Trzebaprzyznać, 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żliwajest zamiana kopii zmiennych na zmienne lokalne. Zmienne lokalne są niedostępne dla innych wątków i tymsamym 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.

Page 45: Java Servlet - Programowanie

Liczniki całościoweModel „jeden egzemplarz na jeden aplet” jest sprawą do omówienia ogólnego. Prawda jest taka, że każdazarejestrowana 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 wydajesię być sensowna, ponieważ klient powinien kojarzyć odmienne nazywanie apletów z ich niezależnymdziałaniem. Osobne kopie są ponadto wymogiem dla apletów zgodnych z parametrami inicjalizacji, tak jak tozostało omówione dalej w tym rozdziale.

Nasz przykładowy SimpleCounter posługuje się kopią liczenia zmiennej przy zliczaniu liczby połączeń z nimwykonanych. 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ę kopiiutworzonych przez serwer (na jedną nazwę) oraz całkowitą liczbę połączeń z tymi kopiami.

Przykład 3.2. Licznik całościowy

import java.io.*;import java.util.*;import javax.servlet.*;import 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ż dotyczywszystkich 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 wpamię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 pomocyzmiennej kopii count, liczbę połączeń wspólnych za pomocą zmiennej klasy oraz liczbę kopii za pomocą tablicyasocjacyjnej — instances (kolejny wspólny element, który musi być zmienną klasy). Widok przykładu ukazujerysunku 3.2.

Page 46: Java Servlet - Programowanie

Rysunek 3.2. Widok licznika całościowego

Odnawianie (powtórne ładowanie) apletuJeś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 (poddomyślnym katalogiem apletów WEB-INF/classes). Jest to procedura wykonywana na bieżąco, która znacznieprzyś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 ClassLoaderzaprojektowane 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żeliokaże się, że tak wtedy serwer nie będzie już używał programu ładującego starej wersji pliku tylko utworzy nowakopię 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 żądanieadministratora.

W wersjach Interfejsów API sprzed wersji 2.2, chwyt z programem ładującym klasy skutkował tym, że inneaplety ładowane były przez odmienne programy ładujące — co skutkowało czasem zgłoszeniemClassCastException jako wyjątku, kiedy aplety wymieniały informacje (ponieważ klasa załadowana przezjeden 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óww tym samym kontekście. Tak więc obecnie większość wdrożeń ładuje każdy kontekst aplikacji WWW wjednym 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 tylkopodczas tworzenia.

Powtórne ładowanie (odnawianie) klasy nie jest przeprowadzane tylko wtedy, kiedy zmianie ulega klasawspomagająca. Celem większej efektywności określenia, czy jest konieczne odnawianie kontekstu, serwerysprawdzają tylko znaczniki czasu apletów klasy. Klasy wspomagające w WEB-INF/classes mogą być takżepowtórnie załadowane, kiedy kontekst jest odnowiony, lecz jeżeli klasa wspomagająca jest jedyną klasą dozmiany, serwer tego prawdopodobnie nie zauważy.

Odnowienie apletu nie jest także wykonywane dla wszystkich klas (apletu lub innych) znajdujących się w ścieżceklasy serwerów. Klasy takie ładowane są przez rdzenny (pierwotny) program ładujący, a nie własny, koniecznydo powtórnego załadowania. Klasy te są również ładowane jednorazowo i przechowywane w pamięci nawetwtedy, gdy ich pliki ulegają zmianie. Jeżeli chodzi o klasy globalne (takie jak klasy użytecznościcom.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ć tychobiektów bez ClassCastException.

Metody „Init” i „Destroy”Tak jak zwykłe aplety, aplety wykonywane na serwerach mogą określać metody init() i destroy(). Serwerwywołuje metodę init() po skonstruowaniu kopii apletu, jednak zanim jeszcze aplet obsłuży jakiekolwiekzlecenie. Serwer wywołuje metodę destroy() po wyłączeniu apletu i zakończeniu wszystkich zleceń lub poprzekroczeniu ich limitu czasowego*.

W zależności od rodzaju serwera oraz konfiguracji aplikacji WWW, metoda init() może zostać wywołana wponiższych momentach:

* 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ą tworzonelub zakańczane oraz podczas wiązania i rozwiązywania atrybutu z kontekstem lub sesją.

Page 47: Java Servlet - Programowanie

• podczas uruchamiania apletu

• podczas pierwszego łączenia się z apletem, przed wywołaniem metody service()• na żądania administratora serweraW każdym przypadku metoda init() zostanie wywołana i zakończona ani zanim jeszcze aplet obsłuży swojepierwsze zlecenie.

Metoda init() jest zwykle wykorzystywana do inicjalizacji apletu — ładowania obiektów używanych przezaplet w procesie obsługi zleceń. W czasie wykorzystywania metody init() aplet może „chcieć” odczytać swojeparametry inicjalizacji (init). Parametry te są dostarczane samemu apletowi i nie są w jakikolwiek sposóbzwią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 tegow 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 nasygnały, kiedy kontekst lub sesja są tworzone lub zakańczane oraz podczas wiązania i rozwiązywania atrybutu zkontekstem lub sesją.

Parametry początkowe dla apletu można znaleźć w deskryptorze wdrożenia, niektóre serwery mają graficzneinterfejsy mogące zmodyfikować ten plik (patrz przykład 3.3).

Przykład 3.3. Ustalanie wartości parametrów w deskryptorze rozmieszczenia

<?xml version = "1.0" kodowanie = "ISO-8859-1"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// DTD Web Application 2.2 // EN" "http: // java.sun.com/j2ee/dtds/web-app_2_2.dtd"><web-app> <servlet> <servlet-name> counter </servlet-name> <servlet-class> InitCounter </servlet-class> <init-param> <param-name> initial </param-name> <param-value> 1000 </param-value> <description> The initial value for the counter <!—optional </description> </init-param> </servlet> </web-app>

Wielokrotne elementy <init-param> mogą zostać umieszczone w znaczniku <servlet>. Znacznik<descriptor> jest opcjonalny, pierwotnie miał on być przeznaczony do graficznych programównarzę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ł iktóre nie będą odzyskiwane. Metoda destroy() daje również apletowi możliwość wypisania jego niezapisanych informacji ze schowka lub innych trwałych informacji, które powinny zostać odczytane podczasnajbliższego wywołania metody init().

Licznik z metodą InitParametry początkowe mają wiele zastosowań. Zasadniczo jednak określają początkowe lub domyślne wartościdla zmiennych apletu lub „mówią” apletowi jak w określony sposób dostosować jego zachowanie. Naprzykładzie 3.4 nasz SimpleCounter został rozszerzony celem odczytania parametru początkowego (zwanegoinitial), który przechowuje wartość początkową dla naszego licznika. Poprzez ustawianie początkowego stanulicznika na wysokie wartości możemy sprawić, że nasza strona będzie wydawała się bardziej popularna niż wrzeczywistości.

Page 48: Java Servlet - Programowanie

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 parametrupoczątkowego zwanego initial. Metoda ta przyjmuje tą nazwę jako String i oddaje wartość również jakoString. 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 chcemywypróbować ten przykład może się okazać konieczne powtórne „zastartowanie” serwera celem wprowadzeniazmian 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ł parametrServletConfig, 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 samegointerfejsu ServletConfig (przekazując wszystkie wywołania do delegowanej konfiguracji pozwalając tymsamym 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, żeobecnie wystarczy tylko jak aplet wdroży wersję bez-argumentową init(), a obsługa ServletConfig iGenericServlet będzie zrealizowana na dalszym planie. Poza scenami, klasa GenericServlet współpracujez 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();

Page 49: Java Servlet - Programowanie

}

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-argumentowegoinit(), 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) iwywoł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żamydestroy() powoduje to, że GenericServlet wdraża destroy(), która to metoda „pisze” wiadomość dorejestru zdarzeń, że aplet jest niszczony.

!!!!!!!!! koniec ramki

Licznik z metodami Init i DestroyDo tego momentu przykłady liczników demonstrowały jak stan apletu utrzymuje się pomiędzy połączeniami. Tojednak rozwiązuje problem tylko częściowo. Za każdym razem, kiedy serwer jest wyłączany lub apletodnawiany, 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ładinitCounter, dodając apletowi możliwość zachowania swojego stanu podczas destroy() oraz podczaspowtórnego ładowania stron w init(). Dla uproszczenia przyjmijmy, że aplet ten nie jest zarejestrowany idostępny tylko pod http://server:port/servlet/InitDestroyCountery. Gdyby ten aplet był zarejestrowany podróż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 stanuFileReader 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 zapisanegocatch (IOException ignored) {} // problem podczas czytaniacatch (NumberFormatException ignored) {} //zniekszta stan zapisanyłćfinally { // Nie zapomnij zamkn plikąć try { if(bufferedReader ! = null) { bufferedReader.close() }}catch (IOException ignored) {}}

Page 50: Java Servlet - Programowanie

// 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 out = res.getWriter();count++out.println ("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 nazwieInitDestroyCounter.initial. Jeżeli nie ma dostarczonej ścieżki dostępu, plik jest zapisywany w procedurzeserweru bieżącego katalogu, zwykle jest to katalog auto-startowy. Sposoby alternatywnej lokalizacji opisano wrozdziale 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ś powodupróba odczytu nie powiedzie się (jak ma to miejsce podczas pierwszego uruchomienia apletu — ponieważ plikjeszcze wtedy nie istnieje), aplet sprawdza, czy parametr początkowy określa liczenie początkowe. Jeżeli i to nieda 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 celuformatem użytkowym pliku, tak jak to zostało tutaj zrobione przez nas. Inne serwery zapisują swój stan jakserializowane 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 stanapletu jest zapisywany rzadko, podczas gdy plik dziennika wprowadza do pamięci przyrostowe aktualizacje wtrakcie 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.

Page 51: Java Servlet - Programowanie

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 takajest problemem dla apletu, który musi zapisywać swój stan w swojej metodzie destroy(). Ratunkiem dla tychapletów jest częstsze zapisywanie swojego stanu. Aplet może „zdecydować się” na zapisanie swojego stanu poobsłudze każdego ze zleceń tak jak powinien to zrobić aplet „chess server” (serwer szachowy), tak że nawetkiedy serwer jest ponownie uruchamiany, gra może zostać wznowiona z ostatnią sytuacją na szachownicy. Inneaplety mogą potrzebować zapisać strony tylko po zmianie jakiejś ważnej wartości — aplet „shopping cart” (listazakupów) musi zapisać swój stan tylko wtedy, gdy klient doda lub usunie pozycję z listy. I w końcu niektóreaplety mogą tracić niektóre ze swoich ostatnich zmian stanu. Takie aplety mogą zapisywać stan po pewnejokreślonej liczbie zleceń. Dla przykładu, w naszym przykładzie InitDestoyCounter, wystarczającympowinno być zapisywanie stanu co dziesięć połączeń. Celem wdrożenia powyższego można dodać prosty wierszna 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 zsynchronizacją. Stworzyliśmy możliwość utraty danych (jeśli saveState() zostanie uruchomionym przez dwawątki, w tym samym czasie) oraz ryzyko, że saveState() nie będzie w ogóle wywołane i jeżeli liczeniezostanie zwiększone przez kilka wątków z rzędu przed sprawdzeniem. Załóżmy, że taka możliwość nie istniałakiedy saveState() było wywoływane tylko z metody destroy() ponieważ metoda destroy() jestwywoływana tylko raz na jedną kopię apletu. Jednakże teraz, kiedy saveState() jest wywoływana w metodziedo Get() musimy ponownie się nad tym zastanowić. Jeżeli zdarzyłoby się kiedyś, że aplet ten byłby odwiedzanytak często, iż byłoby więcej niż 10 niezależnie wywołujących wątków, jest prawdopodobne, że dwa aplety (10osobnych 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ś „zorientujesię”, iż minął czas wywoływania saveState(). Rozwiązanie jest proste: przemieśćmy kontrolę liczenia dobloku zsynchronizowanego, tam gdzie liczenie jest zwiększane:

int local_count ; synchronizeed(this) { local_count = ++count ; if (count % 10 == 0) saveState(); } 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ązanymz 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ą takachęć poprzez wdrożenie interfejsu javax.servlet.SingleThreadModel. Jest to prosty interfejs „tag”, którynie określa żadnych metod, ani zmiennych, służy tylko do oznaczenia apletu, jako „wyrażającego chęć” zmianystanu istnienia.

Serwer, który ładuje aplet SingleThreadModel (Model jedno-wątkowy) musi gwarantować, zgodnie zdokumentacją 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 narycinie 3.3 dzięki temu każdy aplet wdrażający SingleThreadModel może zostać uznany jako bezpieczny codo 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ączachowanie identyczne z metodą zsynchronizowaną service().

Jeżeli nie mamy pecha i nasz serwer nie będzie miał awarii أ podczas stosowania metody destroy(). W przeciwnymwypadku możemy zostać z częściowo zapisanym plikiem stanu — pozostałościami napisanymi na górze naszegopoprzedniego stanu. Celem osiągnięcia całkowitego bezpieczeństwa aplet zapisuje swój stan w pliku roboczym, kopiując gonastępnie na górze oficjalnego pliku stanu w jednej komendzie.

Page 52: Java Servlet - Programowanie

Rysunek 3.3. Model jedno-wątkowy

Czas istnienia SingleThreadModel (Modelu jedno-wątkowego) nie ma zastosowania dla liczników lub innychaplikacji 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 wydzielonegoobiektu 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życiesynchronizacji, pozwalając apletowi na obsługę tylko jednego zlecenia w jednym momencie. Poprzez wdrożenieSingleThreadModel oraz poprzez fakt, iż jest tylko jedno „połączenie” kopii zmiennej, aplet może w prostysposó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 java.io.*;import java.sql.*;import java.util.*;import javax.servlet.*;import 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();

Page 53: Java Servlet - Programowanie

}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 wielelepszym rozwiązaniem dla apletu byłoby użycie wydzielonego obiektu ConnectionPool przechowywanegojako 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 wydzielonydostę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ędziezawsze 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ówmogł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 czynitego z systemem. Interfejs nie zapobiega problemom związanym z synchronizacją, które wynikają zjednoczesnego dostępu apletów do wspólnych zasobów takich jak np. zmienne statyczne czy obiekty pozazasię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 drugoplanoweAplety 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ępnianewielu klientom. Wątki drugoplanowe, uruchomione w init() wykonują pracę w sposób ciągły podczas gdywątki obsługujące zlecenia wyświetlają stan bieżący za pomocą doGet(). Jest to technika podobna to tejuż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 liczbawybierana 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: apletselekcjonuje wszystkie liczby nieparzyste i następnie próbuje podzielić je przez liczby nieparzyste całkowite zprzedział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ą.*

Przykład 3.7. W poszukiwaniu liczb pierwszychimport java.io.*;import java.util.*;import javax.servlet.*;import 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);

* 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 liczbod niego mniejszych.

Page 54: Java Servlet - Programowanie

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 znalezionazostaje zapisana w lastprime, a czas, w którym to się stało w lastprimeModified. Za każdym razem kiedyklient łączy się z apletem, metoda doGet() informuje go jaka największa liczba pierwsza została znaleziona dotej 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ówpołą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łamaszyna wirtualna. Nawet jeżeli aplet zostanie powtórnie załadowany (odnowiony) (jawnie bądź z powoduzmiany pliku klasy) jego wątki nie przestaną działać. Zamiast tego jest prawdopodobne, że nowy aplet utworzykopie dodatkowe wątków drugoplanowych.

Uruchamianie i rozruchAby sprawić, że PrimeSearcher zacznie szukać liczb pierwszych tak szybko jak to możliwe, możemyskonfigurować aplikację WWW apletu w taki sposób, że będzie ładowała aplet przy startowaniu serweru.Dokonuje się tego poprzez dodanie znacznika <load-on-startup> do hasła <servlet>, deskryptorawdrożenia, tak jak na przykładzie 3.8.

Przykład 3.8. Ładowanie apletu przy rozruchu<?xml version = "1.0" kodowanie = "ISO-8859-1"?>

Page 55: Java Servlet - Programowanie

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2 //EN" "http: // java.sun.com/j2ee/dtds/web-app_2_2.dtd">

<web-app> <servlet> <servlet-name> ps </servlet-name> <servlet-class> PrimeSearcher </servlet-class> <load-on-startup/> </servlet> </web-app>

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 ładowanaprzy rozruchu.

Na przykładzie 3.8 znacznik <load-on-startup> 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ówkontekstu. Aplety z niższymi liczbami ładowane są przed tymi z liczbami większymi. Aplety z wartościamiujemnymi lub nie-całkowitymi mogą być ładowane w każdym momencie sekwencji uruchamiania, dokładnakolejność zależna jest wtedy od serwera. Dla przykładu, web.xml ukazany na przykładzie 3.9 gwarantuje, żefirst będzie załadowany przed second, podczas gdy anytime może zostać załadowany w każdym momencierozruchu serwera.

Przykład 3.9. Pokaż możliwości apletu

<web-app> <servlet> <servlet-name> first </servlet-name> <servlet-class> First </servlet-class> <load-on-startup >10</load-on-startup> </servlet> <servlet> <servlet-name> second </servlet-name> <servlet-class> Second </servlet-class> <load-on-startup >20</load-on-startup> </servlet> <servlet> <servlet –name> anytime </servlet-name> <servlet-class> Anytime </servlet-class> <load-on-startup/> </servlet> </web-app>

Buforowanie podręczne po stronie klientaDo tej pory nauczyliśmy się, że aplety obsługują zlecenia GET przy pomocy metody doGet(). I jest to niemalprawda. 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łaniadoGet() generują po prostu stronę, którą użytkownik już oglądał, stronę prawdopodobnie przechowywaną wpamięci podręcznej przeglądarki. To co jest na ten moment najbardziej potrzebne, to sposób w jaki aplet mógłbyinformować o zmianach w jego wydruku wyjściowym. I tutaj właśnie pomocne będzie omówienie metodygetlastModified().

Page 56: Java Servlet - Programowanie

Większość serwerów zawiera, jako część swojej odpowiedzi, nagłówek Last-Modified, wtedy kiedy odsyładokument. 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 GMTPowyższy nagłówek informuje klienta o tym, kiedy ostatnio została zmieniona strona. Informacja sama w sobienie jest specjalnie wartościowa, jednak zyskuje na wartości w momencie kiedy przeglądarka powtórnie ładujestronę.

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 Last-Modified:

Tue, 06-May-98 15:41:02 GMTNagłó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 odokreś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 otym, że wystarczy powtórnie wyświetlić wersję dokumentu schowaną w schowku (ta odpowiedź to: kod stanu304 Not Modified).

Technika ta działa najlepiej w przypadku stron statycznych: serwer może użyć systemu plików w celusprawdzenia kiedy określony plik został zmodyfikowany po raz ostatni. Jednakże dla treści tworzonejdynamicznie, takiej jaka jest odsyłana przez aplety, serwer potrzebuje dodatkowej pomocy. Jednak wszystko comoże zrobić to odtwarzać je bezpiecznie zakładając jednocześnie, że zawartość ulega zmianie z każdympołą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(). Apletwdroż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łówkaodpowiedzi 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 przezgetLastModified() jest taki sam, bądź wcześniejszy niż czas nadesłania nagłówka If-Modified-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 sytuacjachnajlepszym wyjściem jest użycie zachowania domyślnego „play-it-safe”. Większość apletów jednakże doskonaledaje sobie z tym radę. Rozważmy przypadek apletu „bulletin board” („elektroniczny biuletyn informacyjny”),aplet taki może zarejestrować i odesłać kiedy, po raz ostatni treść biuletynu została zmieniona. Nawet gdy tensam aplet obsługuje kilka biuletynów, nadal może przesyłać różne czasy modyfikacji, odpowiednie dlaparametró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óraupł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 wczytaniaLastprimeModified() jako long.Aplet zanim odeśle tą wartość czasową, zaokrągla ją do najbliższej sekundy, dzieląc ją przez tysiąc a następniemnożąc przez tysiąc. Wszystkie czasy odesłane przez getLastModified() powinny być zaokrąglone w tensposób. Powodem tego jest fakt, że nagłówki Last-Modified oraz If-Modified-Since są przypisane donajbliższej sekundy. Jeżeli get-Last-Modified odeśle ten sam czas lecz z wyższą rozdzielczością, czas tenmoże błędnie wydawać się parę milisekund późniejszy niż podany przez If-Modified-Since. Załóżmy dlaprzykł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 GMTTeraz załóżmy znowu., że użytkownik powtórnie ładuje stronę i, że przeglądarka podaje serwerowi poprzeznagłówek If-Modified-Since, czas który uważa za czas ostatniej modyfikacji:

* Aplet może wstawić bezpośrednio swój nagłówek LastModified, w doGet(), przy użyciu technik omówionych wrozdziale 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.

Page 57: Java Servlet - Programowanie

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 on359 milisekund wcześniejszy od odesłanego przez getLastModified(), a następnie fałszywie zakładają, żetreść 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. ObiektHttpServletRequest jest przekazywany do getLastModified() w razie jakby aplet potrzebował oprzećswoje rezultaty na informacjach specyficznych dla określonego zlecenia. Standardowy aplet elektronicznegobiuletynu informacyjnego może to wykorzystać np. do określenia który biuletyn jest przedmiotem zlecenia.

Buforowanie zewnętrzne po stronie serweraMetoda 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ętrznyprzechwycony i umieszczony w pamięci podręcznej na stronie serwera, a następnie odesłany do klientów jak toma miejsce przy metodzie getLastModified(). Taka procedura może znacznie przyspieszyć tworzenie stronyapletu, 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świetlaprzedłożone przez użytkowników komentarze. Aplet przechowuje komentarze użytkowników w pamięci jakoobiekty Vector of GuestbookEntry.W rozdziale 9 „Dołączalność bazy danych” poznamy wersję tego apletudział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 powodutego, że aplet rozszerza CacheHttpServlet, wizualizacja musi mieć miejsce tylko podczas pierwszegozlecenia GET, po dodaniu nowego komentarza. Wszystkie późniejsze zlecenia GET wysyłają odpowiedź zpamię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 java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import 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 ("<HTML><HEAD><TITLE><Guestbook</TITLE></HEAD"); out.println ("<BODY>"); }

Page 58: Java Servlet - Programowanie

private void printForm(printWriter out) throws ServletException { out.println ("<FORM METHOD=POST>"); // księguje do siebie

out.println ("<B>Proszę prześlij swoje informacje kontrolne:<B><BR>"); out.println ("Twoje imię i nazwisko:<INPUT TYPE = TEXT NAME = name><BR>"); out.println ("Twój e-mail: :<INPUT TYPE = TEXT = e-mail><BR>"); out.println ("Komentarz: INPUT TYPE = TEXT SIZE = 50 NAME = comment ><BR>"); out.println ("<INPUT TYPE = SUBMIT VALUE=\"Prześlij informacjekontrolne\"><BR>"); e-mail><BR>"); out.println ("</FORM>); out.println ("<HR>");}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 ("<DL">);out.println ("<DT><B>" + name + "</B>(" + e-mail +") jest następującej treści"); out.println ("<DD><PRE>" + comment + "</PRE>"); out.println ("</DL>");// Wstrzymaj wykonywanie na pół sekundy w celu pobudzenia// powolnego źródła danych try {Thread.sleep(500);} catch (InterruptedExceptionignored) { } } }private void printFooter(printWriter out) throws ServletException { out.println ("</BODY>");} private void handleForm (HttpServletRequest req, HttpServletResponse res) {

GuestbookEntry entry = new GuestbookEntry(); entry.name = req.getParameter("imię i nazwisko"); entry.email = req.getParameter("e-mail"); entry.coment = req.getParameter("komentarz"); entries.addElement(entry); // Zwróć uwagę, ż mamy nowy czas ostatniej modyfikacji lastModified = System.currentTimeMillis(); } public long getLastModified (HttpServletRequest req) { return LastModified; }}class GuestbookEntry { public String name; public String email; public String comment;}

Źródło CacheHttpServlet zostało pokazane na przykładzie 3.11. Kod, w tym miejscu tej książki, możewydawać się bardzo skomplikowany. Po przeczytaniu rozdziału 5, można spróbować odczytać kod dla tej klasy.Przed obsługą zlecenia klasa CacheHttpServlet sprawdza wartość getLastModified(). Jeżeli pamięćpodręczna wydruku wyjściowego jest co najmniej tak aktualna jak czas ostatniej modyfikacji apletu, schowany wpamięci podręcznej wydruk wyjściowy wysyłany jest bez wywoływania metody doGet() apletu.

Jeżeli klasa ta wykryje, że ciąg zapytań, informacje dodatkowej ścieżki dostępu lub ścieżki dostępu apletuzostały zmienione, to w celu zachowania bezpieczeństwa pamięć podręczna jest unieważniana i tworzona odnowa. Unieważniana nie jest jednakże pamięć oparta na różnych nagłówkach zleceń lub cookies; w przypadkuapletów, które różnicują swój wydruk wyjściowy, oparty na takich wartościach (tzn. serwerów śledzących sesję),

Page 59: Java Servlet - Programowanie

klasa ta raczej nie powinna być używana lub metoda getLastModified() powinna zająć się nagłówkami orazcookies. Buforowanie zewnętrzne nie jest stosowane przy zleceniach POST.

Rysunek 3.4. Wydruk wyjściowy księgi gości

CacheHttpServletResponse oraz CacheServletOutputStream są klasami pomocniczymi dla klasy i niepowinny być używane bezpośrednio. Klasa została utworzona na podstawie Interfejsu API 2.2 (Servlet API 2.2),dlatego używanie jej z poprzednimi wersjami InterfejsuAPI przebiega poprawnie; jednakże używanie jej zprzyszłymi wersjami prawdopodobnie nie będzie już przebiegało tak dobrze ponieważ interfejsHttpServletResponse, który CacheHttpServletResponse musi wdrożyć, prawdopodobnie ulegniezmianie i w związku z tym niektóre metody interfejsowe pozostaną nie wdrożone. Jeżeli natknęlibyśmy się nataki problem, to aktualna wersja tej klasy dostępna jest na stronie http://www.servlets.com.

Interesujący jest sposób w jaki CacheHttpServlet przechwytuje zlecenie w celu jego wczesnegoprzetworzenia. Otóż wprowadza on metodę service (HttpServletRequest, HttpServerResponse),którą serwer wywołuje w celu przekazania apletowi kontroli nad obsługą zleceń. Standardowa implementacjaHttpServlet tej metody przesyła zlecenie do doGet(), doPost() oraz innych metod zależnych od metodyHTTP zlecenia. CacheHttpServlet ignoruje taką implementację zyskując tym samym pierwszeństwo kontrolinad obsługą zleceń. Kiedy klasa kończy przetwarzanie może wtedy przekazać z powrotem kontrolę doimplementacji egzekucyjnej za pomocą wywołania super.service().

Przykład 3.11. Klasa CacheHttpServlet

package com.oreilly.servlet;import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;

public abstract class CacheHttpServlet extends HttpServlet {

CacheHttpServletResponse cacheResponse; long cacheLastMod = -1; String cacheQueryString = null; String cachePathInfo = null; String cacheServletPath = null; Object lock = new Object();

protected void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Dla zlece GET wykonuj tylko buforowanie podr czneń ę String method = req.getMethod(); if(!method.equals("GET")) { super.service(req, res); return ; }

Page 60: Java Servlet - Programowanie

// Sprawd czas ostatniej modyfikacji dla tego apletuź long servletLastMod = getLastModified(req);

// Ostatnio zmodyfikowany –1 oznacza, e nie powinni my w ogóle u ywa pami ciż ś ż ć ęlogicznej if(servletLastMod == -1) { super.service(req, res); return ; }

// je eli klient przys a nag ówek If-Modified-Since, w lub poż ł ł ł // czasie ostatniej modyfikacji apletu prze lij krótki kod statusuś // "Nie zmodyfikowany". Zaokr gl do najbli szej sekundyą ż // jako e nag ówki klienta s w sekundachż ł ą if((servletLastMod / 1000 * 1000) <=

req.getDateHeader("If-Modified-Since")) { res.SetStatus(res.S.C._NOTMODIFIED); return ; }

// Wykorzystaj istniej c pami podr czn je eli jest ona aktualna i poprawnaą ą ęć ę ą ż CacheHttpServletResponse localResponseCopy = null; synchronized(lock) { if (servletLastMod <= cacheLastMod && cacheResponse.isValid() && equal (cacheQueryString, req.getQueryString()) && equal (cachePathInfo, req.getPathInfo()) && equal (cacheServletPath, req.getServletPath()) { localResponseCopy = cacheResponse; } } if (localResponseCopy ! = null) { localResponseCopy.writeTo(res); return ; }

// W przeciwnym razie utwórz nowy schowek, aby zachowa odpowiedć ź localResponseCopy = new CacheHttpServletResponse(res); super.service(req, localResponseCopy);

synchronized (lock) { cacheResponse = localResponseCopy; cacheLastMod = servletLastMod; cacheQueryString(); cachePathInfo = req.getPathInfo(); cacheServletPath = req.geServletPath(); }}

private boolean equal(String s1, String s2) { if (s1 == null && s2 == null) { return true; } else if (s1 = null I I s2 == null) { return false;}else { return s1.equals(s2);} } }

class CacheHttpServletResponse implements HttpServletResponse { // Przechowuj kluczowe zmienne odpowiedzi w celu // wstawienia ich pó niejźprivate int status private Hashtable headers;private int contentLength;private String contentType;private Locale locale;private Vector cookies;private boolean didError;private boolean didRedirect;private boolean gotStream;private boolean gotWriter;

private HttpServletResponse delegate; private cacheServletOutputStream out; private PrintWriter writer;

Page 61: Java Servlet - Programowanie

CacheHttpServletResponse (HttpServletResponse res) { delegate = res; try { out = new cacheServletOutputStream(res.getOutputStream());

} catch (IOExcepion e) {

System out.println ( " Otrzyma e IOException tworz c odpowied z pami ci podr cznej:" ł ś ą ź ę ę + e.getMassage());}internalReset() {}

private void internalReset() { status = 200; headers = new Hashtable; contentLength = -1; contentType = null; locale = null; cookies = new Vector(); didError = false; didRedirect = false; gotStream = false; gotWriter = false; out.grtBuffer().reset();}

public boolean isValid() {

// Nie przechowujemy w pami ci podr cznej stron z b damię ę łę// lub przekierowań return didError != true && didRedirect !=true;}

private void internalSetHeader(String name, Object value) { Vector v = new Vector(); v.addElment(value);

headers.put(name, v);}

private void internalAddHeader(String name, Object value) { Vector v = (Vector) headers.get(name); if (v == null ) { v = new Vector(); } v.addElement(value); headers.put(name, v); }

public void writeTo(HttpServletResponse res) { // Pisz kod statusu res.setStatus(status) // Pisz nag ówki upraszczaj ceł ą if (contentType != null)res.setContentType(contentType); if (locale! = null) res.setLocale(locale); // Pisz cookies Enumeration enum = cookies.elements(); while (enum.hasMoreElements()) {Cookie c =(Cookie) enum.nextElement(); res.addCookie(c); } // Pisz nag ówki standardoweł enum = header.keys();

while(enum.hasMoreElements()) { String name = (String) enum.nextElement(); Vector values = (Vector) headers.get(name); // mo e mie wielokrotne warto ciż ć ś Enumeration enum2 = values.elements(); while (enum2.hasMoreElements()) { Object value = enum2.nextElement(); if(value instanceof String ) { res.setHeader(name, (String )value);

} if(value instanceof Integer ) { res.IntHeader(name, ((Integer )intvalue());

Page 62: Java Servlet - Programowanie

}

if((value instanceof Long ) { res.setDateHeader(name, ((Long )value).longValue()); } } } // Pisz d ugo tre cił ść ś res.setContentLength(name, (out.getBuffer().size()); // Pisz treść try { out.getBuffer().writeTo(res.getOutputStream(();

} catch (IOException e) { System.out.println( " Otrzyma e IOException pisemn odpowied tekstow z pami ci ł ś ą ź ą ę podr cznej:" + e.getMessage());ę }

}

public ServletOutputStream() throws IOExcepion { if (gotWriter) { throw new IllegalStateException( "Niemo liwo otrzymania strumienia wyj ciowego po uzyskaniu wykonawcyż ść śzapisu"); } gotStream = true; return out; public PrintWriter getWriter() throws UnsupportedEncodingExcepion {

if (gotStream) { throw new IllegalStateException( " Niemo liwo otrzymania wykonawcy zapisu po uzyskaniu strumieniaż śćwyj ciowego ");ś } gotWriter = true; if(writer == null) { OutputStreamWriter w = new OutputStreamWriter(out, getCharacterEncoding()); writer = new PrintWriter(w, true); // konieczne jest automatyczne // opró nienie pami ci podr cznejż ę ę } return writer; } public void setContentLength(int len) { delegate.setContentLength(len); // Nie ma potrzeby zapisywania d ugo ci,ł ś // mo emy obliczy to pó niejż ć ź }

public void setContentType(String type) { delegate.setContentType(type);

contentType = type; }

public String getCharacterEncoding () {

return delegate.getCharacterEncoding (); }

public void setBufferSize(int size) throws IllegalStateExcepion { delegate.setBufferSize(size); }

public int getBufferSize() {

return delegate.getBufferSize(); }

public void reset() throws IllegalStateExcepion { delegate.reset();

internalReset(); }

public boolean isCommitted() { return delegate.isCommitted();

Page 63: Java Servlet - Programowanie

}

public void flushBuffer() throws IOException { delegate flushBuffer();}

public void setLocale(Locale loc) { delegate.setLocale(loc); locale = loc;}

public Locale getLocale(){return delegate.getLocale();}public void addCookie(Cooki cookie) { delegate.addCookie(cookie); cookies.addElement(cookie);}

public boolean containsHeader(String name) { return delegate.containsHeader(name);}

/**@deprecated*/public void setStatus(int sc, String sm) { delegate.setStatus(sc, sm);

status = sc;}

public void setStatus(int sc) { delegate.setStatus(sc); status = sc;}

public void setHeader(String name, String value) { delegate.setHeader(name, value);

internalSetHeader(name, value);}

public void setIntHeader(String name, int value) { delegate.setIntHeader(name, value); internalSetHeader(name, new Integer(value));}

public void setDateHeader(String name, long date) { delegate.setDateHeader(name, date); internalSetHeader(name, new Long(date));}

public void sendError(int sc, String msg) throws IOException { delegate.sendError(sc, msg); didError = true;}

public void sendError (iny sc) throws IOExcepion { delegate.sendError (sc); didError = true;

}

public void sendRedirect(String location) throws IOExcepion { delegate.sendRedirect(location); didRedirect = true;}

public String encodeURL(String url) { return delegate.encodeURL(url);}

public String encodeRedirectURL(String url) { return delegate.encodeRedirectURL(url);}

public void addHeader(String name, String value) { internalAddHeader(name, value);}

Page 64: Java Servlet - Programowanie

public void addIntHeader(String name, int value) { internalAddHeader(name, newInteger(value));}public void addDateHeader(String name, long value) { internalAddHeader(name, new Long(value));}

/**@deprected*/public String encodeUrl(String url) { return this.encodeURL(url);}

/**@deprected*/public String encodeRedirectUrl(String url) { return this.encodeRedirectURL(url); }}

class CacheServletOutputStream extends ServletOutputStream {

ServletOutputStream delegate;

ByteArrayOutputStream cache;

CacheServletOutputStream(ServletOutputStream out) { delegate = out; cache = new ByteArrayOutputStream(4096); }

public ByteArrayOutputStream getBuffer() { return cache;)

public void write(int b) throws IOException { delegate.write(b); cache.write(b); }

public void write(byte b[]) throws IOException {

delegate.write(b); cache.write(b); }

public void write(byte buf[], int offset, int len) throws IOException { delegate.write(buf, offset, len); cache.write(buf, offset, len);

}}

Page 65: Java Servlet - Programowanie

Rozdział 4. Odczytywanie

informacji

Do utworzenia udanej aplikacji WWW niejednokrotnie potrzebna jest gruntowna znajomość środowiska, wktórym będzie ona uruchamiana. Dla przykładu konieczne może okazać się zaznajomienie z serwerem, którywywołuje nasze aplety lub ze specyfikacjami klienta, który przesyła zlecenia. Bez względu na rodzaj środowiska,w którym uruchomiana jest aplikacja, jest wielce prawdopodobne, że będziemy potrzebowali informacji ozleceniach, które ona obsługuje.

Istnieje wiele metod, które umożliwiają apletom dostęp do takich informacji. Dla większości z nich każdametoda odsyła jeden, określony wynik. W porównaniu ze sposobem, w jakim zmienne środowiskowe używane sąaby przekazać informacji programowi CGI, metoda apletowa ma wiele zalet, które sprawiają, że jest ona lepsza:

• Dokładniejsza kontrola zgodności typów. Aplety otrzymują więcej pomocy ze strony kompilatora wwyłapywaniu błędów. Programy CGI używają jednej funkcji do odczytywania swoich zmiennychśrodowiskowych. Wiele błędów może nie zostać wykrytych aż do czasu kiedy zaczną powodować problemywykonawcze. Teraz rzućmy okiem jak program CGI oraz aplet znajdują port, na którym działa ich serwer.

Skrypt CGI napisany w PERL-u wywołuje:

$port = $ENV{'SERVER_PORT'};gdzie $port jest zmienną beztypową. Program CGI napisany w języku C wywołuje:

char *port = getenv ("SERVER_PORT");gdzie port jest zmienną wskaźnikową dla ciągu znaków. Ryzyko

przypadkowego błędu jest duże. Nazwa zmiennej możezostać źle przeliterowana (zdarza się to dość często)możliwe jest również, że typ danych nie będzie zgodny zodsyłanym przez zmienną środowiskową.

Aplet, z kolei, wywołuje:int port = req.getServerPort()

Pozwala to wyeliminować wiele przypadkowych błędów, ponieważ kompilator gwarantuje

prawidłowy zapis, ponadto wszystkie typy zwracane są pozbawione nieprawidłowości.

• Opóźnione obliczanie. W momencie uruchamiania programu CGI przez serwer wartość dla wszystkichzmiennych środowiskowych oraz każdej z osobna musi być już uprzednio przeliczona i przekazana —niezależnie od tego czy są one używane przez program CGI czy też nie są. Serwer uruchamiając aplet mamożliwość poprawienia wydajności poprzez opóźnienie podobnych kalkulacji oraz wykonywanie ich nażądanie wtedy kiedy jest to potrzebne.

Page 66: Java Servlet - Programowanie

• Więcej interakcji z serwerem. W momencie kiedy program CGI rozpoczyna uruchamianie nie jest jużzależny od swojego serwera. Jedyna ścieżka komunikacji, dla programu to jego standardowy portwyjściowy. Jednakże co się tyczy apletu, to może on nadal współpracować z serwerem. Tak jak to zostałoomówione w poprzednim rozdziale, aplet działa albo (jeżeli to możliwe) w ramach serwera albo (jeżeli jestto konieczne) jako przyłączona procedura, poza nim. Korzystając z takiej możliwości podłączenia apletmoże utworzyć zlecenia ad hoc dla informacji przeliczonych, których może dostarczyć tylko serwer. Dlaprzykładu serwer może wykonać dla apletu translację dowolnej ścieżki uwzględniając swoje zamienniki orazścieżki wirtualne.

Jeżeli przed apletami mieliśmy do czynienia z CGI, tabela 4.1 będzie pomocna w „przestawieniu się” na nowe

nazewnictwo. Na tej tabeli zestawione zostały wszystkie zmienne środowiskowe CGI z odpowiadającymi im

metodami apletów HTTP.

Tabela 4.1.

Zmienne środowiskowe CGI oraz odpowiadające im metody apletowe

Zmienna Środowiskowa CGI Metoda Apletu HTTPSERVER_NAME req.getServerName()SERVER_SOFTWARE getServletContext().getServerInfo()SERVER_PROTOCOL req.getProtocol()SERVER_PORT req.getServerPort()REQUEST_METHOD req.getMethod()PATH_INFO req.getPathInfo()PATH_TRANSLATED req.getPathTranslated()SCRIPT_NAME req.getServletPath()DOCUMENT_ROOT getServletContext().getRealPath(„/”)QUERY_STRING req.getQueryString()REMOTE_HOST req.getRemoteHost()REMOTE_ADDR req.getRemoteAddr()AUTH_TYPE req.getAuthType()REMOTE_USER req.getRemoteUser()CONTENT_TYPE req.getContentType()CONTENT_LENGTH req.getContentLength()HTTP_ACCEPT req.getHeader(„Accept”)HTTP_USER_AGENT req.getHeader(„User-Agent”)HTTP_REFERER req.getHeader(„Referer”)

W pozostałej części tego rozdziału pokażemy kiedy i jak używa się tych metod oraz wieluinnych, które nie mają odpowiedników CGI. Dalej będziemy również używali metod wkonkretnych apletach.

ApletZ każdą zarejestrowaną nazwą apletu mogą być związane inne, specyficzne dla niej parametry początkowe. Apletmoże mieć w każdej chwili dostęp do tych parametrów: są one określone w deskryptorze wdrożenia web.xmloraz stosowane w metodzie init() celem ustalenia wartości początkowych i domyślnych dla apletu lubdostosowania jego zachowania w określony sposób. Parametry początkowe zostały szerzej opisane w rozdziale 3„Czas istnienia (Cykl Życia) Apletu”.

Pobieranie parametru początkowego apletuAplet używa metody getInitParameter() celem dostępu do swoich parametrów początkowych

Page 67: Java Servlet - Programowanie

public String ServletConfig.getInitParameter(String name)

Metoda ta odsyła odsyła: wartość początkowego parametru nazwanego lub: null (jeżeli taki nie istnieje).Odesłana wartość to zawsze pojedynczy String. Interpretacja tej wartości jest domeną apletu.

Klasa GenericServlet wdraża interfejs ServletConfig zapewniając tym samym dostęp do metodygetInitParameter(). Oznacza to, że metoda może zostać wywołana w następujący sposób:

public void init() thrcws ServletException { String greeting = getInitParameter("pozdrowienie");}

Aplet, któremu potrzebne jest połączenie z bazą danych może użyć swoich parametrów początkowych w celu określenia parametrówpołączenia. Celem odseparowania szczegółów JDBC możemy użyć metody dostosowanej establishConnection(), tak jakzostało to pokazane na przykładzie 4.1.

Przykład 4.1.

Użycie parametrów początkowych w celu ustanowienia połączenia z bazą danych

java.sql.Connection con = null;

public void init() throws ServletException { String host = getInitParameter("komputer główny"); int port = Integer.parseInt(getInitParameter( "port")); String db = getInitParameter("db"); String user = getInitParameter("użytkownik"); String password = getInitParameter ("hasło") ; String proxy = getInitParameter ("proxy") ;con = establishConnection(host, port, db, user, password, proxy); }

Istnieje również inny, bardziej zaawansowany, standardowy model oddzielania dla apletówzaprojektowanych dla Java 2, Enterprise Edition (J2EE). Więcej na ten temat w rozdziale 12„Aplety Przedsiębiorstw i J2EE”.

Pobieranie nazw parametrów początkowych apletówUżywając metody getInitParameterNames() aplet może sprawdzić wszystkie swoje parametry początkowe:

public Enumeration ServletConfig. getInitParameterNames()

Metoda ta odsyła nazwy wszystkich parametrów początkowych apletu jako Enumeration (wyliczenie)wszystkich obiektów String lub jak puste Enumeration jeżeli nie istnieją żadne parametry. Znajduje tozastosowanie przy usuwaniu błędów z programu (przy tzw. „debagowaniu”).

Klasa GenericServlet dodatkowo udostępnia bezpośrednio tą metodę apletom. Poniższy przykład 4.2 ukazujeaplet, który drukuje nazwę oraz wartość dla wszystkich swoich parametrów początkowych.

Przykład 4.2.

Pobieranie nazw parametrów początkowych

import java.io.*;import java.util.*;import javax.servlet.*;

public class InitSnoop extends GenericServlet { // Metoda init() nie jest potrzebna

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res. setContentType (" tekst/zwykły") ; PrintWriter out = res.getWriter() ;

Page 68: Java Servlet - Programowanie

out.println("Parametry Początkowe:") ;Enumeration enum = getInitParameterNames() ;while (enum.hasMoreElements()) { String name = (String) enum.nextElement(); out. println( name + ": " + getInitParameter(name));

} } }

Zauważmy, że aplet ten jest bezpośrednio podrzędny do GenericServlet, dowodząc tym samym, że parametrypoczątkowe dostępne są także dla apletów, które nie są apletami HTTP. Standardowy aplet może zostać użyty waplikacji WWW serwera nawet w sytuacji kiedy nie realizuje zespołu funkcji. HTTP-specyficznych.

Pobieranie nazwy apletuInterfejs ServletConfig również zawiera metodę, która odsyła zarejestrowaną nazwę apletu:

public String ServletConfig.getServletName()

Jeżeli aplet jest niezarejestrowany metoda odsyła nazwę klasy apletu. Metoda ta znajduje zastosowanie przypisaniu do dzienników zdarzeń wprowadzaniu informacji o stanie kopii obiektów apletów do wspólnych zasobówtakich jak baza danych czy SessionContext apletu, które wkrótce zostaną przez nas omówione.

Tak jak to zostało pokazane na przykładzie, poniższy kod prezentuje sposób w jaki stosuje sięnazwy apletu w celu odczytania wartości z kontekstu apletu, używając nazwy jako częściklucza wyszukiwania:

public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, lOException { String name = getServletName() ; ServletContext context = getServletContext(); Object value = context. getAttribute (name + ".stan");}

Przy użyciu nazwy apletu w kluczu każda kopia apletu może bez problemu utrzymywać oddzielną wartośćatrybutu we wspólnym kontekście.

Serwer Aplet może uzyskać sporo informacji o serwerze, w którym wywołuje. Może, między innymipoznać nazwę komputera, port na którym serwer oczekuje na sygnały, oprogramowanie,którego używa serwer. Aplet może wyświetlić te informacje klientowi, użyć ich w celudostosowania swojego zachowania opartego na konkretnym zestawie serwera lub nawet użyćich w celu nałożenia wyraźnych ograniczeń na komputery, na których aplet będzie działał.

Pobieranie informacji o serwerzeAplet większość swojego dostępu do informacji serwera uzyskuje poprzez obiekt ServletContext, w którymwywołuje. Przed pojawieniem się wersji API 2.2 ServletContext

traktowany był głównie jako odwołanie do samego serwera. Od API 2.2 wiele rzeczy uległozmianie tak, że obecnie musi być odmienny obiekt ServletContext dla każdej aplikacji WWWna serwerze. Obiekt ServletContext jest teraz odwołaniem do aplikacji WWW a nieodwołaniem do serwera. Dla zapytań prostych serwerów nie sprawia to jednak większejróżnicy.Istnieje pięć metod, które mogą zostać użyte przez aplet celem zdobycia informacji o swoimserwerze: dwie, które są wywoływane przy użyciu obiektu ServletRequest przekazywanego doapletu oraz trzy wywoływane z obiektu ServletContext, w którym aplet wywołuje.

Page 69: Java Servlet - Programowanie

Aplet może zdobyć nazwę serwera oraz numer portu dla poszczególnych zleceń za pomocąmetod getServerName() oraz getServerPort() odpowiednio:

public String ServletRequest.getServerName()public int ServletRequest.getServerPort()

Metody te są atrybutami ServletRequest ponieważ jeżeli serwer ma więcej niż jedną nazwę, wartości mogąulegać zmianie dla różnych zleceń (technika virtual hosting). Odesłana nazwa może np. wyglądać w następującysposób:www.servlets.com, przykładem odesłanego portu może być 8080.

Metody getServerInfo() oraz getAtribut() ServletContext dostarczają informacji ooprogramowaniu serwera i jego atrybutach:

public String ServletContext.getServerInfo()public Object ServletContext.getAttribute(String name)

getServerInfo() odsyła przedzielone ukośnikiem: nazwę oraz wersję oprogramowania serwera. Odesłanyciąg znaków mógłby wyglądać w następujący sposób: Tomcat Web Server/3.2. Niektóre serweryzamieszczają na końcu dodatkowe informacje dotyczące środowiska operacyjnego serwera.

getAttribute() odsyła wartość atrybutu serwera nazwanego, jako Object lub jeżeli atrybut nie istnieje —null. Serwery mogą umieścić atrybuty ustalone w kontekście, dla użytku apletów. O metodzie tej można bypowiedzieć, że jest furtką przez którą aplet może uzyskać dodatkowe informacje o swoim serwerze. Dlaprzykładu serwer mógłby udostępnić statystyki dotyczące obciążenia, odwołanie do puli wspólnych zasobów lubkażdą inną potencjalnie użyteczną informację. Jedynym obowiązkowym atrybutem, który serwer musi udostępnićjest atrybut zwany javax.servlet.context.tempdir, który zapewnia odwołanie java.io.File dokatalogu poufnego dla tego kontekstu.

Aplety mogą również dodawać do kontekstu swoje własne atrybuty używając do tego celu metodysetAttribute(), tak jak to zostało omówione w rozdziale 11 „Współpraca Serwerów”. Nazwy atrybutówpowinny być zapisywane zgodnie z tą samą konwencją co nazwy pakietów. Nazwa pakietów java.* orazjavax* są zarezerwowane dla użytku Java Software division of Sun Microsystems, z kolei com.sun.*zarezerwowana jest dla użytku Sun Microsystems. Lista atrybutów serwera zawarta jest w jego dokumentacji.Wykaz wszystkich aktualnych atrybutów, przechowywanych w pamięci serwera oraz innych apletów dostępnajest przy użyciu getAttributeNames():

public Enumeration ServletContext.getAttributeNames()

W związku z tym, że metody te są atrybutami ServletContext, w którym wywołuje aplet, musimy wywołać jeza pomocą poniższego obiektu:

String serverlnfo = getServletContext().getServerInfo() ;

Najprostszym zastosowaniem informacji o serwerze jest aplet About This Server, sytuacja taka zostałazaprezentowana na przykładzie 4.3.

Przykład 4.3.

„Podsłuchiwanie” serwera

import java.io.* ;import java.uti1.*;import javax.servlet.* ;public class ServerSnoop extends GenericServlet { public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter(); ServletContext context = getServletContext() ; out.printn("req.getServerName(): " + req.getServerName()) ; out.println("req.getServerPort(): " + req.getServerPort()) ; out.println("context.getServerInfo(): "+context.getServerInfo()); out.println("getServerInfo() nazwa: " +

Page 70: Java Servlet - Programowanie

getServerInfoName(context.getServerInfo())); out.println("getServerInfo() version: " + getServerInfoVersion(context.getServerInfo())) ; out.println("context.getAttributeNames():"); Enumeration enum = context. getAttributeNames () ; while (enum.hasMoreElements()) { String name = (String) enum.nextElement(); out.printin(" context.getAttribute(\"" + name + "\"): " + context.getAttribute(name)) ; } }private String getServerInfoName(String serverlnfo) { int slash = serverlnfo.indexOf('/'); if (slash == -1) return serverlnfo; else return serverlnfo.substring(0, slash); }private String getServerInfoVersion(String serverlnfo) {

// Wersja info to wszystko to, co jest zawarte między ukośnikiem a spacją

int slash = serverlnfo.indexOf('/'); if (slash == -1) return null; int space = serverlnfo.indexOf(' ', slash); if (space == -1) space = serverlnfo.length(); return server Info.substring(slash + 1, space); }}

Aplet ten jest bezpośrednio podrzędny (jest bezpośrednią podklasą) do GenericServlet,dowodząc tymsamym, że wszystkie informacje o serwerze dostępne są dla wszystkich typów apletów. Aplet wyświetla prosty,nieprzetworzony tekst, w czasie połączenia z nim drukuje mniej więcej coś takiego:

req.getServerName(): localhostreq.getServerPort(): 8080context.getServerInfo(): Tomcat Web Server/3.2 (JSP 1.1; Servlet 2.2; ...)get Server Info() name: Tomcat Web ServergetServerInfo() version: 3.2context .getAttributeNames () : context. getAttribute (" javax. servlet. context. tempdir") : work/localhost_8080

Zapisywanie w pliku tymczasowymAtrybut javax.servlet.context.tempdir odwzorowuje do katalogu tymczasowego, w którym to kataloguprzechowywane mogą być „krótkotrwałe” pliki robocze. Każdemu kontekstowi przyznawany jest inny katalogtymczasowy. Katalogiem dla poprzedniego przykładu jest server_root/work/localhost_8080. Na przykładzie 4.4pokazano jak zapisywać w pliku tymczasowym, w katalogu tymczasowym.

Przykład 4.4.

Tworzenie pliku tymczasowego w katalogu tymczasowym

public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Katalog podany jest jako obiekt pliku File dir = (File) getServletContext() .getAttribute("javax.servlet.context.tempdir") ; // Utwórz plik tymczasowy w katalogu tymczasowym (metoda JDK 1.2) File f = File.createTempFile("xxx", ".tmp", dir); // Przygotuj się do zapisywania w pliku FileOutputStream fout = new FileOutputStream(f) ; // ...}

Page 71: Java Servlet - Programowanie

Serwer ten najpierw lokalizuje swój katalog tymczasowy. Następnie używa metody createTempFile() celemutworzenia w tym katalogu pliku tymczasowego o nazwie i rozszerzeniu odpowiednio xxx i .tmp i w końcukonstruuje FileOutputStream, ażeby stworzyć możliwość zapisywania w pliku tymczasowym. Pliki, któremuszą pozostawać pomiędzy kolejnymi, ponownymi uruchomieniami serwera nie powinny być umieszczane wkatalogu tymczasowym.

Ograniczanie działania apletu do serweraIstnieje wiele sposobów korzystnego wykorzystania informacji serwera. Załóżmy, żenapisaliśmy aplet i nie chcemy aby można go było uruchomić gdziekolwiek. Dla przykładuchcemy go sprzedać i, w celu ograniczenia ryzyka nielegalnego skopiowania, ograniczyć jegodziałanie do komputera naszego klienta, który kupił od nas aplet. Albo inaczej, napisaliśmylicencyjny program działający jako aplet i chcemy się upewnić, że działa on tylko pozanaszym firewall’em. Można to wykonać względnie szybko ponieważ aplet ma stały dostęp doinformacji o swoim serwerze.

Przykład 4.5 ukazuje aplet, który ogranicza się (swoje działanie) do określonegoserwerowego adresu IP oraz numeru portu. Apletowi temu do usunięcia podobnegoograniczenia i do obsługi zlecenia, potrzebny jest klucz parametru początkowego, właściwydla adresu IP oraz portu jego serwera. Jeżeli aplet nie otrzyma takiego klucza wstrzymadalsze działanie.Algorytm używany do odwzorowywania klucza do adresu IP oraz portu (ivice versa) musi być bezpieczny.

Przykład 4.5.

Aplet ograniczony do serwera import java. io. *;import java.net. *;import java.util.* ;import javax.servlet.*;public class KeyedServerLock.extends GenericServlet { // Aplet ten nie ma klasy ani zmiennej egzemplarza // związanej z blokowaniem (celem uproszczenia zagadnień // związanych z synchronizacją)

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter(); // Kontrola antypiracka nie powinna być wykonywana w init // ponieważ nazwa/port są częścią zlecenia. String key = getInitParameter("key"); String host = req.getServerName() ; int port = req.getServerPort() ;

// Sprawdź czy parametr początkowy „klucz” odblokowuje ten serwer.

if (! keyFitsServer(key, host, port)) { // Wyjaśnij, potęp/ zagróź/ itd. out.println("kopia piracka!"); } else ( // Dostarcz towar out ,println( "kopia licencjonowana") ; // itd... } } // Metoda ta zawiera algorytm używany do dopasowania klucza z // komputerem centralnym i portem. Niniejszy przykład implementacji // jest dalece nie doskonały i nie powinien być używany przez // strony komercyjne.

Page 72: Java Servlet - Programowanie

private boolean keyFitsServer(String key, String host, int port) { if (key == null) return false; long numericKey =0; try { numericKey = Long.parseLong(key); } catch (NumberFormatException e) { return false; } // Klucz musi być liczbą 64-bitową równą logicznej negacji (~) // 32-bitowego adresu IP połączonego z 32-bitowym numerem portu. byte hostIP[] ; try { hostIP = InetAddress.getByName(host) . getAddress (); } catch (UnknownHostException e) { return false; } // Pobierz 32-bitowy adres IP long servercode = 0; for (int i = 0; i < 4; i++) { servercode «= 8; servercode |= hostIP[i]; } // Połącz 32-bitowy numer portu servercode «= 32; servercode |= port; // logiczna negacja long accesscode = -numericKey; // Moment prawdy: Czy klucz pasuje? return (servercode == accesscode) ; } }

Aplet, dopóki nie otrzyma właściwego klucza nie wykona żadnego polecenia. Jednakże celem zapewnieniacałkowitego bezpieczeństwa, prosta logiczna metoda keyFitsServer powinna być zastąpiona przez „mocny”algorytm ponadto cały aplet powinien zostać uruchomiony poprzez zaciemniacz (ażeby uniknąć dekompilacji).Przykład 4.13 (omówiony dalej w tym rozdziale) zawiera kod używany do generowania kluczy. Jeżeli będziemytestowali ten aplet sami, lepiej jest połączyć się z serwerem zużywając jego rzeczywistej nazwy (niż localhost)ponieważ aplet może dzięki temu określić prawdziwą nazwę serwera WWW oraz jego adres IP.

Pobieranie kontekstowego parametru początkowegoParametry początkowe apletu, tak jak to zostało omówione wcześniej przekazywane są dooddzielnych apletów. Jeżeli zdarzyłoby się tak, że aplety wielokrotne otrzymałyby te samewartości parametrów początkowych, wartości te mogłyby zostać przypisane jako parametrpoczątkowy context. Klasa ServletContext ma dwie metody — getInitParameter() orazgetInitParameterNames() — dla odczytywania szerokokontekstowych informacji inicjowania:

public String ServietContext.getInitParameter(String name)public Enumeration ServietContext.getInitParameterNames()

Metody te są modelowane po ich odpowiednikach w ServletConfig. getInitParameter(String name)odsyła wartość łańcucha określonego parametru. getInitParameterNames() odsyła Enumeration zawieranazwy wszystkich parametrów początkowych dostępnych dla aplikacji WWW lub puste Enumeration — wprzypadku gdy nie było żadnych nazw.

Parametry początkowe dla kontekstu zostały określone w deskryptorze rozmieszczenia web.xml, przy użyciuznacznika <context-param> (tak jak zostało to pokazane na przykładzie 4.6).

Przykład 4.6.

Ustalanie kontekstowych parametrów początkowych w deskryptorze wdrożenia

Page 73: Java Servlet - Programowanie

<?xml version="1.0" kodowanie="ISO-8859-l"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://Java.sun.com/j2ee/dtds/web-app_2.2.dtd"><web-app> <context-param><param-name> rmihost< /param-name><param-value> localhost< /param-value> </ context-param> <context-param><param-name> rmiport</param-name><param-value> 1099< /param-value> < / context-param> </web-app>

Wszystkie aplety w tej aplikacji WWW, aby zlokalizować wspólny rejestr RMI, mogąodczytywać kontekstowe parametry początkowe, tak jak to zostało pokazane na przykładzie4.7.

Przykład 4.7.

Znajdowanie rejestru przy użyciu kontekstowych parametrów początkowych

import java.io.*;import java.rmi. registry.*;import javax.servlet.*;import javax.servlet.http.* ;

public class RmiDemo extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter();

try { ServletContext context = getServletContext() ; String rmihost = context.getInitParameter("komputer centralny rmi"); int rmiport = Integer.parseInt(context.getInitParameter("port rmi")); Registry registry = LocateRegistry.getRegistry( rmihost, rmiport) ; // ... } catch (Exception e) { // ... } } }

Nie istnieje uniwersalny mechanizm umożliwiający tworzenie globalnych parametrów początkowych,widocznych we wszystkich kontekstach.

Ustalanie wersji apletuAplet może również zapytać serwer jaka wersja Interfejsu API jest przez niego obsługiwana.Poza tym, co jest przydatne podczas usuwania błędów z programu (debagowania), Apletpodczas wykonywania zadania ma możliwość wybrać czy chce zastosować nowy sposób czy

Page 74: Java Servlet - Programowanie

też starszy, mniej wydajny. W Interfejsie API 2.1 zostały wprowadzone dwie metodyumożliwiające odsyłanie informacji dotyczących wersji:

public int ServletContext.getMajorVersion()public int ServletContext.getMinorVersion()

Dla Interfejsu API 2.1 getMajorVersion() odsyła 2, a getMinorVersion() 1. Oczywiście metody tedziałają tylko w przypadku apletów wywołujących w serwerach obsługujących wersję Interfejsu API 2.1 orazpóźniejsze. W celu określenia aktualnej wersji Interfejsu API na wszystkich serwerach można posłużyć sięcom.oreilly.servlet.VersionDetector. Klasa ta nie „pyta” serwera o wersję; zamiast tego „przygląda”się klasom i zmiennym dostępnym w czasie wykonywania, a opierając się na znajomości historii Interfejsu APIjest w stanie określić jego aktualną wersję z przedziału od 1.0 do 2.3. W związku z tym, że klasa ta nie wywołujeani getMajorVersion() ani też getMinorVersion() — działa tylko w wersjach API, kompiluje jednak wewszystkich wersjach. Używając tej samej techniki klasa VersionDetector jest w stanie określić aktualnąwersję JDK począwszy od 1.0 do 1.3. Sposób ten, w przypadku wdrożeń producentów JVM, okazuje się byćbardziej wiarygodny niż pytanie System class. Na przykładzie 4.8 została zaprezentowana klasaVersionDetector. Uaktualnienia do poniższej klasy umożliwiające obsługę wersji Interfejsów API oraz JDK,będą zamieszczane pod adresem http://www.servlets.com.

Przykład 4.8.

Klasa VersionDetector

package corm.oreilly.servlet;public class VersionDetector { static String servletVersion; static String javaVersion; public static String getServletVersion() { if (servletVersion != null) { return servletVersion; } // javax.servlet.http.HttpSession wprowadzono w Interfejsie API2.0 // javax.servlet.RequestDispatcher wprowadzono w Interfejsie API 2.1; // javax. servlet.http.HttpServletResponse.SC__EXPECTATION_FAILED // wprowadzono w Interfejsie API 2.2 // javax.servlet.Filter planuje się wprowadzić w Interfejsie API 2.3 String ver null; try { ver = "1.0"; Class.forName("javax.servlet.http.HttpSession"); ver = "2.0"; Class.forName("javax.servlet.RequestDispatcher"); ver = "2.1"; Class.forName("javax.servlet.http.HttpServletResponse") .getDeclaredField("SC_EXPECTATION_FAILED") ; ver = "2.2"; Class.forName("javax.servlet.Filter") ; ver = "2.3"; } catch (Throwable t) { } servletversion = ver; return servletVersion; }public static String getJavaVersion() { if (javaVersion != null) {return javaVersion; } // java.lang.Void wprowadzono w JDK 1.1 // java.lang.ThreadLocal wprowadzono w JDK 1.2 // java.lang.StrictMath wprowadzono w JDK 1.3 String ver = null; try { ver = "1.0"; Class. forName (" java. lang. Void") ; ver = "1.1"

Page 75: Java Servlet - Programowanie

Class.forName("java.lang.ThreadLocal") ; ver = "1.2"; Class.forName("java.lang.StrictMath") ; ver = "1.3"; } catch (Throwable t) { } javaVersion = ver; return javaVersion; }}

Klasa na zasadzie przeprowadzania prób ładowania klas oraz zmiennych dostępu — do momentu, w którymNoClassDefFoundError lub NoSuchFieldException, zawiesi wyszukiwanie. Do tego czasu aktualnawersja jest już znana. Przykład 4.9 prezentuje aplet, który „podsłuchuje” serwer oraz wersję Javy.

Przykład 4.9.

„Podsłuchiwanie” wersji

import java. io. *;import javax.servlet.*;import javax.servlet.http.*;import com. oreilly. servlet. VersionDetector;

public class VersionSnoop extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse re throws ServletException, IOException { res.setContentType("tekst/zwykły"); PrintWriter out = res.getWriter(); out.println(" Wersja Interfejsu:"+VersionDetector.getServletVersion());out.println(" Wersja Javy: "+VersionDetector.getJavaVersion()); }}

KlientPrzy każdym zleceniu aplet ma możliwość uzyskania informacji o komputerze klienta a w przypadku stronwymagających uwierzytelnienia — informacji o aktualnym użytkowniku. Informacja takie mogą zostaćwykorzystane przy rejestracji danych dostępu, kojarzeniu informacji z indywidualnymi użytkownikami lub przyograniczaniu do poszczególnych klientów.

Pobieranie informacji o komputerze klientaAplet może użyć getRemoteAddr() oraz getRemoteHost() w celu odczytania adresu IP oraz nazwy węzłakomputera klienta, odpowiednio:

public String ServletRequest.getRemoteAddr()public String ServletRequest.getRemoteHost()

Obie wartości są odsyłane jako obiekty String. Informacje pochodzą z portu, który łączy serwer z klientem,dlatego adres zdalny i nazwa węzła mogą być adresem zdalnym i nazwą węzła serwera pośredniczącego.Przykładowym zdalnym adresem mógłby być 192.26.80.118, przykładowym zdalnym komputeremdist.engr.sgi.com.

Adres IP oraz nazwa węzła odległego mogą zostać konwertowane na obiekt java.net.InetAddress przyużyciu InetAddress.getByName():

InetAddressremoteInetAddress= InetAddress.getByName(req.getRemoteAddr());

Page 76: Java Servlet - Programowanie

Ograniczanie dostępuZ powodu polityki rządu Stanów Zjednoczonych, ograniczającej export silnego szyfrowania,niektóre strony WWW muszą być zachowywać środki ostrożności co do tego komu zezwalająna pobieranie określonego oprogramowania. Aplety ze zdolnością do pobierania informacji okomputerach klientów, dobrze nadają się do tego celu. Aplety te mogą sprawdzić komputerklienta i udostępnić łącza do pobierania określonego oprogramowania, tylko wtedy jeżeliklient będzie pochodził z kraju, który nie jest objęty ograniczeniem.

W pierwszej edycji niniejszej książki krajami, których nie obejmowało ograniczenia eksportubyły tylko Stany Zjednoczone i Kanada, a aplet ten mógł być ściągany tylko przezużytkowników pochodzących z tych dwóch krajów. Od czasu tamtej edycji rząd StanówZjednoczonych złagodził politykę dotyczącą eksportu silnego kodowania, i obecnie większośćoprogramowania związanego z kodowaniem może być sprowadzana przez użytkowników zewszystkich krajów, z wyjątkiem tych, którzy pochodzą z tzw. „Terrorystycznej siódemki”(Kuba, Iran, Irak, Korea Północna, Libia Syria i Sudan). Na przykładzie 4.10 został pokazanyaplet, który pozwala na ładowanie wszystkim z poza owej siódemki.

Przykład 4.10.

Czy można im ufać?import java.io. *;import java.net.* ;import java.util.* ;import javax.servlet.*;import javax.servlet.http.*;public class ExportRestriction extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html") ; PrintWriter out = res.getWriter();

// ...Trochę wstępnego HTML-u...

// Pobierz nazwę węzła klienta String remoteHost = req.getRemoteHost() ;

// Zobacz czy klient ma przyznany dostęp if (! isHostAllowed(remoteHost)) { out .println ("Dostęp <BLINK>nie przyznany</BLINK>") ; } else { out.println("Dostęp przyznany") ; // Wyświetl łącza pobierania... }}// Nie przyznawaj dostępu komputerom głównym o zakończeniach .cu, .ir/ // .iq, .kp, .ly, .sy, and .sd.private boolean isHostAllowed(String host){ return (!host.endsWith(".cu")&& !host.endsWith(".cu")&& !host.endsWith(".ir")&& !host.endsWith(".iq")&& !host.endsWith(".kp")&& !host.endsWith(".ly")&& !host.endsWith(".sy")&& !host.endsWith(".sd")); }}

Aplet uzyskuje nazwę węzła klienta za pomocą wywołania do req.getRemoteHost() i, w oparciu oprzyrostek, ustala czy klient nie pochodzi z któregoś z krajów objętych ograniczeniem. Ograniczenie to możnaobejść przygotowując kod szyfrujący, działanie takie grozi jednak poważnymi skutkami prawnymi.

Page 77: Java Servlet - Programowanie

Pobieranie informacji o użytkownikuZastanówmy się co musimy zrobić jeżeli chcemy ograniczyć dostęp do niektórych z naszych stron WWW ijednocześnie chcemy aby stopień kontroli nad podobnym ograniczeniem był większy niż w omówionej przedchwilą metodzie „państwowej”. Załóżmy, że wydajemy czasopismo internetowe i chcemy ograniczyć dostęp doartykułów użytkownikom, którzy nie uiszczą opłat. Tak więc, uwaga, nie musimy do tego celu używać apletów.

Niemal każdy serwer HTTP ma wbudowaną zdolność do ograniczania dostępu do pewnej liczby lub wszystkichswoich stron dla określonych użytkowników. Jak ustawić ograniczenie dostępu opisano w rozdziale 8„Bezpieczeństwo” teraz przedstawimy tylko ogólną zasadę działania. Kiedy przeglądarka „wchodzi” na jedną ztakich stron, serwer HTTP odpowiada, że potrzebne jest specjalne uwierzytelnienie użytkownika. W momencie,w którym przeglądarka otrzymuje taką odpowiedź, otwiera okno prosząc użytkownika o podanie nazwy i hasła,właściwych dla tej strony, tak jak to zostało pokazane na rysunku 4.1.

Kiedy użytkownik wprowadzi już żądane informacje, przeglądarka próbuje jeszcze raz wejść na tą stronę, tymrazem dołączając nazwę użytkownika oraz hasło razem ze zleceniem. Jeżeli serwer zaakceptuje nazwę i hasło —rozpocznie obsługę zlecenia. Jeżeli jednak serwer nie zaakceptuje nazwy i hasła, przeglądarce ponownieodmawiany jest dostęp, a użytkownik delikatnie mówiąc nie jest z tego zadowolony.

Jak do tego mają się aplety? Otóż jeżeli dostęp do apletu został ograniczony przez serwer, aplet może pobraćnazwę użytkownika. która została zaakceptowana przez serwer, używając do tego metody getRemoteUser():

public String HttpServletRequest.getRemoteUser()

Rysunek 4.1.

Okno logowania dla strony z ograniczonym dostępem

Zauważmy, że informacje te są odczytywane z obiektu apletu HttpServletRequest, Http-specyficznej podklasy ServletRequest. Metoda ta odsyła nazwę użytkownika ustawiajączlecenia jako String lub null — w przypadku kiedy dostęp do apletu nie był ograniczony. Nieistnieje metoda porównawcza do pobrania hasła zdalnego użytkownika (mimo, że można jąustalić ręcznie, tak jak to zostało zaprezentowane na przykładzie 8.2, w rozdziale 8).Przykładem zdalnego użytkownika mógłby być jhunter.

Aplet, w celu ustalenia jaki rodzaj uwierzytelnienia został użyty, może użyć metodygetAuthType():

public String HttpServletRequest.getAuthType()

Page 78: Java Servlet - Programowanie

Metoda ta odsyła użyty typ uwierzytelnienia lub null — jeżeli dostęp do apletu nie był ograniczony. Typamimogą być BASIC, DIGEST, FORM, czy CLIENT–CERT. Więcej informacji o typach uwierzytelnień można znaleźćw rozdziale 8.

Do czasu kiedy aplet wywoła getRemoteUser(), serwer określi już, że użytkownik jest uprawniony dowywołania apletu, jednakże nie oznacza to, że nazwa użytkownika zdalnego jest bezwartościowa. Aplet mógłbyprzeprowadzić ponowną kontrolę uwierzytelnienia, bardziej restrykcyjną i bardziej dynamiczną od tej,przeprowadzanej przez serwer. Dla przykładu bardziej poufne informacje mógłby odesłać tylko wtedy jeżeliosoba złożyłaby na nie zlecenie, aplet mógłby również wprowadzić zasadę, że każdy użytkownik może wywołaćaplet tylko 10 razy dziennie.*

Wtedy, znowu, nazwa klienta może w prosty sposób „poinformować” aplet, kto próbuje go wywołać. W końcuodległy komputer centralny nie jest niepowtarzalny dla każdego użytkownika. Serwery UNIX często obsługująsetki użytkowników, a serwery pośredniczące bramowe mogą działać w imieniu tysięcy. Miejmy jednakświadomość, że za dostęp do nazwy klienta trzeba zapłacić pewną cenę — każdy użytkownik musi byćzarejestrowany przez nasz serwer i, przed wejściem na naszą stronę, musi podać jej nazwę oraz hasło. Ogólnierzecz biorąc uwierzytelnianie nie powinno być używane tylko po to, żeby aplet wiedział z kim ma do czynienia.Rozdział 7 „Śledzenie Sesji” opisuje lepsze, prostsze w obsłudze techniki pobierania informacji oużytkownikach. Jednakże w przypadku gdy aplet jest już chroniony i ma łatwo dostępną nazwę, może jej równiedobrze użyć.

Za pomocą nazwy użytkownika zdalnego aplet może zapisać informacje o każdym kliencie. Po dłuższym okresieaplet może zapamiętać preferencje każdego z użytkowników. Na krótszą metę może on zapamiętać serię stronprzeglądanych przez klienta i użyć jej w celu dodania odczytu stanu do bezstanowego protokołu HTTP. Efektyzwiązane ze śledzeniem sesji (opisane w rozdziale 7) mogą okazać się niepotrzebne w przypadku kiedy aplet znajuż nazwę użytkownika klienta.

Personalizowane powitanieProsty aplet, który używa getRemoteUser() może pozdrawiać swoich klientów imieniem oraz zapamiętywaćkiedy po raz ostatni każdy z nich był zalogowany, tak jak to pokazuje przykład 4.11.

Przykład 4.11.

Hej, Pamiętam Cię!

import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class PersonalizedWelcome extends HttpServlet { Hashtable accesses = new Hashtable(); public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IQException { res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter() ; // ...Trochę wstępnego HTML-u... String remoteUser = req.getRemoteUser(); if (remoteUser == null) { out.println("Witaj!"); } else { out .println ("Witaj, " + remoteUser + "!"); Date lastAccess = (Date) accesses.get(remoteUser); if (lastAccess == null) { out.println("To twoja pierwsza wizyta!");

* W następnym rozdziale pokażemy jak „powiedzieć” „dostęp nie przyznany” po jedenastej próbie wywołania apletu.

Page 79: Java Servlet - Programowanie

} else { out.println("Twoja ostatnia wizyta miała miejsce "+accesses.get(remoteUser)); } if (remoteUser.equals("PROFESSOR FALKEN")) { out.println("Zagramy?"); }

accesses.put(remoteUser, new Date()) ; // ...Kontynuacja obsługi zlecenia... } }

Aplet ten używa Hashtable w celu zapisania czasu ostatniego wywołania dla każdego zdalnego użytkownika.Pierwszą rzeczą, którą aplet wykonuje przy każdym zleceniu jest pozdrowienie osoby jej imieniem orazpoinformowanie jej kiedy miała jej ostatnia wizyta. Następnie rejestruje czas wizyty (w celu przedstawienia gonastępnym razem), poczym kontynuuje obsługę zlecenia.

Zlecenie Po tym jak zapoznaliśmy się ze sposobem, w jaki aplet uzyskuje informacje o serwerze i okliencie, nadszedł czas na omówienia naprawdę ważnej sprawy: w jaki sposób apletdowiaduje się o tym czego chce klient.

Parametry zleceniaKażdemu wywołaniu apletu może towarzyszyć dowolna liczba parametrów zlecenia. Parametrami są zwykle parynazwa/wartość, które dostarczają apletowi wszystkich dodatkowych informacji, które są mu potrzebne do obsługizlecenia. Nie należy mylić parametrów zlecenia z parametrami początkowymi apletu, które są związane z samymapletem.

Aplet HTTP uzyskuje swoje parametry zlecenia jako część swojego ciągu zapytań (dla zleceńGET) lub jako zakodowane dane POST (dla zleceń POST) lub, w niektórych przypadkach wobu formach. Na szczęście każdy aplet odczytuje swoje parametry w ten sam sposób — przyużyciu getParameter() i getParameterValues():

public String ServletRequest.getParameter(String name)public String[] ServletRequest.getParameterValues(String name)

getParameter() odsyła wartość parametru nazwanego jako String lub null — jeżeli parametr nie byłokreślony.* Istnieje pewność, że wartość będzie w swojej zwykłej, zdekodowanej formie. Jeżeli byłobyjakiekolwiek prawdopodobieństwo, że parametr będzie miał więcej niż jedną wartość powinniśmy użyć wzamian metody getParameterValues(). Metoda ta odsyła wszystkie wartości parametrów nazwanych jakoukład obiektów String lub jako null — jeżeli parametr nie został określony. Wartość pojedyncza odsyłana jestjako układ długości 1. Jeżeli wywołamy getParameter() na parametr z wielkościami wielokrotnymi, wartośćodesłana będzie taka sama jak pierwsza wartość odesłana przez getParameterValues().

Uwaga: jeżeli informacje parametrowe zostały wprowadzone jako zakodowane dane POST, wtedy nie będą onejuż dostępne w przypadku gdy dane POST zostały już odczytane ręcznie — przy użyciu metod getReader()lub getInputStream() (ponieważ dane POST mogą zostać odczytane tylko raz).

Liczba możliwych zastosowań dla parametrów zlecenia jest nieograniczona. Istnieją sposoby ogólnegozastosowania poinformowania apletu co ma robić, jak to ma zrobić, lub jedno i drugie. Dla przykładuprzyjrzyjmy się jak aplet słownikowy mógłby zastosować metodę getParameter() w celu wyszukania słówka,które ma za zadanie odnaleźć.

* W Java Web Serwer 1.1 zrezygnowano z metody getParameter() na korzyść metody getParameterValues().Jednakże na skutek protestów, „Sun”, w ostatecznej wersji Interfejsu API 2.0, usunął getParameter() z listywycofanych metod. Była to pierwsza metoda Javy, z której nie zrezygnowano.

Page 80: Java Servlet - Programowanie

Plik HTML mógłby zawierać poniższy formularz, w którym użytkownik jest proszony o podanie słówka dowyszukania:

<FORM METHOD=GET ACTION="/aplet/Słownik">Word to look up: <INPUT TYPE=TEXT NAME="słowo"><P>< INPUT TYPE=SUBMIT><P></FORM>

Poniższy kod odczytuje parametr słowny:

String word = req. getParameter ("słowo");String definition = getDefinition(word) ;out. println (word + ": " + definition);

Ten kod obsługuje tylko wartość na parametr. Niektóre parametry maja wartości wielokrotne, tak jak wprzypadku używania <SELECT>:

<FORM METHOD=POST ACTION="/ aplet /Car0rder">Please select the Honda S2000 features you would like:<BR><SELECT NAME="cechy" MULTIPLE>OPTION VALUE="aero"> Aero Screen </OPTION>OPTION VALUE="cd"> CD Changer </OPTION>OPTION VALUE="spoiler"> Trunk Spoiler </OPTION></SELECT><BR><INPUT TYPE=SUBMIT VALUE="Dodaj do koszyka"></FORM>

Aplet może skorzystać z metody getParameterValues() w celu obsłużenia tego formularza:

String [] words = req. getParameterValues ("cechy") ;if (features != null) {for (int i = 0; i < features.length; i++) {cart.add(features[i]);}}

Poza możliwością uzyskania wartości parametrów, aplet może mieć dostęp do nazw parametrów. używając dotego celu metody getParameterNames():

public Enumeration ServletRequest. getParameterNames ()

Metoda odsyła wszystkie nazwy parametrów jako Enumeration (wyliczenie) obiektu String lub pusteEnumeration — jeżeli aplet nie ma żadnych parametrów. Metoda ta jest najczęściej stosowana dodebagowania. Kolejność nazw nie koniecznie będzie zgodna z kolejnością w formularzu.

Wreszcie, aplet może odczytać nieprzetworzony ciąg zapytań za pomocą getQueryString:

public String ServletRequest.getQueryString()

Metoda ta odsyła nieprzetworzony ciąg zapytań (zakodowaną informację parametru GET) zlecenia lub null —jeżeli nie było ciągu zapytań. Podobne nisko-poziomowe informacje są rzadko użyteczne dla obsługi danychformularzowych. Najlepiej sprawdza się przy obsłudze pojedynczych, nienazwanych wartości, tak jak wprzypadku /servlet/Sqrt?576, gdzie odesłanym ciągiem zapytań jest 576.

Przykład 4.12 ukazuje zastosowanie tych metod z apletem, który drukuje nazwę i wartość dla wszystkich swoichparametrów.

Przykład 4.12.

Page 81: Java Servlet - Programowanie

Podpatrywanie parametrów

import java.io. *;import java.util.*;import javax.servlet.*;import javax.servlet.http. * ;public class ParameterSnoop extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter(); out.println("Ciąg Zapytań:") ; out.println(req.getQueryString()) ; out .printin (); out.println("Parametry Zlecenia:"); Enumeration enum = req.getParameterNames (); while (enum.hasMoreElements()) { String name = (String) enum.nextElement(); String values[] = req.getParameterValues (name) if (values != null) { for (int i = 0; i < values.length; i++) { out.println(name + "("+ i +"):" + values[i]); } } } } }

Wydruk wyjściowy apletu został pokazany na rysunku 4.2.

Rysunek 4.2. Podpatrzone parametry

Począwszy od Interfejsu API 2.2 możliwe jest utworzenie formularz POST za pomocą operacji URL, któryzawiera ciąg zapytań. Jeżeli wykonamy powyższą czynność informacje parametrów globalnych będą dostępnepoprzez metody getParameter() — w przypadku kolizji nazw wartości parametrów ciągu zapytań mająpierwszeństwo przed wartościami POST. Dla przykładu jeżeli w zleceniu znajduje się ciąg zapytań a=hokeyoraz dane POST: a=pokey, metoda req.getParameterValues ("a") odeśle układ {"hokey", "pokey"}.

Generowanie Klucza LicencjiW związku z tym, że mamy już odpowiednią wiedzę jesteśmy gotowi do napisania apletu, który generuje kluczlicencji KeyedServerLock dla każdego danego komputera centralnego oraz numeru portu. Klucz z tego apletumoże zostać użyty do odblokowania (usunięcia ograniczenia) apletu KeyedServerLock. W tym momenciemoże nasuwać się pytanie: „ W jaki sposób ten aplet rozpozna komputer centralny oraz numer portu apletu, któryma zostać odblokowany? Odpowiedź jest prosta: oczywiście za pomocą parametrów zlecenia.

Na przykładzie 4.13 został zaprezentowany kod.

Page 82: Java Servlet - Programowanie

Przykład 4.13.

Odblokowywanie apletu „KeyedServerLock”

import java.io. *;import java.net. *;import java.util.* ;import javax.servlet.*;import javax.servlet.http.*;public class KeyedServerUnlock extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { PrintWriter out = res.getWriter(); // Pobierz komputer centralny i port String host = req.getParameter (" komputer centralny"); String port = req.getParameter (" port"); // Jeżeli nie ma komputera centralnego/ użyj bieżącego komputera

// centralnego if (host == null) { host = req.getServerName() ; } // Konwertuj port na liczbę rzeczywistą, jeżeli nie ma żadnego //użyj portu bieżącego int numericPort; try { numericPort = Integer.parseInt(port); } catch (NumberFormatException e) { numericPort = req.getServerPort() ; } // Generuj i wydrukuj klucz // Jakikolwiek KeyGenerationException jest wyłapywany i wyświetlany try { long key = generateKey (host, numericPort) ; out.println (host+":"+numericPort+" has the key"+key); } catch (KeyGenerationException e) { out.println("Nie dało się wygenerować klucza:"+e.getMessage()); } } // Metoda ta zawiera algorytm używany do dopasowania klucza z // komputerem centralnym serwera i portem. Niniejsza przykładowa // implementacja jest dalece niedoskonała i nie powinna być używana // przez strony komercyjne. // // Zgłasza wyjątek KeyGenerationException ponieważ wszystko bardziej // sprecyzowane // byłoby związane z wybranym algorytmem.

private long generateKey(String host,int port) throws KeyGenerationException {

// Klucz musi być liczbą 64-bitową równą logicznej negacji not (~) // 32-bitowego adresu IP połączonego przez 32-bitowy numer portu.

byte hostIP[]; try { hostIP = InetAddress.getByName(host).getAddress () ;

} catch (UnknownHostException e) { throw new KeyGenerationException(e.getMessage()); }

// Pobierz 320bitowy adres IP long servercode = 0; for (int i = 0; i < 4; i++) { servercode «= 8;

Page 83: Java Servlet - Programowanie

servercode |= hostIP[i]; }

// Połącz 32-bitowy numer portu servercode «= 32; servercode |= port; // Klucz jest logicznym przeczeniem return ~servercode;

}}class KeyGenerationException extends Exception {

public KeyGenerationException() { super() ;}

public KeyGenerationException(String msg) { super (msg) ; }}

Możliwe jest użycie wydruku wyjściowego z tego apletu celem przypisania KeyedServerLock specjalnejzmiennej egzemplarza „key”:

<servlet> <servlet-name> ksl ^ </servlet-name><servlet-class> KeyedServerLock</servlet-class><init-param> <param-name> key </param-name> <param-value> -9151314447111823249 </param-value> </init-param> </servlet>

Pamiętajmy, aby zamienić logiczne generateKey() przed jakimkolwiek rzeczywistymużytkowaniem.

Informacje ścieżkiDodatkowo poza parametrami zlecenie HTTP może zawierać coś, co nazywa się informacjądodatkowej ścieżki (extra path information) lub ścieżką wirtualną (virtual path). Zwykle takainformacja dodatkowej ścieżki używana jest do wskazywania pliku na serwerze, którypowinien zostać użyty przez aplet w jakimś określonym celu. Taka informacja ścieżki jestkodowana w URL-u zlecenia HTTP. Przykładowy URL wygląda w podobny sposób:

http://server:port/servlet/ViewFile/index.html

Wywołuje on aplet ViewFile, przekazując /index.html jako informacje dodatkowej ścieżki. Aplet możemieć dostęp do tych informacji ścieżki, może również przesunąć łańcuch /index.html ścieżki rzeczywistejpliku index.html. Czym zatem jest ścieżka rzeczywista /index.html? Jest to pełna ścieżka systemu plików dopliku — to co serwer by odesłał jeżeli klient poprosiłby bezpośrednio o /index.html. Tym „czymś” okazałobysię z pewnością document_root/index.html, lecz oczywiście serwer mógłby mieć specjalny aliasing zmieniającyto.

Poza tym, podobna informacja dodatkowej ścieżki będąc wyraźnie określoną w URL-u, może być kodowana wparametrze ACTION, formularza HTML:

<FORM METHOD=GET ACTION="/servlet/Dictionary/dict/definitions.txt">Word to look up: <INPUT TYPE=TEXT NAME="słowo"><P><INPUT TYPE=SUBMIT><P>

Page 84: Java Servlet - Programowanie

</FORM>

Formularz ten wywołuje aplet Dictionary w celu obsłużenia swoich przedłożeń oraz przekazuje mu informacjedodatkowej ścieżki /dict/definitions.txt. Aplet Dictionary będzie wtedy wiedział, że ma sprawdzićznaczenia słówek przy użyciu pliku definitions.txt, tego samego, który klient otrzymałby prosząc o /dict/definitions.txt, na serwerze „Tomcat” byłby to prawdopodobnie:server_root/webapps/ROOT/dict/definitions.txt.

Dlaczego Informacje Dodatkowej Ścieżki?

Dlaczego HTTP ma specjalne rozpoznawanie dla informacji dodatkowej ścieżki?

Czy nie jest wystarczy, że aplet ma przekazywany parametr ścieżki? Odpowiedź

brzmi: tak, aplety nie potrzebują specjalnego rozpoznawania, lecz programy CGI

tak.Program CGI nie może współdziałać ze swoim serwerem w czasie uruchamiania, tak więc nie ma możliwości żeby otrzymał onparametr ścieżki, poza poproszeniem serwera o odwzorowanie tego parametru do rzeczywistej lokalizacji systemu plików. Serwermusi jakoś przetłumaczyć ścieżkę zanim wywoła program CGI. Dlatego właśnie musi istnieć rozpoznawanie dla specjalnej„informacji dodatkowej ścieżki”.

Serwery tłumaczą wstępnie dodatkową ścieżkę a następnie przesyłają ją do programu CGI jako zmienną środowiskową. Jest tocałkiem elegancki sposób radzenia sobie z uchybieniami w CGI.

Oczywiście to, że aplety nie potrzebują specjalnej obsługi informacji dodatkowej ścieżki, nie oznacza, że nie powinny jejwykorzystywać. Jest to prosty, wygodny sposób dołączania ścieżki do zlecenia.

Pobieranie informacji ścieżkiAplet może użyć metodę getPathInfo() celem uzyskania informacji dodatkowej ścieżki:

public String HttpSertvletRequest.getPathInfo()

Metoda ta odsyła informacje dodatkowej ścieżki związane ze zleceniem (URL jest dekodowany jeżeli jest takapotrzeba) lub null — jeżeli takich nie podano. Przykładowa ścieżka to /dict/definitions.txt. Informacjaścieżki sama w sobie nie jest specjalnie użyteczna. Aplet zwykle musi jeszcze znać aktualną lokalizację wsystemie plików, pliku podanego w informacji ścieżki, tam gdzie zwykle ma miejsce getPathTranslated():

public String HttpServletRequest.getPathTranslated()

Metoda ta odsyła informacje dodatkowej ścieżki przetłumaczoną na rzeczywistą ścieżkęsystemu plików (URL jest dekodowany jeżeli jest taka potrzeba) lub null — w przypadku gdynie ma informacji dodatkowej ścieżki. Metoda ta, w przypadku kiedy ścieżka nie może byćprzetłumaczona na sensowną ścieżkę pliku, odsyła null. Podobnie dzieje się kiedy aplikacjaWWW wywołuje z archiwum WAR, odległego systemu plików czy z bazy danych. Odesłanaścieżka nie koniecznie wskazuje na istniejący plik lub katalog. Przykładem przetłumaczonejścieżki może być C:\tomcat\webapps\ROOT\dict\definitions.txt.

Page 85: Java Servlet - Programowanie

Przykład 4.14 prezentuje aplet, który używa dwóch metod w celu wydrukowania informacjidodatkowej ścieżki, które otrzymał oraz następujące po tym tłumaczenie na ścieżkęrzeczywistą.

Przykład 4.14.

Pokazywanie dokąd prowadzi ścieżka

import java.io. *;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class FileLocation extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/zwykły") ; PrintWriter out = res.getWriter(); if (req.getPathInfo() != null) { out.println("Plik \"" + req.getPathInfo() + "\""); out.println("Znajduje się w\""+req.getPathTranslated()+"\""); } else { out.println("Informacja ścieżki jest równa zero, plik nie istnieje"); } } }

Przykładowy wydruk wyjściowy tego apletu mógłby wyglądać w następujący sposób:

The file "/index, html"Is stored at "/usr/local/tomcat/webapps/ROOT/index.html"

Tłumaczenia ścieżki ad hocCzasami aplet musi przetłumaczyć ścieżkę, która nie była przekazana w informacji dodatkowej ścieżki. W celuwykonania tego zadania możemy użyć metody getRealPath():

public String ServletContext.getRealPath(String path)

Metoda ta odsyła ścieżkę rzeczywistą jakiejkolwiek ścieżki wirtualnej (virtual path) lub null — jeżelitłumaczenie nie może zostać wykonane. Jeżeli daną ścieżką jest /, metoda odsyła źródło dokumentu (miejsce, wktórym są przechowywane dokumenty) dla serwera. Jeżeli daną ścieżką jest getPathInfo(), metoda odsyła tąsamą ścieżkę rzeczywistą, która byłaby odesłana przez getPathTranslated(). Metoda ta może być używanaprzez aplety standardowe jak również aplety HTTP. Nie istnieje żaden odpowiednik CGI.

Pobieranie ścieżki kontekstuJak dowiedzieliśmy się z rozdziału 2 „Podstawy Apletów Http” aplikacje WWW są odwzorowywane doprzedrostków URI na serwerze. Aplet może określić przedrostek URI, w którym działa przy użyciu metodygetContextPath() w ServletRequest:

public String ServletRequest.getContextPath()

Page 86: Java Servlet - Programowanie

Metoda odsyła String odpowiadający przedrostkowi URI kontekstu obsługującego zlecenie. Wartość zaczynasię na /, nie ma zakończenia /, i dla kontekstu domyślnego jest pusta. Dla zlecenia na /catalog/books/servlet/BuyNow, dla przykładu, metoda getContextPath() odesłałaby /catalog/boks.

Możemy wykorzystać tą metodę do zapewnienia, że nasze aplety będą działały niezależnie odtego do jakiego kontekstu zostały odwzorowane. I tak na przykład kiedy tworzymy łącze dostrony głównej dla kontekstu, powinniśmy zrezygnować z ustalania ścieżki kontekstu i zamiastniej zastosować kod standardowy:

out.println("<a href=\""+req.getContextPath()+"/index.html\">Home</a>");

Pobieranie typów MIMEKiedy już aplet ma ścieżkę pliku, cz estokroć musi jeszcze znać typ pliku. W tym celu można skorzystać zmetodt getMimeType():

public String ServletContexy.getMimeType(String File)

Metoda ta odsyła ty MIME danego pliku, na podstawie jego rozszerzenia lub null — jeżelijest ono nieznane. Najczęściej spotykane typy MIME to: text/html, text/plain, image/giforaz image/jpeg. Poniższy fragment kodu odnajduje typ MIME informacji dodatkowej ścieżki:

String type = getServletContext () .getMimeType(req.getPathTranslated())

Aplety generalnie są zaznajomione z rdzennym zestawem odwzorowań typu file-extention-to-mime. Mogą onebyć ulepszane bądź ignorowane przez hasła w deskryptorze wdrożenia web.xml, dając każdemu kontekstowimożliwe do skonfigurowania zachowanie, tak jak to zostało pokazane na przykładzie 4.15.

Przykład 4.15.

Każdy kocha Mime

<?xml version="1.0" kodowanie="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems/ Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app> <i-- .....--> <mime-mapping> <extension> Java </extension> <mime-type> text/plain </mime-type> </mime-mapping> <mime-mapping> <extension> cpp </extension> <mime-type> text/plain </mime- type> </mime-mapping> </web-app>

Page 87: Java Servlet - Programowanie

Podawanie plikówSerwer „Tomcat” jako taki, używa apletów do obsługi każdego zlecenia. Poza tymprezentowanie możliwości apletów powoduje, że serwer ma budowę modularną, co z koleiumożliwia „hurtową” wymianę (zastąpienie) pewnych aspektów jego sposobu działania. Dlaprzykładu wszystkie pliki są podawane przez aplet org.apache.tomcat.core.DefaultServlet,na którym spoczywa obowiązek obsługi /path (tzn. jest on domyślnym programemobsługującym zlecenie). Jednak nie jest tak, że nie można zastąpić DefaultServlet. Możnatego dokonać poprzez zmianę wzoru URL i użycie innego apletu. Nie jest również problememnapisanie prostego zastępstwa dla DefaultServlet, używając do tego celu metod, które jużomówiliśmy.Na przykładzie 4.16 został zaprezentowany aplet ViewFile, który wykorzystuje metodygetPathTranslated() oraz getMimeType(), ażeby odesłać plik podany w informacjidodatkowej ścieżki.

Przykład 4.16.

Dynamiczne odsyłanie plików statycznych

import java.io. *;import Java.util.* ;import javax.servlet.* ;import javax.servlet.http.* ;import com.oreilly.servlet.ServletUtils;public class ViewFile extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Użyj ServletOutputStream ponieważ możemy przekazać informację // binarną ServletOutputStream out = res.getOutputStream() ; // Pobierz plik do przeglądania String file = req.getPathTranslated(); // Plik nie istnieje, przeglądanie niemożliwe do realizacji if (file == null) { out.println("Nie można przeglądać pliku"); return; } // pobierz i ustal typ pliku String contentType = getServletContext().getMimeType(file); res.setContentType(contentType) ; // Odeślij plik try { ServletUtils.returnFile(file, out); } catch (FileNotFoundException e) { out.println("Plik nie odnaleziony") ; } catch (IOException e) ( out.println("Problem z przesłaniem pliku;"+e.getMessage()); } } }

Aplet najpierw korzysta z metody getPathTranslated() w celu pobrania nazwy pliku, który ma wyświetlić.Następnie używa getMimeType(), ażeby ustalić typ treści tego pliku, ustala także odpowiedni typ treściodpowiedzi. I w końcu odsyła plik, używając do tego metody returnFile() znajdującej się w klasie usługowejcom.oreilly.servlet.ServletUtils:

Page 88: Java Servlet - Programowanie

// Prześlij zawartość pliku do strumienia wyjściowegopublic static void returnFile(String filename, OutputStream out) throws FileNotFoundException, IOException { // FileInputStream jest związany z bajtami FileInputStream fis = null; try { fis = new FileInputStream(filename); byte[] buf = new byte[4 * 1024]; //bufor 4K int bytesRead; while ((bytesRead = fis.read(buf)) != -1) { out.write(buf, 0, bytesRead); } } finally { if (fis != null) fis.close(); } }

Obsługa błędu apletu jest na poziomie podstawowym — aplet odsyła stronę, która opisujebłąd. Sytuacja taka jest do zaakceptowania w przypadku naszego prostego przykładu(podobnie jak w przypadku wielu innych programów), jednakże poznamy w następnymrozdziale lepszy sposób — wykorzystujący kody statusu. Aplet ten można wywołaćbezpośrednio za pomocą URL-u takiego jak poniższy:

http://server: port/servlet/ViewFile/index.html

Lub jeżeli przypiszemy ten aplet do obsługi domyślnego wzoru URL, tak jak poniżej:

<servlet> <servlet-name> vf </servlet-name> <servlet-class> ViewFile </servlet-class></servlet><servlet-mapping> <servlet-name> vf</servlet-name><url-pattern> / </url-pattem></servlet-mapping>

wtedy ViewFile jest wywoływane automatycznie, nawet dla URL-ów takich jak ten:

http://server:port/index.html

Aplet ten jest tylko przykładem, mającym na celu zademonstrowanie ogólnej zasady działania, w związku z tym,nie ma pełnego zespołu funkcji DefaultServlet.

Czytanie z zasobów oddzielonychMetoda getPathTranslated() ma niestety klika uciążliwych ograniczeń. Po pierwsze nie działa ona dla treścipodanej z plików WAR, ponieważ nie istnieje plik o dostępie bezpośrednim. Nie działa ona także wzrównoważonym środowisku rozproszonym, gdzie mógłby istnieć plik o dostępie bezpośrednim — lecz nie naserwerze aktualnie wywołującym aplet. Aby przezwyciężyć te ograniczenia w Interfejsie API 2.1 zostaławprowadzona technika przeznaczona do oddzielania zasobów, która umożliwia serwerowi dostęp do zasobówbez potrzeby dowiadywania się gdzie się one znajdują. Aplet uzyskuje dostęp do plików oddzielonych poprzezzastosowanie metody getResource():

public URL ServletContext.getResource(String uripath)

Page 89: Java Servlet - Programowanie

Metoda ta odsyła URL, który może zostać użyty do badania określonego zasobu oraz do odczytania jego treści.Sposób w jaki parametr ścieżki URI jest odwzorowywany do bieżącego zasobu (pliku, hasła WAR, hasła bazydanych lub innego) określane jest przez serwer WWW. Kolejne dwa ograniczenia to: wymóg, że ścieżka musibyć absolutna (musi zaczynać się od ukośnika),druga sprawa to wymóg, iż URI nie powinien być zasobemaktywnym, tak jak w przypadku innego apletu lub programu CGI. Metoda getResource() oryginalnieobsługuje odczytywanie tylko treści statycznej (nie obsługuje czytania treści dynamicznej i pisanie treści).

Rzeczą o której należy pamiętać podczas korzystania z obiektu kontekstowego, jest nie zawieranie ścieżkikontekstu w zleceniu. Nie należy tego robić ponieważ kontekst „zna” swoją własną ścieżkę, a dzięki nie —określaniu jej w kodzie, sprawiamy, że aplikację będzie można przesunąć do innego przedrostka ścieżki unikającrekompilacji. Poniższy kod pobiera oraz drukuje plik /includes/header.html dla bieżącego kontekstu:

URL url = getServletContext().getResource("/zawiera/nagłówek.html");if (uri != null) { ServletUtils.returnURL (uri, out) ;}

Plik header.html może znajdować się w pliku archiwalnym na serwerze innym niż ten, który obsługuje aplet,jednak nie ma to większego znaczenia. Kod wykorzystuje metodę złożoną z metod podstawowych klasycom.oreilly.servlet.Servlet.Utils:

// Przesyła treści URL do OutputStreampublic static void returnURL(URL url,OutputStream out)throws IOException { InputStream in = url. openStream () ; byte[] buf = new byte[4 * 1024]; //bufor 4K int bytesRead; while ((bytesRead = in.read(buf)) != -1) { out.write(buf, 0, bytesRead); }}// Przesyła treści URL do PrintWriterpublic static void returnURL(URL url,PrintWriter out)throws IO Exception // Określ kodowanie treści URL URLConnection con = uri.openConnection() ; con.connect(); String encoding = con.getContentEncoding() ; // Konstruuj czytnik właściwy dla tego kodowania BufferedReader in = null; if (encoding == null) { in = new BufferedReader( new InputStreamReader(url.openStream())) ; } else { in = new BufferedReader( new InputStreamReader(url.openStream(), encoding));} char[] buf = new char[4 * 1024]; // bufor 4Kchar int charsRead; while ((charsRead = in.read(buf)) != -1) { out.write(buf, 0, charsRead); } }

Tak jak to zostało pokazane w drugiej metodzie returnURL(), w poprzednim kodzie, możliwe jest stworzenieobiektu URL do badania atrybutów zasobu oddzielonego. Nie wszystkie serwery oraz fazy wykonywania Javyobsługują jednak taki sposób działania. Oto kod, który sprawdza stronę główną dla aktualnego kontekstu:

URL url = getServletContext() . getResource ("/index, html"); // kontekstowa strona główna URLConnection con = url.openConnection() ;con.connect() ;int contentLength = con.getContentLength(); // rozpoznawanie nie // kompletneString contentType = con.getContentType(); // rozpoznawanie nie // kompletnelong expiration = con.getExpiration(); // rozpoznawanie nie // kompletnelong lastModified = con.getLastModified(); // rozpoznawanie nie // kompletne// itd...

Page 90: Java Servlet - Programowanie

Pamiętajmy, że treść podana dla ścieżki /URI jest całkowicie determinowana przez serwer. Wcelu uzyskania dostępu do zasobów z innego kontekstu możemy użyć metody getContext():

public ServletContext ServletContext.getContext(String uripath)

A oto sposób w jaki można uzyskać odwołanie do strony głównej serwera:

getServletContext().getContext("/");

Pamiętajmy, że metoda getResource() nie koniecznie będzie zgodna z listą plikówakceptowanych, tak więc getResource"/" może nie odesłać nadającej się do użycia treści.

Poniższa metoda — getResourceAsStream() jest wygodna dla odczytywania zasobów jako strumień:

public InputStream ServletContext.getResourceAsStream(String uripath)

Zachowuje się ona zasadniczo tak samo jak get().openStream(). Celem utrzymania zgodności zpoprzednimi wersjami oraz aby złagodzić przejście na aplety (dla programistów CGI), Interfejs API będzie nadalzawierał metody dostępu do plików, takie jak np. getPathTranslated(). Trzeba tylko pamiętać, że zakażdym razem kiedy uzyskujemy dostęp do zasobu używając obiektu File, „wiążemy się” z określonymkomputerem.

Podawanie zasobówUżywając zasobów oddzielonych możemy napisać poprawioną wersję apletu ViewFile, który będzie działałnawet gdy treść będzie podana z pliku WAR oraz wtedy kiedy owa treść będzie znajdowała się na serwerzeinnym niż ten, który wywołuje aplet. Przykład 4.17 prezentuje aplet UnsafeView Resource. Ma on etykietkę„unsafe” (niebezpieczny, niepewny) ponieważ nie zapewnia on żadnej ochrony zasobów ponadto podaje zasoby„w ciemno” na WEB-INF oraz źródło .jsp.

Przykład 4.17.

Podawanie zasobów (sposób niebezpieczny)

import java.io. *;import java.net. *;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils;public class UnsafeViewResource extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Zastosuj ServletOutputStream ponieważ możemy przekazać informację binarną ServletOutputStream out = res. getOutputStream (); res. setContentType (" tekst/zwykły"); // sanity default // Pobierz zasób do przeglądania String file = req.getPathInfo(); if (file == null) { out. println(" Informacja dodatkowej ścieżki wynosiła zero; powinna być zasobem do przeglądania") return; }

Page 91: Java Servlet - Programowanie

// Konwertuj zasób na URL // UWAGA:Przyznanie dostępu do plików pod źródłem WEB-INF oraz.jsp URL url = getServletContext().getResource(file); if (url == null) { // niektóre serwery odeślą null w przypadku // nieznalezienia out.println("Zasób" + file + " nie odnaleziony"); return; } // Połącz z zasobem URLConnection con = null; try { con = url.openConnection(); con.connect();} catch (IOException e) { out.println("Zasób"+file+" nie może zostać odczytany:"+e.getMessage()); return;}

// Pobierz i ustal typ zasobu String contentType = con.getContentType(); res. setContentType (contentType) ; // Odeślij zasób // UWAGA: Odsyłanie plików źródłowych pod WEB-INF i . jsp try { ServletUtils. returnURL (url, out) ; } catch (IOException e) { res. sendError (res. SC_INTERNAL_SERVER_ERROR, "Problem przy przesyłaniu zasobu: " + e.getMessage()); } }}

Aplet ten przegląda pliki tylko w swoim własnym kontekście. Wszystkie pliki poza tym kontekstem nie sądostępne z metody getServletContext().getResource(). Aplet ten nie zapewnia również żadnej ochronyzasobów, więc pliki pod WEB-INF i pod źródłem .jsp mogą być podawane bezpośrednio. Przykład 4.18prezentuje bezpieczniejszą wersję klasy, przy użyciu metody ServletUtils.getResource() zcom.oreilly.servlet.

Przykład 4.18.

Podawanie zasobu oddzielonego (sposób bezpieczny)

import java.io.*;import Java.net.*;import java.util.*;import javax. servlet. * ;import javax.servlet.http.* ;import com.oreilly.servlet.ServletUtils ;public class ViewResource extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Użyj ServletOutputStream ponieważ możemy przesłać informację // binarną ServletOutputStream out = res.getOutputStream{) ; res.setContentType("tekst/zwykły"); // sanity default // Pobierz zasób do przeglądania URL url = null; try { url=ServletUtils.getResource(getServletContext(),req.getPathInfo());}catch (IOException e) { res.sendError( res.SC_NOT_FOUND, "Informacje dodatkowej ścieżki muszą wskazywać na aktualny plik do przeglądania: " + e.getMessage()); return;

Page 92: Java Servlet - Programowanie

} // Połącz z zasobem URLConnection con = url.openConnection() ; con.connect(); // Pobierz i ustal typ zasobu String contentType = con.getContentType(); res. setContentType( contentType); // Odeślij zasób try { ServletUtils. returnURL (uri, out); } catch (IOException e) { res. sendError (res. SC_INTERNAL_SERVER_ERROR, "Problem przy przesyłaniu zasobu: " + e.getMessage()); } } }

Metoda ServletUtils.getResource() zawija metodę context.getResource() oraz dodaje trzywygodne sprawdzania bezpieczeństwa: zasoby nie są podawane jeżeli zawierają podwójne punkty, kończą sięukośnikiem lub kropką, kończą się na .jsp lub zaczynają WEB-INF lub META-INF. Kod wygląda w następującysposób:

public static URL getResource(ServletContext context. String resource) throws IOException { // Zwarcie jeżeli zasób wynosi zero if (resource == null) { throw new FileNotFoundException( "Żądany zasób wynosił zero (przekazano zero)"); } if (resource.endsWith("/") || resource.endsWith("\\")|| resource.endsWith(".")) { throw new MalformedURLException(‘Ścieżka nie może się kończyć ukośnikiem ani kropką”); } if (resource. indexOf ("...") != -1) { throw new MalformedURLException("Ścieżka nie może zawierać podwójnych kropek"); } String upperResource = resource.toUpperCase() ; if (upperResource.startsWith("/WEB-INF") || upperResource. startsWith (" /META-INF")) { throw new MalformedURLException( "Ścieżka nie może zaczynać się na /WEB-IMF lub /META-INF") ; } if (upperResource.endsWith(".JSP")) { throw new MalformedURLException( "Ścieżka nie może kończyć się na .jsp") ; } // Konwertuj zasób na URL URL url = context.getResource(resource); if (uri == null) { throw new FileNotFoundException( "Żądany zasób był równy zero (° + zasób + ")"); } return url;}

Podawanie zasobów do ściągnięciaAplet ViewResource ma nie tylko zastosowanie jako przykład podręcznikowy. Kiedy będzie ustalał Content-Type (typ treści) odpowiedzi do application/octet-stream, będzie wtedy działał jako program ładującypliki standardowe. Większość przeglądarek w sytuacji kiedy otrzymują treść application/octet-stream,oferuje użytkownikom okno wywoływane pytając gdzie zapisać treść. Wyjątek stanowi Microsoft InternetExplorer, który rozpoznając typ treści empirycznie, ignoruje typ treści przypisany do serwera, i wyświetla treść

Page 93: Java Servlet - Programowanie

normalnie. Ten „atrybut” uniemożliwia ściąganie przez przeglądarkę Internet Explorer typów plików takich jak:GIF, JPEG, HTML oraz wielu innych.

Określanie co było przedmiotem zleceniaAplet ma do dyspozycji wiele metod ażeby dowiedzieć się który dokładnie plik lub aplet był przedmiotemzlecenia klienta. W końcu tylko bardzo pewni siebie twórcy apletów założyliby, że to zawsze ich aplet będziebezpośrednim przedmiotem zlecenia. Aplet może być niczym więcej jak tylko funkcją obsługi dla niektórychtreści.

Żadna metoda nie odsyła bezpośrednio oryginalnego Jednolitego Lokalizatora Zasobów (Uniform ResourceLocator — URL), użytego przez klienta w celu złożenia zlecenia. Klasa javax.servlet.httpHttpUtils,oferuje jednakże metodę (getRequestURL()), która, właściwie to robi.*

public static StringBuffer HttpUtils.getRequestURL(HttpServletRequest req)

Metoda ta rekonstruuje URL opierając się na informacjach zawartych w obiekcie HttpServletRequest.Odsyłany jest StringBuffer, który zawiera schemat (taki jak HTTP), nazwę serwera, port serwera orazinformacje dodatkowej ścieżki. Zrekonstruowany URL powinien być nieomal identyczny jak ten użyty przezklienta. Różnice pomiędzy oryginalnymi a zrekonstruowanymi URL-ami są nieznaczne (obszar zakodowanyprzez klienta jako %20 serwer mógłby zakodować jako +). Jako że metoda ta odsyła StringBuffer, URLzlecenie może być skutecznie modyfikowany (na przykład poprzez dodawanie parametrów zapytań). Metoda tajest często stosowana przy tworzeniu przekierowanych komunikatów i powiadamianiu o błędach.

Przez większość czasu jednakże apletowi tak naprawdę nie jest potrzebny URL zlecenia. Co jest mu potrzebne tozleceniowe URI, które jest odsyłane przez metodę getRequestURI():

public String HttpServleCRequest.getReguestURI()Metoda ta odsyła Uniwersalny Wskaźnik Zasobów (Universal ResourceIdentifier — URI) zlecenia — jeszczeprzed dekodowaniem URL-u. Normalne aplety HTTP mogą postrzegać URI zlecenia jako URL nie zawierającyschematu, komputera centralnego i ciągu zapytań lecz zawierający wszystkie informacji dodatkowej ścieżki.Inaczej mówiąc byłaby to ścieżka kontekstu, plus ścieżka serwera, plus informacja ścieżki. *Na tabeli 4.2zaprezentowano kilkanaście URI zleceniowych oraz ich odpowiedniki URL-owe.

Tabela 4.2.

URI oraz ich URL-owe odpowiedniki

URL zleceniowe Ich składniki URIhttp://server:port/servlet/Classname /servlet/Classnamehttp://server:port/servlet/registeredName /servlet/registeredNamehttp://server:port/servlet/Classname?var=val /servlet/Classnamehttp://server:port/servlet/Classname/pathinfo /servlet/Classname/pathinfohttp://server:port/servlet/Classname/pathinfo?var=val /servlet/Classname/pathinfohttp://server:port/servlet/Classname/path % 20infoa /servlet/Classname/path%

20infohttp://sefver:port/alias. html (alias to a servlet) /alias.htmlhttp://server.port/context/path/servlet/Classname Context/path/servlet/Classname

a %20 reprezentuje obszar zakodowany

* Dlaczego nie istnieje metoda, która bezpośrednio odsyła oryginalny URL wyświetlany w przeglądarce? Ponieważprzeglądarka nigdy nie przesyła pełnego URL-u. Dla przykładu numer portu jest wykorzystywany przez klienta w celurealizacji połączenia HTTP lecz nie jest zawarty w zleceniu składanym serwerowi odpowiadającemu na tym porcie.

Page 94: Java Servlet - Programowanie

W niektórych przypadkach wystarczy że aplet zna nazwę apletową pod którą został wywołany. Informację tąmożemy odczytać przy pomocy metody getServletPath():

public String HttpServletRequest.getServletPath()

Metoda ta odsyła część URI, która odnosi się do wywoływanego apletu (URL jest w razie potrzeby dekodowany)lub null — jeżeli URI nie wskazuje bezpośrednio na aplet. Ścieżka apletu nie zawiera informacji dodatkowejścieżki ani ścieżki kontekstu. Tablica 4.3 prezentuje nazwy apletów zleceniowych URL-ów.

Tabela 4.3.

URL-e i ich ścieżki apletów

URL zleceniowe Ich ścieżki apletówhttp://server:port/servlet/Classname /servlet/Classnamehttp://server:port/servlet/registeredName servlet/registeredNam

ehttp://server:port/servlet/Classname?var=val /servlet/Classnamehttp://server:port/servlet/Classname/pathinfo /servlet/Classnamehttp://server:port/servlet/Classname/pathinfo?var=val /servlet/Classnamehttp://server:port/servlet/Classname/path % 20info /servlet/Classnamehttp://sefver:port/alias.html (alias to a servlet) /alias.htmlhttp://server.port/context/path/servlet/Classname /servlet/ClassnamePoniżej przedstawiamy sposób pomocny w zapamiętywaniu informacji ścieżki:

decoded(getRequestURI) == decoded(getContextPath) + getServletPath + getPathInfo

Sposób złożenia zleceniaAplet poza tym, że ma możliwość dowiedzenia się co było przedmiotem zlecenia, zna również kilka sposobówna zdobycie informacji związanych ze sposobem jego złożenia. Metoda getScheme() odsyła schemat, użyty doskładania tego zlecenia:

public String ServletRequest.getScheme()

Przykłady zawierają http, https oraz ftp jak również nowsze, Java-specyficzne schematy jdbc i rmi.Bezpośredni odpowiednik CGI nie istnieje (chociaż niektóre wdrożenia CGI mają zmienną SERVER_URL, którazawiera schemat). W przypadku apletów HTTP metoda ta informuje o tym czy zlecenie zostało złożone przezpołączenie bezpieczne, przy użyciu SSL — Warstwy Bezpiecznych Gniazdek (Secure Sockets Layer), tak jak wprzypadku schematu https czy też przez połączenie niebezpieczne, wskazane przez schemat http.

Metoda getProtocol() odsyła protocol (protokół) oraz numer wersji użytej do złożenia zlecenia:

public String ServletRequest.getProtocol()

Protokół i numer wersji oddzielone są ukośnikiem. Metoda odsyła null jeżeli nie można ustalić protokołu. Dlaapletów HTTP protokół to zwykle HTTP/1.0 lub HTTP/1.1. Aplety HTTP mogą wykorzystać wersję protokołuw celu określenia czy mogą podczas współpracy z klientem nowych funkcji zawartych wersji 1.1 HTTP.

Page 95: Java Servlet - Programowanie

Aplet używa metody getMethod() ażeby dowiedzieć się jaka metoda została użyta dla zlecenia.

Metoda ta odsyła metodę HTTP wykorzystaną do złożenia zlecenia. Przykłady zawierają GET, POST, orazHEAD. Metoda service(), wdrożenia HttpServlet wykorzystuje tą metodę przy wysyłaniu zleceń.

Nagłówki zlecenioweZlecenia i odpowiedzi HTTP mogą mieć wiele nagłówków HTTP. Nagłówki te dostarczają pewną liczbędodatkowych informacji o zleceniu lub odpowiedzi. Wersja HTTP 1.0 protokołu określa dosłownie dziesiątkimożliwych nagłówków; wersja 1.1 zawiera ich nawet więcej. Opisu wszystkich nagłówków nie byliśmy w staniezamieścić w niniejszej książce; te które przedstawiamy to te, które są najczęściej wykorzystywane przez aplety.Zainteresowanych pełną listą nagłówków HTTP oraz ich zastosowań odsyłamy do książki Clintona Wonga„HTTP Pocket Reference”, pomocna może również być tutaj pozycja autorstwa Stephena Spainhour’a i RobertaEcksteina „Webmaster in a Nutshell”

Aplet rzadko musi odczytywać nagłówki HTTP, które towarzyszą zleceniu. Wiele nagłówków towarzyszącychzleceniu obsługiwanych jest przez sam serwer. Rozważmy dla przykładu sposób, w jaki serwer ogranicza dostępdo swoich dokumentów. Serwer wykorzystuje nagłówki HTTP, a aplet nie musi znać szczegółów. Kiedy serwerotrzymuje zlecenie na stronę, do której dostęp jest ograniczony, sprawdza czy zlecenie zawiera właściwynagłówek Authorization (Uwierzytelnianie), który powinien z kolei zawierać poprawną nazwę użytkownikaoraz hasło. W sytuacji kiedy powyższe dane nie są poprawne, sam serwer wysyła odpowiedź z nagłówkiem WWW-Aythenticate powiadamiając w ten sposób przeglądarkę o tym, że dostęp do zasobu nie został jej przyznany.Kiedy jednak klient prześle właściwy nagłówek Authorization, serwer przyznaje mu dostęp, a apletowi dajewywołany dostęp do nazwy użytkownika poprzez wywołanie metody getRemoteUser().

Inne nagłówki są również używane przez aplety lecz nie bezpośrednio. Dobrym przykładem może tutaj być paraLast-Modified, If-LastModified (omówiona w rozdziale 3). Sam serwer widzi nagłówek If-Last-Modified i wywołuje metodę apletu getLastModified() żeby dowiedzieć się jak postępować.

Istnieje kilka nagłówków HTTP, które aplet może „zechcieć” wywołać. Nagłówki te zostały zaprezentowane wtabeli 4.4.

Tabela 4.4.

Lista przydatnych nagłówków zleceniowych HTTP

Nagłówek ZastosowanieAccept Określa typ nośnika (MIME), który klient preferuje, oddzielony przecinkami. Niektóre starsze

typy przeglądarek przesyłają osobny nagłówek dla każdego typu nośnika. Każdy typ nośnikapodzielony jest na typ i podtyp podawane jako type/subtype. Symbol gwiazdka (*)przyznany jest dla podtypu (type/*) lub zarówno dla typu jak i podtypu (*/*). Na przykład:

Accept: image/gif, image/jpeg, text/*, */*Aplet może posłużyć się tym nagłówkiem jako pomocą w określeniu jaki typ treści odesłać.Jeżeli nagłówek ten nie jest przekazywany jako część zlecenia możemy założyć, ze klientakceptuje wszystkie typy nośników

Accept-Language

Określa język lub języki, które preferuje klient, używając w tym celu skrótów językastandardowego ISO-639 wraz z (wariantowym) kodem kraju ISO-3166. Na przykład:

Accept-Language: en, es, de, ja, zh-TWTaki zapis oznacza, że klient (użytkownik) czyta posługuje się językiem angielskim,hiszpańskim, niemieckim, japońskim oraz Tajwańskim dialektem języka chińskiego. Stosujesię konwencję, według której języki są zapisywane w kolejności preferencji. W rozdziale 13(„Internacjonalizacja”) można znaleźć więcej informacji na temat.

Page 96: Java Servlet - Programowanie

User-Agent Dostarcza informacji na temat oprogramowania klienta. Format odsyłanego strumienia jestwzględnie dowolny, zwykle jednak zawiera nazwę oraz wersję przeglądarki oraz informacje okomputerze na którym jest wykonywany. Netscape 4.7 na SGI Indy uruchamiający IRIX 6.2relacjonuje:

User-Agent: Mozilla/4.7 [en] (Xll; U; IRIX 6.2 IP22)Microsoft Internet Explorer 4.0 działający „pod” Windows’em 95 relacjonuje:

User-Agent: Mozilla/4.0 (conpatible; MSIE 4.0; Windows 95)Aplet może wykorzystać ten nagłówek do prowadzenie statystyk lub do dostosowania swojejodpowiedzi w oparciu o typ przeglądarki.

Referer Podaje URL dokumentu, który odnosi się do URL-u zlecenia (tj. dokumentu, który zawierałłącznik, który z kolei został wykorzystany przez klienta w celu uzyskania dostępu do tegodokumentu)*) Dla przykładu:

Referer: http://developer.java.sun.com/index.htmlAplet może posługiwać się tym nagłówkiem w celu prowadzenia statystyk lub w przypadkuistnienia jakiegoś błędu w zleceniu, utrzymywać ścieżkę dokumentu z błędami.

Authorization

Zapewnia uprawnienie klienta do dostępu do żądanego URI, włącznie z nazwą użytkownika ihasłem kodowanymi w Base64. Aplety mogą stosować taką niestandardową autoryzację(uprawnienie) w sposób omówiony w rozdziale 8.

*) W słowniku słówko to zapisane byłoby jako Referrer. Jednakże fakt, że musimy stosować się do pisownizawartej w specyfikacji HTTP, determinuje taką a nie inną pisownię tego wyrazu.

Dostęp do wartości nagłówkowychDostęp do wartości nagłówkowych uzyskuje się wykorzystując obiektu HttpServletRequest. Wartośćnagłówkowa może zostać odczytana jako String, long (reprezentująca Date) lub int, przy użyciu metodgetHeader(), getDateHeader() i getIntHeader() odpowiednio:

public String HttpServletRequest.getHeader(String name)public long HttpServletRequest.getDateHeader(String name)public int HttpServletRequest.getIntHeader(String name)

getHeader() odsyła wartość nagłówka nazwanego, jako String (ciąg znaków) lub null (zero) — w wypadkukiedy nagłówek nie był przesłany jako część zlecenia. W wypadku w nazwie nie są rozróżniane małe i wielkielitery, przeciwnie do wszystkich tych metod. Przy pomocy tej metody można odczytać wszystkie typynagłówków.

getDateHeader() odsyła wartość nagłówka nazwanego, jako long (reprezentujący Date), określające ilemilisekund upłynęło od „epoki”) lub — 1 jeżeli nagłówek nie został przesłany jako część zlecenia. Metoda tazgłasza wyjątek IllegalArgumentException, kiedy wywoływany jest nagłówek, którego wartość nie możezostać przekształcona na Date. Metoda ta jest użyteczna przy operowaniu nagłówkami takimi jak Last-Modified czy If-Modified-Since.

GetIntHeader() odsyła wartość nagłówka nazwanego jako int, lub 1 — gdy nagłówek nie został przesłanyjako część zlecenia. Metoda zgłasza wyjątek NumberFormatException przy wywoływaniu nagłówka, któregowartość nie może być przekształcona na int.

Aplet może również, używając metody getHeaderNames(), uzyskać nazwy wszystkich nagłówków:

public Enumeration HttpServletRequest.getHeaderNames()

Page 97: Java Servlet - Programowanie

Metoda ta odsyła nazwy wszystkich nagłówków jako Enumeration (wyliczenie) obiektów String. Wprzypadku gdy nie było żadnych nagłówków metoda odsyła puste Enumeration. Interfejs API daje wdrożeniompojemników apletów prawo uniemożliwienia takiego sposobu dostępu do nagłówków, w przypadku któregometoda ta odsyła null.

Niektóre nagłówki takie jak np. Accept czy Accept-Language, obsługują wartości wielokrotne. Zwyklewartości te są przekazywane w jednym nagłówku, oddzielonym odstępami, jednak niektóre przeglądarkipreferują przesyłanie wartości wielokrotnych przez nagłówki wielokrotne:

Accept-Language: enAccept-Language: frAccept-Language: ja

W celu odczytania nagłówka za pomocą wartości wielokrotnych, aplety mogą posłużyć się metodą getHeaders():

public Enumeration HttpServletRequest.getHeaders(String name)

Omawiana metoda odsyła wszystkie wartości dla danego nagłówka jako Enumeration (wyliczenie)obiektówString, lub puste Enumeration — jeżeli nagłówek nie został przesłany jako część zlecenia. W przypadkugdy pojemnik apletu nie pozwala na dostęp do informacji nagłówka, wywołanie odsyła null. Nie istniejąmetody getDateHeaders() ani getIntHeaders().

Na przykładzie 4.19 zostało zaprezentowane zastosowanie powyższych metod w aplecie, który wyświetlainformacje o swoich nagłówkach zleceń HTTP.

Przykład 4.19.

„Podpatrywanie” nagłówków

import java.io. *;import java.util.*;import javax.servlet.*;import javax.servlet.http. *;public class HeaderSnoop extends HttpServlet {public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(tekst/zwykły") ; PrintWriter out = res.getWriter();

out.println("Nagłówki zleceniowe:") ; out.println() ; Enumeration names = req.getHeaderNames(); while (names. hasMoreElements ()) { String name = (String) names. nextElement (); Enumeration values=req.getHeaders(name);//obsługuj wartości wielokrotne if (values != null) { while (values .hasMoreElements ()) { String value = (String) values.nextElement(); out. println (name + ": " + value);

Przykładowy wydruk wyjściowy z tego apletu mógłby wyglądać w następujący sposób:

Request Headers;

Connection: Keep-Alive

Page 98: Java Servlet - Programowanie

If-Modified-Since: Thursday, 17-Feb-OO 23:23:58 GMT; length=297User-Agent: Mozilla/4.7 [en] (WiriNT; U)Host: localhost:8080Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*Accept-Language: enAccept-Language: esAccept-Charset: iso-8859-1,*,utf-8Cookie: JSESSIONID=ql886xlc31

Strumień wyjściowyZ każdym zleceniem, obsługiwanym przez aplet związany jest strumień wejściowy. Tak jak wprzypadku PrintWriter czy OutpurStream związanych z związanych z obiektem odpowiedziapletu, do których może on pisać, w przypadku Reader czy InputStream związanych zobiektem zlecenia apletu, może on z nich czytać. Dane odczytywane ze strumieniawejściowego mogą mieć dowolny typ treści i dowolną długość. Strumień wejściowy ma dwazasadnicze cele:

• Przekazanie apletowi HTTP treści związanej ze zleceniem POST

• Przekazanie apletowi innemu niż HTTP dane nieprzetworzone, przesłane przez klientaW celu odczytania danych znakowych ze strumienia wejściowego, powinniśmy użyć metodygetReader(), pobierając strumień wejściowy jako obiekt BufferReader:

public BufferedReader ServletRequest.getReader() throws IOException

Przewaga używania BufferReader dla odczytywania danych znakowych, to poprawne tłumaczenie zestawówznaków. Metoda ta zgłasza wyjątek IllegalStateException w przypadku gdy metoda getInputStream()została wywołana do tego samego zlecenia. Metoda ta zgłasza również wyjątekUnsupportedEncodingException jeżeli kodowanie znaków sygnału wejściowego jest nieobsługiwane lubnieznane.

W celu odczytania danych binarnych ze strumienia wejściowego, należy posłużyć się metodą getInputStream(), pobierając strumień wejściowy jako obiekt ServletInputStream:

public ServletInputStream ServletRequest.getInputStream() throws IOException

Obiekt ServletInputStream jest bezpośrednią podklasą InputStream i może być traktowany jako normalnyInputStream, z dodaną zdolnością odczytywania sygnału wejściowego (wiersz na jeden raz) w tablicę bitów.Metoda zgłasza wyjątek IllegalStateException jeżeli getReader() została wywołana wcześnie do tegosamego zlecenia. Kiedy już mamy ServletInputStream, możemy odczytać z stamtąd wiersz przy użyciureadLine():

public int ServletInputStream.readLine(byte b[], int off, int len)throws IOException

Metoda ta czyta bity ze strumienia wejściowego do tablicy bitów b, począwszy od pozycji w tablicy podanejprzez off. Przerywa zaś czytanie kiedy napotyka na \n lub kiedy przeczyta liczbę bitów len.W Znak końcowy\n jest również czytany do bufora. Metoda ta odsyła sczytaną liczbę bajtów, lub 1 — jeżeli został osiągniętykoniec strumienia.

W drożenia readLine Interfejsu API 2.0 były obciążone błędem, przekazany parametr len był ignorowany, stwarzającproblemy włącznie z ArrayIndexOutOfBoundsException — jeżeli długość linii przekraczała objętość bufora.Błąd ten został usunięty w Interfejsie 2.1 oraz późniejszych wersjach.

Page 99: Java Servlet - Programowanie

Aplet może dodatkowo sprawdzić typ zawartości oraz długość danych przesyłanych przez strumień wejściowy,przy wykorzystaniu metod getContentType() oraz getContentLength() odpowiednio:

public String ServletRequest.getContentType()public int ServletRequest.getContentLength()

getContentType() odsyła typ nośnika treści przesyłanej przez strumień wejściowy, lub null — jeżeli typ tennie jest znany (tak jak w przypadku kiedy nie ma żadnych danych).

GetContentLength() odsyła długość, w bitach, treści przesyłanej przez strumień wejściowy, lub -1 — jeżelidługość ta nie jest znana.

Ładowanie plików przy użyciu strumienia wejściowegoAplety mogą również otrzymywać załadowania plików przy użyciu swojego strumieniawejściowego. Zanim dowiemy się jak to jest możliwe, musimy zaznaczyć, że ładowanie plikówjest na razie w fazie eksperymentowania i, w związku z tym nie jest obsługiwane przezwszystkie przeglądarki. Co się tyczy Netscape’a to wprowadził on obsługę ładowania plikóww Netscape Navigator’ze 3; w przypadku Microsoftu był to Internet Explorer 4.

Pełna charakterystyka ładowani plików została zamieszczona w eksperymentalnym RFC1867, dostępnym na stronie http:/www.ietf.org/rfc/rfc1867.txt, z uzupełnieniami w RFC 2388,dostępnym na stronie http:/www.ietf.org/rfc/rfc/2388.txt. Podsumowując nasze rozważaniamożemy powiedzieć, że każda liczba plików i parametrów może zostać przesłana jako daneformularzowe, w pojedynczym zleceniu POST. Zlecenie POST jest formatowane inaczej niżstandardowe dane formularzowe application/x-www-form-urlencoded i informuje o tympoprzez ustalenie ich typu treści do multipart/form-data.

Napisanie klientowi połowę ładowania pliku jest rzeczą całkiem prostą. HTML w przykładzie4.20 generuje formularz, który prosi o podanie nazwy użytkownika oraz pliku dozaładowania. Warte odnotowania jest dodanie atrybutu ENCTYPE oraz zastosowanie typu FILEsygnału wejściowego.

Przykład 4.20.

Formularz do wybierania ładowanego pliku

<FORM ACTION="/servlet/UploadTest" ENCTYPE="wieloczęściowe/dane formularzowe"METHOD=POST>What is your name? <INFOT TYPE=TEXT NAME=submitter> <BR>Which file do you want to upload? <INPUT TYPE=FILE NAME=file> <BR><IMPUT TYPE=SUBMIT></FORM>

Użytkownik, który otrzymuje podobny formularz widzi stronę, która wygląda podobnie jak taprzedstawiona na rysunku 4.3. Nazwa pliku może zostać umieszczona w obszarze tekstowymlub wybrana poprzez przeglądanie. Wielokrotne pola danych <INPUT TYPE =FILE> mogą byćstosowane, jednakże obecnie stosowane przeglądarki obsługują ładowanie tylko jednegopliku na pole. Po selekcji użytkownik przedkłada formularz standardowo.

Page 100: Java Servlet - Programowanie

Rysunek 4.3.

Wybieranie pliku do załadowania

Obowiązki serwera podczas ładowania plików są nieco bardziej skomplikowane. Z perspektywy apletu,przedłożenie jest niczym innym jak tylko strumieniem danych nieprzetworzonych w jego strumieniu wejściowym— strumienia danych formatowanych zgodnie z typem treści multipart/form-data podanego w RFC 1867.Interfejs API nie oferuje żadnej metody wspomagającej parsowanie danych. W celu „uproszczenia spraw” Jasonnapisał klasę użyteczności, która nie działa w naszym przypadku. Jej nazwa brzmi MultipartRequest i zostałazaprezentowana na przykładzie 4.22, dalej w tym podrozdziale.

MultipartRequest zawija ServletRequest i przedstawia proste dla programistów apletów API. Klasa ta madwóch konstruktorów:

public MultipartRequest(HttpServletRequest request, String saveDirectory, int maxPostSize) throws IOException public MultipartRequest(HttpServletRequest request, String saveDirectory) throws IOException

Każda z tych metod tworzy nowy obiekt MultipartRequest aby obsłużyć określone zlecenie, zapisującwszystkie załadowane pliki w saveDirectory. Obydwaj konstruktorzy właściwie parsują treśćmultpart/form-data i zgłaszają wyjątek IOException w razie jakichkolwiek problemów (tak więc apletyużywając tej klasy nie mogą czytać strumienia wejściowego). Konstruktor, który przyjmuje parametrMaxPostSize zgłasza również wyjątek IOException w wypadku gdy ładowana treść jest większa niżmaxPostSize. Drugi konstruktor przyjmuje domyślne MaxPostSize 1Mb.

Serwer, który otrzymuje ładowanie, którego długość treści jest zbyt wielka ma do wyboru dwa wyjścia: popierwsze, spróbować przesłać stronę błędu, poczekać aż klient się rozłączy i podczas „cichego” czekaniazniszczyć całą załadowaną treść. Postępowanie takie jest zgodne z opisem HTTP / 1.1 w RFC2616, sekcja 8.2.2,

Page 101: Java Servlet - Programowanie

która mówi, że klient powinien oczekiwać na „stan błędu” podczas ładowania (porównaj stronęhttp:/www.ietf.org/rfc/rfc2616.txt) oraz zatrzymać ładowanie w momencie otrzymania informacji o błędzie.Procedura taka gwarantuje, że każdy klient będzie widział właściwy komunikat błędu, jednakże dla wieluprzeglądarek, które nie oczekują na status błędu oznacza marnowanie przepustowości serwera ponieważ klientbędzie przeprowadzał pełne ładowanie. Z tego właśnie powodu wiele serwerów wdraża drugą opcję: próbująprzesłać stronę błędu i w razie potrzeby bezwzględnie rozłączyć. Sytuacja taka powoduje, że wielu klientówpozostaje bez komunikatu błędu, lecz zapewnia to bezwzględne przerwanie ładowania.

Klasa MultipartRequest dysponuje siedmioma publicznymi metodami, które umożliwiają dostęp doinformacji o zleceniu. Jak się przekonamy większość z tych metod jest modelowanych po metodachServletRequest. Aby uzyskać nazwy wszystkich parametrów zlecenia należy skorzystać z metodygetParameterNames():

public Enumeration MultipartRequest.getParameterNames()

Metoda ta odsyła nazwy wszystkich parametrów jako Enumeration (wyliczenie) obiektów String, lub pusteEnumeration jeżeli nie ma żadnych parametrów.

W celu uzyskania nazwy parametru nazwanego należy zastosować metodę getParameter() lub metodęgetParameterValues():

public String MultipartRequest.getParameter(String name)

Metoda ta odsyła wartość parametru nazwanego jako String, lub null — jeżeli parametr nie został podany.Jest pewność, że wartość będzie w swojej zdekodowanej formie. Jeżeli parametr ma wartości wielokrotne, tylkoostatnia z nich jest odsyłana.

public String[] MultipartRequest.getParameterValues(String name)

Metoda ta odsyła wszystkie wartości parametrów nazwanych jako String, lub null — jeżeli parametr niezostał podany. Pojedyncza wartość jest odsyłana w tablicy o długości 1.

W celu pobrania listy wszystkich załadowanych plików wykorzystuje się metodę getFileNames():

public Enumeration MultipartRequest.getFileNames()

Powyższa metoda odsyła nazwy wszystkich ładowanych plików jako Enumeration obiektów String, lub pusteEnumeration — jeżeli nie ma żadnych załadowanych plików. Zwróćmy uwagę, iż wszystkie nazwy plików sąokreślane przez atrybut nazwy formularza HTML, a nie przez użytkownika. Kiedy mamy już nazwę plikumożemy uzyskać jego nazwę systemu plików, wykorzystując w tym celu metodę getFilesystemName():

public String MultipartRequest.getFilesystemName(String name)

Metoda ta odsyła nazwę systemu plików określonego pliku, lub null — jeżeli plik nie został dołączony władowaniu. Nazwa systemu plików jest określana przez użytkownika. Jest to jednocześnie nazwa pod którą plikjest zapisywany. Używając metodę getContentType() możemy uzyskać typ treści pliku:

public String MultipartRequest.getContentType(String name)

Metoda ta odsyła typ treści danego pliku (jako taki, który jest obsługiwany przez przeglądarkę klienta), lub null— jeżeli plik nie był zawarty w ładowaniu. Wreszcie możemy również uzyskać dla pliku obiekt java.io.File,zasób pomocą metody grtFile():

Page 102: Java Servlet - Programowanie

public File MultipartRequest.getFile(String name)

Powyższa metoda odsyła obiekt File określonego pliku zapisanego w systemie plików serwera, lub null —jeżeli plik nie był zawarty w ładowaniu.

Przykład 4.21 ukazuje sposób w jaki aplet stosuje MultipartRequest. Jedyną rzeczą, którą wykonuje apletjest statystyka tego co zostało załadowane. Zwróćmy uwagę, że aplet nie usuwa plików, które zapisuje.

Przykład 4.21.

Obsługiwanie ładowania plików import java.io.*;import java.util.*;import javax.servlet. *;import javax.servlet.http.*;import com.oreilly.servlet.MultipartRequest ;public class UploadTest extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html") ; PrintWriter out = res.getWriter(); try { // Weź to "na wiarę" iż jest to wieloczęściowe zlecenie danych // formularzowych // Skonstruuj MultipartRequest żeby usprawnić czytanie //informacji. // Przekaż zlecenie, katalog, w którym mają być zapisane pliki //oraz // maksymalny rozmiar POST, który powinniśmy spróbować obsłużyć. // Tutaj niestety piszemy do bieżącego katalogu oraz nakładamy // ograniczenie 5-cio megowe MultipartRequest multi = new MultipartRequest(req, ".", 5 * 1024 * 1024); out.println("<HTML>") ; out.println ("<HEAD><TITLE>UploadTest</TITLE></HEAD>") ; out.println("<BODY>") ; out.println("<Hl>UploadTest</Hl>") ; // Wydrukuj parametry, które otrzymaliśmy out.println("<H3>Params:</H3>") ; out.println("<PRE>") ; Enumeration params = multi. getParameterNames (); while (params. hasMoreElements ()) { String name = (String)params.nextElement(); String value = multi.getParameter (name); out.println(name + " = " + value); } out.println("</PRE>") ; // Pokaż pliki, które otrzymaliśmy out.println("<H3>Files:</H3>") ; out.println("<PRE>") ; 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("</PRE>") ;} catch (Exception e) {

Page 103: Java Servlet - Programowanie

out.println("<PRE>") ; e.printStackTrace(out) ; out.println("</PRE>") ; } out.println( "</BODY></HTML>") ; }

}Aplet przekazuje swój obiekt zlecenia do konstruktora MultipartRequest razem z katalogiem względnym dokatalogu 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ępnieMultipartRequest 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 apletwykorzystuje swojego MultipartRequest do przechodzenia przez przesłane pliki. Dla każdego plikuuzyskiwana 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 zapisywanegopliku. 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ędziemymogli zaobserwować klasę MultipartRequest wykorzystującącom.oreilly.servlet.multipart.MultipartParser „w tle”, w celu wykonania parsowaniazlecenia. Klasa MultipartParser zapewnia dostęp do poziomu podstawowego ładowaniapoprzez „przechodzenie” przez zlecenie kawałek po kawałku. Pozwala to, na przykład nabezpośrednie ładowanie plików do bazy danych lub na sprawdzenie czy plik przekazujeokreślone kryteria przed zapisaniem. Kod oraz dokumentacja klasy MultipartParser możnaznaleźć na stronie http://www.servlets.com (podziękowania od autorów dla Geoffa Souter'aza wykonanie pracy niezbędnej do stworzenia parsera).

Należy mieć świadomość tego, iż wielu producentów serwerów nie testuje należycie swoichwyrobó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 tejklasy, powinniśmy spróbować innego serwera (jak np. „Tomcat’a”) w celu ustalenia czyrzeczywiście są one związane z serwerem — jeżeli okaże się, że to prawda należyskontaktować się z producentem serwera.

Przykład 4.22.

Klasa MultipartRequest

package com.oreilly.servlet ;import java.io.* ;import java.uti1.*;import javax.servlet.* ;import javax.servlet.http. *;import com.oreilly.servlet.multipart.MultipartParser ;import com.oreilly.servlet.multipart.Part;import com.oreilly.servlet.multipart.FilePart ;import com.oreilly.servlet multipart.ParamPart ;// Klasa użytkowa do obsługi<code>multipart/form-data</code> 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) ;

Page 104: Java Servlet - Programowanie

} 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 <= 0) { throw new IllegalArgumentException ("maxPostSize musi być dodatnie"); // Zapisz katalog File dir = new File(saveDirectory) ; // Sprawdź czy saveDirectory jest rzeczywiście katalogiem if (!dir.isDirectory()) throw new IllegalArgumentException("Nie katalog;"+saveDirectory); // Sprawdź czy w saveDirectory można zapisywać if (!dir.canWrite()) threw new IllegalArgunientException("Nie można zapisywać: " + saveDirectory); // Parsuj nadchodzące, wieloczęściowe, zapisywane pliki w podanym // katalogu // oraz ładuj do bazy meta obiekty, które opisują co znaleźliśmy MultipartParser parser = new MultipartParser (request, maxPostSize); Part part; while ((part = parser.readNextPart()) != null) { String name = part. getName() ; if (part.isParam()) { // To jest parametr part, dodaj go do wektora wartości ParamPart paramPart = (ParamPart) part; String value = paramPart.getStringValue() ; Vector existingValues = (Vector) parameters, get(name); if (existingValues == null) { existingValues = new Vector(); parameters. put (name, existingValues) ; } existingValues. addElement(value) ;}else if (part.isFile()) { // To jest część pliku FilePart filePart = (FilePart) part; String fileName = filePart.getFileName(); if (fileName != null) { // Część rzeczywiście zawierała plik filePart.writeTo(dir) ; files.put(name, new UploadedFile( dir.toString(), fileName' filePart.getContentType())) ;}else { // Pole danych nie zawierało pliku files.put(name, new UploadedFile(null, null, null)); } } } } // Konstruktor ze starą sygnaturą, utrzymywany dla kompatybilności // wstecznej.public MultipartRequest(ServletRequest request, String saveDirectory) throws IOException { this ((HttpServletRequest) request, saveDirectory) ;}// Konstruktor ze starą sygnaturą, utrzymywany dla kompatybilności // wstecznej.public MultipartRequest (ServletRequest request, String saveDirectory, int maxPostSize) throws IOException { this((HttpServletRequest)request, saveDirecfcory, maxPostSize)}public Enumeration getParameterNames() {

Page 105: Java Servlet - Programowanie

return parameters.keys() ;}public Enumeration getFileNames() { return files.keys() ;}public String getParameter(String name) { try { Vector values = (Vector) parameters, get (name); if (values == null || values.size() == 0) { return null; } String value = (String)values.elementAt(values.size() - 1) ; return value; } catch (Exception e) { return null; }}public String[] getParameterValues(String name) { try { Vector values = (Vector) parameters, get (name); if (values == null || values.size() == 0) { return null; } String[] valuesArray = new String [values, size ()]; values.copyInto(valuesArray) ; return valuesArray; } catch (Exception e) { return null; }}public String getFilesystemName (String name) { try { UploadedFile file = (UploadedFile)files.get(name); return file.getFilesystemName() ; // może być zero } catch (Exception e) { return null; }}public String getContentType(String name) { try { UploadedFile file = (UploadedFile)files.get(name); return file. getContentType(); // może być zero } catch (Exception e) { return null; }}

public File getFile(String name) { try { UploadedFile file = (UploadedFile)files.get(name); return file.getFile(); // może być zero } catch (Exception e) { return null; } }}// Klasa do przechowywania informacji o załadowanym pliku.class UploadedFile { private String dir; private String filename; private String type; UploadedFile(String dir. String filename, String type) { this.dir = dir; this.filename = filename; this.type = type; }

Page 106: Java Servlet - Programowanie

public String getContentType() { return type;} public String getFilesystemName () { return filename;} public File getFile() { if (dir == null || filename == null) { return null;)else { return new File(dir + File.separator + filename); } } }

Klasa MultipartRequest jest produktem markowym, i w przeciwieństwie do wielu bibliotek ładowań plików,obsługuje dowolnie duże ładowania. Zadajmy sobie pytanie dlaczego klasa ta nie wdraża interfejsuHttpServletRequest? Odpowiedź brzmi: ponieważ ograniczałoby to jej kompatybilność z przyszłymiwersjami. Jeżeli MultipartRequest wdrożyłaby HttpServletRequest i Interfejs API 2.3 dodałby tąmetodę do interfejsu, klasa nie wdrażałaby już całkowicie interfejsu powodując zakłócenia kompilacji apletówużywających tej klasy.

Dodatkowe atrybutyCzasem aplety potrzebują informacji na temat zlecenia, dostępu do których nie można uzyskać poprzez metodywymienione wcześniej. W takich przypadkach jest jeszcze jedna, ostatnia alternatywa — metoda getAttribute(). Przypomnijmy sobie w jaki sposób ServletContext uzyskiwał metodę getAttribute, która odsyłałaspecyficzne-serwerowo atrybuty związane z samym serwerem. ServletRequest również ma metodęgetAttribute():

public Object ServletRequest.getAttribute(String name)

Metoda ta odsyła wartość serwerowo-specyficznego atrybutu dla zlecenia, lub null — w przypadku gdy serwernie obsługuje nazwanego atrybutu zlecenia. Metoda ta pozwala również serwerowi na dostarczanie apletowiniestandardowych informacji o zleceniu. Serwery mają wolną rękę w dostarczaniu jakichkolwiek atrybutów lubmogą ich w ogóle nie dostarczać. prostu nie. Jedynym wymogiem jest to, że nazwy atrybutów powinny być w tejsamej konwencji co nazwy pakietów, z nazwami pakietów java.* oraz javax.* zarezerwowanymi dla użytku„Java Software division of Sun Microsystems”, a com.sun.* zarezerwowane dla „Sun Microsystems”. Opisatrybutów naszego serwera znajdziemy w jego dokumentacji, pamiętajmy, że używanie serwerowo-specyficznych atrybutów ogranicza przenośność naszej aplikacji.

Aplety mogą również dodać do zlecenia swoje własne atrybuty wykorzystując metodę setAttribute() (takjak to zostało omówione w rozdziale 11). Wydruk wszystkich aktualnych atrybutów, ustalonych przez serwer lubumieszczonych przez aplety, można otrzymać przy pomocy metody getAttributeNames():

public Enumeration ServletRequest.getAttributeNames()

Poniższy kod wyświetla wszystkie aktualne atrybuty:

Enumeration enum = req.getAttributeNames();while (enum.hasMoreElements()) {

String name = (String) enum.nextElement() ;out.println(" req.getAttributet(\"" + nazwa + "\"): " +req.getAttribute(name));}

Page 107: Java Servlet - Programowanie

Istnieje kilka standardowych atrybutów, które dotyczą zleceń (porównaj rozdział 11) orazcyfrowych certyfikatów po stronie serwera (porównaj rozdział 8).

Page 108: Java Servlet - Programowanie

Rozdział 5. Przesyłanie informacji

HTTP

W poprzednim rozdziale dowiedzieliśmy się, że aplet ma dostęp do różnego rodzaju informacji — informacji okliencie, o serwerze, o zleceniu i nawet o sobie samym. Czas więc abyśmy się zapoznali z tym, co aplet możezrobić z tymi informacjami — dowiemy się jak są ustalane i przesyłane informacje.

Rozdział ten zaczyna się od ogólnej charakterystyki sposobu, w jaki aplet odsyła standardową odpowiedź HTTP.W tym rozdziale omówimy również szczegółowo niektóre metody, które omówiliśmy tylko pobieżnie wpoprzednich przykładach. Dalej powiemy jak zmniejszyć obciążenie związane z utrzymywaniem połączenia zklientem, dowiemy się również jak w tym celu wykorzystać buforowanie odpowiedzi. Następnie poznamy parędodatkowych, przydatnych zastosowań HTML-u i HTTP takich jak np. odsyłanie błędów, i innych kodówstatusu, przesyłanie niestandardowych informacji nagłówkowych, przekierowywanie zlecenia, wykorzystywanieściągania klienta, obsługa wyjątków apletu, ustalanie momentu rozłączenie klienta oraz wpisywanie danych dodziennika zdarzeń serwera.

W przeciwieństwie do swojego odpowiednika z poprzedniego wydania niniejszej książki, rozdział ten nie opisujeszczegółowo generowania treści HTML-owej. Jest on wprowadzeniem do następnych rozdziałów książki, wktórych omówionych zostanie szereg oddzielnych osnów.

Struktura odpowiedziAplety HTTP odsyłają trzy, różne rodzajowo kwestie: pojedynczy kod statusu, dowolną liczbę nagłówkówHTTP oraz treść odpowiedzi. Kod statusu jest liczbą całkowitą, która opisuje, jak się łatwo można domyślećstatus odpowiedzi. Kod statusu może informować o sukcesie bądź niepowodzeniu może również poinformowaćoprogramowanie klienta, iż należy powziąć dodatkowe kroki w celu zakończenia zlecenia. Numerycznemukodowi statusu często towarzyszy reason phrase, które opisuje status językiem bardziej przystępnym dla ludzi.Zwykle kod statusu działa „w tle” i jest interpretowany przez oprogramowanie przeglądarki. W niektórychprzypadkach, kiedy pojawiają się problemy, przeglądarka może pokazać kod statusu użytkownikowi. Najbardziejchyba znanym kodem statusu jest kod 404NotFound, przesyłany przez serwer WWW, kiedy ten nie możeznaleźć żądanego URL-u.

W poprzednim rozdziale zapoznaliśmy się ze sposobem, w jaki klient wykorzystuje nagłówki HTTP w celuprzesłania dodatkowych informacji razem ze zleceniem. W tym rozdziale zobaczymy jak aplet może przesyłać tenagłówki jako część swojej odpowiedzi,

Korpus odpowiedzi jest główną treścią odpowiedzi. Dla strony HTML, korpusem odpowiedzi jest sam HTML.W przypadku grafiki, korpus odpowiedzi jest złożony z bitów, które składają się na obrazek. Korpus odpowiedzimoże mieć różny typ oraz różną długość; klient czytając oraz interpretując nagłówki HTTP zawarte wodpowiedzi, wie czego może się spodziewać.

Page 109: Java Servlet - Programowanie

Standardowe aplety są znacznie mniej skomplikowane od apletów HTTP — odsyłają tylko korpus odpowiedzido swojego klienta. Co się jednak tyczy podklasy GenericServlet, to może ona przedstawiać API, które dzielipojedynczy korpus odpowiedzi na bardziej skomplikowaną strukturę, dając wrażenie odsyłania wielokrotnychpozycji ewidencyjnych. W rzeczywistości jest to dokładnie to co robią aplety HTTP. Na najniższym poziomie,serwer WWW przesyła całą odpowiedź do klienta jako strumień bitów. Wszystkie metody, które ustalają kodystatusu lub nagłówki są w stosunku do tego abstrakcjami.

Jest rzeczą ważną, aby zrozumieć, mimo iż programista apletów nie musi znać szczegółów protokołu HTTP, żeprotokół ten jednak ma wpływ na kolejność z jaką aplet wywołuje swoje metody. Cechą charakterystycznąprotokołu HTTP jest to, że kod statusu oraz nagłówki muszą zostać przesłane przed korpusem odpowiedzi.Dlatego właśnie aplet musi dopilnować, aby zawsze ustalić najpierw swój kod statusu oraz nagłówki zanimjeszcze prześle klientowi jakikolwiek korpus odpowiedzi. Aplet może umieścić swój korpus odpowiedzi wpamięci podręcznej, żeby uzyskać większą swobodę, jednak kiedy korpus odpowiedzi zostanie już wysłany,odpowiedź uważana jest jako zatwierdzona, wtedy kod statusu oraz nagłówki nie mogą już zostać zmienione.

Przesyłanie standardowej odpowiedzi Nasze rozważania na temat odpowiedzi apletów rozpoczniemy od powtórnego przyjrzenia się pierwszemuapletowi, którego przedstawiliśmy w tej książce jako pierwszego, mowa oczywiście o aplecie HelloWorld(aplet HelloWorld został pokazany w przykładzie 5.1). Mamy nadzieję, iż w tej chwili wydaje się on jużznacznie prostszy niż w rozdziale 2 „Podstawy Apletów HTTP”.

Przykład 5.1.

Jeszcze raz „Hello”

import java.io.* ;import javax.servlet.* ;import javax.servlet.http. *;public class HelloWorld extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res. setContentType (" tekst/html") ; PrintWriter out = res.getWriter();

out.println("<HTML>") ; out.println("<HEAD><TITLE>Hello World</TITLE></HEAD>") ; out.println("<BODY>") ; out.println("<BIG>Hello World</BIG>") ; out.println("</BODY></HTML>") ; } }

Aplet ten używa dwóch metod oraz jednej klasy, które do tej pory zostały omówione tylkoskrótowo. Metoda setContentType() ServletResponse (odpowiedzi serwera) ustala typtreści odpowiedzi, aby był on typem określonym.

public void ServletResponse.setContentType(String typ)

W aplecie HTTP metoda ta ustala nagłówek HTTP Content-Type.

Metoda getWriter() odsyła PrintWriter w przypadku pisania danych odpowiedzi opartych na znakach:

public PrintWriter ServletResponse.getWriter() throws IOException

Page 110: Java Servlet - Programowanie

Program wykonujący zapis koduje znaki zgodnie z zestawem znaków, podanym w typie treści. Jeżeli nie zostałokreślony żaden zestaw znaków, tak jak to najczęściej ma miejsce, program wykonujący zapis używa kodowaniaISO-8859-1 (Latin-1) właściwego dla języków zachodnio-europejskich. Zestawy znaków zostały omówioneszczegółowo w rozdziale 13 „Internacjonalizacja”, na ten moment zapamiętajmy tylko, że dobrze jest zawszeustalić typ treści przed otrzymaniem PrintWriter. Metoda ta zgłasza wyjątek IllegalStateException —w przypadku gdy metoda getOutputStream() została już wywołana do tej odpowiedzi; która to z koleimetoda zgłasza wyjątek UnsupportedEncodingException jeżeli kodowanie strumienia wyjściowego jest niejest obsługiwane lub nieznane.

Poza możliwością wykorzystania PrintWriter w celu odesłania odpowiedzi, aplet może posłużyć się specjalnąpodklasą java.io.OutoutStream, ażeby móc pisać dane binarne — mowa o ServletOutputStream, którazostała zdefiniowana w javax.servlet. Klasę ServletOutputStream można uzyskać za pomocą metodygetOutputStream():

public ServletOutputStream ServletResponse.getOutputStream () throws IOException

Metoda ta odsyła ServletOutputStream dla pisania binarnych danych odpowiedzi. Nie jest w takimprzypadku wykonywane żadne kodowanie. Metoda ta zgłasza wyjątek IllegalStateeException jeżeligetWriter() została już dla tej odpowiedzi wywołana.

Klasa ServletOputputStream przypomina standardową klasę Javy — PrintStream. W Interfejsie API 1.0,klasa ta byłą używana dla całego strumienia wyjściowego apletu (zarówno tekstowego jak i binarnego). Jednakżew wersji 2.0 Interfejsu API oraz późniejszych, została ograniczona tylko do obsługi danych binarnych. Będącbezpośrednią podklasą OutputStream, klasa ta zapewnia dostęp do metod klasy OutputStream takich jak:write(), flush() oraz close().Poza powyższymi metodami klasa dodaje również swoje własne print() iprintln() klasy ServletOutputStream, umożliwiające pisanie większości elementarnych Javowskich typówdanych (w celu uzyskania kompletnego zestawienia patrz Uzupełnienie A: „Interfejs API — charakterystykaogólna”). Jedyna różnica pomiędzy interfejsem ServletOutputStream a interfejsem klasy PrintStream, jesttaka, że metody print() oraz println() klasy servletOutputStream nie mogą (nie jest dokładnie jasnedlaczego) drukować bezpośrednio parametrów typu Object czy char[].

Korzystanie ze połączeń stałychPołączenia stałe (persistent connections) mogą zostać wykorzystane w celu optymalizacji sposobu, w jaki apletodsyła treść do klienta. Aby zrozumieć na czym polega taka optymalizacja musimy najpierw zapoznać się zdziałaniem połączeń HTTP. Będziemy je poznawali możliwie najmniej szczegółowo, tak tylko aby poznaćogólną zasadę działania. Problem jest omówiony dogłębnie w książce Clintona Wong’a „HTTP PocketRefference”.

Kiedy klient np. przeglądarka, chce złożyć do serwera zlecenie na określony dokument sieci WWW, rozpoczynapoprzez ustanowienie połączenia do portu serwera. Właśnie poprzez to połączenie klient składa swoje zlecenieoraz otrzymuje odpowiedź serwera. Klient daje znać, że jego zlecenie zostało zakończone przesyłając czystąlinię; serwer zaś, żeby poinformować, że odpowiedź została zakończona przerywa połączenie poprzez port.

Na powyższym etapie wszystko przebiega bez zakłóceń, lecz zastanówmy się jak wyglądałaby sytuacja, kiedywyszukana strona zawierałaby znaczniki <IMG> lub znaczniki <APPLET >, które obligują klienta do odczytaniawiększej ilości treści z serwera? W takim przypadku tworzone jest jeszcze jedno połączenie portowe. Jeżelistrona zawiera 10 znaków graficznych wraz z apletem składającym się z 25 klas, daje to razem 36 połączeńpotrzebnych do przesłania strony. Wobec takich utrudnień nie dziwi zdenerwowanie internautów długimiczasami oczekiwań w Internecie. Taką sytuacją można porównać do zamawiania pizzy poprzez składaniezamówienia na każdą jej warstwę.

Page 111: Java Servlet - Programowanie

Znacznie lepszym rozwiązaniem jest wykorzystywanie jednego połączenia do pobrania więcej niż jednej strony— metoda zwana trwałym połączeniem. Problem z trwałym połączeniem polega na tym, że klient i serwer musząjakoś uzgodnić gdzie zaczyna się odpowiedź serwera a gdzie zaczyna się następne zlecenie klienta.Rozwiązaniem, które się nasuwa jest takie, że można by wykorzystać w tym celu jakiś element składniowy takijak np. czysta linia. Co by się jednak stało gdyby już sama odpowiedź zawierała czystą linię? Idea trwałegopołączenia polega na tym, że serwer informuje klienta o tym jakie rozmiary będzie miał korpus odpowiedzi,ustalając nagłówek Content-Length jako część odpowiedzi. Klient wie dzięki temu, że jeżeli wszystko będziezgodne będzie miał znowu kontrolę nad połączeniem.

Większość serwerów wewnętrznie zarządza nagłówkiem Content-Length dla plików statycznych, które sąprzez nie podawane; nagłówek Content-Length jest ustanawiany aby dostosować długość pliku. Aby określićdługość treści generowanego przez serwer wydruku wyjściowego, serwer korzysta z pomocy apletu. Aplet możeustanowić odpowiedź Content-Length i zyskać w ten sposób korzyści płynące ze stosowania trwałegopołączenia dla treści dynamicznej poprzez użycie metody setContentLength():

public void ServletResponse.setContentLength(int len)

Metoda ta ustala długość (w bitach) treści odsyłanej przez serwer. W przypadku apletu HTTP, metoda ustanawianagłówek HTTP Content-Length. Zauważmy, że stosowanie tej metody jest opcjonalne. Jeżeli jednakzdecydujemy się na jej zastosowanie, nasze aplety będą mogły czerpać korzyści płynącyce ze stosowaniatrwałych połączeń, jeżeli takie się pojawią. Klient będzie również w stanie wyświetlać dokładne monitorowaniestopnia bieżącego zaawansowania podczas ładowania.

Jeżeli wywołujemy metodę setContentLength(),musimy pamiętać o dwóch bardzo ważnych sprawach: apletmusi wywołać tą metodę przed wysłaniem korpusu odpowiedzi oraz o tym, że podana długość musi byćdokładnie odwzorowana. W razie najmniejszej rozbieżności (nawet o jeden bit) musimy liczyć się zpotencjalnymi problemami.

Buforowanie odpowiedziPocząwszy od wersji 2.2 Interfejsu API aplety maja kontrolę nad tym czy serwer buforuje swoją odpowiedź czynie oraz mogą mieć wpływ na wielkość bufora używanego przez serwer. W poprzednich wersjach API większośćserwerów wdrażała buforowanie odpowiedzi jako sposób na poprawienie wydajności; wielkość bufora byładeterminowana przez serwer. Ogólnie rzecz biorąc serwery miały bufory wielkości około 8K.

Bufor pamięci pozwala apletowi na pisanie pewnej ilości wydruku wyjściowego z gwarancją, że odpowiedź niebędzie od razu zatwierdzona. Jeżeli aplet wykryje błąd, kod statusu oraz nagłówki będą mogły być jeszczezmienione (do czasu opróżnienia bufora).

Buforowanie odpowiedzi jest również prostym sposobem uniknięcia stosunkowo trudnego szacowania długościtreści. Aplet może wykorzystać buforowanie aby automatycznie obliczyć długość treści, tak jak to zostałozaprezentowane na przykładzie 5.2.

Przykład 5.2.

Aplet wykorzystujący buforowania do automatycznej obsługi trwałych połączeń import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class KeepAlive extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

Page 112: Java Servlet - Programowanie

res.setBufferSize(8*1024); // 8K buffer // Poproś o bufor 16k bitów; nie ustalaj długości treści res.setBufferSize(16 * 1024);

PrintWriter out = res. getWriter () ; out.println("<HTML>") ; out.println("<HEAD><TITLE>Hello World</TITLEx/HEAD>) ; out.println("<BODY>") ; out.println("<BIG>Mniej niż 16 k korpusu odpowiedzi</BIG>") ; out.println("</BODY></HTML>°) ;

Aplet wywołuje metodę setBufferSize() aby poprosić o minimum 16kB bufor odpowiedzi, a następniewysyła jak zwykle, swoją odpowiedź. Serwer wykorzystuje bufor wewnętrzny o pojemności co najmniej 16.384bajtów aby przechować korpus odpowiedzi, po czym czeka z wysłaniem treści do klienta do momentuwypełnienia bufora lub do czasu kiedy aplet poprosi o jego opróżnienie. Jeżeli cały korpus odpowiedzi mieści sięw buforze, serwer może (lecz nie musi) automatycznie ustawić nagłówek odpowiedzi Content-Length.

Trzeba wspomnieć, że buforowanie odpowiedzi związane jest z pewnymi kosztami.Buforowanie całego wydruku wyjściowego i przesyłanie go w jednym wsadzie wymagadodatkowej pamięci i może opóźnić moment, w którym klient zacznie otrzymywać dane. Dlaapletów, wysyłających krótkie odpowiedzi trwałe połączenia są dobrym rozwiązaniem, lecz wprzypadku apletów z długimi odpowiedziami, niedogodności związane z dodatkową pamięciąoraz opóźnienia w przesyłaniu danych mogą okazać się większe niż korzyści wynikające zmniejszą liczbą połączeń.

Warto również zauważyć, iż nie wszystkie serwery i nie wszyscy klienci obsługują trwałepołączenia. Oznacza to, iż dla apletu jest nadal ważne ustalanie swojej długości orazbuforowanie swojego wydruku wyjściowego, tak żeby serwer mógł określić długość.Informacja o długości treści będzie wykorzystywana przez te serwery i klientów, którzyobsługują trwałe połączenia a ignorowana przez innych.

Regulowanie bufora odpowiedziIstnieje pięć metod w ServletResponse, które zapewniają kontrolę nad buforowaniem odpowiedzi. Abypoinformować serwer o tym jaki minimalny rozmiar (podany w bajtach) bufora jest akceptowany przez naszaplet, możemy użyć metody setBufferSize():

Public void ServletResponse.setBufforSize(int size)throws IllegalStateException

Serwer może zapewnić bufor większy niż żądany — może zdecydować, że bufory będąutrzymywane w np. 8k blokach, w celu umożliwienia ponownego użytku. Większy buforpozwala na napisanie większej ilości treści zanim jeszcze cokolwiek zostanie wysłane, dająctym samym apletowi więcej czasu na ustawienie kodów statusu oraz nagłówków. Mniejszybufor zmniejsza obciążenie pamięci serwera i pozwala na wcześniejsze rozpoczęcieprzesyłania danych do klienta. Metoda ta musi zostać wywołana zanim jeszcze zostanienapisana jakakolwiek treść korpusu odpowiedzi; w przypadku gdy treść została już napisanametoda ta zgłasza wyjątek IllegalStateException. W celu określenia rozmiaru bufora możnaposłużyć się metodą getBufferSize():

public int ServletResponse.getBufferSize()

Metoda ta odsyła int informując o tym jak duży jest właściwie bieżący bufor, lub 0 — w (małoprawdopodobnym) przypadku nie użycia żadnego buforowania.

Page 113: Java Servlet - Programowanie

Ażeby określić czy jakaś część zlecenia została już wysłana możemy wywołać metodęisCommitted():

public boolean ServletResponse.isCommitted()

Jeżeli metoda ta odeśle true oznacza to, iż jest za późno aby zmienić kod statusu i nagłówki, a Content-Length (długość treści) nie może zostać obliczona automatycznie.

Jeżeli musimy rozpocząć naszą odpowiedź od nowa, możemy wywołać metodę reset() Ażeby wyczyścić buforodpowiedzi, aktualnie przypisany kod statusu oraz nagłówki odpowiedzi:

public void ServletResponse.reset() throws IllegalStateException

Metoda ta musi zostać wywołana zanim jeszcze odpowiedź nie zostanie zatwierdzona, w przeciwnym wypadkujest zgłaszany wyjątek IllegalStateException. Metody sendError() oraz sendRedirect() (którezostaną omówione później) zachowują się podobnie i opróżniają bufor odpowiedzi, metody te jednak niemodyfikują nagłówków odpowiedzi.

Możemy również wymusić zapisanie określonej treści do klienta w buforze, poprzez wywołanie metodyflushBuffer(), pozwalając tym samym na natychmiastowe rozpoczęcie przesyłania danych do klienta:

public void ServletResponse.flushBuffer() throws IOException

Wywołanie tej metody powoduje automatycznie zatwierdzenie odpowiedzi, oznacza to, iż kodstatusu oraz nagłówki zostaną napisane a reset() (restartowanie) nie będzie już możliwe.

Na przykładzie 5.3 pokazano aplet, który wykorzystuje metodę reset() aby napisać a następnie wyzerowaćtreść. Aplet drukuje domyślny rozmiar bufora do rejestru zdarzeń, zamiast do klienta, w ten sposób nie możliwejest wyzerowanie (za pomocą metody reset()).

Przykład 5.3.

Zarządzanie buforem odpowiedzi

import javax.servlet.* ;import javax.servlet.http.*;import java.io.*;public class Buffering extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setBufferSize(8 * 1024); // 8-kilowy bufor res. setContentType (" tekst/html") ; PrintWriter out = res.getWriter(); int size = res.getBufferSize(); // odsyła 8096 lub większy // Zapisz rozmiar domyślny, w rejestrze zdarzeń log("Domyślny rozmiar bufora to " + rozmiar); out.println("Klient nie będzie tego widział"); res.reset(); out.println("Tego też nie będzie widział klient!"); res.reset() ; out.println("A tego nie będzie można zobaczyć jeżeli zostanie wywołana metoda sendError()"); if (req.getParameter("ważny_parametr") == null) { res. sendError (res. SC_BAD_REQUEST, " ważny_parametr potrzebny") ;

Page 114: Java Servlet - Programowanie

} } }

Kody statusuDo chwili obecnej nasze przykłady apletów nie ustalały kodów HTTP statusu odpowiedzi.Korzystaliśmy z tego, że jeżeli aplet nie ustala kodu statusu, serwer wykonuje „krok doprzodu” i ustala swoją wartość na domyślny kod statusu 200 OK. Jest to duża dogodnośćjeżeli odsyłamy standardowe, zwykle zakończone sukcesem odpowiedzi. Jednakże używająckod statusu aplet może zrobić ze swoją odpowiedzią więcej. Dla przykładu może onprzekierować zlecenie lub zawiadomić o błędzie.

Najczęściej spotykane liczby kodu statusu są określane jako stałe skrótów mnemonicznych (pola danych publicfinal static int) w klasie HttpServletResponse. Parę z nich zostało wyszczególnionych w tabeli 5.1.Kompletna listę można znaleźć w Dodatku D „Kody Statusu HTTP”.

Tabela 5.1.

Kody statusu HTTP

Stała skrótumnemonicznego

Kod Komunikatdomyślny

Znaczenie

S.C._OK. 200 OK Zlecenie klienta zakończyło się sukcesem, odpowiedź serwera zawierażądane dane. Jest to domyślny kod statusu.

S.C._NO_CONTENT

204 Nie ma treści Zlecenie zakończyło się sukcesem, lecz nie było żadnego korpusuodpowiedzi do odesłania. Przeglądarki, które otrzymają ten kod powinnyzachować swój aktualny widok dokumentu. Kod ten jest bardzo przydatnyapletowi, kiedy przyjmuje dane z formularza, lecz chce aby widokprzeglądarki pozostał na formularzu, ponieważ unika komunikatu błędu„Dokument nie zawiera żadnych danych”.

S.C._MOVED_PERMANENTLY

301 Staleprzenoszone

Zamawiany zasób ma stale przenoszone miejsce lokalizacji. Przyszłeodwołania powinny użyć w zleceniu nowy URL. Nowa lokalizacja podawanajest przez nagłówek Location. Większość przeglądarek automatycznieustanawia połączenie z nową lokalizacją.

S.C._MOVED_TEMPORARILY

302 Czasowoprzeniesione

Zamawiany zasób ma tymczasowo zmienioną lokalizację, jednak przyszłeodniesienia powinny nadal używać oryginalnego URL-u, aby ustanowićpołączenie z nową lokalizacją.

S.C._UNAUTHORIZED

401 Nieupoważniono

Zleceniu brak było właściwego upoważnienia. Używane razem znagłówkami WWW-Authenticate i Authorization

S.C._NOT_FOUND

404 Nie znaleziono Zamówiony zasób nie został odnaleziony lub jest niedostępny

S.C._INTERNAL_SERVER_ERROR

500 Błądwewnętrznyserwera

Na serwerze zdarzył się nieoczeki wany błąd, który uniemożliwił muwykonanie zlecenia.

S.C._NOT_IMPLEMENTED

501 Niewprowadzono

Serwer nie obsługuje zestawu funkcji potrzebnych do zrealizowania zlecenia.

S.C._SERVICE_UNAVAILABLE

503 Obsługaniedostępna

Obsługa (serwer) jest tymczasowo niedostępna, lecz powinna być dostępna wniedalekiej przyszłości. Jeżeli serwer wie kiedy wznowi obsługę, nagłówekRetry-After może zostać użyty

Ustanawianie kodu statusuAby kod statusu odpowiedzi aplet może skorzystać z metody setStatus():

public void HttpServletResponse. setStatus (int sc)

Metoda ta ustala kod HTTP statusu dla danej wartości. Kod może zostać określony jako liczba, lub za pomocąjednego z kodów S.C._XXX określonych w HttpServletResponse. Pamiętajmy, że metoda ta musi zostaćwywołana przed zatwierdzeniem odpowiedzi, w przeciwnym wypadku wywołanie będzie zignorowane.

Page 115: Java Servlet - Programowanie

Jeżeli aplet ustala kod statusu, który informuje o błędzie w czasie obsługi zlecenia, zamiast setStatus()możeon wywołać metodę sendError():

public void HttpServletResponse.sendError(int sc)throws IQException, IllegalStateExceptionpublic void HttpServletResponse.sendError(intsc, String sm) throws IOException, IllegalStateException

Metoda sendError() powoduje, że serwer generuje i przesyła właściwą, serwerowo-specyficzną stronęopisującą błąd, pozwalając na to aby apletowa strona błędu miała podobny wygląd do innych stron błęduserwera. Wywoływanie metody setStatus() do błędu obliguje serwer do wygenerowania strony błędu. Jeżelizastosowana zostanie dwu-argumentowa wersja tej metody, wtedy parametr komunikatu statusu może zostaćumieszczony bezpośrednio w korpusie odpowiedzi, w zależności od wdrożenia serwera. Metoda ta powinnazostać wywołana zanim jeszcze odpowiedź zostanie zatwierdzona, w przeciwnym razie zgłoszony zostaniewyjątek IllegalStateException. Metoda ta wykonuje niejawne restartowanie bufora odpowiedzi przedgenerowaniem strony błędu. Nagłówki ustalone przed sendError(), pozostają nadal ustalone.

Ulepszanie apletu „ViewFile” przy pomocy kodów statusuJak dotąd nie zadawaliśmy sobie trudu wywołania jakiejkolwiek z wyżej opisanych metod wcelu ustalenia kodu statusu odpowiedzi. Korzystaliśmy po prostu z tego, że kod statusuustalany jest domyślnie jako S.C._OK. Niestety czasem zdarzają się wypadki kiedy aplet musiodesłać odpowiedź, która nie ma kodu statusu S.C._OK. — sytuacje kiedy odpowiedź niezawiera zamówionych danych. Jako przykład przypomnijmy sobie jak aplet ViewFile, wrozdziale 4 „Odczytywanie informacji” FileNotFoundException:

// Odeślij pliktry { ServletUtils.returnFile(file, out);}catch (FileNotFoundException e) { out.println("Plik nie odnaleziony");}

Bez ustalenia kodu statusu, jedyną rzeczą, którą serwer może zrobić jest napisanie wyjaśnienia problemu,przesyłając to wyjaśnienie jako, na ironię, część strony, która miała zawierać treści pliku. Jednakże stosując kodystatusu, aplet może wykonać dokładnie to co robi DefaultServlet: ustawić kod odpowiedzi naS.C._NOT_FOUND, w celu zasygnalizowania, że plik będący przedmiotem zlecenia nie został odnaleziony i niemoże zostać odesłany. Oto ulepszona wersja apletu:

// Odeślij pliktry {ServletUtils.returnFile(file, out);}catch (FileNotFoundException e) {res.sendError(res.SC_NOT_FOUND) ;}

Wygląd strony wygenerowanej przez wywołanie metody sendError() jest determinowany przez serwer, jednakbardzo przypomina standardową stronę błędu serwera. Dla serwera „Apache/Tomcat” generowana jest jegowłasna strona 404 NotFound, razem z jego stopką redakcyjną (tak jak to zostało pokazane na rysunku 5.1).Zwróćmy uwagę, iż strona jest identyczna z każdą inną stroną 404NotFound Apache’a, co pozwala apletowi nawspółdziałanie z serwerem.

Page 116: Java Servlet - Programowanie

Rysunek 5.1.

Strona „404NotFound” serwera „Apache/Tomcat”

Nagłówki HTTP Aplety mogą w celu dostarczenia dodatkowych informacji o swojej odpowiedzi ustalićnagłówki HTTP. Tak jak to wyjaśniliśmy w rozdziale 4, pełne omówienie wszystkichmożliwych nagłówków HTTP 1.0 i HTTP 1.1 wybiega poza zakres tej książki. Na tabeli 5.2wyszczególniono nagłówki HTTP — ustawiane najczęściej przez aplety jako częśćodpowiedzi.

Tabela 5.2.

Nagłówki HTTP odpowiedzi

Nagłówek ZastosowanieCache-Control Określa ewentualny specjalny sposób, w jaki buforowanie zewnętrzne

powinno traktować ten dokument. Najbardziej popularne wartości to no-cache (służąca dopoinformowania, że ten dokument nie powinien być buforowany), no-store (służąca dopoinformowania, że ten dokument nie powinien być -buforowany ani nawet przechowywany przezserwer pośredniczący, zwykle z powodu niejawnego charakteru jego treści) oraz max-age=seconds (służąca do określania ile czasu upłynąć, żeby dokument został uznany jako„przestarzały”). Nagłówek ten został wprowadzony w HTTP 1.1.

Pragma Jest to odpowiednik HTTP 1.0 dla Cache-Control, z wartością no-cache jako jedyną możliwąwartością. Szczególnie korzystne jest zastosowanie Pragma w połączenui z Cache-Control, pozwala na obsługę przeglądarek starszych typów.

Connection Nagłówek ten jest używany do poinformowania o tym czy serwer wyraża chęć utrzymywaniaotwartego (trwałego) połączenia z klientem. W przypadku gdy serwer wyraża taką chęć, wartośćnagłówka ustawia na jest na keep-alive.W przeciwnym wypadku wartością tą będzie close.Większość serwerów WWW obsługuje ten nagłówek w imieniu swoich apletów automatycznieustalając wartość nagłówka na keep-alive — w przypadku gdy aplet ustawia swój nagłówekContent_Length lub kiedy serwer jest w stanie automatycznie określić długość treści.

Retry-After Nagłówek ten okresla moment, w którym serwer będzie ponownie mógłobsługiwać zlecenia, jest on używany z kodem statusuS.C._SERVICE_UNAVAILABLE. Jego wartość to int, które reprezentujeliczbę sekund lub ciąg danych reprezentujący czas rzeczywisty.

Expires Określa czas, w którym dokument może się zmienić lub kiedy jegoinformacje staną się nieważne. Tym samym implikuje, że jest rzecząniemożliwą aby dokument się zmienił przed upływem tego czasu.

Location Określa nową lokalizację dokumentu, stosowany zwykle z kodami statusu S.C._CREATED,S.C._MOVED_PERMANENTLY oraz S.C._MOVED_TEMPORARILY. Jego wartością musi być wpełni kwalifikowany URL (włącznie z „http://”)

WWW-Authenticate

Określa schemat autoryzacji dziedzinę autoryzacji wymaganą przez klienta podczas wywoływaniaURL-u będącego przedmiotem zlecenia. Używany z kodem statusu S.C._UNAUTHORIZED.

Page 117: Java Servlet - Programowanie

Content-Encoding

Określa schemat użyty do kodowania korpusu odpowiedzi. Przykładowewartości to gzip (lub x-gzip) compress (lub x-compress). Wartościwielokrotne powinny być przedstawiane jako oddzielone przecinkamilisty, w kolejności w jakiej kodowania były stosowane do danych.

a Programy Netscape Navigator oraz Microsoft Internet Explorer są obarczone błędami,które sprawiają, że nagłówek ten nie zawsze jest wykonywany. Kiedy chcemy miećpewność, że wydruk wyjściowy nie będzie buforowany każde zlecenie kierowane doapletu powinno wykorzystywać trochę inny URL, np. z dodatkiem pewnej ilościinformacji parametrów drugorzędnych

Ustawianie nagłówków HTTP Klasa HttpServletResponse daje dostęp do wielu metod, pomagających apletom w ustalaniu nagłówkówHTTP odpowiedzi. Aby ustalić wartość nagłówka używamy metody setHeader():

public void HttpServletResponse.setHeader(String name, String value)

Metoda ta ustala wartość nagłówka nazwanego, jako String. Nazwa nie uwzględnia wielkości liter, tak jak mato miejsce w przypadku tego typu metod. Kiedy nagłówek został już uprzednio ustalony, nowa wartośćzapisywana jest w miejsce starej. Przy pomocy tej metody mogą być ustalane nagłówki wszystkich typów.

Jeżeli musimy określić znacznik czasu dla nagłówka, możemy wykorzystać w tym celu metodę setDateHeader():

public void HttpServletResponse. setDateHeader (String name, long date)

Metoda ta ustala wartość nagłówka nazwanego na określoną datę i czas. Metoda akceptuje wartość daty jakolong, reprezentujące liczbę milisekund, jaka upłynęła od „epoki” (od północy 1 stycznia, 1970 roku, GMT).Jeżeli nagłówek został już wcześniej ustalony, nowa wartość jest zapisywana w miejsce poprzedniej.

I wreszcie możemy użyć metody setIntHeader() celu określenia wartości całkowitej dla nagłówka:

public void HttpServletResponse.setIntHeader(String name, int value)

Metoda ta ustala wartość nagłówka nazwanego na int. Jeżeli nagłówek został już uprzednio ustalony starawartość jest zastępowana przez nową.

Metoda containsHeader() demonstruje sposób, w jaki można sprawdzić czy nagłówek już istnieje:

public boolean HttpServletResponse.containsHeader(String name)

Metoda ta odsyła true w przypadku gdy nagłówek nazwany został już ustalony, w przeciwnym razie metoda taodsyła false.

Dla względnie małej liczby przypadków kiedy to aplet musi odesłać wartości wielokrotne dla tego samegonagłówka, stosuje się dodatkowe metody.

public void HttpServletResponse.addHeader(String name, String value)public void HttpServletResponse.addDateHeader (String name, long value)public void HttpServletResponse.addIntHeader(String name, int value)

Page 118: Java Servlet - Programowanie

Metody te ustalają nagłówki na daną wartość, lecz podczas gdy tradycyjna metoda setHeader() zamieniłabyjakąkolwiek istniejącą wartość lub wartości, metoda addHeader() pozostawia dotychczasowe ustawienia bezzmian, ustalając tylko wartość dodatkową.

I w końcu, w specyfikacji HTML 3.2 został przedstawiony alternatywny sposób ustalania wartości nagłówków,zakładający użycie znacznika <META HTTP_EQUIV> wewnątrz samej strony HTML:

<META HTTP-EQUIV="nazwa" CONTENT=" wartość ">

Znacznik ten musi zostać przesłany jako część sekcji <HEAD> strony HTML. Powyższa technika nie przynosijakichś specjalnych korzyści dla apletów; została ona stworzona z myślą o dokumentach statycznych, które niemają dostępu do swoich nagłówków.

Przekierowywanie zleceniaJednym z pożytecznych zastosowań, do których aplet może wykorzystać kody statusu i nagłówki jestprzekierowywanie zleceń. Jest to realizowane poprzez przesłanie do klienta instrukcji, zalecających użycieinnego URL-u w odpowiedzi. Przekierowanie generalnie stosuje się kiedy dokument jest przemieszczany(wysyłanie klienta do nowej lokalizacji), dla wyrównywania obciążenia (tak że jeden URL może rozprowadzaćobciążenia do kilku różnych komputerów) lub przy prostej randomizacji (przy losowym wybieraniu miejscaprzeznaczenia).

Na przykładzie 5.4 został pokazany aplet, który wykonuje proste losowe przekierowanie,wysyłając klienta do strony wybranej losowo z jego listy stron. Bazując na liście stron, apletpodobny do poniższego mógłby mieć wiele zastosowań. W stanie obecnym jest to punktwyjściowy do selekcji ciekawych stron apletów. W przypadku listy stron zawierającej obrazkireklamowe, aplet ten może zostać użyty do przechodzenia do następnego paska reklamowego.

Przykład 5.4.

Losowa zwrotnica

import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class SiteSelector extends HttpServlet { Vector sites = new Vector(); Random random=new Random(); public void init() throws ServletException { sites.addElement("http://www.oreilly.com/catalog/jservlet2"); sites.addElement("http://www.servlets.com"); sites.addElement("http://Java.sun.com/products/servlet"); sites.addElement("http://www.newlnstance.com"); }public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html"); PrintWriter out = res.getWriter() ; int sitelndex = Math. abs( random, nextInt ()) % sites. size(); String site = (String)sites.elementAt(sitelndex); res.setStatus(res.SC_MOVED_TEMPORARILY) ; res.setHeader("Lokalizacja",site); } }

Page 119: Java Servlet - Programowanie

Rzeczywiste przekierowanie jest realizowane w dwóch wierszach:

res.setStatus(res.SC_MOVED_TEMPORARILY) ;res.setHeader("Lokalizacja", site);

Pierwszy wiersz ustala kod statusu, aby poinformować, że ma zostać wykonane przekierowanie, w drugim zaświerszu podana jest nowa lokalizacja. Aby mieć pewność, że metody te będą działać musimy wywołać je zanimjeszcze zostanie zatwierdzona odpowiedź. Pamiętajmy, że protokół HTTP przesyła kody statusu oraz nagłówkiprzed bryłą treści. Również protokół HTTP określający nową stronę musi zostać podany jako URL bezwzględny(np. http://server:port/path/file.html). Jakikolwiek brak w podobnym URL-u może spowodować dezorientacjęklienta.

Wyżej wspomniane dwa wiersze mogą zostać uproszczone do jednego przy użyciu metody złożonej z metodpodstawowych: sendRedirect():

public void HttpServletResponse.sendRedirect(String location)throws IOException, IllegalStateException

Dla potrzeb naszego przykładu powyższe da wiersze upraszczane są do:

res.sendRedirect(site);

Metoda ta przekierowuje odpowiedź do określonej lokalizacji, automatycznie ustalając kod statusu oraznagłówek Location. Poza obsługą klientów nie wyposażonych w zdolność przekierowania czy tych nierozpoznających kodu statusu S.C._MOVED_TEMPORARILY, metoda ta pisze krótki korpus odpowiedzizawierający hiperłącze do nowej lokalizacji. Tak więc kiedy używamy tej metody nie piszemy własnego korpusuodpowiedzi. Metoda sendRedirect() może, począwszy od Interfejsu API 2.2 akceptować URL względny.Specyfikacja HTTP „mówi”, że wszystkie przekierowywane URL-e muszą być bezwzględne; metoda ta jednakprzekształca URL-e względne na formę bezwzględną (automatycznie protokół bieżący, serwer oraz port — lecznie ścieżkę kontekstu, w razie konieczności należy to zrobić we własnym zakresie) zanim prześle go do klienta.Prawda, że to proste? Pamiętajmy, że metodę tą należy wywołać przed zatwierdzeniem odpowiedzi, wprzeciwnym wypadku zostanie zgłoszony wyjątek IllegalStateException. Metoda ta wykonuje niejawnezerowanie buforu odpowiedzi przed generowaniem przekierowywanej strony. Nagłówki ustalone przedsendRedirect() nadal pozostają ustalone.

Nadzorowanie łączników do innych stronPrzekierowywanie może być również wykorzystane do zdobycia informacji na temat tego z jaką lokalizacją łącząsię klienci po opuszczeniu naszej strony. Załóżmy, że mamy kilka stron zawierających listy łączników do innychstron. zamiast łączenia bezpośrednio z zewnętrznymi stronami, możemy połączyć się z apletemprzekierowywującym, który może zarejestrować każdorazowy wybór zewnętrznego łącznika. HTML wyglądawtedy w następujący sposób:

<a href="/goto/http://www.servlets.com">Servlets.com</a>

Aplet może być zarejestrowany ażeby obsługiwać przedrostek ścieżki /goto/*, gdzie uzyska wybrany URL jakoinformację dodatkowej ścieżki, przekierowując następnie klienta do lokalizacji po uprzednim zapisaniu adnotacjiw rejestrze zdarzeń serwera. Kod apletu GoTo został zaprezentowany na przykładzie 5.5.

Przykład 5.5

Jak myślisz dokąd się udajesz?”

Page 120: Java Servlet - Programowanie

import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class GoTo extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Określ stronę do której chcą się udać String site = req.getPathInfo(); String query = req.getQueryString(); // Obsłuż błędne zlecenie if (site == null) { res.sendError(res.SC_BAD_REQUEST, "Wymagane informacje dodatkowej ścieżki"); // Odetnij interlinię "/" oraz dodaj ciąg zapytań // Zakładamy, że ścieżka informacji URL jest zawsze bezwzględna String url=site.substring(1)+(query==null ?"" : "?" + query"); // Zaloguj URl będący przedmiotem zlecenia i przekieruj log (url); //lub pisz do pliku specjalnego res.sendRedirect(url); } }

Aplet podobny do omawianego powyżej jest w stanie obsługiwać aliasing, tak że /got/servlets automatycznieprzekierowywałoby do http://www.servlets.com. Informacje dotyczące sprzedaży i dystrybucji podobnychzamienników można znaleźć na stronie http://go.to oraz na http://i.am.

Klienckie ściąganie z serweraKlienckie ściąganie z serwra przypomina przekierowywanie, istnieje jednak, jedna istotna różnica: przeglądarkawyświetla w danym momencie treść z pierwszej strony, a następnie czeka pewien czas na pobranie i wyświetlenietreści następnej strony. Nazwa brzmi klienckie ściąganie z serwera ponieważ to klient jest odpowiedzialny zaściąganie treści następnej strony.

Ktoś mógłby zapytać: „czy jest to metoda skuteczna?” Odpowiedź brzmi: „tak”. Po pierwsze, treść pierwszejstrony może wyjaśnić klientowi, że strona będąca przedmiotem zlecenia została przeniesiona przedautomatycznym załadowaniem strony następnej. Po drugie, strona może być pobierana w sekwencji,umożliwiając tym samym powolną animacje strony.

Informacje klienckiego ściąganie z serwera są przesyłane do klienta przy użyciu nagłówka HTTP Refresh.Wartość tego nagłówka określa liczbę sekund do wyświetlenia strony, przed ściągnięciem następnej, nagłówekten zawiera dodatkowo ciąg URL, określający z którego URL-u ma zostać przeprowadzone ściąganie. Wprzypadku gdy żaden URL nie jest podany używany jest ten sam. Poniżej przedstawiamy wywołanie do metodysetHeader(), które informuje klienta, o tym, że ma on powtórnie załadować ten sam aplet, po uprzednimpokazaniu przez 3 sekundy swojej treści:

setHeader("Odśwież", "3");

A oto wywołanie, które „mówi” klientowi, że ma wyświetlić stronę główną Netscape’a po trzech sekundach:

setHeader("Odśwież", "3; URL=http://home.netscape.com");

Przykład 5.6 ukazuje aplet, który wykorzystuje klienckie ściąganie z serwera w celu wyświetlenia czasurzeczywistego, uaktualnianego co 10 sekund.

Page 121: Java Servlet - Programowanie

Przykład 5.6.

Czas rzeczywisty uaktualniany

import java.io.*;import java.util.*;import javax.servlet.* ;import javax.servlet.http.*;public class ClientPull extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res. setContentType (" tekst/zwykły") ; PrintWriter out = res.getWriter() ; res.setHeader("Odśwież", "10"); out.println(new Date().toString()) ; }}

Jest to przykład animacji opartej na tekście — animacje graficzne omówimy w następnym rozdziale. Zwróćmyuwagę, iż nagłówek Refresh nie powtarza się. Nie ma konieczności aby ładować dokument w sposóbpowtarzający się. Nagłówek Refresh jest jednakże określany przy każdym pobraniu, tworząc wyświetlanieciągłe.

Wykorzystanie klienckiego ściągania z serwera w celu pobrania drugiego dokmentu zostało zaprezentowane naprzykładzie 5.7. Aplet ten przekierowuje zlecenia kierowane do jednego komputera centralnego, na innykomputer centralny, podając klientowi wyjaśnienie przed wykonaniem wspomnianego przekierowania.

Przykład 5.7.

„Wyjaśniona” zamiana komputera centralnego

import java.io. *;import java.util.*;import javax.servlet.* ;import javax.servlet.http.* ;public class ClientPullMove extends HttpServlet { static final String NEW_HOST = "http://www.oreilly.com";public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html") ; PrintWriter out = res.getWriter(); String newLocation = NEW_HOST + req.getRequestURI(); res.setHeader("Odśwież", "10; URL=" + newLocation) ; out.println("URL będący przedmiotem zleceni został przekazany do innego komputera centralnego.<BR>"); out.println("Jego nowa lokalizacja to " + newLocation + "<BR>"); out.println(" Twoja przeglądarka połączy Cię z tym miejscem za 10 sekund."); } }

Aplet ten generuje nową lokalizację z URI będącego przedmiotem zlecenia, które pozwala na przekierowywaniewszystkich zleceń złożonych na stary serwer, na tą samą ścieżkę na nowym serwerze. Przy użyciu deskryptorawdrożenia web.xml, aplet ten mógłby być skonfigurowany do obsługi każdego zlecenia, stopniowo przekazującklientów do nowej lokalizacji.

Rozwiązywanie problemówNadszedł czas abyśmy zmierzyli się ze sprawami najtrudniejszymi — błędami, problemami, awariami. Jest wieleprzyczyn występowania problemów: nieprawidłowe parametry, brakujące zasoby i bieżące błędy. To co jest naten moment istotne, to to, że aplet musi być przygotowany na problemy i to zarówno na przewidywalne jak i nate, których przewidzieć nie sposób. Dwie najważniejsze sprawy podczas występowania problemów to:

• Ograniczenie uszkodzeń serwera

• Właściwe poinformowanie klienta

Page 122: Java Servlet - Programowanie

Ponieważ aplety są pisane w Javie, potencjalne uszkodzenia serwera są minimalne. Serwermoże bezpiecznie osadzić aplety (nawet w swojej formalnej procedurze postępowania), takjak to może równie bezpiecznie zrobić przeglądarka WWW z załadowanymi apletami.Bezpieczeństwo to oparte jest na właściwościach bezpieczeństwa Javy, takich jak np.zastosowanie zabezpieczonej pamięci, obsług wyjątków czy programy zarządzaniebezpieczeństwem. Javowska ochrona pamięci gwarantuje, że aplety nie mogą przypadkowo(lub celowo) uzyskać dostępu do organizacji wewnętrznej serwera. Javowska obsługa wyjątkupozwala serwerowi na wychwycenie każdego wyjątku zgłoszonego przez aplet. Nawet kiedyaplet przypadkowo podzieli przez zero lub wywoła metodę na obiekt null, serwer może dalejsprawnie działać. Mechanizm zarządzania bezpieczeństwem Javy umożliwia serweromumieszczania niewiarygodnych apletów w „piaskownicy”, ograniczając ich możliwości i niedopuszczając aby powodowały problemy.

Należy pamiętać, że wiarygodne aplety wywołujące poza „piaskownicą” mechanizmu zarządzaniabezpieczeństwem mają możliwości, które mogą potencjalnie wywołać szkody na serwerze. Dla przykładu apletmoże zamazać obszar pliku serwera lub nawet wywołać metodę System.exit(). Jest również prawdą, że apletnie powinien nigdy spowodować szkód (chyba, że przypadkowo, co jest raczej trudne w przypadkuwywoływania metody System.exit()). Jednak, jeżeli jest taka potrzeba, nawet aplety wiarygodne mogą być (iczęsto są) uruchamiane wewnątrz całkiem „łagodnego” lecz sprawdzającego poprawność programu zarządzaniabezpieczeństwem.

Właściwe opisanie problemu klientowi, nie może być wyłącznie pozostawione technologii języka Java. Istniejewiele spraw, które trzeba wsiąść pod uwagę, takich jak np.:

Jak wiele powiedzieć klientowi?

Czy aplet powinien przesłać standardowy kod statusu strony błędu, proste wyjaśnienie błędu czy też (wprzypadku zgłoszonego wyjątku) szczegółowy ślad kaskady? A co kiedy aplet ma odesłać nie-tekstową treść,taką jak np. obrazek?

Jak zarejestrować problem?

Czy powinien on zostać zapisany w pliku? czy do rejestru zdarzeń serwera, ma być przesłany do klienta czy teżzignorowany?

Jak pozbyć się problemu?

Czy ta sama kopia apletu może obsługiwać następujące po sobie zlecenia? Lub czy serwer jest uszkodzony itrzeba go powtórnie załadować?

Odpowiedzi na powyższe pytania zależą od apletu oraz zastosowania dla którego został onzaprojektowany, powinny one być kierowane do każdego apletu na zasadzie „przypadek poprzypadku”. Sposób w jaki poradzimy sobie z błędami zależy od nas samych i powinien byćoparty na poziomie niezawodności i odporności na błędy, wymaganymi dla naszego apletu.Kolejną kwestią, której poświęcimy naszą uwagę jest ogólne omówienie mechanizmówobsługi błędu apletu, których używamy w celu wdrożenia jakiejkolwiek ze strategii.

Kody statusuNajprostszym (i prawdopodobnie najlepszym) sposobem, w który aplet może powiadomić o błędzie jestwykorzystanie metody sendError(), aby ustawić poprawnie 400 lub 500 szeregów kodu statusu. Dlaprzykładu, w przypadku gdy do apletu zostanie złożone zlecenie na plik, który nie istnieje, może on odesłaćS.C._NOT_FOUND. Kiedy od apletu wymaga się czegoś, co przekracza jego możliwości odsyła onS.C._NOT_IMPLEMENTED. W razie wystąpienia okoliczności całkowicie nieprzewidywalnych, aplet możeodesłać S.C._INTERNAL_SERVER_ERROR.

Poprzez zastosowanie metody sendError() w celu ustalenia kodu statusu, serwer może zamienić korpusodpowiedzi apletu, serwerowo-specyficzną stroną wyjaśniającą błąd. Jeżeli błąd ma taką naturę, iż apletpowinien wystosować swoje własne wyjaśnienie do klienta w korpusie odpowiedzi, może on ustalić kod statusuza pomocą metody setStatus() i następnie wysłać właściwy korpus odpowiedzi — który może być zarównotekstowym, wygenerowanym obrazkiem jak i też czymkolwiek właściwym.

Page 123: Java Servlet - Programowanie

Aplet musi uważać aby wyłapać i poradzić sobie z wszystkimi ewentualnymi błędami zanim jeszcze odpowiedźzostanie zatwierdzona. Jak pamiętamy (wspominaliśmy o tym kilka razy), HTTP określa, że kod statusu oraznagłówki HTTP muszą zostać wysłane przed korpusem odpowiedzi. W momencie kiedy jakiekolwiek danezostały już przesłane do klienta, jest już za późno aby zmienić nasz kod statusu czy nagłówki HTTP. Najlepszymsposobem uniknięcia takiej sytuacji jest jak najwcześniejsze sprawdzenie poprawności oraz zastosowaniebuforowania w celu opóźnieni przesłania danych do klienta.

Konfiguracja stron błęduMoże się zdarzyć, ze zamiast używania standardowych stron błędu serwera, będziemy chcieli utworzyć zestawstandardowych stron błędu dla aplikacji WWW, stron które będą mogły być wykorzystywane bez względu na togdzie aplikacja zostanie zainstalowana. Dla przykładu aplikacja WWW może zostać skonfigurowana za pomocąstrony błędu 404, zawierającej pole wpisywania wyszukiwarki, w przypadku gdy użytkownik zechciałbyskorzystać z pomocy w lokalizacji zasobu NotFound. Możemy tego dokonać poprzez bezpośrednie użyciemetody setStatus() i spowodowanie, że każdy aplet będzie generował identyczną stronę błędu, jednakżelepszym podejściem jest zainstalowanie reguły <error-page> w deskryptorze wdrożenia web.xml. Naprzykładzie 5.8 została zaprezentowana omawiana sytuacja.

Przykład 5.8.

Konfiguracja stron błędu 400 i 404

<?xml version="1.0" kodowanie="ISO-8859-l"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//Aplikacja WWW DTD 2.2//EM" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app> <!--.....--> <error-page> <error-code> 400 </error-code> <location> /400.html </location> </error-page> <error-page> <error-code> 404 </error-code> <location> /404.html </location> </error-page></web-app>

Powyższe dwa pola wpisywania <error-page> „mówią” serwerowi, że jakiekolwiek wywołanie do metodysendError() z kodem statusu 400 powinno wyświetlić treści z zasobu /400.html oraz, że jakiekolwiekwywołanie do tej metody z kodem 404 powinno wyświetlić zasób /404.html. Obsługa adaptacji stron błędujest praktyką powszechnie stosowana przez serwery WWW, jednakże pola wpisywania w pliku web.xmlzastępują domyślną konfiguracji serwera i udostępniają mechanizm, umożliwiający aplikacjom WWWdopuszczanie standardowych stron błędu do użytku we wszystkich wdrożeniach serwerowych. Serwery sązobligowane to tego by przestrzegać zasad <error-page> dla wszystkich treści podawanych z aplikacji WWW,nawet dla plików statycznych.

Należy pamiętać, iż wartość <location>, która musi zaczynać się od ukośnika, jest traktowana jakoumiejscowiona w kontekstowym katalogu macierzystym i musi odwoływać się do zasobu w kontekście. Abyodnieść się do zasobu poza bieżącym kontekstem, możemy wskazać na plik HTML wewnątrz kontekstu, któryzawiera bezpośrednie przekierowanie poza kontekstem:

<META HTTP-EQUIV="Odśwież" TREŚĆ"=0;URL=http://www.errors.com/404.html">

Page 124: Java Servlet - Programowanie

Przeznaczenie <location> może być zasobem dynamicznym, takim jak aplet czy JSP. Dla zasobówdynamicznych serwer udostępnia dwa specjalne atrybuty zlecenia, dostarczające informacji o błędzie:

javax.servlet.error.status_codeInteger (liczba całkowita) podająca kod statusu błędu. Typ był początkowo nie określony, dlategoniektóre wczesne wdrożenia serwerowe mogą odsyłać kod jako String.

javax.servlet.error.messageString (strumień) podający komunikat statusu, na ogół przekazywany jako drugiargument do metody sendError().

Powyższe atrybuty pozwalają nam na napisanie apletu wyświetlania strony błędu, ogólnego zastosowania,zaprezentowane na przykładzie 5.9. W celu użycia tego apletu, należy przypisać go ścieżkę dla znacznika<location>.

Przykład 5.9.

Dynamiczne tworzenie kodu statusu strony błędu

import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class ErrorDisplay extends HttpServlet{ public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst /html") ; PrintWriter out = res.getWriter(); Object codedbj = req.getAttribute("javax.servlet.error.status_code"); Object messageObj = req. getAttribute ("javax.servlet.error.message");

// Kod i komunikat nigdy nie powinny być równe zero w przypadku // apletów zgodnych API 2.2 String code = (codeObj != null ? codeObj.toString() : "Brakujący kod statusu"); String message = (messageObj != null ? messageObj . toString () : "Komunikat Brakującego Błędu") ; out.println("<HTML>"); out.println("<HEAD><TITLE>" + kod + ": " + komunikat + "</TITLE><HEAD>"); out.println("<BODY>"); out.println("<H1>" + kod + "</H1>"); out.println("<H2>" + komunikat + "</H2>"); out.println("<HR>"); out.println("<I>dostęo do błędu " + req.getRequestURI() + "</I>"); out.println (" < /BODY></HTML>") ; } }

Ścieżka do tego apletu może zostać skonfigurowana jako przeznaczenie dl wszystkich kodów statusu, którepotrzebują prostego tłumaczenia strony błędu. Bardziej zaawansowana wersja tego apletu byłaby w stanie śledzićkody błędów generowane w celu wykrycia dziwnych tendencji, takich jak na przykład ilość błędów 404 dla tegosamego URI, wskazując z dużym praedopodobieństwem, nowo przesłany błędny łącznik gdzieś, gdzie powinienbyć poprawiony.

Page 125: Java Servlet - Programowanie

Podsumowując jest to fałszywa strona dla przesyłania kodów statusu błędu. Metoda setStatus() pozwala naustalenie kodu statusu błędu przy zatrzymaniu pełnej kontroli nad odpowiedzią. Metoda sendError() pozwalaz kolei na ustalenie kodu statusu błędu, przy przekazaniu kontroli nad tworzeniem strony do serwera. Serwerprzesyła domyślnie swoją standardową stronę błędu dla tego kodu. Za pomocą pozycji <error-page> możemypolecić serwerowi aby przesłał specjalną stronę błędu.

RejestracjaAplety mają zdolność zapisywania, czynności które wykonują oraz błędów, które popełniają, do pliku rejestruzdarzeń. Używają w tym celu metody log():

public void GenericServlet. log (String msg)public void GenericServlet.log(String msg, Throwable t)

Metoda jedno-argumentowa zapisuje dany komunikat w dzienniku zdarzeń apletu, którym jestzwykle plik rejestru zdarzeń. Dwu-argumentowa wersja metody zapisuje dany komunikat orazślad kaskady Throwable w dzienniku zdarzeń apletu. Dokładny format wydruku wyjściowegooraz lokalizacja dziennika zdarzeń, są serwerowo-specyficzne, jednak na ogół zawierająznacznik czasu oraz zarejestrowaną nazwę apletu.

Metoda log() wspomaga usuwanie błędów (debagowanie) poprzez zapewnianie sposobu śledzenia operacjiapletu. Zapewnia ona również możliwość zapisywania kompletnego opisu wszystkich błędów, z którymi miał doczynienia aplet. Opis ten może być taki sam, jak ten przesłany do klienta lub bardziej wyczerpujący iszczegółowy.

Teraz już możemy wrócić do dalszego ulepszania apletu ViewFile, tak że będzie używał metodę log() dorejestrowania na serwerze w sytuacji kiedy plik będący przedmiotem zlecenia nie będzie istniał, odsyłając doklienta prostą stronę 404NotFound:

// Odeślij pliktry {ServletUtils.returnFile(file, out) ;}catch (FileNotFoundException e) {log("Nie można było odnaleźć pliku : " + e.getMessage());res.sendError(res.SC_NOT_FOUND) ;}

W przypadku bardziej skomplikowanych błędów aplet może zarejestrować kompletny ślad kaskady, tak jak tozostało przedstawione poniżej:

// Odeślij pliktry { ServletUtils.returnFile(file, out) ;)catch (FileNotFoundException e) { log("Pliku nie można było odnaleźć: " + e.getMessage()); res.sendError(res.SC_NOT_FOUND) ;}catch (IOException e) { log("Problem z przesyłaniem pliku", e); res.sendError(res.SC_INTERNAL_SERVER_ERROR) ;}

Page 126: Java Servlet - Programowanie

RaportowaniePoza rejestrowaniem błędów i wyjątków dla administratora serwera, podczas rozbudowy jest rzeczą korzystnąaby drukować pełny opis problemu wraz z śladem kaskady. Niestety wyjątek nie może odesłać swojego śladukaskady jako String (strumień) — może on tylko drukować swój ślad kaskady do PrintStream lub doPrintWriter. W celu odczytania śladu kaskady jako String, musimy pokonać pewne trudności. Musimypozwolić Exception (wyjątkowi) drukować do specjalnego PrintWriter utworzonego wokółByteArrayOutputStream. ByteArrayOutputStream może wychwycić strumień wyjściowy i przekształcićgo na String. Klasa com.oreilly.servlet.ServletUtils ma metodę getStackTraceAsString(),która wykonuje mniej więcej coś takiego:

public static String getStackTraceAsString(Throwable t) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); PrintWriter writer = new PrintWriter(bytes, true); t.printStackTrace(writer) ; return bytes.toString() ;}

Oto sposób w jaki aplet ViewFile jest w stanie dostarczyć informacji, zawierających ślad kaskadyIOException:

// Odeślij pliktry { ServletUtils.returnFile(file, out);}catch (FileNotFoundException e) { log("Pliku nie można było odnaleźć: " + e.getMessage()); res. sendError (res. SC_NOT_FOUND) ;}catch (IQException e) { log("Problem przy przesyłaniu pliku", e) ; res.sendError(res.SC_INIEKNAL_SERVER_EEROR, ServletUtils.getStackTraceAsString(e)) ;}

Wydruk wyjściowy dla przykładowego wyjątku został przedstawiony na rysunku 5.2.

Rysunek 5.2.

Rzetelne informowanie klienta „na bieżąco”

Page 127: Java Servlet - Programowanie

WyjątkiTak jak powiedzieliśmy wcześniej, każdy wyjątek, który jest zgłaszany lecz nie wyłapywany przez aplet, jestwyłapywany przez swój serwer. Sposób w jaki serwer obsługuje wyjątek jest zależny tylko od niego samego:może on przekazać komunikat klientowi lub nie. Może on nawet wywołać metodę destroy() na aplet. Leczrównie dobrze może tego nie robić.

Aplety zaprojektowane i skonstruowane do tego aby działać na określonym serwerze mogą optymalizować w tymcelu zachowanie serwera. Aplet zaprojektowany do współdziałania z wieloma serwerami nie może „oczekiwać”ze strony serwera żadnej szczególnej obsługi błędu, musi on wyłapać swoje własne wyjątki i je obsłużyćodpowiednio.

Istnieje pewna ilość typów wyjątków, które aplet musi wyłapać we własnym zakresie. Aplet może propagowaćdo swojego serwera tylko te wyjątki, które są podklasami IOException, ServletException lubRuntimeException. Powodem są sygnatury metod. Metoda service() Servlet deklaruje swoją klauzulęthrows, którą zgłasza wyjątki IOException oraz ServletException. Dla metody tej (lub metod doGet() idoPost() które wywołuje) zgłoszenie wyjątku i nie wyłapanie niczego więcej powoduje błąd związany zczasem kompilacji. Wyjątek RuntimeException jest wyjątkiem specjalnym, który nigdy nie musi byćdeklarowany w klauzuli throws. Popularnym przykładem może tutaj być NullPointerException.

Metody init() i destroy() również mają swoje własne sygnatury. Metoda init() deklaruje zgłoszeniewyjątków tylko ServletException, metoda destroy(), nie deklaruje z kolei zgłaszania żadnych wyjątków.

Wyjątki apletowe „ServletException”ServletException jest podklasą klasy java.lang.Exception, która jest określona dla apletów — klasa tajest zdefiniowana w zestawie javax.servlet. Aplet zgłasza ten wyjątek, aby zasygnalizować swój ogólnyproblem. Ma on takich samych konstruktorów co java.lang.Exception: jednego, który nie przyjmujeżadnych argumentów i jednego, który przyjmuje pojedynczy ciąg komunikatu:

javax.servlet.ServletException() javax.servlet.ServletException(String msg)

Klasa ServletException może również obsługiwać „główną przyczynę” obiektu Throwable. Pozwala todziałać ServletException jako opakowanie wszystkich typów wyjątków i błędów, umożliwiając serwerowi„dowiedzenie się” jaki powód „główny” spowodował zgłoszenie wyjątku ServletException. W celu obsługipowyższego klas ServletException ma dwóch dodatkowych konstruktorów:

javax.servlet.ServletException(Throwable rootCause)javax.servlet.ServletException(String msg, Throwable rootCause)

Używając zdolności powodu głównego, możemy przekazać jakikolwiek stosowany wyjątek lubbłąd:

try {thread.sleep(60000) ;}catch (InterruptedException e) {throw new ServletException (e); // zawiera pełny stosowany wyjątek }

Serwer może pobrać i rozpatrzyć stosowany wyjątek, ślad kaskady i inne, poprzez wywołanie metodygetRootCause():

public Throwable ServletException.getRootCause()

Page 128: Java Servlet - Programowanie

W razie gdy nie istnieje żaden wyjątek zagnieżdżony, wywołanie odsyła null.

Wyjątki apletowe „UnavailableException”Zestaw javax.servlet określa jedną podklasę klasy ServletException — UnavailableException,możemy oczywiście dodać swoją własną. Wyjątek ten informuje, że aplet jest niedostępny — tymczasowo lubtrwale.

Trwała niedostępność oznacza, iż kopia apletu zgłaszająca wyjątek UnavailableException nie może usunąćbłędu. Aplet mógł zostać źle skonfigurowany lub jego stan nie jest poprawny. Aplet, który zgłasza trwały wyjątekUnavailableException podczas obsługi zlecenia zostaje wyłączony z pracy, a na jego miejsce zostajeutworzona nowa kopia kontynuująca obsługę zleceń. Jeżeli nie zostanie utworzony żadna „dostępna” kopiaapletu, klient otrzyma błąd. Aplet który zgłasza trwały wyjątek UnavailableException (lub regularniewyjątek ServletException) podczas stosowania swojej metody int() nigdy nie będzie obsługiwał zleceń.;zamiast tego serwer będzie próbował inicjalizować nową kopię do obsługi przyszłych zleceń.

Tymczasowa niedostępność oznacza, że aplet nie może obsługiwać zleceń przez pewien okres czasu, z powoduogólnosystemowych problemów. Dla przykładu, mogłaby zdarzyć się sytuacja, że trójpoziomowy serwer niebyłby dostępny lub też nie byłoby wystarczająco dużo pamięci czy miejsca na dysku żeby obsługiwać zlecenia.Problem może rozwiązać się sam, tak jak w przypadku nadmiernego obciążenia, może również być i tak, żeadministrator będzie zmuszony się nim zająć.

Podczas okresu niedostępności zlecenia apletu obsługuje serwer — poprzez odsyłanie kodu statusuS.C._SERVICE_UNAVAILABLE (503) z nagłówkiem Retry-After, informując o przybliżonym czasieniedostępności apletu. Jeżeli aplet zgłosi tymczasowy wyjątek UnavailableException podczas stosowaniaswojej metody int(), nie będzie on już nigdy zajmował się obsługą zleceń, a serwer będzie próbowałinicjalizować nową kopię po okresie niedostępności. Dla wygody, serwery mogą traktować czasowąniedostępność jak niedostępność trwałą.

Wyjątek UnavailableException ma dwóch konstruktorów:

javax.servlet.UnavaliableException(string msg)javax.servlet.UnavaliableException(String msg, int seconds)

Konstruktor jedno-argumentowy tworzy nowy wyjątek, informujący o tym, że aplet jest trwale niedostępny, zwyjaśnieniem podanym przez msg. Natomiast wersja dwu-argumentowa tworzy nowy wyjątek informujący otym, że aplet jest niedostępny tymczasowo, z wyjaśnieniem podanym również przez msg. Poprawnie napisanyaplet powinien zamieścić w swoim wyjaśnieniu powód powstania problemu oraz działania, które powinienpodjąć administrator systemu aby przywrócić dostępność apletu. Wspomniany czas trwania niedostępności apletupodawany jest w sekundach, jest on jednak tylko szacunkowy. Jeżeli nie można wykonać oszacowania czasu,można użyć wartości nie-dodatniej. Wyjątek UnavailableException zapewnia dostęp do metod:isPermanent() i getUnavailableSeconds() w celu pobrania informacji na temat wyjątku.

Konfiguracja Stron WyjątkówGeneralnie, zachowanie serwera podczas wyłapywania przez niego wyjątków apletu zależy od samego serwera,jednakże aplikacja WWW może wskazać poprzez swój deskryptor wdrożenia, zestaw stron błędu dla obsługiokreślonych rodzajów wyjątków. Strony błędu określane są przy użyciu znacznika <error-page> (tak jak wprzypadku kodów statusu), z wyjątkiem sytuacji kiedy zastępujemy znacznik <error-code> znacznikiem<exception-type> (porównaj przykład 5.10).

Page 129: Java Servlet - Programowanie

Przykład 5.10.

Konfiguracja stron błędu wyjątków

<?xml versions"1.0"kodowanie="ISO-8859-l"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//Aplikacja WWW DTD 2.2//EN" "http://Java.sun.com/j2ee/dtds/web-app_2.2.dtd"><web-app> <!-- ..... --> <error-page> <exception-type> javax.servlet.ServletException </exception-type> <location> /servlet/ErrorDisplay </location> </error-page></web-app>

Pozycja wskazuje, że jakikolwiek ServletException zgłoszony do serwera, powinien zostać wyświetlonyprzy użyciu apletu ErrorDisplay. Zwróćmy uwagę, iż znacznik <exception-type> musi być w pełni zgodnyz nazwą zestawu; sam ServletException nie będzie działał. Zasada ta stosuje się również do wszystkichpodklas klasy servletException, takich jak UnavailableException (jeżeli nie ma w deskryptorzewdrożenia bardziej uściślającej zasady, którą stosuje się najpierw.

Dla przeznaczeń <location> (lokalizacji) dynamicznych, aplet udostępnia dwa atrybuty zleceniowe, służące doopisania zgłoszonego wyjątku:

javax.servlet.error.exception_typeKopia java.lang.Class podająca typ wyjątku. Typ atrybutu był początkowo nieokreślony, dlategoniektóre wczesne wdrożenia serwera mogą odsyłać formularz String nazwy klasy.

javax.server.error.messageString (strumień) podający komunikat wyjątku, przekazywany do konstruktora wyjątku. Nie ma żadnejmożliwości uzyskania wyjątku lub jego śladu kaskady.

Wykorzystując powyższe atrybuty jesteśmy w stanie ulepszyć ErrorDisplay z przykładu 5.9, tak że będziedziałał jako zasób monitora ogólnego błędu, obsługujący wyświetlanie zarówno kodów statusu błędów iwyjątków, tak jak to zostało ukazane na przykładzie 5.11.

Przykład 5.11.

Dynamiczne tworzenie strony błędu ogólnego zastosowania

import java.io.*;import javax.servlet.* ;import javax.servlet.http.*;public class ErrorDisplay extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html") ; PrintWriter out = res.getWriter(); String code = null, message = null, type = null; Object codeObj, messageObj, typeObj; // Pobierz trzy możliwe atrybuty błędu, niektóre z nich mogą być równe // zero codeObj = req.getAttribute("j avax.servlet.error.status_code") ;

Page 130: Java Servlet - Programowanie

messageObj = req.getAttribute("javax.servlet.error.message") ; typeObj = req.getAttribute("javax.servlet.error.exception_type") ; // Zamień atrybuty na wartości ciągu if (codeObj != null) code = codeObj.toString(); if (messageObj != null) message = messageObj.toString(); if (typeObj != null) type = typeObj.toString(); // Przyczyną błędu jest albo kod statusu albo typ błędu String reason = (code != null ? code : type); out .println ("<HTML>") ; out.println("<HEAD><TITLE>" + przyczyna + ": " + komunikat + "</TITLE></HEAD>") ; out.println("<BODY>"); out.println("<H1>" + przyczyna + "</H1>"); out.println("<H2>" + komunikat + "</H2>"); out.println("<HR>") ; out.println("<I>Łaczenie z błędem " + req.getRequestURI() + "</I>"); out.println( "</BODY></HTML>") ; }}

Niestety, tylko typ wyjątku i komunikat — a nie ślad kaskady wyjątku — są dostępne dla apletu ErrorDisplay,ograniczając użyteczność strony błędu wyjątku. Aby zagwarantować wyświetlanie lub rejestrację śladu kaskadywyjątku, musimy wychwycić wyjątek w oryginalnym apletcie i obsłużyć go przed jego propagacją do serwera.Będziemy zmuszeni napisać dodatkowy, niewielkich rozmiarów kod, jednak dzięki osobistej obsłudze wyjątkówmamy pewność, że obsługa błędu będzie spójna i właściwa. Oczekuje się, że w Interfejsie API 2.3 zostaniedodany nowy atrybut zlecenia, zawierający sam wyjątek, ma to zostać wykonane przy użyciu nazwyjavax,servlet.error.exception.

Jak rozpoznać, że nikt nie oczekuje na sygnały?Czasem bywa i tak, że klienci przerywają połączenie z serwerami. Tak, to prawda to jest niefair, lecz niestety zdarza się. Raz spowodowane jest to tym, że klient połączył się ze złą stroną,innym znów razem powodem jest zbyt długi czas generowania odpowiedzi apletu.Pamiętajmy, że przez cały czas kiedy aplet przygotowuje swoją odpowiedź, użytkownik możeprzerwać połączenie — klikając po prostu przycisk „stop”. Zastanówmy się: co się dziejekiedy ów przycisk zostaje uruchomiony?

Niestety aplet nie jest bezpośrednio informowany, że użytkownik kliknął przycisk „stop”, aplet nie wie o tym, żepowinien przerwać generowanie odpowiedzi. Dowiaduje się on o tym dopiero w momencie kiedy próbuje wysłaćwydruk wyjściowy do nieistniejącego klienta, kiedy to pojawiają się błędy.

Aplet, który wysyła informacje przy użyciu ServletOutputStream „widzi” zgłaszany wyjątek IOException,w momencie kiedy próbuje napisać wydruk wyjściowy. W przypadku apletów, które buforują swój wydrukwyjściowy, wyjątek IOException jest zgłaszany w momencie wypełnienia bufora i usuwania jego zawartości.

Ponieważ wyjątek IOException może zostać zgłoszony zawsze kiedy aplet próbuje wysłać swój wydrukwyjściowy, poprawnie napisane aplety uwalniają swoje zasoby w bloku finally. (Blok finally jestdodatkową częścią konstrukcji try/catch/finally. Występuje on po zerze lub większej ilości bloków,ponadto jego kod jest wywoływany tylko raz — bez względu na to jak wywołuje kod w bloku try). Poniżejprzedstawiamy wersję metody returnFile() z apletu ViewFile, gdzie do zagwarantowania zamknięcia jejFileInputStream, wykorzystywany jest blok finally:

void returnFile( String filename, OutputStream out) throws FileNotFoundException, IOException {

Page 131: Java Servlet - Programowanie

FileInputStream fis = null; try { fis = new FileInputStream(filename); byte[] buf = new byte[4 * 1024]; // bufor 4-kilowy int bytesRead; while ((bytesRead = fis.read(buf)) != -1) { out.write(buf, 0, bytesRead); } } finally { if (fis != null) fis.close(); }}

Dodanie bloku finally nie zmienia faktu, że metoda ta propaguje wszystkie wyjątki do programu żądającego,jednak gwarantuje to, że przed propagacją, metoda otrzyma szansę zamknięcia otwartego FileInputStream.

Aplet przesyłający dane znakowe, wykorzystujący w tym celu PrintWriter, nie otrzymuje wyjątkuIOException w czasie kiedy próbuje napisać wydruk wyjściowy, ponieważ metody PrintWriter’a nigdy niezgłaszają wyjątków. Zamiast tego aplet przesyłający dane znakowe musi wywołać metodę PrintWriter’acheckError(). Metoda ta opróżnia wydruk wyjściowy — zatwierdzając odpowiedź, a następnie odsyłaboolean, informując czy pojawił się problem przy pisaniu do stosowanego OutputStream. Jeżeli klientzatrzyma zlecenie, odsyłane jest true.

Długo-wywołujacy aplet, który nie „ma nic przeciwko” wczesnemu zatwierdzaniu odpowiedzi, powinienregularnie wywoływać metodę checkError() aby określić czy przetwarzanie może zostać zatrzymane przedzakończeniem. Jeżeli żaden wydruk wyjściowy nie był wysyłany od ostatniego sprawdzenia, aplet może przesłaćtreść wypełniającą. Dla przykładu:

out.println("<H2>Here's the solution for your differential equation:</H2>");if (out. checkError ()) return;

preliminaryCalculation() ;out.print(" "); // treść wypełniająca, dodatkowe światło na stronie //jest ignorowane w HTML-uif (out.checkError()) return ;

additionalCalculation() ;

Należy tutaj zaznaczyć, że serwer nie jest zobligowany do zgłaszania wyjątku IOException lub do ustawieniaznacznika błędu PrintWriter’a, po rozłączeniu się klienta. Serwer może zdecydować, że pozwoli odpowiedzina zakończenie, przy zignorowaniu jej wydruku wyjściowego. Ogólnie rzecz biorąc sytuacja taka nie stwarzaproblemów, jednak oznacza to, iż aplet wywołując w takim serwerze powinien zawsze mieć ustawiony punktkońcowy i nie powinien być pisany do ciągłego wykonywania pętli, aż do naciśnięcia „stopu” przez użytkownika.

Sześć sposobów wyciągania korzyści z apletów Od czasu pierwszej edycji niniejszej książki aplety stały się, de facto, standardem dla Javowskiegokonstruowania oprogramowania WWW po stronie serwera. „Ewolucyjna walka” rozgrywana pomiędzy apletamiwykonywanymi na serwerach, zwykłymi apletami strony serwera oraz innymi, podłączalnymi, opartymi na Javiestrukturami, ostatecznie się zakończyła, a aplety są jej zwycięstwami, obsługiwane dzisiaj przez każdy serwerWWW oraz każdy serwer aplikacji. Nowa dziedzina intensywnych innowacji usytuowana jest już poza apletami,na prezentacjach (obrazowaniach) oraz na poziomach osnów, w obszarze na którym osoby fizyczne orazprzedsiębiorstwa szukają sposobu zbudowania, na bazie apletów, tworzenia efektownych stron WWW.

Rozwiązania takie są bardzo potrzebne ponieważ proste podejście do generowania treści znaczników, zmuszaprogramistów apletów do pisania wywołania out.println() dla każdego wiersza treści, okazało się byćdużym problemem w świecie rzeczywistym. Przy podejściu out.println(), treść znaczników musi byćtworzona wewnątrz kodu, a to staje się zadaniem wymagającym wiele wysiłku i czasu w przypadku długich stron.Poza tym twórcy stron muszą prosić programistów o dokonanie wszystkich niezbędnych zmian strony.

Page 132: Java Servlet - Programowanie

Celem wspólnym dla niemal wszystkich utworzeń treści jest „oddzielenie treści od prezentacji (obrazowania)”.Termin treść oznacza tutaj nieprzetworzone dane strony i manipulowanie danymi. Być może lepszymokreśleniem byłoby przetwarzanie danych, jednakże „oddzielanie przetwarzania danych od prezentacji” niebyłoby najtrafniejszym określeniem. Termin prezentacja oznacza reprezentację danych, podanych ostatniemuużytkownikowi (często są to dane HTML, jednakże przy obecnym wzroście urządzeń podłączanych do sieciWWW, coraz częściej zdarza się, że są to dane WML — języka znaczników w telefonii bezprzewodowej).Oddzielenie treści od prezentacji daje wiele korzyści, przede wszystkim prostsze tworzenie stron oraz jejobsługa, jako że treść strony (determinowana przez programistę) może być konstruowana i zmieniana niezależnieod jej prezentacji (determinowanej przez projektanta lub producenta). Czasem określa się takie oddzielenia jakostrukturę Model-Widok-Kontroler (Model-View-Controler) — MVC. Model jest odpowiednikiem treści, widok— prezentacji, kontroler zaś ma wiele odniesień, w zależności od technologii.

W późniejszych rozdziałach niniejszej książki omówimy bliżej kilka z popularnych alternatyw dla apletowegotworzenia treści. Dla każdego przedstawionej alternatywy zapewnimy pomoc w postaci odpowiednich narzędzi,zademonstrujemy jak używa się owych narzędzi, i wypróbujemy gdzie najlepiej działa. Z powodu ograniczeńobjętościowych oraz czasowych jak również i faktu, że każdy omawiany temat jest „szybko umykającym celem”,nie jesteśmy w stanie dostarczyć pełnego omówienia dla każdej alternatywy. Jednego możemy być pewni: jest tocoś więcej niż tylko pięć implementacji tej samej idei. Każda alternatywa traktuje o problemie tworzenia treści zinnej perspektywy, w związku z tym techniki rozwiązywania problemów różnią się znacznie. Narzędzie, któresprawdza się w jednej sytuacji może w ogóle nie działać w innej. Tak więc nie czytajmy przykładów wponiższych rozdziałach, z myślą o poznaniu najlepszej technologii — starajmy się w zamian rozglądać zanajlepszą technologią dla naszych projektów.

Dotąd unikaliśmy dyskusji na temat swoistych, komercyjnych rozwiązań, z powodu związanego z tym ryzykaograniczania się do rozwiązań jednego producenta, nacechowanych motywami finansowymi.*Alternatywy, którezostaną omówione w dalszej części książki są następujące:

JavaServer PagesJavaServer Pages (JSP) to technologia stworzona przez „Sun Microsystems”, ściślezwiązana z apletami. Tak jak w przypadku apletów, „Sun” wydaje specyfikację JSP, asprzedawcy nie związani z tą firmą starają się wprowadzić swoje wdrożenia jakostandardowe. Fakt, iż JSP zostało wydane przez „Sun” stawia tą technologię wuprzywilejowanej pozycji i gdyby JSP było rozwiązało wystarczającą liczbę problemówużytkowników, prawdopodobnie zdobyłoby rynek jeszcze przed pojawieniem się na niminnych pozycji. Ponieważ zaskakująco wielu użytkowników jest niezadowolonych z JSP,alternatywy dla tej technologii zyskują popularność. Więcej na ten temat w rozdziale 18„JavaServer Pages” oraz na stronie http://java.sun.com/products/jsp.

TeaTea jest nowym autorskim produktem firmy „Walt Disney Internet Group” (wcześniejznanej jako GO.com), rozwijanym przez lata na potrzeby wewnętrzne, do zaspokojeniaich ogromnego zapotrzebowania na produkcję sieciową, dla stron takich jakESPN.com. Jest to technologia przypominająca JSP, jednak pozbawiona wielu jegobłędów, ma już ponadto znaczną obsługę narzędziową. Więcej na ten temat w rozdziale14 „Osnowa technologii tea” oraz na stronie http://opensource.go.com.

* Przekonaliśmy się o tym „na własnej skórze” — w pierwszej edycji tej książki omówiliśmy komercyjny zestaw htmlKonawyprodukowany przez „WebLogic” ponieważ nie było w tym czasie żadnej dostępnej alternatywy, ponadto zostaliśmyzapewnieni przez „WebLogic”, że cena zestawu pozostanie na rozsądnym poziomie. Po przejęciu „WebLogic” przez „BEASystems” , zapewnienie nie było już aktualne, i w rzeczywistości htmlKona stał się mało znaczącym szczegółem w oferciecenowej „BEA Systems”. Szczęśliwie htmlKona został ponownie wdrożony jako otwarte źródło projektu „ElementConstruction Set” (ECS) przez „Apache Jakarta Project” i teraz będzie dostępny tak długo jak długo będzie cieszył sięzainteresowaniem.

Page 133: Java Servlet - Programowanie

WebMarcoWebMarco jest mechanizmem wzorcowym stworzonym przez „Semiotek, Inc.” jako część projektu„Shimari”, który w przyszłości pewnie połączy się z Projektem „Apache Jakarta Project”.Istnieje wielemechanizmów wzorcowych wartych omówienia, jednak WebMarco jest najbardziej popularny, jestużywany na wyjątkowo często uczęszczanych stronach, takich jak AltaVista.com., został wbudowany wosnowach o otwartym dostępie do kodu źródłowego takich jak „Turbine” czy „Melati”, ponadto jestużywany w przodujących projektach o otwartym dostępie do kodu źródłowego takich jak „JetSpeed”.

Więcej na ten temat można znaleźć w rozdziale 15 „WebMarco” oraz na stronach: http://webmarco.orgi http://jakarta.apache.org/velocity.

Element Construction SetZestaw Element Construction Set (ECS), będący częścią „ApacheJakartaProject,” jest zestawem klasmodelowanym po htmlKona, produkcie „WebLogic” (obecnie „BEA Systems”). ECS ma wieleograniczeń, jednak rozwiązuje pewną grupę problemów, ponadto zapoznanie się z podejściem ECS jestdobrym punktem wyjściowym dla omawiania bardziej elastycznego XMLC (porównaj rozdział 17„XMLC” oraz stronę http://jkarta.apache.org/ecs)

XMLCXMLC wykorzystuje XML, uzyskując prawie wszystkie możliwości ECS, unikając wielu jegoograniczeń. XMLC został wyprodukowany przez „Lutris” jako część ich projektu otwartego dostępu dokodu źródłowego: „Enhydra Application Server” i może być używany jako oddzielny komponent.Więcej informacji na ten temat w rozdziale 17 „XMLC” oraz na stronie http://xmlc.enhydra.org.

CocoonCocoon to kolejna, oparta na XML-u alternatywa stworzona przez „XML Apache Project”. Cocoonwykorzystuje XML oraz XSLT do obsługa treści tworzenia czegoś, co określa się jako osnowapublikacji WWW. Cocoon jest sterowaną serwerowo osnową przez co XML, tworzony statycznie lubdynamicznie, uruchamiany jest przez filtr XSLY, jeszcze przed prezentacją klientowi. Filtr ten możesformatować treść jako każdy typ treści włącznie z HTML, XML, WML czy PDF — a osnowa możewybrać, w zależności od klienta, różne filtry. Nie będziemy tutaj omawiali szerzej Cocoon’a ponieważma on więcej wspólnego z programowaniem XSLT niż z programowaniem Javy. ponadto (przynajmniejw chwili obecnej) na ogół nie jest to często wybierane narzędzie dla pisania interaktywnych aktywacjiWWW. Cocoon bardziej nadaje się do obszernych, najczęściej statycznych, stron, które dopuszczająwystępowanie w swojej treści wielokrotnych obrazów. Więcej na temat Cocoon można znaleźć nastronie http://xml.apa.cheorg oraz w książce Brett’a McLaughlin’a „Java and XML”. Rozdział tejksiążki traktujący o Cocoon’ie dostępny jest na stroniehttp://www.oreily.com/catalog/javaxml/chapter/ch09.html.

Jeżeli nasze ulubione lub popularne narzędzie nie zostało tutaj omówione, nie jest to niczymnadzwyczajnym. Przyjrzyjmy się dacie produkcji tego narzędzia: jest prawdopodobniepóźniejsza od terminu oddania tej książki do druku. Tempo wprowadzania w tym obszarzeinnowacji jest ogromne, z XML-em dostarczającym nam nowych możliwości orazdołączanych do Internetu urządzeń wymagających nowych właściwości, dziedzina ta toeldorado dla nowych rozwiązań. Miejmy również świadomość tego, że ulepszenia tychnarzędzi będą również wprowadzane bardzo szybko. Każdy rozdział omawiający jakiśproblem jest skazany na co najmniej częściowe przedawnienie w momencie, kiedy czytelnikbędzie go czytał. Aby być na bieżąco zorientowanym w temacie należy zaglądać na stronęhttp://www.servlets.com.

Page 134: Java Servlet - Programowanie

Rozdział 6. Przesyłanie treści

multimedialnej

Do tej pory wszystkie aplety, które pisaliśmy odsyłały standardową stronę HTML. Sieć WWWnie składa się jednak tylko z HTML-u, dlatego właśnie w niniejszym rozdziale zajmiemy siębardziej ciekawymi aspektami odsyłania apletowego. Rozpoczniemy od omówienia protokołuWAP oraz języka znaczników WML używanego w telefonii komórkowej oraz urządzeniachprzenośnych i poznamy jak aplety mogą wykorzystywać tą technologię. Następnie pokażemyjak tworzyć dynamicznie, obrazy z apletów aby wygenerować wykresy i manipulowaćzdjęciami. Pod koniec rozdziału dowiemy się kiedy i jak przesyła się skompresowaneodpowiedzi, ponadto zbadamy użycie odpowiedzi wieloczęściowych w celu wdrażaniaserwera cyklicznego.

WAP i WMLProtokół Aplikacji Bezprzewodowej (WAP) jest de facto standarem dla dostarczania łączności internetowej dotelefonów komórkowych, pejdżerów oraz do Osobistych Asystentów Cyfrowych (PDA) w sieciach komunikacjibezprzewodowej na całym świecie. Protokół ten został stworzony przez Ericssona, Nokię, Motorolę orazPhone.com (wcześniej „Unwired Planet”), które to firmy rozsądnie wyraziły chęć stworzenia protokołustandardowego, a nie własnych, konkurujących między sobą protokołów. Owe cztery firmy stworzyły razemforum WAP (http://www.wapforum.org), demokratycznie zarządzaną organizację, która dzisiaj liczy już 400członków.

Protokół WAP składa się z szeregu specyfikacji, opisujących projektowanie aplikacji przeznaczonych douruchamiania w sieciach komunikacji bezprzewodowej. Protokół WAP obejmuje zarówno poziom aplikacji(język znaczników WML oraz język skryptów WMLScript, znane pod wspólną nazwą Środowisko AplikacjiWWW lub WAE) jak i stosowanej sieci transportu poziomów (protokołów: WDP, WTLS, WTP oraz WSP).Kaskada WAP jest odpowiednikiem kaskady protokołu WWW, tak jak to zostało zaprezentowane na rysunku6.1.

Page 135: Java Servlet - Programowanie

Rysunek 6.1.

Kaskady: WWW i WAP

Podstawowa różnica pomiędzy WAP-em a WWW polega na tym, że WAP jest zoptymalizowany do nisko-przepustowej komunikacji bezprzewodowej. Dla przykładu WAP używa formatu binarnego dla strukturodpowiedzi i zlecenia, zamiast formatu tekstowego przez WWW.

Brama WAP odgrywa rolę pośrednika pomiędzy siecią WAP a siecią WWW. Zadaniem bramy jestkonwertowanie zleceń WAP na zlecenia WWW oraz odpowiadające im odpowiedzi WWW na odpowiedziWAP. O Bramie WAP można powiedzieć, iż jest ona „konwertorem kaskady protokołu”* Bramy WAPudostępniają urządzeniom WAP dostęp do standardowych serwerów WWW. Zostało to zaprezentowane narysunku 6.2.

Rysunek 6.2.

Rola Bramy WAP

* Bramy WAP są zwyczajowo dostarczane przez właściciela sieci bezprzewodowej. Czytelników zainteresowanychutworzeniem swojej własnej bramy lub tym jak działają bramy odsyłamy do Bramy WAP zwanej „Kannel” dostępnej nastronie http://www.wapgateway.org.

Page 136: Java Servlet - Programowanie

Prostą sprawą jest konstruktor bezprzewodowy — możemy zignorować protokół warstwy transportowej. BramaWAP „dba” o to, aby zlecenie urządzenia wyglądało jak zlecenie HTTP. Warstwą na której musimy sięskoncentrować jest warstwa aplikacji, w której zamiast generowania HTML-u, generujemy WML.

WMLUrządzenia przenośne są znacznie bardziej ograniczone niż komputery osobiste. Mają one zwykle wolneprocesory, małe pojemności pamięci, małe wyświetlacze oraz niezwykle ograniczoną przepustowość. Z powodupodobnych ograniczeń urządzenia WAP nie współdziałają ze zwykłymi treściami HTML i treściamiobrazkowymi. Zamiast tego używają one języka znaczników telefonii komórkowej (WML) — dla treścitekstowej, języka WMLScript dla wykonywania skryptów oraz Bezprzewodowej Mapy Bitowej (WirelessBitmap) WBMP — monochromatycznego formatu obrazu dla grafiki.

WML jest aplikacją XML, podobną do HTML lecz z mniejszą ilością znaczników. Można tutaj zastosowaćporównanie do talii kart, gdzie każda karta odpowiada ekranowi strony, a każda strona zbiorowi kart, które mogązostać natychmiast przesłane. Przesyłanie zawartości talii za jednym razem, zamiast jednej karty, minimalizujeopóźnienie (okres potrzebny do obejrzenia kolejnej karty) podczas „łączenia się” z treścią.

Przykład 6.2 ukazuje statyczny dokument WML, który spełnia rolę „minibarmana”. Wyświetla listę drinków dowyboru, a następnie składniki wybranego drinka.* Zwróćmy uwagę, iż jest to dokument XML z dobrze znanymDTD.

Przykład 6.1.

„Minibarman WML”, drinks.xml

<?xml versions"1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http: / /www. wapforum.org/DTD/wml_l.1. xml" ><wml> <card id="Barman" title="Wybierz Drinka"> <p> Select a Drink:<anchor> Kamikaze <go href="#Kamikaze" /></anchor><br/><anchor> Margarita <go href="#Margarita" /></anchor><br/><anchor> Boilermaker <go href="#Boilermaker" /> </anchor><br/> </P></card><card id="Kamikadze" title="Kamikadze"> <p> To make a Kamikaze:<br/> 1 part Vodka<br/> 1 part Triple Sec<br/> 1 part Lime Juice<br/> </P> </card> <card id="Margarita" title="Margarita"> <p> To make a Margarita:<br/> 1 1/2 oz Tequila<br/> 1/2 oz Triple Sec<br/> 1 oz Lime Juice<br/> Salt<br/>

* Pełna implementacja tej idei dostępna jest na stronie http://www.wap2bar.com/index.wml

Page 137: Java Servlet - Programowanie

</p></card><card id="Boilermaker" title="Boilermaker"> <p> To make a Boilermaker :<br/> 2 oz Whiskey<br/> 10 oz Beer<br/> </p> </card> </wml>

Dokument zawiera cztery karty. Pierwsza karta, wyświetlana domyślnie, ukazuje krótką listę drinków. Każdanazwa drinka to hiperłącze do następnej karty dokumentu, z nazwą karty podaną przy użyciu składni#Cardname. Każda późniejsza karta wyświetla składniki dla drinka. Cały dokument może być przesłany dourządzenia podczas jednej operacji, mimo iż w danym momencie będą dostępne tylko jego części.

Symulatory urządzeń WAPAby umożliwić wywoływanie aplikacji WWW, bez odwoływania się do fal powietrznych, różnefirmy tworzą symulatory telefoniczne obsługujące WAP, oprogramowanie do uruchamianiana komputerach osobistych, które działają jak telefon (i nawet tak wyglądają). Na rysunku 6.3zaprezentowano jak wygląda dokument na symulatorze Phone.com UP.Simulator (dostępnymna http://phone.com). Inne symulatory oraz zestawy projektowania WAP można znaleźć nanastępujących stronach: http://www.nokia.com, http://www.ericsson.com i,http:/www.motorola.com oraz na wielu innych. Niektóre standardowe przeglądarki WWW,włącznie z przeglądarką „Opera” również obsługują WML.

Rycina 6.3. „Drinkowe telefonowanie”

Podawanie treści WAPAby treść WAP została podana poprawnie, serwer musi przesłać plik WML z jawnym typem treścitext/vnd.wap.wml. Niektóre serwery ustalają ten typ treści automatycznie na pliki .wml. Inne muszą być

Page 138: Java Servlet - Programowanie

informowane o nowym typie. Serwer musi być powiadomiony o typie treści odnoszącym się do WAP, zkolejnymi elementami <mime-mapping> w deskryptorze wdrożenia web.xml serwera. Bit informacji web.xml zprzykładu 6.2 dodaje właściwy typ WML-u, WMLScript oraz pliki bezprzewodowej Mapy Bitowej.

Przykład 6.2.

Dodawanie typów nośników WAP do web.xml

<!- ... -><mime-mapping> <extension> wml </extension> <mime-type> text /vnd. wap. wml </mime-type></mime-mapping><mime-mapping> <extension> wmls </extension> <mime-type> text /vnd. wap. wmlscript </mime-type> </mime-mapping> <mime-mapping> <extension> wbmp </extension> <mime-type> image /vnd. wap. wbmp </mime-type>< /mime-mapping><!-- ... -->

Dodatkowo niektórzy programiści aplikacji WWW uważają, że dobrze jest dodać index.wml do domyślnej listyplików wyszukiwania w katalogu, znanej jako welcome file list. Blok deskryptora wdrożenia w przykładzie 6.3ustawia welcome file list do index.html, index.htm oraz do index.wml, w takiej właśnie kolejności. Taka kolejrzeczy daje znać serwerowi o zleceniu na http://localhost/wap/index.wml jeżeli index.html oraz index.htm nieistnieją.

Przykład 6.3.

Dodawanie plików WAP Welcome Files do web.xml

<!-- ... --><welcome-file-list> <welcome-file> index.html</welcome-file><welcome-file> index.htm</welcome-file><welcome-file> index.wml </welcome-file></welcome-file-list><!-- ... -->

Dynamiczna treść WAPZ technicznego punktu widzenia nie ma prawie żadnej różnicy pomiędzy tym jak aplet podaje dynamiczną treśćWAP a sposobem w jaki podawana jest dynamiczna treść WWW. Aby podać treść WAP aplet musi po prostuzmienić typ treści odpowiedzi na text.vnd.wap.wml a treść odpowiedz z HTML-u na WML. Dla apletu,zlecenie jest zwykłym HTTP; zewnętrzna Brama WAP obsługuje WML oraz konwersję kaskady protokołuWWW. Podczas gdy różnica techniczna jest niewielka, przypadków w których lepiej jest posłużyć się apletemdla dynamicznego tworzenia treści może być niewiele.

Page 139: Java Servlet - Programowanie

W sieci WWW, nie ma praktycznie żadnych sankcji za kontaktowanie się z serwerem tylko po to aby wykonaćmało znaczące zadania czy uaktualnienia strony. Przy zastosowaniu WAP-u, sankcje te mogą zostaćustanowione. W wyniku tego proste zadania takie jak: nawigacja kartowa i kontrola poprawności danychformularzowych, które w sieci WWW mogłyby angażować aplet, są najlepiej przeprowadzane w WAP przyużyciu metafory kartowej WML oraz zdolności wykonywania skryptów po stronie klienta WMLScript. Dlaprzykładu poprzedni aplet „barman” używa zestawu kart w celu podawania drinków bez kontaktowania się zserwerem.

Aplety nie pozostają oczywiście bez użyteczne. Aplet (lub inny skrypt serwera) jest potrzebny do generowaniatalii kart zawierających informacje dynamiczne pobrane z bazy danych. Aplety są również potrzebne do obsługizleceń na dane zbyt duże aby zmieścić się na zestawie kart. Urządzenia są często ograniczone do przechowaniatylko 1400 bitów skompilowanych danych strony.

Na przykładach 6.4 i 6.5 zaprezentowano, formularz WML oraz aplet generujący WML, które razem dostarczająaplikację wyszukania kodu kierunkowego. Klient może wprowadzić do formularz telefoniczny kod kierunkowy,przedłożyć go do apletu oraz dowiedzieć się który stan lub region odpowiada temu kodowi. Osoby posiadającetelefony obsługujące WAP mogą wykorzystać tą aplikację do fizycznej lokalizacji jakiegokolwiek numeruidentyfikacyjnego rozmówcy.

Przykład 6.4.

Wykorzystanie WML-u w celu uzyskania o kodu kierunkowego

<?xml version="1.0"?><!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http: //www. wapforum. org/DTD/wml_l.1. xml"><wml> <card id="AreaCode" title="Wprowadź Kod Kierunkowy"> <do type="akceptuj" label="Wprowadź"> <go href="aplet /AreaCode?code=$(code)"/> </do> <p> Enter an Area Code: <input type="tekst" nazwa="kod"/></p></card></wml>

Ten dokument WML przechowuje prosty formularz z obszarem wejściowym tekstu. Niezależnie od tego jaki kodkierunkowy zostanie wprowadzony, zostaje przesłany do apletu AreaCode jako parametr code. Aby utworzyćręcznie ciąg zapytań używamy zmiennej zastąpienia $(code).

Przykład 6.5.

Wykorzystywanie WAP do zlokalizowania kodu kierunkowego

import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class AreaCode extends HttpServlet { Properties lookup = new Properties() ;public void init() {// Przenieś poniższe dane nieprzetworzone do a Listy właściwości // szybkiego odszukania for (int i = 0; i < data.length; i++) { Object[] record = data[i]; String state = (String) record[0] ;

Page 140: Java Servlet - Programowanie

int[] codes = (int[]) record[1] ; for (int j = 0; j < codes.length; j++) { lookup.put(String.valueOf(codes[j]),state); } } }public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/vnd.wap.wml") ; PrintWriter out = res.getWriter();

String msg = null; String code = req.getParameter("kod") ; String region = null;if (code != null) { region = lookup.getProperty(code) ;}out.println("<?xml version=\"1.0\"?>") ;out.println ("<!DOCTYPE wml PUBLIC " + "\"-//WAPFORUM//DTD WML 1.1//EN\" " + " \" http: / /www.wapforum. org/DTD/wml_l.1.xml \" >") ;out.println("<wml>") ;out.println("<card id=\"Code\" title=\"Code\">") ;out.println(" <p>") ;out.println (" Numer kierunkowy '" + code + '"<br/>");if (region != null) { out.println(" jest " + region + ",<br/>");}else { out.println(" jest nie ważny.<br/>");}out.println(" </p>") ;out.println("</card>") ;out.println("</wml>") ;}

// Nieprzetworzony numer kierunkowy dla każdego regionuprivate Object[][] data = new Object[][] { { "Toll Free", new int[] { 800, 855, 866, 877, 888 } }, { "Alabama", new int[] { 205, 256, 334 } }, { "Alaska", new int[] { 907 } }, { "Alberta", new int[] { 403, 780 }}, { "Arizona", new int[] {480, 520, 602, 623 } }, { "Arkansas", new int[] { 501, 870 } }, { "British Columbia", new int[] { 250, 604 } }, { "California", new int[] { 209, 213, 310, 323, 369, 408, 415, 424, 510,530, 559, 562, 619, 626, 627, 650, 661, 707, 714, 760,805,818,831,858, 909, 916, 925, 949 } }, { "Colorado", new int[] { 303, 719, 720, 970 }}, { "Connecticut", new int[] { 203, 475, 860, 959 }}, { "Deleware", new int[] { 302 } }, { "District of Columbia", new int[] {202 } }, { "Florida", new int[] { 305, 321, 352, 407, 561, 727, 786, 813, 850, 863,904, 941, 954 } }, { "Georgia", new int[] { 229, 404, 478, 678, 706, 770, 912 }}, { "Hawaii", new int[] { 808 } }, { "Idaho", new int[] { 208 } }, { "Illinois", new int[] { 217, 224, 309, 312, 618, 630, 708, 773,815,847}}, { "Indiana", new int[] { 219, 317, 765, 812 }}, { "Iowa", new int[] { 319, 515, 712 } }, { "Kansas", new int[] { 316, 785, 913 } }, { "Kentucky", new int[] { 270, 502, 606, 859 } }, { "Louisiana", new int[] { 225, 318, 337, 504 } }, { "Maine", new int[] { 207 } }, { "Manitoba", new int[] { 204 } }, { "Maryland", new int[] { 240, 301, 410, 443 } }, { "Massachusetts", new int[] { 413, 508, 617, 781, 978 } }, { "Michigan", new int[] { 231, 248, 313, 517, 586, 616, 734, 810,906,} }, { "Minnesota", newint[] { 218, 320, 507, 612, 651, 763, 952 } }, { "Mississippi", new int[] { 228, 601, 662 } }, { "Missouri", new int[] { 314, 417, 573, 636, 660, 816 } }, { "Montana", new int[] { 406 } }, { "Nebraska", new int[] { 308, 402 } }, { "Nevada", new int[] { 702, 775 }},

Page 141: Java Servlet - Programowanie

{ "New Brunswick", new int[] { 506 } }, {"New Hanpshire", new int[] { 603 } }, {"New Jersey", new int[] { 201, 609, 732, 856, 908, 973 } } {"New Mexico", new int[] { 505 } }, {"New York", new int[] { 212, 315, 347, 516, 518, 607, 631, 646,716,718, 845, 914, 917 }}, {"Newfoundland", new int[] { 709 } }, {"North Carolina", new int[] { 252, 336, 704, 828, 910, 919,918 }}, {"North Dakota", new int[] { 701 } }, {"Northwest Territories", new int[] { 867 } }, {"Nova Scotia", new int[] { 902 } }, {"Ohio",new int[] { 216, 234, 330, 419, 440, 513, 614, 740,937 } }, {"Oklahoma", new int[] { 405, 580, 918 ) }, {"Ontario", new int[] { 416, 519, 613, 647, 705, 807, 905 } !, {"Oregon", new int[] { 503, 541, 971 }}, {"Pennsylvania", newint[] { 215, 267, 412, 484, 570, 610,717, 724,814,878,902 } } {"Puerto Rico", new int[] { 787 } }, {"Quebec", new int[] { 418, 450, 514, 819 } }, {"Rhode Island", new int[] { 401 } }, {"Saskatchewan", new int[] { 306 } }, {"South Carolina", new int[] { 803, 843, 864 } }, {"South Dakota", new int[] { 605 } }, {"Tennessee", newint[] { 423, 615, 865, 901, 931 } }, {"Texas", new int[] { 210, 214, 254, 281, 361, 409, 469, 512,682,713 806, 817, 830, 832, 903, 915, 940, 956, 972 } }, {"US Virgin Islands", new int[] { 340 } },

{ "Utah", new int[] { 435,.801 } }, {"Vermont", new int[] { 802 } }, {"Virginia", new int[] { 540, 571, 703, 757, 804 } }, {"Washington", new int[] ( 206, 253, 360, 425, 509, 564 }}, {"West Virginia", new int[] ( 304 } }, {"Wyoming", new int[] ( 307 } }, {"Yukon Territory", new int[] { 867 } ), };}

Aplet ten otrzymuje zlecenie WAP jako zwykłe zlecenie HTTP GET. W swojej metodzie doGet() aplet ustawiatyp treści na text/vnd.wap.wml, pobiera parametr code, lokalizuje odpowiadający kod poprzez tabelępowiązaną, i wreszcie generuje dokument WML zawierający region. Informacja numeru kierunkowego pochodziz ręcznie wprowadzanej tablicy, która jest konwertowana na tabelę Properties po inicjalizacji apletu. „Zrzut”ekranu wydruku wyjściowego, pokazano na rysunku 6.4.

Page 142: Java Servlet - Programowanie

Rysunek 6.4.

Wykorzystanie WAP-u do lokalizacji rozmówców

Poznajmy WAPJeżeli chcielibyśmy nauczyć się czegoś więcej na temat tego jak się tworzy strony obsługujące urządzenia WAP,sięgnijmy po książkę Martina Frosta pt. „Learning WML & WMLScript”. Pomocne mogą również być dla nasstrony źródłowe takie jak: http://www.wap-resources.net, http://AnywhereYouGo.com orazhttp://www.wirelessdevnet.com. Istniej również administrowana przez „Sun” lista adresowa archiwizująca nahttp://archives.java.sun.com/archives/web-wireless.html.

ObrazkiLudzie to istoty postrzegające świat głównie poprzez obraz — chcą oglądać, nie tylko czytać informacje. Wkonsekwencji jest prawie niemożliwym znalezienie strony WWW, nie wykorzystującej w jakiś sposób obrazków,niestety najczęściej są to obrazki wykonane nieprofesjonalnie. Podsumowując zacytujmy wyświechtany banał„Obraz jest wart tysiące słów”.

Szczęśliwie aplet może względnie łatwo przesłać obrazek jako odpowiedź. Właściwie już poznaliśmy aplet,który to robi: aplet ViewResource, opisany w rozdziale 4 „Odczytywanie informacji”. Jak na pewno pamiętamyaplet ten może odesłać wszystkie pliki pod dokumentem katalogu macierzystego serwera. Jeżeli zdarzy się tak, żeplik będzie plikiem obrazu, aplet wykrywa ten fakt za pomocą metody getMimeType() a następnie ustawiaswoją treść odpowiedzi za pomocą metody setContentType(), jeszcze przed przesłaniem nieprzetworzonychbitów do klienta.

Wymaganiem potrzebnym do zastosowania powyższej techniki potrzeba, aby żądane przez nas pliki były jużzapisane na dysku, co nie zawsze jest naszym udziałem. Często aplet musi wygenerować lub przesunąć plikprzed przesłaniem go klientowi. Rozpatrzmy przykład strony, która zawiera obrazek zegara analogowego,wyświetlającego aktualny czas. Oczywiście można by zapisać 720 obrazków (60 minut pomnożone przez 12godzin) na dysku, a następnie wykorzystać aplet do wysłania jednego — właściwego. Lecz z pewnością niepowinniśmy tego robić. Zamiast tego powinniśmy napisać aplet, który będzie generował dynamicznie obrazekcyferblatu oraz wskazówek zegara lub wariantowo — aplet, który ładuje obrazek cyferblatu dodając tylkowskazówki. Będąc oszczędnymi programistami mamy również do dyspozycji buforowanie obrazka przez aplet(przez około minutę) ażeby zapisać cykle serwera.

Można sobie wyobrazić wiele powodów, dla których moglibyśmy chcieć, aby aplet odesłał obrazek. Dziękigenerowaniu obrazków, aplet może wyświetlić rzeczy takie jak: stan magazynu z dokładnością do minuty,aktualny rezultat dla meczu bejzbolowego (opatrzonego w ikonki reprezentujące biegaczy na bazach) czygraficzną reprezentację stanu butelek Coca-Coli w automacie na bilon. Poprzez manipulowanie wcześniej-istniejącymi obrazkami aplet może dokonać nawet więcej. Może zmieniać ich podstawowe parametry takie jak:kolor, rozmiar lub wygląd; może również kombinować kilka obrazków w jeden.

Generowanie obrazkówZałóżmy, że mamy nieprzetworzone dane pikselowe, które chcemy komuś

przesłać. Ktoś zapyta: ”W jaki sposób można tego dokonać?”.Już odpowiadamy. Załóżmy więc znowu, iż jest to 24-bitowyobrazek prawdziwych kolorów (3 bity na jeden piksel), oraz żema on wymiary 100x100 pikseli. Moglibyśmy zrobić rzecz, któranasuwa się od razu — przesłać jeden piksel na jeden raz, wstrumieniu 30.000 bitów. Jednak czy to wystarczy? Skądodbiorca będzie wiedział co z nimi zrobić? Odpowiedź brzmi:nie będzie wiedział. Naszym obowiązkiem jest równieżpoinformowanie, o tym, że przesyłamy wartości pikseloweprawdziwych kolorów, o tym że zaczynamy od lewego, górnego

Page 143: Java Servlet - Programowanie

rogu, o tym że będziemy przesyłali wiersz po wierszu oraz otym, iż każdy wiersz ma długość 100 pikseli. A co, jeżelizdecydujemy się na przesłanie mniejszej liczby bitów stosująckompresję? W takim przypadku musimy poinformować jaki rodzajkompresji stosujemy, aby odbiorca mógł zdekompresować obrazek.I nagle sprawa staje się bardziej skomplikowana.

Na szczęście jest to problem, który został rozwiązany i to rozwiązany na wiele różnych sposobów. Każdy formatobrazu (GIF, JPEG, PNG, TIFF, itd.) odpowiada jednemu rozwiązaniu. Każdy format obrazu określa jedenstandardowy sposób kodowania obrazków, tak że może on później zostać zdekodowany dla oglądania lubmanipulowania. Każda z technik kodowania ma swoje zalety i ograniczenia. Dla przykładu kompresja używanadla kodowania GIF lepiej obsługuje obrazki generowane komputerowo, jednak forma GIF ograniczony jest do256 kolorów. Kompresja używana dla kodowania JPEG, sprawdza się znowuż lepiej przy obrazkach foto-realistycznych, które zawierają miliony kolorów, jednak to lepsze działanie „okupione” jest zastosowaniemkompresji „stratnej”, która może rozmyć szczegóły zdjęcia. Kodowanie PIN (wymawiane jako „ping”) jestrelatywnie nowym kodowaniem, którego celem jest zastąpienie GIF, jako że jest ono mniejsze, obsługuje milionykolorów, wykorzystuje „bezstratną” kompresją, ponadto ma kanał alfa dla efektów przezroczystości — jestrównież wolna od problemów związanych z patentami, które były zmorą GIF.*

Zrozumienia na czym polega kodowanie obrazków, pomoże nam zrozumieć sposób w jaki aplety obsługująobrazki. Aplety takie jak ViewResource są w stanie odesłać wcześniej istniejące obrazki poprzez przesłanieswojej zakodowanej reprezentacji — niezmienionej do klienta, przeglądarka dekoduje wtedy obrazek dooglądania. Jednak aplet, który generuje lub modyfikuje obrazek musi skonstruować przedstawienie wewnętrzneobrazka, rozmieścić go, a następnie, przed przesłaniem go do klienta, zakodować go.

Obrazek „Hello World”Przykład 6.6 ukazuje prosty przykład apletu, który generuje oraz odsyła obrazek GIF. Grafika ukazuje napis„Hello World!”, tak jak na rysunku 6.5.

Przykład 6.6.

Grafiki „Hello World”

import java.io.*;import java.awt.* ;import javax.servlet.* ;import javax.servlet.http.* ;import Acme.JPM.Encoders.GifEncoder;public class HelloWorldGraphics extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ServletOutputStream out = res.getOutputStream (); // binarny wydruk wyjściowy!Frame frame = null;Graphics g = null;try { // Utwórz niepokazywaną ramkę frame = new Frame () ; frame. addNotify (); // Pobierz obszar grafiki, przy użyciu ramki Image image = frame.createlmage(400, 60); g = image.getGraphics() ;

* Więcej informacji na temat PNG można znaleźć na stronie: http://graphicswiz.com/png.

Page 144: Java Servlet - Programowanie

// Rysuj "Hello World!" do poza-ekranowego kontekstu grafiki

g. setFont (new Font (" Szeryfowy", Font. ITALIC ,48)); g.drawString("Hello World!", 10, 50);

// Zakoduj poza-ekranowy obrazek w GIF i prześlij go klientowi res.setContentType("obrazek/gif"); GifEncoder encoder = new GifEncoder(image, out); encoder.encode();}finally { // Wyczyść zasoby if (g != null) g. dispose(); if (frame i= null) frame.removeNotify() ; } } }

Mimo iż aplet ten wykorzystuje zestaw java.awt, właściwie nigdy nie wyświetla okna na wyświetlaczu serwera.Nie wyświetla on również okna na wyświetlaczu klienta. Wykonuje on natomiast wszystkie operacje w poza-ekranowym kontekście grafiki, pozwalając przeglądarce wyświetlać obrazek. Strategia jest następująca:stworzenie poza-ekranowego obrazka, grafik do, umieszczenie jego grafik w kontekście, rysowanie do kontekstugrafiki, a następnie zakodowanie wynikłego obrazka dla transmisji do klienta.

Rysunek 6.5.

Grafiki „Hello World”

Otrzymywanie poza-ekranowego obrazka wiąże się z pokonaniem kilku trudności. W Javie, obrazek jestreprezentowany przez klasę java.awt.Image. Niestety w JDK 1.1, obiekt Image nie może zostaćkonkretyzowany bezpośrednio przez konstruktora. Musi on być uzyskany bezpośrednio przez fabryczną metodętaką jak np. createImage() Component lub metodę getImage() Toolkit. Jako że tworzymy nowy obrazekużywamy do tego celu metody createImage(). Zwróćmy uwagę, iż zanim komponent butworzyć obrazek, jegowłasny peer musi już wtedy istnieć. W wyniku tego zanim utworzymy Image musimy utworzyć Frame, utworzyćpeer ramki z wywołaniem do addNotify(), i wreszcie użyć ramki do stworzenia naszego Image.*

W JDK 1.2 cały proces został uproszczony, tak że Image może zostać utworzony bezpośrednio przezskonstruowanie nowego java.awt.image.BufferedImage. Nasze przykłady w tym rozdziale używajątechniki frame.createImage() dla maksymalnej przenośności we wszystkich wersjach JDK.

Kiedy mamy już obrazek rysujemy na nim używając kontekstu grafiki, którą można pobrać przy użyciuwywołania do getGraphics(), metody Image. W tym przykładzie rysujemy tylko prosty strumień.

* Dla serwerów WWW działających na systemach Unix, własny peer ramki musi zostać utworzony w serwerze X. Następnie,w celu optymalnej ustawienia wydajności, należy upewnić się, że zmienna środowiskowa DISPLAY (która określa, któryserwer X będzie użyty) nie jest ustawiona lub ustawiona na lokalny serwer X, który może wymagać zastosowania komputeracentralnego x lub uwierzytelniania x.

Serwery „Headless”, mogą bez działającego serwera X, mogą użyć Xvfb (wirtualnego bufora ramki X) obsłużyć pracepomocnicze grafiki; należy tylko pamiętać, żeby wskazać DISPLAY na serwerze Xvfb.

Page 145: Java Servlet - Programowanie

Po rysowaniu do kontekstu grafiki, wywołujemy setContentType() aby ustawić typ nośnika na image/gifponieważ mamy zamiar użyć kodowania GIF. Dla przykładów w tym rozdziale użyliśmy kodera GIF napisanegoprzez Jefa Poskanzera. Jest to koder napisany solidnie i dostępny na stronie http://www.acme.com.

Zwróćmy uwagę, iż algorytm kompresji LZW użyty dla kodowania GIF jest chroniony patentami „Unisys’u”oraz IBM-a, które w zgodzie z fundacją wspierającą bezpłatne oprogramowanie, umożliwiają dostęp dobezpłatnego oprogramowania generującego format GIF. Koder Acme GIF wykorzystuje kompresję LZ, pozwolito być może uniknązć patentu. Więcej informacji można znaleźć na stronach:http://www.fsf.org/philozofy/gif.html i http://www.burnallgifs.org. Oczywiście aplet może zakodować Image nakażdy format obrazu. Dla treści WWW, JPEG i PNG są najbardziej odpowiednimi alternatywami dla GIF.Istnieje wiele dostępnych koderów JPEG i PNG. Dla użytkowników JDK wersji 1.2 i późniejszych, dostępny jestkoder wbudowany w zestawie com.sun.image.codec.jpeg; kodery dla PNG i innych formatów dostępne są(dla JDK 1.2) w oficjalnym „Java Advanced Imagining API” na stronie http://java.sun.com/products/java-media/jai. Dla użytkowników, którzy potrzebują również obsługi JDK 1.1, istnieje program narzędziowy JavaImage Management Interface (JIMI), wcześniej komercyjny produkt „Activated Intelligence”, obecnie „Sunlibrary” dostępny bezpłatnie na stronie http://java.sun.com/products/jimi.

Aby zakodować obrazek tworzymy obiekt GifEncoder, przekazując mu obiekt Image iServletOutputStream dla apletu. Kiedy wywołamy na obiekt GifEncoder metodę encoder(), obrazekzostanie zakodowany i przesłany do klienta.

Po przesłaniu obrazka, aplet wykonuje to, co wszystkie poprawnie zachowujące się aplety powinny zrobić:zwalnia swoje wszystkie zasoby graficzne. Będą one automatycznie odzyskane podczas odśmiecania, jednakzwolnienie ich pomaga systemom przy zasobach ograniczonych. Kod służący do zwolnienia zasobów,umieszczony został w bloku finally w celu zagwarantowania jego wywołania, nawet w przypadku gdy apletzgłasza wyjątek.

Dynamicznie generowany schematTeraz przyjrzyjmy się apletowi, który generuje bardziej interesujący obrazek. Przykład tworzy wykres słupkowy,który porównuje jabłka do pomarańczy, w odniesieniu do ich rocznej konsumpcji.

Przykład 6.7.

Wykres porównujący jabłka do pomarańczy

import java.awt.*;import java.io.*;import javax.servlet.*;import javax.servlet.http.*;import Acme.JPM.Encoders.GifEncoder; import javachart.chart.*; // z Techniki Wizualnejpublic class SimpleChart extends HttpServlet (static final int WIDTH = 450;static final int HEIGHT = 320;public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException ,IOException { ServletOutputStrearo out = res.getOutputStream(); Frame frame = null; Graphics g = null;try { // Utwórz prosty wykres BarChart chart = new BarChart ("Jabłka i Pomarańcze"); // Nadaj mu tytuł chart.getBackground().setTitleFont(new Font("Szeryfowy", Font.PLAIN, 24));

Page 146: Java Servlet - Programowanie

chart.getBackground().setTitleString("Porównywanie Jabłek i Pomarańczy"); // Pokaż miejsce i dostosuj jego objaśnienia chart.setLegendVisible(true) ; chart.getLegend().setL1X(0.4); // normalizowane z niższego lewego chart.getLegend(),setLlY(0.75); // normalizowane z niższego lewego chart.getLegend().setIconHeight(0.04); chart.getLegend().setIconWidth(0.04); chart.getLegend().setIconGap(0.02); chart.getLegend().setVerticalLayout(false) ; // Nadaj mu jego dane i oznaczenia double[] appleData = {950, 1005, 1210, 1165, 1255}; chart.addDataSet("Jabłka", appleData) ; double[] orangeData = {1435, 1650, 1555, 1440, 1595}; chart.addDataSet("Pomarańcze", orangeData) ; String[] labels = {"1993", "1994", "1995", "1996", "1997"}; chart.getXAxis().addLabels(labels) ; // Pokoloruj jabłka na czerwono, a pomarańcze na pomarańczowo chart.getDatasets()[0].getGc().setFillColor(Color.red) ; chart.getDatasets()[1],getGc().setFillColor(Color.orange); // Nazwij osie chart.getXAxis().setTitleString("Rok") ;

chart.getYAxis().setTitleString("Tony Skonsumowane") ; // Nadaj odpowiedni rozmiar chart.resize(WIDTH, HEIGHT);

// Utwórz niepokazywaną ramkę frame = new Frame () ; frame.addNotify() ; // Pobierz obszar grafiki o odpowiednim rozmiarze, używając Ramki Image image = frame.createlmage(WIDTH, HEIGHT); g = image.getGraphics(); // Poproś wykres aby wrysował się w poza-ekranowy kontekst grafiki chart.drawGraph(g) ; // Zakoduj i odeślij to, co zostało namalowane res. setContentType ("obraz/gif") ; GifEncoder encoder = new GifEncoder(image, out); encoder.encode() ;} finally ( // Wyczyść zasoby if (g != null) g.dispose(); if (frame != null) frame.removeNotify(); } } }

Na rysunku 6.6 zostały zaprezentowane rezultaty. Właściwie nie ma potrzeby, aby wykres ten był generowanydynamicznie, mimo to pozwala osiągnąć wiele, bez zbędnego kodowania.

Page 147: Java Servlet - Programowanie

Rysunek 6.6.

Wykres porównujący jabłka do obrazków

Zasady są takie same: utworzyć poza-ekranowy obrazek oraz pobrać jego kontekst grafiki, rysować do tegokontekstu, a następnie zakodować rysunek do transmisji dla klienta. Różnica polega na tym, że aplet konstruujeobiekt BarChart do rysowania. W Javie dostępnych jest kilkanaście zestawów tworzących wykresy. KlasaBarChart, naszego przykładu pochodzi z zestawu „Visual Engineering’s KavaChart” (wcześniej: JavaChart),który dostępny jest na stronie http://www.ve.com/kavachart. Jest to produkt komercyjny jednakże dlaczytelników niniejszej książki został przyznany bezpłatny dostęp do „porcji” API zaprezentowanej w naszymprzykładzie. Zestaw „KavaChart” zawiera również bezpłatny pakiet apletów generujących wykresy, ze stronyhttp://www.ve.com/kavachart/servlets.html. Kolejnym dobry zestawem tworzącym wykresy jest zestaw „Sitraki”,dostępny na stronie http://www.sitraka.com (uprzednio KL Group).

Składanie obrazuJak dotąd rysowaliśmy nasze grafiki na puste obrazki. W tym rozdziale omówimy w jaki sposób przyjmowaćwcześniej istniejące obrazki i rysować na ich szczycie lub zespalać je w obrazki łączone. Przeanalizujemyrównież obsługę błędu w apletach odsyłających obrazki.

Rysowanie obrazkówCzasami aplet może czerpać korzyści z rysowania na górze istniejącego obrazka. Dobrym przykładem może tutajbyć aplet -lokator składowy, który wie gdzie znajduje się każdy pracownik. Kiedy zapytamy gdzie znajduje sięokreślony pracownik, wyświetli dużą, czerwoną kropkę nad jego biurem.

Jedną ze zwodniczo prostych technik dla rysowania wcześniej istniejących obrazków jest uzyskiwanie obrazkówza pomocą Yoolkit.getDefaultToolkit().getImage(imagename), pobranie jego kontekstu grafiki przypomocy wywołania do metody Image – getGraphics(), a następnie wykorzystanie odesłanego kontekstugrafiki do rysowania na górze obrazka. Niestety nie jest to takie proste jak się może wydawać. Powodem jest to,że nie można użyć metody getGraphics() jeżeli obrazek nie został utworzony przy pomocy metodyComponent createImage(). W przypadku pracy z narzędziami wzorowanymi na Windows AWT, koniecznyjest zawsze własny peer, generujący w tle grafikę.

Istnieje jednak rzecz, którą można wykonać zamiast powyższego: pobranie wcześniej istniejącego obrazkapoprzez metodę Toolkit.getDefaultToolkit().getImage(imagename), a następnie „powiedzenie” mu,

Page 148: Java Servlet - Programowanie

aby narysował się na inny kontekst grafiki utworzony za pomocą metody createImage() Component, tak jakto zostało w poprzednich dwóch przykładach. Teraz możemy już wykorzystać omawiany kontekst grafiki dorysowania na górze oryginalnego rysunku.

Przykład 6.8 wyjaśnia na czym polega ta technika na konkretnym przykładzie. Prezentuje on aplet, który pisze„POUFNE” na każdym rysunku, który odsyła. Obrazek jest przekazywany do apletu jako informacja dodatkowejścieżki; aplet załaduje obrazek z odpowiedniej lokalizacji pod serwerowym katalogiem źródłowym dokumentu,wykorzystując metodę getResource() w celu obsługi rozproszonego wywołania.

Przykład 6.8.

Rysowanie obrazka do oznaczenia go jako poufny

import java.awt.*;import java.io.*;import java.net.* ;import javax.servlet.* ;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils;import Acme.JPM.Encoders.GifEncoder;public class Confidentializer extends HttpServlet { Frame frame = null;

public void init() throws ServletException { // Skonstruuj niepokazywaną ramkę wielokrotnego użytku frame = new Frame(); frame.addNotify() ; } public void doGet (HttpServletRequest req, HttpServietResponse res) throws ServletException, IOException { ServletOutputStream out = res.getOutputStream(); Graphics g = null; try { // Pobierz lokalizację obrazka z informacji ścieżki // Dla bezpieczeństwa użyj ServletUtils (Rozdział 4) URL source = ServletUtils.getResource(getServletContext(), req.getPathInfo()) ; } // Ładuj obrazek (z bajtów do obiektu obrazka)MediaTracker mt = new MediaTracker(frame); // ramka działa jako ImageObserverImage image = Toolkit.getDefaultToolkit().getlmage(source);mt.addlmage(image, 0) ;try { mt .waitForAll();}catch (InterruptedException e) { res.sendError(res.SC_INTERNAL_SERVER_ERROR, "Przerwanie podczas ładowania obrazka: " + ServletUtils.getStackTraceAsString(e)) ;return;}// Pobierz szerokość oraz długośćint w = image.getWidth(frame) ;int h = image.getHeight(frame);// Upewnij się, że odczytujemy poprawne dane obrazuif (w <= 0 || h <= 0) { res.sendError(res.SC_NOT_FOUND, "Informacje dodatkowej ścieżki muszą wskazywać na poprawny obrazek"),return;}// Skonstruuj odpowiedni rozmiar poza-ekranowego kontekstu grafiki

Page 149: Java Servlet - Programowanie

Image offscreen = frame.createlmage (w, h);g = offscreen.getGraphics();// Rysuj obrazek do poza-ekranowego kontekstu grafikig.drawlmage( image, 0, 0, frame);// Napisz POUFNE na jego górzeg.setFont(new Font("Monospaced", Font.BOLD | Fond.ITALIC, 30));g.drawString("POUFNE", 10, 30);// Zakoduj poza-ekranowe grafiki w GIF i prześlij je do klientares.setContentType("obraz/gif") ;GifEncoder encoder = new GifEncoder(off screen, out);encoder.encode() }finally {// Wyczyść zasobyif (g != null) g.dispose(); }}public void destroy() ( // Wyczyść zasoby if (frame != null) frame.removeNotifyt() ; }}

Przykładowy wydruk wyjściowy można zobaczyć na rysunku 6.7.

Rysunek 6.7.

Rysowanie obrazka, w celu oznaczenia go jako poufny

Jak widać aplet ten przeprowadza każdy krok, dokładnie tak jak opisaliśmy to wcześniej, dodając trochętematyki gospodarstwa domowego. Aplet tworzy swoją niepokazywaną ramkę w metodzie init().TworzenieFrame, a następnie powtórne wykorzystywanie to optymalizacja, nie przeprowadzona uprzednio z powodówestetyczno-porządkowych. Dla każdego zlecenia aplet zaczyna od odczytania nazwy wcześniej istniejącegoobrazka z informacji dodatkowej ścieżki, a następnie konwertuje ścieżkę do zasobu używając metodygetResource(). Następnie pobiera odwołanie do obrazka za pomocą metody Toolkit — getImage() ifizycznie ładuje go do pamięci korzystając z pomocy MediaTracker.Zwykle ładowanie asynchroniczne nie jestproblemem dla obrazka, ponieważ jego częściowe rezultaty malowane są wraz z postępem ładowania, jednak wtym przypadku obrazek jest malowany tylko raz i musimy mieć pewność, że zostanie on z góry załadowany w

Page 150: Java Servlet - Programowanie

całości. W następnej kolejności aplet pobiera szerokość i długość ładowanego obrazka i tworzy poza-ekranowyobrazek do dopasowania. I wreszcie — decydująca chwila: ładowany obrazek jest rysowany na górze nowo-utworzonego, pustego. Potem wszystko dzieje się szybko. Aplet pisze swoje wielkie „POUFNE” i kodujeobrazek do transmisji. Zauważmy w jaki sposób aplet radzi sobie z warunkami występowania błędu, wywołującmetodę sendError(). Podczas odsyłania obrazków trudno o coś bardziej wyszukanego. Takie ujęcie sprawypozwala apletowi na wykonanie tego, co uważa za słuszne.

Zestawianie obrazkówAplet mają również możliwość zestawiania obrazków w jeden — obrazek kombinowany. Wykorzystując tąmożliwość aplet-lokator składowy mógłby wyświetlić uśmiechnięta twarz pracownika — zamiast czerwonejkropki nad jego biurem. Technika używana przy zestawianiu obrazków, przypomina używaną do rysowania nagórze obrazka: odpowiednie obrazki są ładowane, rysowane na utworzonym w odpowiedni sposób obiekcieImage, i w końcu obrazek ten jest kodowany do transmisji.

Przykład 6.9 pokazuje jak wykonać coś podobnego dla apletu, który wyświetla liczbę wywołań jako sekwencjęosobnych liczbowych obrazków kombinowanych w jeden duży. Obrazki liczbowe, używane przez wspomnianyaplet, dostępne są na http://www.geocities.com/SiliconValley/6742, razem z kilkoma innymi stylami.

Przykład 6.9.

Zestawianie obrazków do jednego — kombinowanego

import java.awt.*;import java.io.*;import java.net.*;import javax.servlet.*;import javax.servlet.http.* ;import com.oreilly.servlet.ServletUtils;import Acme.JPM.Encoders.GifEncoder;public class GraphicalCounter extends HttpServlet { public static final String DIR = "/images/odometer"; public static final String COUNT = "314159"; public void doGet(HttpServletRequest reg, HttpServletResponse res)throws ServletException, IQException { ServletOutputStream out = res.getOutputStream (); Frame frame = null; Graphics g = null;try { // Pobierz liczenia do wyśeietlenia, musi być wartość wyłączna w ciągu // Lub użyj domyślnej String count = (String)req.getQueryString(); if (count == null) count = COUNT; int countlen = count.length() ; Image images[] = new Image[countlen];for (int i = 0; i < countlen; i++) { URL imageSrc = getServletContext().getResource(DIR + "/" + count. charAt(i) + "GIF")if (imageSrc == null) { imageSrc = new URL (" plik:"); // znak-wypełniacz, błędy obsłuż póżniej } images[i] = Toolkit.getDefaultToolkit().getImage(imageSrc) ;}// Utwórz niepokazywaną ramkęframe == new Frame () ;frame.addNotify () ;// Ładuj rysunki

Page 151: Java Servlet - Programowanie

MediaTracker mt = new MediaTracker(frame) ;for (int i = 0; i < countlen; i++) { mt.addImage(images[i], i) ;}try { mt .waitForAll() ;}catch (InterruptedException e) { res. sendError (res. SC_INTERNAL_SERVER_ERROR, "Przerwanie podczas ładowania obrazka: " + ServletUtils.getStackTraceAsString(e)) ; return;}// Sprawdź czy nie pojawiają się problemy podczas ładowania obrazkówif (mt.isErrorAny()) { //Mieliśmy problem ustalić który obrazek(i) StringBuffer problemChars = new StringBuffer() ; for (int i = 0; i < countlen; i++) { if (mt.isErrorID(i)) { problemChars.append(count.charAt(i)) ; }}res.sendError(res.SC_INTERNAL_SERVER_ERROR, "Nie można było załadować obrazka dla tych znaków: " + probleinChars. toString ()) ;return;}// Pobierz zbiorowy rozmiar obrazkówint width = 0;int height = 0;for (int i = 0; i < countlen; i++) { width += images[i].getWidth(frame) ; height = Math.max(height, images[i].getHeight(frame));}// Pobierz obszar grafiki do dopasowania, używając RamkiImage image = frame.createImage(width, height);g = image.getGraphics() ;// Rysuj obrazkiint xindex = 0;for (int i = 0; i < countlen; i++) { g. drawlmage(images[i], xindex, 0, frame) ; xindex += images [i].getWidth (frame);}// Zakoduj i odeślij kompozytres.setContentType("obrazek/gif") ;GifEncoder encoder = new GifEncoder(image, out)encoder.encode() ;}finally {// Wyczyść zasobyif (g != null) g.dispose();if (frame != null) frame.removeNotify();

Strumień wyjściowy został pokazany na rysunku 6.8.

Rysunek 6.8.

Kombinowanie obrazków do graficznego licznika

Page 152: Java Servlet - Programowanie

Aplet ten pobiera liczbę do wyświetlenia poprzez odczytanie nieprzetworzonego ciąguzapytań. Dla każdej liczby w liczeniu, aplet wyszukuje i ładuje odpowiadający jej obrazekliczbowy z katalogu podanego przez DIR. (DIR znajduje się zawsze pod serwerowymkatalogiem macierzystym dokumentu. Jest on podany jako ścieżka wirtualna i konwertowanyna abstrakcyjną ścieżkę zasobu.) Następnie oblicza kombinowaną szerokość i maksymalnąwysokość wszystkich tych obrazków i konstruuje poza-ekranowy rysunek do dopasowania.Aplet rysuje każdy obrazek liczbowy, na przemian, od lewa do prawa. W końcu kodujeobrazek do transmisji.

Aby na coś się przydać aplet ten musi zostać wywołany przez inny aplet, który zna liczbęwywołań, która ma być wyświetlona i umieszcza tą liczbę w ciągu zapytań. Dla przykładuaplet ten może zostać wywołany przez stronę JSP lub inną, dynamicznie utworzoną stronęprzy użyciu składni jak poniżej:

<IMG SRC="/aplet/Licznik Graficzny?121672">Aplet obsługuje warunki powstania błędu w ten sam sposób, w jaki robi to aplet poprzedni,poprzez wywołanie metody sendError() i pozostawienie jej serwerowi aby zachowywał się wpoprawny sposób.

Obrazki — efektyDowiedzieliśmy się już w jaki sposób aplety mogą tworzyć i kombinować obrazki. W tympodrozdziale dowiemy się jak apety mogą wykonywać specjalne efekty związane z obrazkami.Aplet może na przykład ograniczyć czas transmisji obrazka, poprzez zmianę jego rozmiaruprzed transmisją. Może on również dodać specjalne cieniowanie do obrazka, abyprzypominał on przycisk, który można nacisnąć. Dla przykładu przyjrzyjmy się w jaki sposóbaplet może konwertować obrazek kolorowy na czarno biały.

Konwertowanie obrazka na „czerń i biel”Na przykładzie 6.10 został ukazany aplet, który przed odesłaniem obrazka, konwertuje go naskalę czarno-białą. Aplet wykonuje ten efekt właściwie bez tworzenia poza-ekranowegokontekstu grafiki. Zamiast tego tworzy obrazek używając specjalnego ImageFilter (filtruobrazu). (Celowo nie zamieszczamy tutaj obrazków, ponieważ nie wyglądały by one najlepiejw czarno białej książce).

Przykład 6.10.

Efekt obrazkowy — konwertowanie obrazka na czarno białą skalę

import java.awt.* ;import java.awt. image.* ;import java.io.* ;import java.net.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils ;import Acme.JPM.Encoders.* ;public class Decolorize extends HttpServlet {public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("obraz/gif") ; ServletOutputStream out = res.getOutputStream(); // Pobierz lokalizację obrazka z informacji ścieżki

Page 153: Java Servlet - Programowanie

URL source = ServletUtils.getResource(getServletContext(), req.getPathInfo()) ; if (source == null) { res.sendError(res.SC_NOT_FOUND, "Informacja dodatkowej ścieżki musi wskazywać na dodatkowy obrazek");return;} // Skonstruuj niepokazywaną ramkę // Nie będzie żadnej addNotify()ponieważ jej peer nie jest potrzebny Frame frame = new Frame () ; // Ładuj obrazek Image image = Toolkit.getDefaultToolkit().getImage(source); MediaTracker mt = new MediaTracker(frame); mt. addImage (image, 0); try { mt .waitForAll<) ; } catch (InterruptedException e) { res. sendError (res. SC_INTERNAL_SERVER_ERROR, "Przerwanie podczas ładowania pliku: " + ServletUtils.getStackTraceAsString(e)) ; return;} // Pobierz rozmiar obrazka int width = image. getWidth( frame) ; int height = image.getHeight(frame);

// Upewnij się, że odczytujemy poprawne dane if (width <= 0 || height <= 0) { res.sendError(res.SC_NOT_FOUND, "Informacje dodatkowej ścieżki muszą wskazywać na poprawny obrazek") ; return;} // Utwórz obrazek do dopasowania, zastosuj filtr Image filtered = frame.createImage( new FilteredImageSource(image.getSource(), new GrayscaleImageFilter()));

//Zakoduj i odeślij przefiltrowany obrazek GifEncoder encoder = new GifEncoder(filtered, out); encoder.encode() ; }}

Kod dla tego apletu pokrywa się w większości z kodem przykładu Confidentialer. Główna różnica zostałazaprezentowana poniżej:

// Utwórz obrazek do dopasowania, Zastosuj filtrImage filtered = frame.createImage( new FilteredImageSource(image.getSource(), new GrayscaleImageFilter())) ;

Aplet nie używa metody Component — createImage(int, int), którą wykorzystywaliśmy dotychczas.Zamiast tego czerpie korzyści z innej metody Component — createImage(ImageProducer). Aplet tworzy„image producer” za pomocą FilteredImageSource, które następnie przekazuje obrazek poprzezGrayscaleImageFilter. Filtr konwertuje każdy kolorowy piksel na jego czarno biały odpowiednik. Dziękitemu obrazek jest konwertowany na skalę czarno białą w czasie kiedy jest tworzony. Kod dlaGrayscaleImageFilter został pokazany na przykładzie 6.11.

Przykład 6.11.

Klasa „GrayscaleImageFilter”

import java.awt.* ;

Page 154: Java Servlet - Programowanie

import java.awt.image.*;public class GrayscaleImageFilter extends RGBImageFilter { public GrayscaleImageFilter() { canFilterIndexColorModel = true; } // Konwertuj kolorowe piksele na skalę czarno białą // Algorytm jest zgodny ze specyfikacją NTSC public int filterRGB(int x, int y, int pixel) { // Pobierz średnie natężenie RGB int red = (pixel & OxOOffOOOO) » 16; int green = (pixel & OxOOOOffOO) » 8; int blue = pixel & OxOOOOOOff;

int luma = (int) (0.299 * red + 0.587 * green + 0.114 * blue); // Odeślij wartość luma dla każdego komponentu RGB // Uwaga: (Przezroczystość) Alfa jest zawsze ustalona na wartość maksymalną (nie przeżroczysta) return (Oxff « 24) | (luma « 16) | (luma « 8) | luma; }}

Dla każdej wartości w mapie kolorów, filtr ten otrzymuje wartość pikselową i odsyła nową przefiltrowanąwartość pikselową. Dzięki ustawieniu zmiennej canFilterIndexColorModel na true, zaznaczamy, że filtrten działa na mapie kolorów a nie na poszczególnych wartościach pikselowych. Wartość pikselowa podana jestjako 32-bitowe int, gdzie pierwszy bajt reprezentuje wartość (przezroczystości) alfa, drugi bajt natężenieczerwieni, trzeci oktet natężenie zieleni, a czwarty bajt natężenie koloru niebieskiego. Aby konwertować wartośćpikselową na skalę czarno białą, natężenia czerwieni, zieleni i koloru niebieskiego muszą zostać ustalone na takiesame wartości. Można również obliczyć wartość średnią dla wartości czerwieni, zieleni oraz koloru niebieskiego,a następnie zastosować średnią wartość natężenia każdego koloru. Spowodowałoby to konwersję obrazka naskalę czarno białą. Jeżeli jednak weźmiemy pod uwagę sposób, w jaki ludzie postrzegają kolory (i inneczynniki), okaże się jednak, że potrzebna jest średnia ważona. Ustawianie według priorytetów 0,299 0,587 0,114,które zostało tutaj zastosowane odpowiada temu, które jest stosowane przez Komitet Państwowego SystemuTelewizyjnego USA (National Television Systems Committee) dla telewizji czarno białej. Więcej na ten tematmożna znaleźć w książce Charlesa A. Poyntona pt. „A Technical Introduction to Digital Video” oraz na stroniehttp://www.color.org.

Buforowanie przekonwertowanego obrazkaProces tworzenia i kodowani obrazka może być rzeczą kosztowną, biorąc pod uwagę zarówno czas jak i cykleCPU serwera. Buforowanie zakodowanych obrazków zawsze znacznie podnosi wydajność. Zamiastwykonywania wszystkich czynności dla każdego zlecenia, rezultaty mogą zostać zapisane i przesłane ponowniedla późniejszych zleceń. Idea tarczy zegara, o której wspomnieliśmy wcześniej może tutaj być idealnymprzykładem. Obrazek zegara musi być tworzony co najwyżej raz na minutę. Wszystkim innym zleceniomzłożonym w tej minucie może zostać przesłany ten sam obrazek. Wykres dla tabulacji głosowania stanowinastępny przykład. Jest on tworzony raz, a następnie zmieniany wtedy kiedy przychodzą nowe głosy.

Nadklasa com.oreilly.servlet.CacheHttpServlet, z rozdziału 3 „Czas istnienia (cykl życia) apletu”dostarcza prosty mechanizm buforujący dla obrazków oraz dla tekstu. Aplet generujący obrazek zegara mógłbyrozszerzyć CacheHttpServlet oraz wdrożyć metodę getLastModified(), odsyłającą czas, w którym zostałoddany ostatni głos.

Nasz przykład Decolorize mógłby rozszerzyć CacheHttpServlet za pomocą metody getLastModified(), która odsyła czas ostatniej zmiany zasobu obrazka. Niestety CacheHttpServlet buforuje tylko ostatniąodpowiedź, dając niewiele korzyści, w (prawdopodobnej) sytuacji, w której aplet DeColorize będzie wywołanyna więcej niż jeden obrazek. DeColorize powinien prawdopodobnie używać bardziej wyszukanego algorytmubuforującego do obsługi obrazków wielokrotnych. Cykl życia apletu sprawia, że jest to całkiem proste. Naszaplet DeColorize może zapisać każdy przekonwertowany obrazek tablicę bitów,

Page 155: Java Servlet - Programowanie

przechowywaną w Hashtable nastrojonego przez nazwę obrazka. Najpierw aplet musiutworzyć zmienną kopii Hashtable. Musi ona zostać zdeklarowana poza metodą doGet():

Hashtable gifs = new Hashtable() ;

W celu wypełnienia tej tablicy-przewodnika musimy utrwalić zakodowane grafiki. Tak więc zamiast podawaniaGifEncoder ServletOutputStream, podajemy mu ByteArrayOutputStream. Następnie kodujemyobrazek za pomocą metody encode(), zakodowany obrazek jest przechowywany wByteArrayOutputStream. Potem składujemy utrwalone bity w tablicy-przewodniku, by w końcu napisać je doServletOutputStream i odesłać obrazek do klienta. Oto nowy kod do kodowania, składowania oraz odsyłaniaprzefiltrowanego obrazka:

// zakoduj, składuj i odeślij przefiltrowany obrazekByteArrayOutputStream baos = new ByteArrayOutputStream(1024;; / 1K initialGifEncoder encoder = new GifEncoder(filtered, baos);encoder.encode () ;gifs.put(source, baos) ;baos.writeTo(out) ;

Taki kod wypełnia tablicę-przewodnika zakodowanymi obrazkami nastrojonymi przez nazwęobrazka. Teraz, tak jak wcześniej w aplecie, możemy przejść bezpośrednio do pamięcipodręcznej, kiedy zostałaby poproszona o odesłanie wcześniej zakodowanego obrazka. Kodten powinien znajdować się bezpośrednio po kodzie wykonanym jeżeli source==null (źródło=zero).

// Zwarcie jeżeli to zostało wykonane wcześniej if (gifs.containsKey(source)) { ByteArrayOutputStream baos = (ByteArrayOutputStream) gifs.get(source);baos.writeTo(out); return;}

W trakcie podobnych modyfikacji każdy obrazek, który znajdzie się w pamięci podręcznej jest szybko odsyłany,bezpośrednio z pamięci.

Oczywiście buforowanie obrazków wielokrotnych pociąga za sobą zużywanie ogromnych ilości pamięci.Buforowanie pojedynczego obrazka nie jest problemem, jednakże aplet taki jak ten powinien użyć w tym celujakiejś metody. Dla przykładu mógłby on buforować jedynie 100 ostatnich obrazków, na które składano zlecenia.Bardziej rozbudowana wersja tego apletu, mogłaby również sprawdzać znacznik czasu pliku, aby upewnić się, żeoryginalny obrazek nie zmienił się od czasu buforowania czarno białej wersji.

Treść skompresowanaZestaw java.util.zip został wprowadzony w JDK 1.1. Zestaw ten zawiera klasy, któreobsługują odczytywanie i pisanie formatów kompresji ZIP i GZIP. Mimo iż zestaw ten zostałdodany w celu obsługi Plików Archiwalnych Javy (Java Archive Files) — JAR, zapewnia onrównież apletowi wygodny, standardowy sposób przesyłania treści skompresowanej.

Końcowy użytkownik nie widzi żadnej różnicy pomiędzy treścią skompresowaną, a treścią nie skompresowaną,ponieważ pierwsza jest dekompresowana przez przeglądarkę przed wyświetleniem. Jednak mimo, iż prezentujesię tak samo, może przysłużyć się użytkownikowi poprzez ograniczanie czasu ładowania treści z serwera. Dlabardzo „kompresowalnych” treści, takich jak HTML, kompresja może zredukować czas transmisji poprzezdyrektywę rozmiaru. Uświadomijmy sobie, że dynamiczne kompresowanie treści zmusza serwer dowykonywania dodatkowej pracy, tak więc ewentualne przyspieszenie transmisji musi wiązać z obniżeniemwydajności serwera.

Page 156: Java Servlet - Programowanie

Jak pamiętamy, aplet może przesyłać nagłówek Content-Type jako część swojej odpowiedzi, ażebypoinformować klienta o tym jaki typ ma przesyłana informacja. Aby przesłać treść skompresowaną, aplet musirównież przesłać nagłówek Content-Encoding, aby poinformować klienta o schemacie, zgodnie z którymtreść została zakodowana. Możliwe schematy kodowania specyfikacji HTTP 1.1 to: gzip (lub x-gzip),compress (lub x-compress) oraz deflate.

Nie wszyscy klienci rozumieją wszystkie kodowania. Aby poinformować aplet o tym, który schemat kodowaniajest rozumiany przez klienta, klient może przesłać nagłówek Accept-Encoding, który określa akceptowaneprzez klienta schematy kodowania jako oddzielaną przecinkami listę. Nie wszystkie przeglądarki (wyłączając,kilka które obsługują kodowanie skompresowane) dostarczają ten nagłówek. Na ten moment aplet musizdecydować, że bez tego nagłówka nie prześle treści skompresowanej lub że musi zbadać nagłówek User-Agent, aby stwierdzić czy przeglądarka obsługuje kompresję. Większość spośród obecnych, popularnychprzeglądarek (jednak nie wszystkie) obsługuje kodowanie gzip, żadna nie obsługuje jednakże kodowaniakompresji, a tylko MIcrosoft Internet Explorer 4 lub 5 (w Windowsie) obsługuje kodowanie typu deflate. Wprostym określeniu zdolności przeglądarki, w tym tego czy obsługuje GZIP, można posłużyć się„BrowserHawk4J” — produktem „Cyscape”. Ten komercyjny produkt wykrywa obsługę GZIP, jak równieżwiele innych właściwości przeglądarki, takich jak: typ przeglądarki, wersję przeglądarki, system operacyjnyklienta, zainstalowane moduły dodatkowe, szerokość i wysokość przeglądarki, szerokość i wysokość monitoraklienta, wyłączenie cookies, zablokowany JavaScript, a nawet prędkość połączenia. Więcej na:http://www.cysape.com/products/bh4j. 5

Mimo iż uzgadnianianie, który format kompresji ma zostać użyty może wiązać się ze sporą ilością logiki,właściwie rzecz biorąc trudno sobie wyobrazić łatwiejszy sposób przesyłania treści skompresowanej. Aplet poprostu zawija swój servletOutputStream przy pomocy GZIPOutputStream lub ZIPOutputStream.Pamiętajmy aby wywołać out.close() kiedy nasz aplet pisze wydruk wyjściowy, tak żeby właściwy dlaformatu kompresji znak kończący został napisany.

Przykład 6.12 ukazuje aplet ViewResource z rozdziału 4, powtórnie napisany, tak żeby mógł przesyłaćskompresowaną treść kiedy jest to możliwe. Nie zamieszczamy tym razem zrzutu ekranu, ponieważ nie ma nanim nic ciekawego do oglądania. Tak jak powiedzieliśmy wcześniej, użytkownik końcowy nie jest w stanierozpoznać czy serwer przesłał treść skompresowaną — może z wyjątkiem ograniczonych czasów ładowania.

Przykład 6.12.

Przesyłanie treści skompresowanej

import java.io.*;import java.net.*;import java.util.*;import java.util.zip.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils ;public class ViewResourceCompress extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException ( OutputStream out = null;

// Wybierz właściwe kodowanie treści oparte na // nagłówku klienta Accept-Encoding. Wybierz GZIP jeżeli nagłówek // zawiera "gzip". Wybierz ZIP jeżeli nagłówek zawiera "compress". // W przeciwnym wypadku nie wybieraj żadnej kompresji. Upewnij się, że // Content-Encoding wykorzystuje przedrostek "x-" wtedy i tylko wtedy // jeżeli robi to Accept-Encoding. String encodings = req.getHeader("Accept-Encoding"); if (encodings != null && encodings.indexOf("gzip") != -1) { //Startuj z GZIP if (encodings.index0f("x-compress") != -1) { res.setHeader("Content-Encoding", "x-compress") ;} else { res.setHeader("Content-Encoding ", "compress") ;} out = new ZipOutputStream(res.getOutputStream()) ;}

5

Page 157: Java Servlet - Programowanie

((ZipOutputStream)out),putNextEntry(new ZipEntry("nazwa pusta"));} else { // Nie kompresuj out = res.getOutputStreamO ;}res.setHeader("Vary", "Accept-Encoding") ;// Pobierz zasób do przeglądaniaURL url = null;try { url=ServletUtils.getResource(getServletContext(), req.getPathInfo());}catch (IOException e) ( res.sendError( res. SC_NOT_FOUMD,"Informacje dodatkowej ścieżki muszą wskazywać na poprawny zasób do przeglądania:" +e.getMessaget));}// Połącz z zasobemURLConnection con = url.openConnection();con.connect() ;// Pobierz i ustal typ zasobuString contentType = con.getContentType();res-setContentTypetcontentType);// Odeślij zasóbtry { ServletUtils.retumURL(url, out);}

catch (IOException e) res.sendError(res.Sc_INTERNAL_SERVER_ERROR, "Problem przy przesyłaniu zasobu: " + e.getMessage());}// Pisz znak kończący i zamknij strumień wyjściowyout.closet); } }

Aplet rozpoczyna od zdeklarowania null OutputStream, a następnie ustala to OutputStream naGZIPOutputStream lub ZipOutputStream lub na ServletOutputStream, w zależności od otrzymanegonagłówka Accept-Encoding.

Jako że aplet wybiera wydruk wyjściowy, który ma zostać przez niego użyty, ustala odpowiedni nagłówekContent-Encoding. Podczas przesyłania treści skompresowanej, nagłówek ten musi być ustawiony dla klientaw sposób umożliwiający działanie odpowiedniego algorytmu dekompresji. Aplet ustala również nagłówek Varyna wartość Accept-Encoding, aby „być grzecznym” i poinformować klienta, że aplet różnicuje swój wydrukwyjściowy w zależności od nagłówka Accept-Encoding. Większość klientów ignoruje ten nagłówek.

Po tej wczesnej logice, aplet może traktować wydruk wyjściowy jako kolejny OutputStream. Aplet mógłbyzawinąć strumień za pomocą PrintStream lub PrintWriter, mógłby również Przekazać go do GifEncoder.Jednak bez względu na to co zrobi, musi wywołać metodę out.close(), kiedy już zakończy przesyłanie treści.Wywołanie to pisze właściwy znak kończący do skompresowanego strumienia.

Jest jednak pewien rodzaj treści, która nie powinna być kompresowana. Dla przykładu obrazki GIF i JPEG, sąjuż kompresowane jako część ich kodowania, tak więc powtórne ich kompresowanie jest bezcelowe (a niektóreprzeglądarki mają problemy przy wyświetlaniu obrazków przesłanych w skompresowanej formie). Ulepszonawersja apletu FileViewCompressed byłaby w stanie wykryć kiedy odsyła obrazek, i zaniechałaby dalszejkompresji

Serwer cyklicznyDo tej pory każda strona, odesłana przez aplet zawierała tylko jeden typ treści. Jednak tak wcale nie musi być.Dlaczego aplet miałby nie odsyłać kilku stron, każda zawierająca inny typ treści, wszystkie znajdujące się wjednej odpowiedzi? To może wydawać się nie do zrealizowania, jednak przy użyciu techniki zwanej serweremcyklicznym (server push) jest to całkiem proste.

Page 158: Java Servlet - Programowanie

W przypadku stosowania tej techniki serwer umieszcza na stosie lub przesyła sekwencję stron odpowiedzi doklienta. Porównajmy tą technikę do techniki z poprzedniego rozdziału — ściągania klienckiego, w przypadkuktórej to techniki klient musi otrzymać lub ściągnąć każdą stronę z serwera. Mimo iż rezultaty zastosowania obutechnik są podobne dla użytkownika — pojawienie się sekwencji stron, szczegóły wdrażania oraz ich poprawnezastosowania są odmienne dla każdej z nich.

Przy stosowaniu serwera cyklicznego, połączenie gniazdowe pomiędzy serwerem a klientem pozostaje otwartedo czasu przesłania ostatniej ze stron. Takie rozwiązanie daje serwerowi możliwość szybkiego przesyłaniauaktualnień strony oraz kontrolę nad tym, w jakim czasie wspomniane uaktualnienia będą przesyłane. Z takimimożliwościami technika „serwer cykliczny” nadaje się idealnie do stron, które wymagają częstych uaktualnień(takich jak animacje wykonywane na poziomie podstawowym) lub dla stron, które wymagają kontrolowanychprzez serwer, jednak nie częstych uaktualnień (takich jak uaktualnienia aktywnego statusu). Zwróćmy jednakuwagę, iż metoda serwer cykliczny nie jest jeszcze obsługiwana przez Microsoft Internet Explorera i powinno sięunikać jej szerokiego stosowania jako, iż okazało się to szkodliwe dla liczenia dostępnego portu serwera.

Przy stosowaniu klienckiego ściągania. połączenie gniazdowe jest przerywane po każdej stronie, tak więcodpowiedzialność za uaktualnianie stron spada na klienta. Klient wykorzystuje wartość nagłówkową Refresh,przesłaną przez serwer, aby określić kiedy przeprowadzić swoje uaktualnienie, tak więc technika klienckieściąganie jest najlepszym rozwiązaniem dla stron, które nie wymagają częstych uaktualnień lub, które mająuaktualnienia w znanych przerwach.

Serwer cykliczny może również okazać się pomocny dla animacji o ograniczonej długości oraz dla uaktualnieństatusu, wykonywanych w czasie rzeczywistym. Weźmy dla przykładu aplet, który jest w stanie przesłać doserwera cztery najświeższe pogodowe mapy satelitarne, tworząc animację poziomu podstawowego. Spróbujmysobie przypomnieć aplet PrimeSearcher z rozdziału 3, zastanówmy się w jaki sposób moglibyśmy zastosowaćserwer cykliczny, aby zaznaczyć ograniczoną liczbę klientów, bezpośrednio po tym jak serwer znajdzie każdąnową liczbę pierwszą.

Na przykładzie 6.13 został zaprezentowany aplet, który wykorzystuje serwer cykliczny do wyświetlaniaodliczania czasu pozostałego do startu rakiety. Rozpoczyna się to wysłaniem serii stron odliczających od 10 do1. Każda strona zastępuje swoją poprzedniczkę. Kiedy odliczanie dotrze do 0, aplet przesyła zdjęcie starturakiety. Aplet używa klasy użyteczności com.oreilly.servlet.MultipartResponse (pokazanej naprzykładzie 6.14), aby rozwiązać problemy związane ze szczegółami serwera cyklicznego.

Przykład 6.13.

Odliczanie do startu rakiety

import java.io.*;import javax.servlet.*;import javax.servlet.http.*;

import com.oreilly.servlet.MultipartResponse;import com.oreilly.servlet.ServletUtils;public class Countdown extends HttpServlet { static final String LAUNCH = "/images/launch.gif"; public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ServletOutputStream out = res. getOutputStream (); / / trochę // binarnego wydruku wyjściowego // Przygotuj odpowiedź wieloczęściową MultipartResponse multi = new MultipartResponse(res); // Najpierw prześlij odliczanie for (int i = 10; i > 0; i--) { multi.startResponse("tekst/zwykły") ; out.println(i + "..."); multi.endResponse() ; try ( Thread.sleep(lOOO); } catch (InterruptedException e) { }}

// Następnie prześlij obrazek startu multi.startResponse("obraz/gif") ; try { ServletUtils.returnFile(req.getRealPath (LAUNCH), out) ; } catch (FileNotFoundException e) { throw new ServletException ("Nie można było odnaleźć pliku: " + e.getMessage()); }

Page 159: Java Servlet - Programowanie

// Pamiętaj aby zakończyć odpowiedź wieloczęściową multi.finish() ; } }

Klasa MultipartResponse ukrywa większość paskudnych, uciążliwych szczegółów związanych zestosowaniem techniki serwer cykliczny. Możemy swobodnie stosować tą klasę w naszych własnych apletach. Jakwidzieliśmy na poprzednim przykładzie jej stosowanie jest całkiem proste.

Najpierw tworzy się obiekt MultipartResponse, przekazując mu obiekt odpowiedzi apletu. KlasaMultipartResponse wykorzystuje obiekt odpowiedzi aby załadować wydruk wyjściowy apletu oraz abyustawić typ treści odpowiedzi. Następnie, dla każdej strony treści, zaczynamy od wywołania metodystartResponse() i przekazania jej typu treści dla tej strony. Treść dla tej strony przesyłamy, jak zwykle dostrumienia wyjściowego. Wywołanie do endResponse() kończy stronę i wysyła treść z bufora, tak że klientmoże ją zobaczyć. Na tym etapie możemy dodać wywołanie do metody sleep(), lub jakiś inny rodzajopóźnienia, stosowanego do czasu w którym następna strona będzie gotowa do wysłania. Wywołanie do metodyendResponse() nie jest konieczne, ponieważ metoda startResponse() rozpoznaje czy poprzedniaodpowiedź została zakończona i zakańcza ją w razie potrzeby. W przypadku gdy spodziewamy się opóźnieniapomiędzy czasem zakończenia jednej odpowiedzi a początkiem następnej, powinniśmy jednak wywołać metodęendResponse(). Pozwala to klientowi na wyświetlanie ostatniej odpowiedzi, podczas oczekiwania na następną.Ostatecznie, po przesłaniu wszystkich stron, wywołanie do metody finish() kończy wieloczęściowąodpowiedź i przesyła kod informujący klienta, że nie żadne odpowiedzi nie będą już przesyłane.

Przykład 6.14 zawiera kod dla klasy MultipartResponse.

Przykład 6.14.

Klasa „MultipartResponse”

package com.oreilly.servlet;import java.io.* ;import javax.servlet.* ;import jvax.servlet.http.* ;public class MultipartResponse {HttpServletResponse res ;ServletOutputStream out;boolean endedLastResponse = true;

public MultipartResponse(HttpServletResponse response) throws IOException { // Zapisz obiekt odpowiedzi i strumień wyjściowy res = response; out = res.getOutputStream() ; // Ustaw wszystko res. setContentType ("mulltipart/x-mixed-replace; boundary=End") ; out.println() ; out.println (" --End") ;}public void startResponse(String contentType) throws IOException { // W razie konieczności zakończ ostatnią odpowiedź if (!endedLastResponse) { endResponse() ; } // Rozpocznij następną out.println("Content-Type"+ contentType) ; out.println() ; endedLastResponse = false; }public void endResponse() throws IOException ( // Zakończ ostatnią odpowiedź i opróżnij bufor, tak żeby klient mógł // zobaczyć treść out.println() ; out.println("--End") ; out.flush() ;

Page 160: Java Servlet - Programowanie

endedLastResponse = true;}public void finish() throws IOException { out.println("--End-") ; out.flush(); }}

Page 161: Java Servlet - Programowanie

Rozdział 7. Śledzenie sesji

HTTP jest protokołem bezstanowym: nie ma wbudowanej funkcji, która umożliwiałabyserwerowi rozpoznawanie, że cała sekwencja pochodzi w całości od tego samegoużytkownika. Zwolennicy poszanowania prywatności mogą postrzegać taką sytuacjępozytywnie, jednakże większość programistów WWW uważa to za wielki kłopot, jako żeaplikacje WWW nie są bezstanowe. Rozbudowane aplikacje WWW muszą współdziałać zklientem zarówno wstecznie jak i z wyprzedzeniem, zapamiętując informacje o nim pomiędzyzleceniami. Klasycznym przykładem może tutaj być aplikacja koszyka zakupów — klient musimieć możliwość wkładania artykułów do swojego wirtualnego koszyka, a serwer musizapamiętać te artykuły, aż do czasu kiedy klient wyloguje się kilka stron zleceniowych później(często trwa to nawet kilka dni!).Problem stanu HTTP można najlepiej zrozumieć wyobrażając sobie szerokie forum dyskusji,na którym jesteśmy gościem honorowym. Następnie wyobraźmy sobie, na tym forum,dziesiątki internautów rozmawiających z nami w tym samym czasie. Wszyscy zadają nampytania, odpowiadając na nasze, powodując że klepiemy w klawiaturę jak opętali usiłującodpowiedzieć wszystkim. Teraz wyobraźmy sobie, z kolei, że kiedy ktoś do nas pisze forumdyskusyjne nie podaje jego tożsamości, jedyne co widzimy to mieszanina pytań i odpowiedzi.W forum tego typu najlepsze co możemy zrobić to prowadzenie prostych konwersacji, np.poprzez odpowiadanie na bezpośrednie pytania. Jeżeli będziemy próbowali zrobić coś więcej,np. zadać swoje pytanie, może okazać się, że nie będziemy wiedzieli w którym momencienadejdzie odpowiedź. Na tym właśnie polega problem stanu HTTP. Serwer HTTP „widzi”tylko serię zleceń, potrzebuje więc dodatkowej pomocy aby dowiedzieć się kto właściwieskłada zlecenie.*

Rozwiązaniem, jak już może się zdążyliśmy domyśleć, jest przedstawienie się klienta wmomencie składania każdego zlecenia. Każdy klient musi dostarczyć niepowtarzalnyidentyfikator, który umożliwi serwerowi jego identyfikację, mogą to być również informacje,które pozwolą serwerowi poprawnie obsłużyć zlecenie. Wykorzystując przykład forumdyskusji, każdy uczestnik musi rozpocząć swoje każdą wypowiedź od czegoś na wzór: „Cześć,Mam na imię Jason i...” lub „Cześć, właśnie zapytałem o twój wiek i...”. Jak się przekonamypodczas lektury tego rozdziału, istnieje wiele sposobów umożliwiających klientom „HTTP-owym” na przesłanie wstępnych informacji wraz z każdym zleceniem.Pierwsza połowa tego rozdziału poświęcona będzie tradycyjnym technikom śledzenia sesji,wykorzystywanych przez programistów CGI: uwierzytelnianie użytkownika, ukryte poladanych formularza, przepisywanie URL-u oraz trwałe cookies. Natomiast druga połowarozdziału omawia wbudowaną obsługę dla śledzenia sesji w Interfejsie API. Obsługa ta jestzbudowana na szczycie tradycyjnych technik i znacznie upraszcza zadanie śledzenia sesji w

* Jeżeli ktoś zastanawia się nad tym, dlaczego serwer HTTP nie może zidentyfikować klienta poprzez adres IP komputerałączącego, odpowiedź brzmi: zgłoszonym adresem IP może być adres serwera pośredniczącego lub serwera, który obsługujewielokrotnych użytkowników.

Page 162: Java Servlet - Programowanie

naszych apletach. Wszystkie omówienia zawarte w tym rozdziale opierają się na założeniu, żeużywany jest serwer pojedynczy. Rozdział 12 „Aplety Przedsiębiorstw i J2EE” wyjaśnia jakjest realizowana obsługa stanu wspólnej sesji w serwerach wielo-wejściowych.

Uwierzytelnianie użytkownikaJednym ze sposobów przeprowadzenia śledzenia sesji jest wykorzystanie informacjipochodzącej z uwierzytelniania użytkownika. Uwierzytelnianie użytkownika zostałoomówione w rozdziale 4 „Odczytywanie Informacji”, jednak w celu przypomnienia,uwierzytelnianie użytkownika stosowane jest kiedy serwer WWW ogranicza dostęp doniektórych ze swoich zasobów tylko do tych klientów, którzy logują się przy użyciuokreślonego hasła i nazwy użytkownika. Po zalogowaniu się klienta, nazwa użytkownika jestdostępna dla apletu poprzez metodę getRemoteUser().Możemy wykorzystać nazwę użytkownika do śledzenia sesji klienta. Kiedy użytkownik już sięzaloguje, przeglądarka pamięta jego nazwę i odsyła ją wraz z hasłem kiedy użytkownikprzegląda kolejne strony witryny WWW. Aplet może zidentyfikować użytkownikawykorzystując jego nazwę, tym samym może również wyśledzić jego sesję. Dla przykładukiedy użytkownik doda coś do swojego wirtualnego koszyka na zakupy, fakt ten może zostaćzapamiętany (we wspólnej klasie lub np. w zewnętrznej bazie danych) i wykorzystany wprzyszłości przez inny aplet — kiedy użytkownik wejdzie na stronę końcową.Aplet, który wykorzystuje uwierzytelnianie użytkownika, mógłby dla przykładu dodać artykułdo koszyka użytkownika, przy pomocy następującego kodu:

String name = req.getRemoteUser();if (name == null) { // Wyjaśnij, że administrator serwera powinien chronić tą stronę}else ( String[] items = req.getParameterValues("artykuł"); if (items != null) { for (int i = 0; i < items.length; i++) ( addItemToCart(name, items[i]) ; } } }

Inny aplet, może w przyszłości odczytać artykuły z koszyka zakupów za pomocą następującego kodu:

String name = req.getRemoteUser();if (name == null) { // Wyjaśnij, że administrator serwera powinien chronić tą stronę}else ( String[] items = getItemsFromCart(name);}

Największą zaletą wykorzystywania uwierzytelniania użytkownika w celu przeprowadzaniaśledzenia sesji jest prosta implementacja. Po prostu mówimy serwerowi, żeby chroniłokreślony zestaw stron (zgodnie z instrukcjami z rozdziału 8 „Bezpieczeństwo”), a następniestosujemy metodę getRemoteUser() w celu identyfikacji każdego klienta. Kolejna zaleta jesttaka, iż technika ta działa nawet kiedy użytkownik wchodzi na naszą stronę z różnychkomputerów. Technika ta działa również kiedy użytkownik nie łączy się z naszą witryną lubkiedy zamyka przeglądarkę przed powrotem na naszą stronę.

Największym mankamentem uwierzytelniania użytkownika jest fakt, iż każdy użytkownik musi zarejestrować sięna konto, a następnie logować się za każdym razem kiedy odwiedza naszą witrynę. Większość użytkownikówtoleruje rejestrowanie i logowanie się jako zło konieczne kiedy uzyskują dostęp do informacji poufnych,jednakże w przypadku zwykłego śledzenia sesji jest to prawdziwa uciążliwość. Kolejna uciążliwość to fakt, że

Page 163: Java Servlet - Programowanie

podstawowe uwierzytelnianie HTTP nie zapewnia żadnego mechanizmu wylogującego; użytkownik musizamknąć swoją przeglądarkę żeby się wylogować. Problemy, w przypadku uwierzytelniania użytkownika stwarzaniemożność utrzymania przez użytkownika więcej niż jednej sesji na tej samej stronie (witrynie) WWW.Wniosek z powyższych rozważań nasuwa się sam: koniecznie potrzebujemy innych rozwiązań dla obsługiśledzenia anonimowego konta i dla obsługi uwierzytelnionego śledzenia sesji z wylogowaniem.

Ukryte pola danych formularzaJednym ze sposobów obsługi śledzenia anonimowego konta jest zastosowanie ukrytych pól danych formularza.Jak sama nazwa wskazuje, są to pola dodane do formularza HTML, które nie są wyświetlane w przeglądarceklienta. Pola te są odsyłane do serwera, kiedy formularz zawierający je, zostaje przedłożony. Ukryte pola danychformularza dołączamy przy pomocy HTML-u podobnego do poniższego:

<FORM ACTION="/ servlet /MovieFinder" METHOD="POST"> ... <INPUT TYPE=hidden NAME="zip" VALUE=" 94040 "><INPUT TYPE=hidden MAME="poziom" VALUE="expert">...</FORM>

W pewnym sensie ukryte pola danych formularza definiują zmienne stałe dla formularza. Dlaapletu otrzymującego przedłożony formularz nie ma żadnej różnicy pomiędzy ukrytym polemdanych a polem, które jest widoczne.

Przy zastosowaniu ukrytych pól danych formularzowych, możemy przepisać nasze apletykoszyka zakupów, tak że użytkownicy będą mogli dokonywać zakupów anonimowo, do czasuzakończenia. Na przykładzie 7.1 została zaprezentowana technika wykorzystująca aplet, którywyświetla zawartość koszyka zakupów użytkownika i pozwala użytkownikowi na dodaniewiększej liczby artykułów lub na zakończenie zakupów. Przykładowy widok ekranu zostałprzedstawiony na rysunku 7.1.

Przykład 7.1.

Śledzenie sesji przy użyciu ukrytych pól danych formularza

import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class ShoppingCartViewerHidden extends HttpServlet {public void doGet(HttpServletReguest req, HttpServletResponse res) throws ServletException, IOException { res. setContentType (" tekst/html") ; PrintWriter out = res.getWriter(); out.println("<HEAD><TITLE>Current Shopping Cart Items</TITLE></HEAD>"); out.println("<BODY>") ; // Artykuły z koszyka przekazywane są jako parametr artykułu. String [ ] items = req.getParameterValues ("artykuł") ;

// Drukuj bieżące artykuły z koszyka. out.println("Aktualnie w swoim koszyku masz następujące artykuły cart:<BR>"); if (items == null) { out.println("<B>None</B>") ; } else ( out.println("<UL>") ; for (int i = 0; i < items.length; i++) { out.println("<LI>" + items[i]);}

Page 164: Java Servlet - Programowanie

out.println"</UL>") ;} // Spytaj czy użytkownik chce jeszcze dodać jakieś artykuły, czy też // chce zakończyć. // Dołącz bieżące artykuły jako ukryte pola danych, tak żeby mogły być // przekazane. out.println("<FORM ACTION=\"/servlet/ShoppingCart\" METHOD=POST>"); if (items != null) { for (int i = 0; i < items.length; i++) { out.println("<INPUT TYPE=HIDDEN NAME="artykuł" VALUE=\"" + items[i] + "\">"); }}out.println("Chciałbyś<BR>");out.println("<INPOT TYPE=SUBMIT VALUE=\" Dodaj Więcej Artykułów \">");out.println("<INPUT TYPE=SUBMIT VALUE=\" Zakończ \">");out.println("</FORM>")out.println("</BODY></HTML>");}}

Rysunek 7.1.

Zawartość koszyka na zakupy

Aplet zaczyna od czytania artykułów, które już znajdują się w koszyku, używając getParameterValues("item"). Przypuszczalnie wartości parametrów artykułów zostały przesłane do tego apletu przy wykorzystaniuukrytych pól danych. Następnie aplet wyświetla użytkownikowi bieżące artykuły i pyta go czy chce jeszczejakieś dodać, czy też chce już zakończyć zakupy. Pytania zadawane są za pomocą formularza zawierającegoukryte pola danych, tak więc adresat formularza (aplet ShoppingCart) otrzymuje aktualne artykuły jako częśćprzedłożenia.

W sytuacji, w której sesji klienta towarzyszy coraz więcej informacji, przekazywanie ich za pomocą ukrytych póldanych formularza może okazać się uciążliwe. W takich sytuacjach, można przesłać tylko jedną, określonąidentyfikację (ID) sesji, która umożliwi zidentyfikowanie określonej (niepowtarzalnej) sesji klienta. ID sesjimoże być częścią kompletnych informacji o sesji, które są przechowywane na serwerze.

Zwróćmy uwagę, iż identyfikacje sesji muszą być przechowywane jako informacje tajne serwera, ponieważ klientznający ID sesji innego klienta mógłby, używając sfałszowanego ukrytego pola danych formularza, przyjąć drugątożsamość. W konsekwencji identyfikacje sesji powinny być generowane w taki sposób, żeby nie dało się ichłatwo odgadnąć lub sfałszować, a bieżące ID sesji powinny być chronione, tak więc dla przykładu, nieupubliczniajmy loginu dostępu serwera, ponieważ zarejestrowane URL-e mogą zawierać identyfikacje sesji dlaformularzy, przedłożonych za pomocą zleceń GET.

Page 165: Java Servlet - Programowanie

Ukryte pola danych formularza mogą zostać wykorzystane do wdrożenia uwierzytelniania bez wylogowania się.Prezentujemy po prostu formularz HTML jako ekran logujący, kiedy użytkownik został już raz uwierzytelnionyprzez serwer, jego tożsamość może zostać powiązana z jego określonym ID sesji. Przy wylogowywaniu się IDsesji może zostać usunięte (poprzez nie przesyłanie do klienta późniejszych formularzy) lub po prostu niezapamiętane. Rozwiązanie to zostało szerzej omówione w rozdziale 8.

Zaletą ukrytych pól danych formularza jest ich powszechność oraz obsługa „anonimowości”. Ukryte pola danychsą obsługiwane we wszystkich popularnych przeglądarkach, ponadto mogą być wykorzystywane przez klientów,którzy się nie zarejestrowali lub nie zalogowali. Główną, z kolei, wadą tej techniki jest fakt, że sesja utrzymujesię tylko poprzez sekwencję dynamicznie generowanych formularzy. Sesja nie może być utrzymana za pomocądokumentów statycznych, dokumentów przesłanych jako e-mail, dokumentów z których utworzono zakładki czyza pomocą zamknięć przeglądarki.

Przepisywanie URL-uPrzepisywanie URL-u jest kolejnym sposobem na obsługę śledzenia anonimowego konta. Wprzypadku stosowania przepisywania URL-u, każdy lokalny URL, na który kliknie użytkownik,jest dynamicznie modyfikowany lub przepisywany — w celu dołączenia dodatkowychinformacji. Te dodatkowe informacje mogą być w formie informacji dodatkowej ścieżki, wformie parametrów dodanych lub w formie jakiejś własnej, serwerowo — specyficznej zmianyURL-u. Z powodu ograniczonej przestrzeni dostępnej dla przepisywania URL, dodatkoweinformacje są zwykle ograniczane do ID określonej sesji. Dla przykładu, poniższe URL-ezostały przepisane aby przekazać identyfikację sesji 123:*

http://server:port/servlet/Rewritten originalhttp://server:port/servlet/Rewritten/123 extra path infonnationhttp://server:port/servlet/Rewritten?sessionid=123 added parameterhttp://server:port/servlet/Rewritten;jsessionid=123 custom change

Każda technika przepisywania ma swoje złe i dobre strony. Informacja dodatkowej ścieżkidziała poprawnie na wszystkich serwerach, z wyjątkiem sytuacji kiedy serwer musi użyć tąinformację jako informację prawdziwej ścieżki. Zastosowanie parametru dodanego równieżsprawdza się na wszystkich serwerach, jednak może ono powodować konflikt nazwparametrów. Standardowa, serwerowo-specyficzna zmiana działa we wszystkich warunkachdla apletów, które obsługują tą zmianę.

Przykład 7.2 prezentuje poprawioną wersję naszego „shopping cart viewer”, którywykorzystuje przepisywanie URL-u (w formie informacji dodatkowej ścieżki) w celuanonimowego śledzenia koszyka zakupów.

Przykład 7.2.

Śledzenie sesji za pomocą przepisywania URL-u

import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class ShoppingCartViewerRewrite extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IQException { res.setContentType (" tekst/html") ; PrintWriter out = res.getWriter();

* Pamiętajmy, że wartości ID sesji powinny być trudne do odgadnięcia i sfałszowania. Identyfikacja 123 nie jest dobrymprzykładem ID sesji, nadaje się tylko jako przykład książkowy.

Page 166: Java Servlet - Programowanie

out.println("<HEAD><TITLE>Current Shopping Cart Items</TITLE></HEAD>"); out.println("<BODY>"); // Pobierz bieżące ID sesji lub, w razie konieczności, wygeneruj String sessionid = req.getPathInfo(); if (sessionid == null) { sessionid = generateSessionId(); } // Artykuły z koszyka związane są z ID sesji String[] items = getItemsFromCart(sessionid); // Drukuj bieżące artykuły z koszyka. out.println("Aktualnie masz w swoim koszyku nastepujące artykuły:<BR>"); if (items == null) { out.println("<B>None</B>") ; } else { out.println("<UL>") ;for (int i = 0; i < items.length; i++) { out.println("<LI>" + items[i]) ;}out.println("</UL>") ;}

// Spytaj użytkownika czy chce jeszcze dodać jakieś artykuły, czy też //zakończyć.// Dołącz ID sesji do URL-u operacyjnego.out.println("<FORM ACTION=\"/servlet/ShoppingCart/" + sessionid+"\" METHOD=POST>") ;out.println("Czy chcesz<BR>");out.println("<INPUT TYPE=SUHMIT VALUE=\" Dodać Więcej Artykułów \">"),out.println("<INPUT TYPE=SUBMIT VALUE=\" Zakończyć \">");out.println("</FORM>") ;// zaproponuj stronę pomocy. Dołącz ID sesji do URL-u.out. println ("W celu uzyskania pomocy kliknij <A HREF=\"/servlet/Help/" + sessionid+ "?topic=ShoppingCartViewerRewrite\">here</A>"); out.println("</BODY></HTML>") ;}private static String generateSessionId() { String uid = new java.rmi.server. UID().toStringO; // gwarantowana niepowtarzalność return Java.net.URLEncoder.encode(uid); // zakoduj wszystkie specjalne // znaki}

private static String[] getItemsFromCart(String sessionid) { // Nie wdrożone }}

Pierwszą rzeczą wykonywaną przez ten aplet jest pobranie aktualnej identyfikacji sesji przy użyciu metodyIDgetPathInfo(). Jeżeli ID sesji nie jest określone, aplet wywołuje generateSessionId(), abywygenerować nową, niepowtarzalną identyfikację sesji, wykorzystując w tym celu specjalnie do tego celuzaprojektowaną klasę RMI. ID sesji jest również wykorzystywane do załadowania i wyświetlenia aktualnychartykułów z koszyka. Identyfikacja jest później dodawana do atrybutu formularza ACTION, tak że może on zostaćodczytany przez aplet ShoppingCart. Identyfikacja sesji jest również dodawana do URL-u pomocy, którywywołuje aplet Help. Coś takiego nie było możliwe przy ukrytych polach danych formularza, ponieważ apletHelp nie jest adresatem przedłożenia formularza.

Złe i dobre strony przepisywania URL-u ściśle pokrywają się z dobrymi i złymi stronami ukrytych pól danychformularza. Obie techniki są stosowane we wszystkich typach przeglądarek, pozwalają na dostęp anonimowyobie również mogą zostać użyte do wdrożenia uwierzytelniania z wylogowaniem się. Główna różnica międzytymi technikami polega na tym, że przepisywanie URL-u działa dla wszystkich dynamicznie utworzonychdokumentów, takich jak aplet Help, a nie tylko, jak ma to miejsce w przypadku ukrytych pól danych formularza— dla formularzy. Dodatkowo, przy poprawnej obsłudze serwera, własne przepisywanie URL-u może działać,nawet z dokumentami statycznymi. Niestety często przepisywanie URL-u bywa męczące.

Page 167: Java Servlet - Programowanie

Trwałe cookiesCzwarta z kolei technika umożliwiająca śledzenie sesji wiąże się z trwałymi cookies. Cookie(ciasteczko) to bit informacji przesyłany do przeglądarki przez serwer WWW, który następniemoże zostać odczytany z tej przeglądarki. Kiedy przeglądarka otrzymuje cookie, zapisuje go, inastępnie odsyła go z powrotem do serwera, za każdym razem kiedy wchodzi na jakąś jegostronę, zgodnie z pewnymi zasadami. Ponieważ wartość cookie może jednoznaczniezidentyfikować klienta, cookies są często używane do śledzenia sesji.

Cookies zostały jako pierwsze wprowadzone w Netscape Navigator’ze. Mimo iż nie były one oficjalną częściąspecyfikacji HTTP, szybko stały się, de facto, standardem we wszystkich popularnych przeglądarkach, włączniez Netscape 0.94 Beta (i późniejszych) oraz Microsoft Internet Explorer’ze 2 (i późniejszych). Obecnie gruparobocza Internet Engineering Task Force (IETF) — HTTP Working Group, pracuje nad przekształceniemcookies w oficjalny, napisany w RFC 2109, standard. Więcej informacji o cookies można znaleźć w specyfikacjiNetscape'a — Netscape's cookie specification, na stronie http://home.netscape.com/newsref/sts/cookie_spec.htmli w RFC 2109, na stronie http://www.ietf.org/rfc/rfc2109.txt. Inną, godną polecenia stroną poświęconą cookies,jest http://www.cookiecentral.com.

Praca z cookiesInterfejs API ofreuje klasę javax.servlet.http.Cookie, dla pracy z cookies. Szczegóły nagłówka HTTPdla cookies, są obsługiwane przez Interfejs API. „Ciasteczko” (cookie) tworzymy przy pomocy konstruktoraCookie():

public Cookie(String name, String value)

Konstruktor ten tworzy nowe cookie z początkową wartością i nazwą. Zasady dlapoprawnych nazw i wartości podane są w specyfikacji „Netscape’s cookie specyfication” i wRFC 2109.

Aplet może przesłać „ciasteczko” do klienta przekazując obiekt Cookie do metodyHttpServletResponse — addCookie():

public void HttpServletResponse.addCookie(Cookie cookie)

metoda ta dodaje określone cookie do odpowiedzi. Dodatkowe „ciasteczka” są dodawane poprzez kolejnewywołania do metody addCookie(). Ponieważ cookie są wysyłane przy użyciu nagłówków HTTP, powinnyzostać dodane do odpowiedzi zanim jeszcze zostanie ona zatwierdzona. Od przeglądarek wymaga się, żebyprzyjmowały 20 cookies na witrynę WWW, 300 (wszystkich) na użytkownika, mogą one ponadto ograniczyćrozmiar każdego „ciasteczka” do 4096 bajtów.

Kod potrzebny do ustawienia cookie prezentuje się w poniższy sposób:

Cookie cookie = new Cookie("ID", "123");res.addCookie(cookie) ;

Aplet pobiera cookies poprzez wywołanie metody HttpServletRequest — getCookies():

public Cookie[] HttpServletRequest.getCookies()

Page 168: Java Servlet - Programowanie

Metoda ta odsyła tablicę obiektów Cookie, zawierających wszystkie cookies przesłane przez przeglądarkę jakoczęść zlecenia lub pustą tablicę — jeżeli żadne cookies nie zostały przesłane. Wersje Interfejsu API, z przed 2.1powodowały, że metoda getCookies() odsyłała null, jeżeli żadne cookies nie zostały przesłane. Dlamaksymalnej kompatybilności najlepiej jest przyjąć null, albo jakąkolwiek pustą tablicę.

Kod, potrzebny do ściągania cookies wygląda w następujący sposób:

Cookie[] cookies = req.getCookies();if (cookies != null) { for (int i = 0; i < cookies.length; i++) { String name = cookies[i].getName() ; String value = cookies[i].getValue(); }}

Niestety, w standardowym Interfejsie API nie ma żadnej metody umożliwiającej ściągnięcie wartości cookiepoprzez nazwę. Poza jego wartością i nazwą, możemy ustawić wiele atrybutów dla cookie. Poniższe metodyużywane są do ustawiania tych atrybutów. Jak można się przekonać przeglądając uzupełnienie B „HTTP InterfejsAPI — Krótkie Omówienie” dla każdej metody „set” istnieje odpowiadająca jej metoda „get”. Metody „get” sąrzadko używane, ponieważ kiedy cookie jest przesyłane do serwera, zawieraja tylko swoje imię, wartość i wersję.Jeżeli ustawimy atrybut na cookie, otrzymane od klienta, musimy dodać go do odpowiedzi, żeby zmiana doszłado skutku, powinniśmy również dopilnować, aby wszystkie atrybuty z wyjątkiem nazwy, wartości i wersji zostaływyzerowane, podobnie jak cookie.

public void Cookie.setversion(int v)

ustala wersję „ciasteczka”. Aplety mogą przesyłać i otrzymywać cookies sformatowane, takaby pasować zarówno do „Netscape persistent cookies” (Wersja 0) lub nowszego, jednaktrochę eksperymentalnego RFC 2109 cookies (Wersja 1). Nowo zaprojektowane cookiesdomyślne dla wersji 0, utworzone dla zmaksymalizowania interoperacyjności.

public void Cookie.setDomain(String pattern)

określa wzór ograniczenia domeny. Wzór domeny, z kolei, określa które serwery powinnyzobaczyć cookie. Domyślnie „ciasteczka” są odsyłane tylko do komputera centralnego któryje zapisał. Określanie wzoru nazwy domeny przesłania to. Wzór musi rozpoczynać się kropkąi zawierać co najmniej dwie kropki. Wzór pokrywa się tylko z jedną pozycją, poza początkowąkropką. Dla przykładu .foo.com jest poprawne i pokrywa się z www.foo.com i zupload.foo.com, lecz nie z www.upload.foo.com.* Więcej o wzorach domen można znaleźć wspecyfikacji „Netscape’s cookie specyfication i z RFC 2109.

public void Cookie.setMaxAge(int expiry)

określa maksymalny wiek cookie w sekundach, po którym traci ono ważność. Wartośćnegatywna wskazuje domyślnie, że „ciasteczko” powinno się „przeterminować” kiedyprzeglądarka „wychodzi”. Wartość zerowa informuje przeglądarkę, że ma natychmiastwyzerować cookie.

public void Cookie.setPath(String uri)

* Technicznie rzecz ujmując, zgodnie ze specyfikacją „Netscape cookie specyfication”, dla domen najwyższej klasywymagane są dwie kropki, domenami tymi mogą być .com, .edu, .net, .org, .gov, .mil, i .int, natomiast dla pozostałych domenwymagane są trzy kropki. Reguła nie pozwala serwerowi na przypadkowe lub szkodliwe ustawianie cookie dla domen .co.uki przekazywanie ich do wszystkich przedsiębiorstw w Wielkiej Brytanii, dla przykładu. Niestety prawie wszystkieprzeglądarki ignorują metodę trzech kropek. Więcej na ten temat na stroniehttp://homepages.paradise.net.nz~glineham/cookiemonster.html.

Page 169: Java Servlet - Programowanie

— określa ścieżkę dla cookie, które jest podzbiorem URI, do których cookie powinno być wysłane. Domyślnie„ciasteczka” są wysyłane do strony, która ustawia cookie oraz do wszystkich stron w tym katalogu lub pod tymkatalogiem. Dla przykładu, jeżeli /servlet/CookieMonster ustawi cookie, wartością domyślną ścieżki będzie /servlet. Ścieżka ta informuje o tym, że cookie powinno być przesłane do /servlet/Elmo oraz do /servlet/subdir/BigBird, jednak nie do zamiennika apletu /Oscar.html lub do jakichkolwiek programów pod /cgi-bin. Ścieżka ustalona na / powoduje iż cookie jest przesyłane do wszystkich stron na serwerze. Ścieżka cookiemusi zawierać aplet, który ustawia cookie.

public void Cookie.setSecure(boolean flag)

informuje czy cookie powinno zostać przesłane wyłącznie przez bezpieczny kanał, taki jak SSL. Wartościądomyślną jest false.

public void Cookie.setComment(String comment)

ustawia pole komentarza „ciasteczka”. Komentarz opisuje cel, dla którego cookie zostało stworzone.Przeglądarki WWW mogą zdecydować się na wyświetlenie tego tekstu użytkownikowi. Komentarze nie sąobsługiwane przez wersję „0” cookies.

public void Cookie.setValue(String newValue)

— przypisuje nową wartość do cookie. W przypadku wersji „0” cookies, wartości nie powinny zawierać: spacji,nawiasów kwadratowych, nawiasów zwykłych, znaków równości, przecinków, cudzysłowów, ukośników,znaków zapytania, znaków @, dwukropków oraz średników. Puste wartości nie mogą zachowywać się w ten samsposób na wszystkich przeglądarkach.

Robienie zakupów przy pomocy trwałych cookiesPrzykład 7.3 ukazuje wersję naszego „shopping cart viewer”, zmodyfikowanego abyobsługiwać koszyk zakupów przy użyciu trwałych cookies.

Przykład 7.3.

Śledzenie sesji wykorzystujące trwałe cookies

import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class ShoppingCartViewerCookie extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException ( res.setContentType (" tekst/html") ; PrintWriter out = res.getWriter() ; // Pobierz aktualne ID sesji poprzez przeszukanie otrzymanych cookies. String sessionid = null; Cookie[] cookies = req.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { if (cookies[i].getName().equals("sessionid")) { sessionid = cookies[i].getValue() ; break; } } }

// Jeżeli ID sesji nie zostało przesłane, wygeneruj je. // Następnie prześlij go do klienta razem z odpowiedzią. if (sessionid == null) {

Page 170: Java Servlet - Programowanie

sessionid = generateSessionId(); Cookie c = new Cookie("sessionid", sessionid); res.addCookie(c); } out.println("<HEAD><TITLE>Current Shopping Cart Items</TITLE></HEAD>"); out.println("<BODY>") ; // Artykuły z koszyka są związane z ID sesji String[] items = getItemsFronCart(sessionid);

// Drukuj aktualne artykuły z koszyka. out.println("aktualnie masz w koszyku następujące artykuły cart;<BR>"); if (items == null) { out. println (" <B>None< /B>") ;)else { out.println("<UL>") ; for (int i = 0; i < items.length; i++) { out.println("<LI>" + items[i]) ;) out.println("</UL>") ;}// Spytaj czy chcą dodać jeszcze jakieś artykuły czy też zakończyć // zakupy.out.println. ("<FORM ACTION=\"/servlet/ShoppingCart\" METHOD=POST>°);out.println("Czy chcesz <BR>");out.println("<INPUT TYPE=SUBMIT VALUE=\" Dodać Więcej Artykułów \">");out.println ("<INPUT TYPE=SUBMIT VALUE=\" Check Out \">");out.println("</FORM>") ;// Zaproponuj stronę pomocy.out.println("Dla pomocy, kliknij <A HKEF=\"/servlet/Help" + "?topic=ShoppingCartViewerCookie\">here</A>") ;out.println( "</BODY></HTML>") ;}

private static String generateSessionId() { String uid = new java.rmi.server.UID().toString(); // gwarantowana // niepowtarzalniość return java.net.URLEncoder.encode(uid); // zakoduj wszystkie specjalne znaki} private static String[] getItemsFromCart (String sessionid) { // Nie wdrożono }}

Aplet ten próbuje najpierw ściągnąć identyfikację klienta, przechodząc kolejno przez cookies,które otrzymał jako część swojego zlecenia. Jeżeli żadne „ciasteczko” nie zawiera ID sesji,aplet generuje nowe identyfikację, wykorzystując metodę generateSessionId() i dodająccookie zawierające nowe ID sesji do zlecenia. Pozostała część apletu pokrywa się z wersją —przepisywanie URL-u, z wyjątkiem wersji, która nie przeprowadza przepisywania.Trwałe cookie oferują elegancki, prosty i wydajny sposób wdrażania śledzenia sesji.„Ciasteczka” zapewniają najprostsze z możliwych wprowadzeń dla każdego zlecenia. Dlakażdego również zlecenia, cookie może automatycznie dostarczyć identyfikację klienta lubnawet listę preferencji klienta. Dodatkowo, zdolność „Ciasteczek” do dostosowywania, dajeim dodatkową przewagę i urozmaicenie.Największym mankamentem cookies jest fakt, iż przeglądarki nie zawsze je akceptują. Czasemjest to spowodowane tym, że po prostu nie obsługują „Ciasteczek”. Częściej jednak dzieje siętak z powodu tego, że użytkownik ma skonfigurowaną przeglądarkę aby odrzucała cookies (wgrę może wchodzić, np. chęć zachowania prywatności). Urządzenia WAP, z powoduograniczonej objętości pamięci, również obecnie nie obsługują „Ciasteczek”, a bramy WAPdopiero niedawno zaczęły radzić sobie z nimi, w imieniu swoich urządzeń. Jeżeli któryś znaszych klientów nie zaakceptuje cookies, powinniśmy wtedy odwołać się do rozwiązańomówionych wcześniej w tym rozdziale.

Page 171: Java Servlet - Programowanie

API — śledzenie sesjiSzczęśliwie dla nas, projektantów apletów, aplety nie muszą wykorzystywać do obsługiswoich własnych sesji, technik które omówiliśmy wcześniej. Interfejs API dostarcza wielumetod oraz klas zaprojektowanych specjalnie w celu obsługi krótko terminowego śledzeniasesji, w imieniu apletów. Inaczej mówiąc aplety mają wbudowane krótko terminowe śledzeniesesji.*

Śledzenie Sesji API, jako że powołujemy się na fragment Interfejsu API, poświęcony śledzeniusesji, powinno być obsługiwane przez wszystkie serwery WWW obsługujące aplety. Jednakżestopień obsługi zależny jest od serwera. Większość serwerów obsługuje śledzenie sesjipoprzez stosowanie trwałych cookies, zachowując zdolność powrotu do przepisywania URL-u, w sytuacji gdy „Ciasteczko” zawiedzie. Niektóre serwery pozwalają na to, aby obiekty sesjibyły zapisywane na dysku serwera lub, w razie wypełnienia pamięci lub wyłączenia serwera,w bazie danych. (Artykuły które umieszczamy w sesji, aby opcja ta przynosiła korzyści,wymagają implementacji interfejsu Serializable). Szczegóły związane z naszym serweremmożna znaleźć w jego dokumentacji. Pozostała część tego podrozdziału opisuje funkcję —najniższy wspólny mianownik, wymagany przez Interfejs API 2.2 dla nie rozproszonychserwerów. Rozproszone śledzenie sesji zostało omówione w rozdziale 12.

Podstawy śledzenia sesjiŚledzenie sesji jest zadziwiająco „eleganckie”. Każdy użytkownik jest związany z obiektemjavax.servlet,http.HttpSession, który może zostać wykorzystany przez aplety doprzechowywania i pobierania informacji dotyczących użytkownika. W obiekcie sesji możemyzapisać dowolny zestaw obiektów Javy. Dla przykładu obiekt sesji użytkownika zapewniawygodną lokalizację do przechowywania (przez aplet) zawartości koszyka na zakupyużytkownika lub, jak się przekonamy czytając rozdział 9 Podłączalność do bazy danych”połączenie z bazą danych użytkownika.W celu zapewnienia niezależności działania aplikacji WWW, sesje są „kalibrowane” napoziomie aplikacji WWW. Oznacza to, iż każdy ServletContext utrzymuje swoją własną pulękopii HttpSession oraz że aplet działający wewnątrz jednego kontekstu nie ma dostępu doinformacji sesji zapisanej przez aplet w innym kontekście.Aplety wykorzystują metodę getSession() swojego obiektu zlecenia, w celu pobraniabieżącego obiektu HttpSession:

public HttpSession HttpServletRequest.getSession(boolean create)Metoda ta odsyła bieżącą sesję, związaną z użytkownikiem składającym zlecenie. Wprzypadku gdy użytkownik nie ma żadnej aktualnej sesji, metoda ta tworzy ją, jeżeli createjest prawdziwe (true) lub odsyła null (zero) — gdy create jest fałszywe (false).Standardowym zastosowaniem tej metody jest getSession (true), tak więc w celuuproszczenia, w Interfejsie API 2.1 nie wprowadzono żadnej bezargumentowej wersji, któraprzyjmowałaby znacznik create lub true.

public HttpSession HttpServletRequest.getSession()Aby mieć pewność, że sesja jest właściwie utrzymywana, metoda getSession() musi zostaćwywołana przynajmniej raz przed zatwierdzeniem odpowiedzi.Za pomocą metody setAttribute() możemy dodać dane do obiektu HttpSession:

* Takie, a nie inny układ materiału niniejszej książki może przypominać sytuację nauczyciela trzeciej klasy szkołypodstawowej, który tłumaczy swoim uczniom rachunkowe zasady dzielenia, tylko po to, aby powiedzieć im później, żedzielenie najlepiej wykonuje się za pomocą kalkulatora. Wierzymy jednak, iż poznanie tradycyjnych metod pozwala nalepsze zrozumienie problemu.

Page 172: Java Servlet - Programowanie

public void HttpSession.setAttribute(String name. Object value)Metoda wiąże określoną wartość obiektu z określoną nazwą. Wszystkie wcześniejszepowiązania z tą nazwą są usuwane. Aby pobrać obiekt z sesji, wykorzystujemy metodęgetAttribute():

public Object HttpSession.getAttribute(String name)Metoda ta odsyła związany z określoną nazwą lub null — jeżeli nie ma żadnego powiązania.Możemy również uzyskać nazwy wszystkich obiektów związanych z sesją, za pomocą metodygetAttributeNames():

public Enumeration HttpSession.getAttributeNames()odsyła Enumeration (wyliczenie) zawierające nazwy wszystkich obiektów związanych z tąsesją jako obiekty String lub puste Enumeration — jeżeli nie było żadnych powiązań. Możemyteż wreszcie usunąć obiekt z sesji przy pomocy metody removeAttribute():

public void. HttpSession.removeAttribute(String name)Metoda ta usuwa obiekt związany z określoną nazwą lub, jeżeli nie ma wiązania, niewykonuje żadnych operacji. Każda z tych metod może zgłosić wyjątekjava.lang.IllegalStateException, w przypadku gdy sesja będąca przedmiotem połączeniajest nieprawidłowa (nieprawidłowe sesje zostaną omówione w kolejnym podrozdziale).Warto zwrócić uwagę, iż w Interfejsie 2.2 zostały zmienione nazwy tych metod z: setValue(), getValue(),getValueNames() oraz removeValue(), na bardziej standardowe setAttribute(), getAttribute(),getAttributeNames() oraz removeAttribute(). Metody „value” działają nadal, jednak nie są zalecane.

Wykorzystywanie śledzenia sesji — liczba wizytPrzykład 7.4 prezentuje prosty aplet, wykorzystujący śledzenie sesji do zliczania ile razyklient go wywoływał, tak jak to zostało pokazane na rysunku 7.2. Aplet wyświetla równieżwszystkie powiązania z bieżącą sesją.

Przykład 7.4.

Sesja śledząca liczbę wizyt

import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class SessionTracker 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 HttpSession session = req.getSession(); // powiększ o jednostkę liczbę wizyt dla tej strony. Wartość //jest zapisywana w sesji tego klienta pod nazwą " tracker.count". Integer count = (Integer)session.getAttrifcute("tracker.count"); if (count == null) count = new Integer(1) ; else count = new Integer(count.intValue() + 1); session.setAttribute("tracker.count", count) ; out. println("<HTML><HEAD><TITLE>SessionTracker< /TITLE></HEAD>") ; out.println("<BODY><Hl>Session Tracking Demo</Hl>"); // Wyświetl liczbę wizyt dla tej strony out.println("Odwiedziłeś tą stronę " + count + ((count. intValue () ==1) ? " raz." : " razy."));

Page 173: Java Servlet - Programowanie

out.println("<P>") ; out.println("<H2>Oto twoje dane sesji:</H2>"); Enumeration enum = session.getAttributeNames(); while (enum.hasMoreElements()) { String name = (String) enum.nextElement() ; out.println(name + ": " + session.getAttribute(name) + "<BR>"); } out.println("</BODY></HTML>") ; } }

Rysunek 7.2.

Liczenie liczby wizyt

Aplet pobiera najpierw obiekt HttpSession, związany z aktualnym klientem. Poprzez wykorzystaniebezargumentowej wersji metody getSession(), aplet prosi aby w razie konieczności sesja została utworzona.Aplet pobiera następnie obiekt Integer, związany z nazwą tracker.count. Jeżeli nie m żadnego obiektu,rozpoczyna liczenie od nowa. W przeciwnym wypadku, zastępuje obiekt Integer nowym obiektem Integer,którego wartość została zwiększona o jeden. W końcu wyświetla aktualną liczbę wizyt oraz wszystkie aktualnepary nazwa-wartość, w sesji.

Czas istnienia (cykl życia) sesjiSesje nie trwają nieskończenie. Czas istnienia sesji kończy się automatycznie, albo po jakimś,określonym czasie nie wykorzystywania, (dla serwera „Tomcat” domyślny czas niewykorzystywania wynosi 30 minut) lub jest to wykonywane ręcznie — w wypadku gdy sesjajest unieważniona przez aplet w sposób wyraźny. Wyłączenie serwera może, ale nie musioznaczać unieważnienia sesji, jest to zależne od możliwości serwera. Kiedy czas istnieniakończy się (lub kiedy sesja jest unieważniana) obiekt HttpSession oraz wartości danych, którezawiera, są usuwane z systemu.Pamiętajmy o tym, że wszystkie informacje zapisane w obiekcie sesji użytkownika zostająutracone w momencie, w którym sesja jest unieważniana. Jeżeli takie informacje potrzebnebędą nam jeszcze w późniejszym terminie, powinniśmy je przechowywać w lokalizacjizewnętrznej, (takiej jak baza danych) i zaopatrzyć nasz własny mechanizm wiążącyużytkownika, w pozycje bazy danych użytkownika. Powszechnymi rozwiązaniami tegoproblemu są: jakaś forma logowania się, ustawianie długo terminowego cookie, które możebyć odczytywane przez lata, i (lub) dostarczenie klientowi przepisanego do zakładki URL-u.Przykładem wziętym z codziennego życia jest „My Yahoo!” gdzie użytkownicy logują się przyużyciu formularza HTML, ich sesja logująca jest śledzona przy użyciu „Ciasteczka”, któregoczas istnienia kończy się wraz z zamknięciem przeglądarki, i gdzie (jeżeli zaznaczą pole„Remember My ID & Password box — Zapamiętaj moją identyfikację i hasło) ich tożsamośćjest rejestrowana przez ustawione w tym celu, trwałe cookie, aż czasu kiedy ostatecznie sięwylogują. Jest to szczególnie interesujące, że nawet gdy „Yahoo” zna tożsamość

Page 174: Java Servlet - Programowanie

użytkownika, dzięki trwałemu cookie, nadal jednak wymaga od użytkownika wprowadzeniaswojego hasła — przed uzyskaniem dostępu do poczty elektronicznej oraz innych poufnychinformacji.Wbudowana zdolność śledzenia sesji może być nadal używana w połączeniu zdługookresowymi sesjami, w celu obsłużenia sesji logowania, przechowania dojścia dodanych zewnętrznych i (lub) utrzymywania przechowywanej w pamięci podręcznej bazydanych, informacji, w celu szybkiego z nich skorzystania w przyszłości.

Ustawianie terminu ważności sesjiIdealną sytuacją byłoby unieważnienie sesji w momencie kiedy użytkownik zamyka swojąprzeglądarkę, łączy się z inną stroną (witryną), lub odchodzi od swojego komputera. Niestetyserwer nie ma żadnej możliwości wykrycia podobnych zdarzeń. W konsekwencji sesje „żyją”przez cały okres bezaktywności, po upływie którego serwer zakłada, że użytkownik nie będziejuż dalej aktywny i że w związku z tym nie ma sensu utrzymywania dalej dla niego stanu sesji.Domyślny termin ważności (limit czasu) sesji może zostać określony przy pomocy deskryptorawdrożenia web.xml; odnosi się to do wszystkich sesji, utworzonych w danej aplikacji WWW.Zobaczmy jak prezentuje się sytuacja na przykładzie 7.5.Przykład 7.5.

Ustawianie terminu ważności na jedną godzinę

<?xml version="1.0" kodowanie="ISO-8859-l"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.// Aplikacja WWW DTD 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"><web-app> <!-- ..... -> <session-config> <session-tiineout> 60 <!-- minutes --> </session-timeout> </session-config></web-app>

Znacznik <session-timeout> utrzymuje wartość limitu czasu (terminu ważności) podaną w minutach. Dlaprzykładu, ustaliliśmy, że jeżeli użytkownik nie złoży żadnego zlecenia do aplikacji WWW, wtedy serwer możeunieważnić sesję użytkownika i „rozwiązać” obiekty w niej przechowywane. Specyfikacja apletu wymaga, abywartość terminu ważności wyrażona była liczbą całkowitą, co wyklucza wartości ujemne, jednak niektóreserwery używają takich wartości, aby zasygnalizować, że sesja nigdy nie ulegnie przeterminowaniu.

Wartości limitu czasu mogą być również dla sesji konfigurowane indywidualnie. Obiekt HttpSessiondysponuje metodą setMaxInactivateInterval() do takiego precyzyjnego sterowania:

public void HttpSession.setMaxInactiveInterval(int secs)Metoda ta określa wartość terminu ważności dla sesji, podanego w sekundach. Wartośćujemna przedziału oznacza iż sesja nie ulegnie nigdy przeterminowaniu. Tak, jednostki niepokrywają się z tymi ze znacznika <session-timeout>, nie jest to niczym uwarunkowane, jestto po prostu przypadek. Aby łatwiej zapamiętać, które jednostki należy zastosować, stosujmynastępujący schemat myślenia: „Dla precyzyjnej kontroli używamy precyzyjnych jednostek”.Aktualna (bieżąca) wartość limitu czasu może być uzyskana przy wykorzystaniu metodygetMaxInactiveInterval():

public int HttpSession.getMaxInactiveInterval()

Page 175: Java Servlet - Programowanie

Metoda ta odsyła wartość terminu ważności dla każdej sesji, w sekundach. Jeżeli nie określimy znacznika<session-timeout> możemy wywołać tą metodę na nową sesję, aby określić domyślny limit czasu naszegoserwera.

Na przykładzie 7.6 zaprezentowaliśmy omawiane metody, w aplecie, który wyświetla bieżącą wartość limituczasu, a następnie ustawia nową wartość terminu ważności na dwie godziny. Podczas pierwszego wywołania,bieżąca wartość limitu czasu wyświetla ogólno-aplikacyjne ustawienia. Podczas drugiego wywołania aktualnylimit czasu wyświetla dwie godziny — ponieważ jest to limit czasu ustawiony podczas pierwszego wywołania.

Przykład 7.6.

Ustawianie terminu ważności

import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class SessionTimer extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IQException { res.setContentType("tekst/html") ; PrintWriter out = res.getWriter(); // Pobierz aktualny obiekt sesji, w razie konieczności utwórz go HttpSession session = req.getSession(); out.println("<HTML><HEAD><TITLE>SessionTimer</TITLE></HEAD>") ; out.println("<BODY><Hl>Session Timer</Hl>") ; // Wyświetl poprzedni termin ważności out.println("Poprzedni termin ważności to " + session. getMaxInactiveInterval ()) ; out.println("<BR>") ; // 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("</BODY></HTML>") ; }}

Wybieranie właściwego terminu ważnościTak 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 niedeterminuje 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łabytrwać godzinami.Określenie prawidłowego okresu bez-aktywności musi być kompromisem pomiędzy wygodą użytkownika, jegobezpieczeń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 abysprawdzić pocztę elektroniczną — bez utraty stanu. Krótsze limity czasu zwiększają bezpieczeństwouż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żowcześniej.

Na wstępie zadajmy sobie pytanie jaki jest maksymalny czas oczekiwania naszego użytkownika pomiędzyzleceniami? Zwykle odpowiedź brzmi: pół godziny.

Rozważmy sobie tą odpowiedź i poznajmy parę niezmiennych zasad:

Page 176: Java Servlet - Programowanie

• Bezpieczne aplikacje WWW, takie jak bankowość „on line”, powinny mieć krótsze niż zwykłe limity czasuaby 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, wprzypadku 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 sesjijeż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 limitczasu 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 ustawieniawłasnego limitu czasu, opartego na preferencjach użytkownika lub nawet w celu jego zmiany w czasie trwaniasesji — np. aby uczynić limit czasu krótszym po przechowywaniu „kosztownego” artykułu, możemy posłużyć sięmetodą setMaxInactiveInterval().

Metody czasu trwaniaIstnieje 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 sesjizostają „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ść longreprezentującą liczbę milisekund, która upłynęła od północy, 1 stycznia, 1970 roku. Bieżące zlecenie niejest uznawane jako ostatnie.

Każda z metod może zgłosić wyjątek java.lang.IllegalStateException w przypadku gdy sesja, do którejuzyskiwany jest dostęp jest nieważna.

„Ręczne” unieważnianie starej sesjiAby 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ższyniż jedną godzinę.

Przykład 7.7.

Unieważnianie starej sesji

import java. io. *;import java.uti1.*;import javax.servlet.*;import javax.servlet.http.*;public class ManualInvalidate extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("tekst/html") ;

Page 177: Java Servlet - Programowanie

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 sesjiTak więc zastanówmy się jak serwer WWW wdraża śledzenie sesji? Kiedy użytkownik po raz pierwszyuruchamia aplikację WWW, jest mu przypisywany nowy obiekt sesji HttpSession oraz niepowtarzalnaidentyfikacja sesji (ID sesji). ID sesji identyfikuje użytkownika i jest wykorzystywana do dopasowaniauż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 kopiiHttpSession. Z kolei inne serwery przypisują jedną identyfikację sesji do jednej aplikacji WWW, jakododatkowe 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órzynie 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ątekIllegalStateException w przypadku gdy sesja nie jest ważna.

Pamiętajmy, iż identyfikacja sesji powinna być traktowana jako tajna informacja serwera. Zwracajmy uwagę nato, co robimy z tą wartością.

Wycofanie Obiektu HttpSessionContextW 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 jeszczeistnieją. Klasa ciągle jeszcze istnieje — dla zachowania kompatybilności binarnej, jednakpoczą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 łatwodostępnej, pojedynczej lokalizacji, zwłaszcza jeżeli istnienie takiej lokalizacji nie daje żadnychznaczących korzyści, poza „debagowaniem”.

Page 178: Java Servlet - Programowanie

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. Serwerodczytuje 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żaHttpURLConnection w taki sposób, że kiedy aplet tworzy połączenie HTTP, środowisko automatycznie dodajezawierające cookies przeglądarki, do zlecenia. Pozwala to apletowi na uczestniczenie w tej samej, co innezlecenia 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ć wpodobnych środowiskach jest przesyłane identyfikacji sesji do apletu oraz pozwalanie apletowi na przekazanietego ID z powrotem do serwera, jako sztucznie utworzone cookie JSESSIONID. W rozdziale 10 „Komunikacjaaplet zwykły — aplet tworzony na serwerze” została zamieszczona klasa HttpMessage — aby pomagać w tegotypu sytuacjach. Aplet może otrzymać identyfikację sesji jako zwykły parametr apletu (dodany dynamicznie dostrony 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órenie 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 goklientowi. 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 potrzebnealbo 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 kodujespecjalne 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 jestpotrzebne 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 metodyHttpServletResponse — 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 <A HREF=\"" +res.encodeURL(req.getRequestURI()) + "\">here</A>");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 jestzaprezentowany aplet przekierowujący użytkownika do URL-u zakodowanego tak, abyzawierał ID sesji:

res.sendRedirect(res.encodeRedirectURL("/servlet/NewServlet")) ;

Aplet jest w stanie wykryć czy identyfikacja sesji, wykorzystywana do zidentyfikowania aktualnego obiektuHttpSession, pochodzi od cookie czy od zakodowanego URL-u wykorzystującego metodyisRequestedSessionIdFromCookie() i isRequestedSessionIdFromURL():

Page 179: Java Servlet - Programowanie

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 identyfikacjasesji 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 URL-u, wielokrotneokna przeglądarki mogą należeć do różnych sesji lub do tej samej sesji, w zależności od tego w jaki sposób oknate 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 java.io.*;import java.util.*;import javax.servlet.*;import 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("<HTML><HEAD><TITLE>SessionSnoop</TITLE></HEAD>"); out.println("<BODY><Hl>Session Snoop</Hl>"); // Wyświetl ilość wejść na tą stronę out.println("Odwiedziłeś już tą " + count + ((count.intValue() == 1) ? " raz." : " razy.")); out.println("<P>") ; out.println("<H3>Oto twoje zachowane dane sesji:</H3>"); Enumeration enum = session.getAttributeNames(); while (enum.hasMoreElements()) { name = (String) enum.nextElement(); intln (name + ": " + session.getAttribute (name) + "<;BR>");} out.println("<H3>Oto parę ważniejszych stanów twojej sesji:</H3>"); out.println("ID sesji: " + session.getid() + " <I>(keep it secret )</I><BR>");

Page 180: Java Servlet - Programowanie

out.println("Now sesja: " + session.isNew() + "<BR>"); out.println("Termin ważności: " + session.getMaxInactiveInterval()); out.println("<I>(" + session.getMaxInactiveInterval() / 60 + " minut) < / Ix><BR>") ; out.println("Czas utworzenia: " + session. getCreationTime() ); out. println ("<I>(" + new Date (session.getCreationTime ()) + ")</I>><BR>") ; out.println("Ostatni czas wejścia: " + session.getLastAccessedTime()); out.println("<I>(" + new Date(session.getLastAccessedTime()) + ")</I><BR>") ; out.println("ID sesji będące przedmiotem zlecenia z URL: " + req. isRequestedSessionIdFromURL () + " <BR>") ; out.println("ID sesji, będące przedmiotem zlecenia jest ważne: " + req.isRequestedSessionIdValid() + "<BR>"); out.println("<H3>Test URL Rewriting</H3>"); out.println("Kliknij <A HKEF=\"" + res.encodeURL (req.getRequestURI()) + "\">here</A>") ; 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("</BODY></HTML>") ; }}

Aplet rozpoczyna tym samym kodem, który został zamieszczony w przykładzie 7.4, w aplecieSessionTracker. Następnie przechodzi do dalszego wyświetlania bieżącego ID sesji, tego czyjest to sesja nowa, wartości limitu czasu, czasu utworzenia sesji oraz czas ostatniegopołączenia z sesją. Później aplet wyświetla czy identyfikacja sesji (jeżeli jest taka), będącaprzedmiotem zlecenia, pochodzi od cookie czy od URL-u oraz czy to ID jest ważne. W końcuaplet drukuje zakodowany URL, który może zostać wykorzystany do powtórnego załadowaniatej strony, w celu sprawdzenia czy przepisywanie URL-u działa nawet wtedy kiedy cookies niesą obsługiwane.

Zdarzenia wiążące sesjęNiektóre obiekty mogą „chcieć” przeprowadzić operację zarówno wtedy, kiedy są związane zsesją, jak i wtedy kiedy nie są z nią związane. Dla przykładu, połączenie z bazą danych możerozpoczynać transakcję (obsługę zlecenia) w stanie związania z sesją, a kończyć kiedy nie są znią związane. Wszystkie obiekty, które wdrażają interfejsjavax.servlet.http.HttpSessionBindingListner, są powiadamiane o związaniu z sesją orazo tym, że nie są z nią związane. Interfejs deklaruje dwie metody: valueBound() orazvalueUnbound(), 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ą obiektjest wiązany (lub odwiązywany od niej) z metodą getName():

public String HttpSessionBindingEvent.getName()

Page 181: Java Servlet - Programowanie

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 odktó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 orazHttpSessionBindingEvent, z odbiornikiem rejestrującym w czasie związania jak i w czasie rozwiązania zsesją.

Przykład 7.9.

Zdarzenia wiążące śledzenie sesji

import java.io.*;import java.util.*;import javax.servlet.* ;import 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

Page 182: Java Servlet - Programowanie

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 niewywoł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” ipowtórnie wiązany z sesją, w czasie tego samego wywołania setAttribute(). Czwarta pozycja ma miejscepół godziny później, kiedy limit czasu sesji kończy się, a ona sama jest unieważniana.

Robienie zakupów przy użyciu śledzenia sesjiZakończmy ten rozdział spojrzeniem na to, jak zadziwiająco prostym może stać się nasz aplet „shopping cartviewer, przy zastosowaniu śledzenia sesji. Na przykładzie 7.10 zaprezentowano aplet zapisujący każdy artykuł zkoszyka, 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.*;

Page 183: Java Servlet - Programowanie

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("<HTML><HEAD><TITLE>SessionTracker</TITLE></HEAD>"); out.println("<BODY><Hl>Session Tracking Demo</Hl>");

// Drukuj aktualne artykuły z koszyka. out.println("Aktualnie masz w swoim koszyku następuje artykuły:<BR>"); if (items == null) { out.println("<B>None</B>") ;}else ( out.println("<UL>") ; for (int i = 0; i < items.length; i++) { out.println("<LI>" + items[i]); } out.println("</UL>") ;}// Spytaj czy chcą dodać jeszcze do koszyka jakieś artykuły, czy też // chcązakończyć.out.println("<FORM ACTIQN=\"" +res.encodeURLC/servlet/ShoppingCart") + "\" METHOD=POST>") ;out.println("Czy chcesz<BR>");out.println("<INPUT TYPE=SUBMIT VALUE=\" Dodać Więcej Artykułów \">");out.println("<INPUT TYPE=SUBMIT VALUE=\" Zakończyć \">");out.println("</FORM>") ;// Zaproponuj stronę pomocy. Zakoduj w razie potrzeby.out.println("Dla pomocy, kliknij <A HREF=\"" + res.encodeURL("/servlet/Help?topic=ShoppingCartViewer") + "\">here</A>") ;out.println("</BODY></HTML>") ; }}

Page 184: Java Servlet - Programowanie

Rozdział 8. Bezpieczeństwo

Bezpieczeństwo danych jest jednym z najważniejszych zagadnień programowania sieciowego, ponieważ corazwięcej firm decyduje się na obsługę klientów za pośrednictwem sieci i ich Intranety zapełniane są tajnymidanymi. Bezpieczeństwo danych zajmuje się ochroną tajnych informacji przed niepowołanymi użytkownikami.W przypadku aplikacji sieciowych bezpieczeństwo rozpatrujemy jako:

• Uwierzytelnienie — możliwość weryfikacji tożsamości biorących udział w wymianie informacji.

• Autoryzacja — udostępnienie zasobów systemu wybranej grupieużytkowników lub programów.

• Poufność — pewność, że przesyłane dane mogą odczytać tylkoprzez nas określeni użytkownicy.

• Integralność — możliwość sprawdzenia, czy informacja niezostała zmieniona podczas transmisji.

Wymienione powyżej aspekty bezpieczeństwa można zilustrować prostym przykładem: klient chce być pewien,że jest połączony z właściwym serwerem (uwierzytelnienie) i żadna informacja (np. numer karty kredytowej) niezostanie przechwycona (poufność). Firma, sprzedając usługę lub przesyłając tajne informacje poprzez sieć dopracowników, chce zabezpieczyć dane przed nieuprawnionymi użytkownikami. Ponadto obie strony (klient firmyi jej pracownik) chcą mieć pewność, że otrzymali niezmienioną informację.

Wiarygodność, autoryzacja, poufność i integralność są zapewniane przy użyciu technologii certyfikatówcyfrowych. Certyfikaty cyfrowe pozwalają serwerom i klientom na używanie zaawansowanych technikkryptograficznych do zapewnienia identyfikacji i kodowania protekcyjnego. Java zawiera wbudowany zestawnarzędzi wspomagających użycie certyfikatów cyfrowych, co sprawia, że serwlety są wspaniałą platformą doobsługi bezpiecznych aplikacji sieciowych używających technologii certyfikatów cyfrowych.

Bezpieczeństwo powinno również zapewnić ochronę danych przechowywanych na serwerze przed krakerami.Java od podstaw została zaprojektowana jako bezpieczny, sieciowo-zorientowany język, w którym istniejemożliwość wykorzystania komponentów gwarantujących bezpieczeństwo własnych i obcych danych na serwerze.

Niniejszy rozdział przedstawia podstawy zabezpieczania danych w sieci i użycia technik certyfikatów cyfrowychw serwletach. Opisano tu również sposoby zabezpieczania serwera podczas uruchamiania serwletówpochodzących z niepewnych źródeł. W stosunku do poprzednich rozdziałów przedstawiono tu mniej przykładów,gdyż podejmuje on problem wyższego poziomu, a wiele poruszonych tu tematów wymaga implementacji obsługiserwera. Serwlety potraktowano tu jako dodatki.

Autorzy niniejszej książki nie biorą odpowiedzialności za problemy związane z bezpieczeństwem danych, któremogą pojawić się po zastosowaniu rad tu zawartych. Szerszy opis dotyczący problematyki bezpieczeństwa wsieci można znaleźć w książce Web Security & Commerce napisanej przez Simsona Garfinkela i Gene Spafford(O'Reilly). Oczywiście, oni także nie wezmą na siebie odpowiedzialności.

Page 185: Java Servlet - Programowanie

Uwierzytelnienie poprzez HTTPW rozdziale 4, „Odczytywanie informacji” wspomniano, że protokół HTTP posiada wbudowaną obsługęuwierzytelnienia — zwaną uwierzytelnieniem podstawowym, która jest oparta o prosty model wezwanie-odpowiedź, nazwa_użytkownika-hasło. Korzystając z tej techniki serwer zarządza bazą danych zawierającąnazwy użytkowników i hasła dostępu oraz identyfikuje określone zasoby (pliki, katalogi, serwlety, itp.) jakochronione. Gdy użytkownik zażąda dostępu do zastrzeżonych zasobów, serwer odpowie żądaniem podanianazwy użytkownika i hasła. W tym momencie przeglądarka zwykle wyświetla okno dialogowe zawierające pola,w które użytkownik wpisuje swoją nazwę i hasło, po czym dane te przesyłane są do serwera. Jeśli przesłaneserwerowi informacje istnieją w jego bazie danych, dostęp do zasobów chronionych jest przyznawany. Całyopisany tu proces uwierzytelnienia odbywa się na serwerze.

Uwierzytelnienie podstawowe nie zapewnia poufności i integralności, co sprawia, że jest to słaby systemzabezpieczenia dostępu do danych. Problem polega na tym, że przesyłane poprzez sieć hasła są kodowane zapomocą powszechnie znanej metody Base64. Każdy monitorując strumień danych TCP/IP ma pełny inatychmiastowy dostęp do przesyłanej informacji łącznie z nazwą użytkownika i hasłem chyba, że zostaniewykorzystana dodatkowa metoda kryptografii SSL (omówiona w dalszej części rozdziału). Ponadto hasła sąprzechowywane na serwerze w postaci czystego tekstu czyniąc je łatwym łupem dla każdego, kto włamie się dosystemu plików serwera. Strony zabezpieczone opisaną tu metodą nie mogą zostać uznane za bezpieczne.

Uwierzytelnienie szyfrowane (typu digest) bazuje na schemacie omówionej metody, ale poprzez sieć przesyłanyjest łańcuch tworzony za pomocą algorytmu szyfrowania MD5 z nazwy użytkownika, hasła, URI, metodyżądania HTTP i losowo generowanej przez serwer wartości jednokrotnego użycia (nounce). Obie stronytransakcji (klient i serwer) znają hasło i na jego podstawie generują szyfr. Dostęp do danych jest dozwolony, jeśliłańcuchy klienta i serwera są zgodne. Transakcje zabezpieczone w ten sposób są bezpieczniejsze, gdyż łańcuchjest ważny tylko dla jednego żądania i jednej wartości jednokrotnego użycia (nonce value). Niestety, tak jak wpoprzedniej metodzie, serwer nadal zawiera bazę danych z oryginalnymi (nie zaszyfrowanymi) hasłami. Pozatym niewiele przeglądarek obsługuje tą technikę.

Podsumowując można stwierdzić, że uwierzytelnianie za pomocą protokołu HTTP jest użyteczne wśrodowiskach wymagających niskiego poziomu bezpieczeństwa danych. Dobrym przykładem takiego środowiskajest płatna witryna gazety internetowej, gdzie twórcom bardziej zależy na dostępności serwisu niż na ścisłymbezpieczeństwie danych. W tym przypadku uwierzytelnianie za pomocą HTTP jest wystarczającą metodą.

Konfiguracja uwierzytelnienia HTTPW starszych interfejsach Servlet API (wersje wcześniejsze niż 2.2) sposób konfiguracji uwierzytelnienia różniłsię w zależności od typu serwera. Począwszy od wersji 2.2 sposób uwierzytelniania został znormalizowany. Wpliku opisu rozmieszczenia* web.xml określa się metodę zabezpieczenia danych. Za pomocą tego pliku możnastosować wybraną metodę zabezpieczenia w różnych serwerach.

Instalacja mechanizmu zabezpieczeń dla interfejsu Servlet API 2.2 na serwerze nie jest konieczna, aby zapewnićkompatybilność z tym interfejsem. Zalecana jest implementacja pełnego systemu bezpieczeństwa, ale kontenerserwletu może użyć tylko część mechanizmu bezpieczeństwa lub nie użyć go wcale. Implementacja pełnegomechanizmu bezpieczeństwa na serwerze jest wymagana tylko wtedy, gdy chcemy, aby serwer był kompatybilnyz bardziej zaawansowaną platformą Java 2 (Enterprise Edition — J2EE),w której Servlet API 2.2 stanowi tylkoczęść systemu. Poza tym, bezpieczeństwo jest jednym z najnowszych i nie do końca poznanych aspektówinterfejsu Servlet API 2.2, co sprawia, że serwery sieciowe mogą różnić się pod względem implementacjimechanizmów bezpieczeństwa. Aby mieć pewność, że witryna pozostanie bezpieczna, należy ją co najmniej razprzetestować podczas przenoszenia danych pomiędzy serwerami.

Uwierzytelnienie w oparciu o rolęZa pomocą znaczników (umieszczonych w pliku rozmieszczenia aplikacji sieciowej) można określić strony, doktórych dostęp mają tylko użytkownicy posiadający specjalne uprawnienia. W ten sposób zapewniamy dostępwszystkim użytkownikom do witryny, ale niektóre strony zawarte w witrynie możemy udostępniać przez naswybranym użytkownikom. Jednym ze sposobów zapewnienia ograniczonego dostępu jest skorzystanie z metodyuwierzytelnienia opartego o rolę. W tej metodzie, zezwolenia dostępu są przyznawane abstrakcyjnemupodmiotowi security role, a dostęp do danych mają tylko użytkownicy (lub grupy użytkowników) należący dookreślonej roli. Można, na przykład, utworzyć witrynę w ten sposób, aby strony zawierające informacje ozamiennie.

Page 186: Java Servlet - Programowanie

wynagrodzeniach były dostępne tylko użytkownikom w roli manager. W pliku opisu rozmieszczenia określanyjest tylko sposób dostępu do danych w zależności od roli użytkownika, a konkretne przypisanie rólużytkownikom (lub grupom użytkowników) ma miejsce podczas wdrażania aplikacji, przy pomocy narzędziserwerowych. To przypisanie można realizować w oparciu o informacje zawarte w plikach tekstowych, tabelachbaz danych, systemie operacyjnym, itd.

Ograniczanie dostępu do serwletuZałóżmy, że chcemy zapewnić ograniczony dostęp do serwletu, co pokazano w przykładzie 8.1. (Za pomocąomawianej metody można zabezpieczać nie tylko serwlety, ale również pliki i inne aplikacje).

Przykład 8.1.

Czy jesteś pewien, że masz pozwolenie na czytanie tego przykładu?import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class SalaryServer extends HttpServlet {public void doGet (HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {res.setContentType ("text/plain");PrintWriter out = res.getWriter();out.println("Informacja ściśle tajna:"); out.println("Każdy zarabia więcej odCiebie!"); }}

Załóżmy, że posiadamy bazę danych użytkowników naszego serwera, zawierającą listę identyfikatorów, haseł iról. Nie wszystkie serwery obsługują taką bazę danych, a te, które to robią, implementują ją w dowolny sposób.W przypadku serwera Tomcat 3.2 dane użytkowników są umieszczane w pliku conf/tomcat-users.xml,pokazanym w przykładzie 8.2. Kolejne wersje Tomcata pozwolą na bezpieczniejszy zapis informacji (naszawersja umieszcza dane w postaci niezaszyfrowanej).

Przykład 8.2. Plik conf/tomcat-users.xml

<tomcat-users> <user name="Dilbert" password="dnrc" roles="engineer" /> <user name="Willy" password="iluvalice" roles="engineer,slacker" /> <user name="MrPointyHair" password="MrPointyHair" roles="manager,slacker" /></tomcat-users>

Należy zauważyć, że tylko MrPointyHair jest w roli manager. Zakładając, że potrafi podłączyć swójkomputer do sieci, powinien być jedyną osobą mającą dostęp do naszego tajnego serwletu. Określimy to w plikuweb.xml, pokazanym w przykładzie 8.3.

Zwróćmy uwagę na to, że istotne znaczenie ma kolejność wystąpienia znaczników w pliku web.xml. Należyzawsze stosować znaczniki w następującym porządku: <security-constraint>, <login-config>, anastępnie <security-role>. Nie bez znaczenia jest również kolejność występowania elementów wewnątrztych znaczników. Tworząc pliki web.xml najłatwiej skorzystać z pomocy narzędzi graficznych, któreautomatycznie „zadbają” o odpowiednią strukturę pliku.

Przykład 8.3.

Ograniczenie dostępu za pomocą uwierzytelniania podstawowego.<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE web-appPUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN""http://java.sun.com/j2ee/dtds/web_app_2_2.dtd"><web-app><servlet><servlet_name>secret</servlet-name><servlet-class>SalaryServer</servlet-class></servlet><security-constraint><web-resource-collection><web-resource-name>

Page 187: Java Servlet - Programowanie

SecretProtection</web-resource-name><url-pattern>/servlet/SalaryServer</url-pattern> <url-pattern>/servlet/secret</url-pattern><http-method>GET</http-method><http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name> manager </role-name> </auth-constraint></security-constraint> <login-config>

<auth-method>BASIC <!-- BASIC, DIGEST, FORM, CLIENT-CERT --></auth-method><realm-name> Default <!-- Opcjonalne, użyteczne tylko dla BASIC --> </realm-name></login-config><security-role>

<role-name> manager</role-name></security-role>

</web-app>Za pomocą tego pliku opisu rozmieszczenia zabezpieczono wszystkie metody GET i POST, które udostępniają /servlet/secret i /servlet/SalaryServer użytkownikom w roli manager, zalogowanym za pomocą uwierzytelnieniapodstawowego. Pozostała część witryny jest dostępna bez ograniczeń.

Znacznik <security-constraint> chroni dostęp do danych, których ścieżki dostępu znajdują sięwewnątrz znacznika <web-resource-collection> w ten sposób, że dostęp jest zapewniony tylkoużytkownikom, których role umieszczono wewnątrz znacznika <auth-constraint>. Każdy znacznik<web-resource-collection> zawiera nazwę, chronione adresy URL oraz kilka metod HTTP oograniczonym dostępie. Dla wzorców adresów URL można użyć identycznych symboli wieloznacznych, jakichużywa się do mapowania serwletu — co opisano w rozdziale 2 „Podstawy serwletu HTTP”. Powinno sięwyznaczyć metody chronione, przynajmniej GET i POST. Jeśli nie wpiszemy znacznika <http-method>, towszystkie metody zostaną uznane za chronione. Znacznik <auth-constraint> zawiera nazwy rólużytkowników, którym udostępnia się zbiór zasobów.

Znacznik <login-config> określa sposób logowania się, którego używa aplikacja. W tym przypadkuokreślamy podstawowe uwierzytelnienie za pomocą dyrektywy Default. W znaczniku <auth-method>umieszcza się wartości BASIC, DIGEST, FORM i CLIENT-CERT, które odpowiadają typom uwierzytelnienia:podstawowemu, szyfrowanemu, w oparciu o formularz i przy użyciu certyfikatów klienckich. Ouwierzytelnieniach w oparciu o formularz i przy użyciu certyfikatów klienckich będzie mowa w dalszej częścirozdziału. Znacznik <realm-name> określa zakres użycia loginu (tu zawarta jest informacja określającauprawnienia poszczególnych ról). Ten znacznik jest używany tylko w uwierzytelnieniu podstawowym iszyfrowanym.

Znacznik <security-role> zawiera listę ról, która może być użyta przez aplikację.

W omówionej metodzie nie ma możliwości udostępniania zasobów wszystkim z pominięciem użytkowników z„czarnej listy”. Najprostszym wyjściem z tej sytuacji może być utworzenie grupy użytkowników nie należącychdo „czarnej listy”.

Teraz, gdy plik web.xml jest już gotowy, wszystkie żądania dostępu do chronionych danych zostanąprzechwycone przez serwer i dane uwierzytelniające użytkownika zostaną sprawdzone. Dostęp jest przydzielany,jeśli uprawnienia są ważne, a serwer przypisuje użytkownikowi rolę manager. W przeciwnym razie dostęp jestzabroniony, a okno dialogowe przeglądarki poprosi użytkownika o kolejną próbę zalogowania się.

Page 188: Java Servlet - Programowanie

Wyszukiwanie informacji uwierzytelnieniaSerwlet może odczytać informację na temat uwierzytelnienia dzięki dwóm metodom przedstawionym w rozdziale4: getRemoteUser() i getAuthType(). Serwlet API 2.2 zawiera dodatkową metodęgetUserPrincipal(), która zwraca obiekt implementujący interfejs java.security.Principal

public java.security.Principal HttpServletRequest.getUserPrincipal()Principal to termin techniczny określający uwierzytelniany podmiot. Może nim być użytkownik, grupa,korporacja lub po prostu identyfikator. Interfejs Principal zawiera metodę getName() zwracającą nazwępodmiotu. Metoda getUserPrincipal() służy do określenia uwierzytelnionej tożsamości użytkownika,podczas gdy getRemoteUser()do zapewnienia kompatybilności ze skryptami CGI. MetodaIsUserInRole() została także wprowadzona w Servlet API 2.2. Ta metoda zwraca wartość true, gdyużytkownik należy do określonej roli:

public boolean HttpServletRequest.isUserInRole(String role)Ta metoda pozwala na wykonanie pewnych decyzji wewnątrz serwletu. Załóżmy, że deskryptor rozmieszczeniapozwala uzyskać dostęp wielu różnym rolom. Wywołanie tej metody pozwala serwletowi na zróżnicowaniedostępu do danych zależności od uwierzytelnionej roli użytkownika.

W deskryptorze rozmieszczeń (pliku opisu rozmieszczenia) można utworzyć pseudonimy, (można sprawić, byzasięg roli mgr będzie taki sam, jak zasięg roli manager). Ma to zastosowanie podczas integracji serwletówpochodzących z innych aplikacji sieciowych, które używają innych nazw ról niż nasz serwlet. Pseudonimy sąkonfigurowane osobno dla każdego serwletu, za pomocą znacznika <security-role-ref> wewnątrzznacznika <servlet>, jak pokazano w poniższym fragmencie web.xml:

<servlet><servlet-name>secret</servlet-name><servlet-class>SalaryViewer</servlet-class><security-role-ref><role-name> mgr <!-- Nazwa używana przez serwlet --> </role-name> <role-link> manager <!-- Nazwa używana przez deskryptor rozmieszczenia --> </role-link></security-role-ref></servlet>

Może istnieć dowolna ilość znaczników <security-role-ref>. Należy pamiętać, że dane pseudonimy sąważne tylko podczas dostępu do serwletu poprzez jego zarejestrowaną nazwę.

W przykładzie 8.4 serwlet wyświetla klientowi jego nazwę, podmiot, rodzaj uwierzytelnienia (BASIC, DIGEST,FORM, CLIENT-CERT) oraz przynależność do roli manager. Aby uruchomić ten serwlet należy zainstalowaćgo na serwerze sieciowym i zabezpieczyć korzystając ze schematu objaśnionego w poprzednim podpunkcie(należy przy tym upewnić się, że dostęp do nowego <url-pattern> jest ograniczony).

Przykład 8.4.Szpiegowanie informacji uwierzytelniania

import java.io.*;import java.security.*;import javax.servlet.*;import javax.servlet.http.*;public class AuthenticationSnoop extends HttpServlet {public void doGet (HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {res.setContentType("text/html");PrintWriter out = res.getWriter();out.println("<HTML><HEAD><TITLE>AuthenticationSnoop</TITLE></HEAD><BODY>");out.println("<PRE>");out.println("Nazwa Uzytkownika: "+ req.getRemoteUser());String name = (req.getUserPrincipal() == null) ?null : req.getUserPrincipal().getName();out.println("Nazwa Podmiotu: " + name);out.println("Typ uwierzytelnienia: " + req.getAuthType());out.println("Przynależność do roli Manager: " + req.isUserInRole("manager"));out.println("</PRE>");out.println("</BODY></HTML>");

. } }

Page 189: Java Servlet - Programowanie

Po wykonaniu programu powinniśmy zobaczyć na ekranie:This is a password protected resourceNazwa Użytkownika: jhunterNazwa Podmiotu: jhunterTyp Uwierzytelnienia: BASICPrzynależność do roli Manager: false

Uwierzytelnienie w oparciu o formularzZamiast uwierzytelnienia poprzez HTTP, serwlety mogą używać formularzy HTML. Użycie tej techniki pozwalaużytkownikom wejść na chronioną witrynę poprzez odpowiednio zaprojektowaną, przyjazną użytkownikowistronę logującą. Wyobraźmy sobie, że prowadzimy bank internetowy. Lepiej utworzyć niestandardowy formularzlogujący (rys. 8.1), który poprosi o odpowiednie dane, niż korzystać z obskurnego okna przeglądarki.

Wiele banków i innych serwisów online wybrało uwierzytelnienie oparte o formularz. Implementacja takiegosystemu jest stosunkowo prostym zadaniem, ponieważ takie uwierzytelnienie jest wbudowane w Servlet API 2.2.Aby przekształcić uwierzytelnienie z podstawowego na oparte o formularz należy zamienić fragment plikuweb.xml <login-config> z przykładu 8.3 na fragment z przykładu 8.5.

Rysunek 8.1. Strona logująca do banku online

Przykład 8.5.

Konfiguracja uwierzytelnienia opartego o formularz<login-config><auth-method> FORM <!-- BASIC, DIGEST, FORM, CLIENT-CERT </auth-method><form-login-config> <!-- Przydatne tylko dla FORM --> <form-login-page> /loginpage.html </form-login-page> <form-error-page> /errorpage.html </form-error-page></form-login-config></login-config>

Należy zauważyć, że wewnątrz znacznika <auth-method> zmieniono tryb BASIC na FORM. To oznacza, żew aplikacji sieciowej należy użyć uwierzytelnienia za pomocą formularza HTML. Znacznik <realm-name>zamieniono na <form-login-config>. Ten znacznik określa stronę logującą i stronę błędnego logowaniaużywane w procesie uwierzytelnienia. Obie strony powinny być dobrze zaprojektowane i opisowe, przy czym

Page 190: Java Servlet - Programowanie

strona logująca powinna prosić użytkownika o podanie danych, a strona błędnego logowania informować serwero błędnych danych. Obie ścieżki adresów URL muszą być umieszczone w context root.

Za każdym razem, gdy serwer otrzymuje żądanie do chronionych danych, sprawdza czy użytkownik nie jest jużzalogowany. Serwer może na przykład przeszukiwać obiekt HttpSession w celu znalezienia obiektuPrincipal. Jeśli serwer odnajdzie ten obiekt, porównuje zawarte w nim role do tych, które mają dostęp dodanych. Dostęp przyznawany jest tylko temu użytkownikowi, którego rola jest umieszczona w obiekciePrincipal. Jeśli serwer nie zlokalizuje obiektu Principal albo ten obiekt nie zawiera uprawnionych ról,klient jest przekierowywany na stronę logującą (ale najpierw serwer zapisuje żądany adres URL w obiekcieużytkownika HttpSession).

Strona logująca zawiera formularz, gdzie użytkownik wprowadza nazwę i hasło, które są przesyłane do serwera.Dostęp do zasobów jest możliwy tylko wtedy, gdy nazwa użytkownika i hasło są ważne i należą douprawnionych ról obiektu Principal. Wówczas serwer przekierowuje użytkownika do chronionych zasobów,a w przeciwnym przypadku do strony błędnego logowania.

Strona logująca musi zawierać formularz ze specjalnymi wartościami, aby zapewnić dostarczenie właściwychdanych do serwera. Formularz musi być zadeklarowany jako metoda POST dla URL j_security_check(nie należy poprzedzać go ukośnikiem, chociaż niektóre serwery mogą uznać to za błąd) z nazwą użytkownikaprzesłaną w zmiennej j_username i hasłem w j_password, na przykład:<FORM METHOD=POST ACTION="j_security_check">Username: <INPUT TYPE=TEXT NAME="j_username"><br>Password: <INPUT TYPE=PASSWORD NAME="j_password"><br><INPUT TYPE=SUBMIT></FORM>Przykład 8.6 przedstawia plik loginpage.html, który generuje formularz pokazany na rysunku 8.2.

Przykład 8.6.Plik loginpage.html

<HTML><TITLE>Login</TITLE><BODY><FORM METHOD=POST ACTION=j_security_check><CENTER><TABLE BORDER=0><TR><TD COLSPAN=2><P ALIGN=center>Witam! Proszę o podanie nazwy użytkownika <br>i hasła aby się zalogować.</TD></TR><TR><TD><P ALIGN=right><B>Nazwa:</B></TD><TD><P><INPUT TYPE=TEXT NAME="j_username" VALUE="" SIZE=15></TD></TR><TR><TD><P ALIGN=RIGHT><B>Hasło:</B></TD><TD><P><INPUT TYPE=PASSWORD NAME="j_password" VALUE="" SIZE=15></TD></TR><TR><TD><CENTER><INPUT TYPE=submit VALUE=" OK. "></CENTER></TD></TR></TABLE></FORM></BODY></HTML>

Rysunek 8.2 przedstawia wygenerowany formularz.

Page 191: Java Servlet - Programowanie

Rysunek 8.2. Formularz logujący przyjazny dla użytkownika

Strona błędnego logowania określona w sekcji <login-config> pliku web.xml może być jakimkolwiekplikiem HTML. Nie przewidziano specjalnych znaczników, by ten plik dołączyć. Niestety, nie ma też dostępu dożadnych specjalnych informacji mówiących o tym, dlaczego dostęp do danych jest zabroniony i wskazującychużytkownikowi stronę, za pomocą której powinien spróbować zalogować się ponownie. Przykład 8.7 ilustrujeprostą stronę błędnego logowania.

Przykład 8.7.Plik errorpage.html

<HTML><TITLE> Login Denied <TITLE><BODY>Niestety, operacja logowania nie powiodła się.Proszę wrócić do poprzedniej strony i spróbować jeszcze raz.</BODY></HTML>

W porównaniu z uwierzytelnieniem podstawowym, logowanie przez formularz jest lepsze, ponieważ użytkownikmoże wejść do witryny poprzez przyjazną i przejrzystą stronę logującą. W obu uwierzytelnieniach hasło jestprzesyłane jako tekst jawny chyba, że kanał komunikacyjny zostanie zabezpieczony innymi metodami.

Oba omówione uwierzytelnienia nie zapewniają mechanizmu wylogowania. W przypadku uwierzytelnieniaopartego o formularz można wywołać metodę session.Invalidate(), ale nie gwarantuje to zamierzonegoefektu. W obu sposobach serwer sprawdza ważność użytkowników nawet wówczas, gdy kontrola nie powinnabyć dokonywana przez serwer (na przykład: niektóre banki wymagają podania numeru konta, hasła i PIN-u, byumożliwić dostęp). Aby rozwiązać ten problem, należy skorzystać z uwierzytelnienia niestandardowego.

Uwierzytelnienie niestandardoweZwykle uwierzytelnienie klienta odbywa się w serwerze sieciowym. Deskryptor rozmieszczenia przekazujeserwerowi informację o zastrzeżonych zasobach i sposobie przydzielania dostępu.

Często takie podejście jest wystarczające, ale czasem pożądany system ochrony nie może być zaimplementowanyna serwerze. Może być to spowodowane tym, że lista użytkowników jest zapisana w formacie nieznanymserwerowi albo udostępnianie zasobów użytkownikom odbywa się za pomocą wspólnego hasła. Aby poradzićsobie w takich sytuacjach, można skorzystać z serwletów. Serwlety mogą pobierać informacje o użytkownikachze specjalnie sformatowanego pliku lub relacyjnej bazy danych. Dodatkowo można zastosować określony systemochrony (standardowy lub niestandardowy). Za pomocą tych serwletów można dodawać, usuwać lub zmieniaćdane użytkowników.

Serwlety używają kodu stanu i nagłówków HTTP do zarządzania własnym systemem ochrony. Serwlet otrzymujezakodowane dane użytkownika w nagłówku Authorization. Odmawia dostępu danemu użytkownikowipoprzez przesłanie statusu SC_UNAUTHORIZED i nagłówka WWW-Authenticate, opisującego określonedane użytkownika. Serwer zazwyczaj obsługuje wyżej opisane zdarzenia bez udziału serwletu, ale nie ograniczadostępu do danych, gdy autoryzacja jest przeprowadzona w serwlecie.

Nagłówek Authorization przesłany przez klienta zawiera jego nazwę i hasło. W podstawowym schemacieautoryzacji, nagłówek Authorization zawiera łańcuch tekstowy username:password, zakodowany za

Page 192: Java Servlet - Programowanie

pomocą kodera Base64. Na przykład, nazwa webmaster i hasło try2gueSS są przesyłane w nagłówkuAuthorization o wartości:

Authorization: BASIC d2VibWFzdGVyOnRyeTJndWVTUwSerwlet może przesłać nagłówek WWW-Authenticate, aby poinformować klienta o sposobie autoryzacji i oobszarze weryfikacji użytkowników. Obszar stanowi zbiór kont użytkowników i chronionych zasobów. Naprzykład, do przesłania klientowi informacji, aby użył uwierzytelnienia podstawowego w dziedzinie Admin,nagłówek WWW-Authenticate wygląda następująco:

WWW-Authenticate: BASIC realm="Admin"W przykładzie 8.8 pokazano, jak serwlet wykonuje niestandardową autoryzację, otrzymując nagłówekAuthorization i wysyłając status SC_UNAUTHORIZED oraz nagłówek WWW-Authenticate. Takskonfigurowany serwlet ogranicza dostęp do „ściśle tajnego zbioru” tylko do grona użytkowników, którychrozpozna na liście. W tym przykładzie, lista jest przechowywana w prostej tablicy mieszającej, a jejzawartość jest zakodowana. Listę użytkowników można także dołączyć poprzez zewnętrzną relacyjną bazędanych.

Aby odczytać nazwę i hasło zakodowane za pomocą Base64, należy skorzystać z dekodera Base64. Na szczęściejest on dostępny w darmowej wersji. W naszym serwlecie użyjemy własnej klasycom.oreilly.servlet.Base64Decoder dostępnej na stronie http://www.servlets.com. Szczegółydotyczące kodowania Base64 można znaleźć na stronie http://www.ietf.org/rfc/rfc1521.txt*.

Przykład 8.8.

Bezpieczeństwo w serwlecie.import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.Base64Decoder;public classs CustomAuth extends HttpServlet {Hashtable users = new Hashtable();public void init (ServletConfig config) throws ServletException {super.init(config);// Nazwy i hasła są tajne! users.put("Wallace:cheese", "allowed"); users.put("Gromit:sheepnapper", "allowed"); users.put("Penguin:evil", "allowed");}public void doGet (httpServletRequest req, HttpServletResponse res)throws ServletException, IOException {res.setContentType("text/plain");PrintWriter out = res.getWriter();// pobierz nagłówek autoryzacjiString auth = req.GetHeader("Authorization");//Czy zezwalamy na dostęp temu użytkownikowi?if(!allowUser(auth)) {//nie zezwalamy, nie jest autoryzowanyres.setHeader("WWW-Autheticate", "BASIC realm=\"users\"");res.sendError(res.S.C._UNAUTHORIZED);//można zaoferować mu wpisanie na listę zatwierdzonych użytkowników }else{//dopuszczony, wiec pokażmy mu tajne rzeczyout.println("Ściśle tajne rzeczy"); }}//ta metoda sprawdza informacje użytkownika przesłana w nagłówku Authorization//z baza danych użytkowników zawarta w tablicy mieszającej usersprotected boolean allowUser (String auth) throws IOException {if (auth==null) return false; //brak authif(!auth.toUpperCase().startsWith("BASIC"))return false;//my tylko przeprowadzamy tryb BASIC//popierz zakodowaną nazwę użytkownika i hasło po "BASIC"String userpassEncoded = auth.substring(6);//zdekoduj, używając jakikolwiek dekoder Base64 (my używamy com.oreilly.servlet)String userpassDecoded = Base64Decoder.decode(userpassEncoded);//sprawdź nasza listę użytkowników czy ten jest "allowed"if("allowed".equals(users.get(userPassDecoded)))return true;

* Można także użyć klasy sun.misc.BASE64Decoder, która towarzyszy JDK.

Page 193: Java Servlet - Programowanie

elsereturn false; }

Dostęp do serwletu mają wszyscy użytkownicy. W serwlecie dokonuje się proces uwierzytelnienia: dostęp dochronionych zasobów jest przydzielany użytkownikom których dane uwierzytelniające znajdują się na liścieuprawnionych. Po dokonaniu kilku modyfikacji za pomocą tego serwletu można udostępnić dane użytkownikomposiadającym hasło grupowe albo, podobnie do anonimowego FTP, można udostępnić zasoby użytkownikowi onazwie anonymous i haśle, którym jest adres email.

Autoryzację niestandardową można użyć nie tylko do ograniczenia dostępu do pojedynczego serwletu.Gdybyśmy taką logikę dodali do naszego serwletu ViewResource, moglibyśmy zaimplementowaćniestandardowy sposób dostępu do pełnego zestawu plików. Reguła mapowania za pomocą przedrostków URLmogłaby być utworzona w ten sposób, aby ochrona serwletu ViewResource udostępniała całą strukturękatalogów chronionych plików. Gdybyśmy utworzyli specjalną podklasę z HttpServlet i dodali do niej tąlogikę, moglibyśmy w łatwy sposób ograniczyć dostęp do każdego serwletu pochodnego tej podklasy. Wmetodzie niestandardowej autoryzacji sposób ograniczenia zabezpieczeń serwera nie wpływa na sposóbzabezpieczenia jego serwletów.

Autoryzacja niestandardowa w oparciu o formularzSerwlety mają możliwość wykonania autoryzacji niestandardowej w oparciu o formularz. Poprzez logowanieniestandardowe oparte o formularz, aplikacja sieciowa może użyć strony logującej HTML. Na przykład, abyzalogować się do niektórych aplikacji bankowych, należy oprócz nazwy i hasła podać także numer konta i PIN.Takiej możliwości nie zapewnia użycie FORM <auth-method>, lecz autoryzacja niestandardowa w oparciu oformularz. Jest to bardziej skomplikowane zadanie, bo szczegóły procesu logowania muszą być obsługiwaneręcznie.

Najpierw trzeba stworzyć stronę logującą, którą można napisać tak jak inne formularze HTML. Przykład 8.9przedstawia plik login.html, który generuje formularz pokazany na rysunku 8.3.

Przykład 8.9Plik login.html

<HTML><TITLE Login </TITLE><BODY><FORM ACTION=/servlet/LoginHandler METHOD=POST><CENTER><TABLE BORDER=0><TR><TD COLSPAN=2><P ALIGN=CENTER>Witamy!<br>Proszę wprowadzić numer rachunku, <br>hasło i PIN, aby się zalogować </TD></TR><TR><TD><P ALIGN=RIGHT><B> Rachunek:</B></TD><TD><P><INPUT TYPE=TEXT NAME="account" VALUE="" SIZE=15></TD></TR><TR><TD><P ALIGN=RIGHT><B>Hasło: </B></TD><TD><P><INPUT TYPE=TEXT NAME="password" VALUE="" SIZE=15></TR></TD><TR><TD><P ALIGN=RIGHT><B>PIN:</B></TD><TD><P><INPUT TYPE=TEXT NAME="PIN" VALUE="" SIZE=15></TR></TD><TR><TD COLSPAN=2><CENTER><INPUT TYPE=SUBMIT VALUE=" OK. "></CENTER></TD></TR></TABLE>

Page 194: Java Servlet - Programowanie

</FORM></BODY></HTML>

Rysunek 8.3 przedstawia formularz.

Rysunek 8.3. Przyjazny formularz logowania się do banku

Ten formularz prosi klienta o podanie numeru konta, hasła i kodu PIN, po czym przesyła informację do serwletuLoginHandler zatwierdzającego logowanie. Klient może dostać się do tej strony bezpośrednio lub poprzezlink ze strony głównej witryny. Gdyby użytkownik chciał dostać się bezpośrednio do zasobów (bez logowaniasię) powinien być automatycznie skierowany na stronę logującą, a po pomyślnym zalogowaniu przekierowany zpowrotem. Proces powinien przebiegać niezauważalnie. Użytkownik odnosi wrażenie, że to jego przeglądarkaotworzyła okno.

Przykład 8.10 przedstawia serwlet implementujący przekierowania. Serwlet ten wysyła tajne informacje tylkowtedy, gdy obiekt sesji klienta wskazuje zalogowanie. Jeśli tak nie jest, serwlet zapamiętuje żądany URL w sesjido przyszłego użytku i przekierowuje klienta na stronę logującą.

Przykład 8.10.Chronione Źródło

import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class protectResource extends HttpServlet {public void doGet(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {res.setContentType("text/plain";PrintWriter out = res.getWriter();//pobierz sesjęHttpSession session = req.getSession();//czy sesja rozpozna użytkownika jako już zalogowanego?Object done=sesion.getAttribute("logon.isDone");//zaznacz obiektif (done == null) {//zaprzeczenie logon.isDone znaczy, że użytkownik się nie zalogował//zapisz zadany URL jako prawdziwy cel i prześlij do login page session.setAttribute("login.target", HttpUtils.getRequestURL(req).toString()); res.sendRedirect(„/login.html”); return; }//użytkownik zalogował się i może zobaczyć towaryout.println("Nieopublikowane książki wydawnictwa Helion czekają na ciebie!"); }}

Ten serwlet sprawdza, czy klient jest już zalogowany szukając obiektu logon.isDone. Jeśli taki obiektistnieje, serwlet ma pewność, że klient już się zalogował, w związku z czym pozwala mu przeglądać tajneinformacje. Brak tego obiektu oznacza, że klient nie jest zalogowany, wobec czego serwlet zapisuje żądany adresURL w zmiennej login.target i przekierowuje klienta na stronę logującą. W niestandardowej autoryzacjiopartej o formularz należy zapewnić takie zachowanie. Użycie podklas lub klas użytkowych może uprościć tozadanie.

Page 195: Java Servlet - Programowanie

W naszym przykładowym serwlecie sprawdzana jest ważność numeru konta, hasła i kodu PIN. Jeśliwprowadzone przez klienta dane są nieprawidłowe, dostęp do informacji jest zabroniony. W przeciwnymwypadku, fakt ten jest zapisywany w obiekcie sesji i klient jest przekierowywany na żądaną stronę.

Przykład 8.11.

Obsługa logowania.import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {res.setContentType("text/html");PrintWriter out = res.getWriter();//pobierz numer rachunku, hasło i PIN użytkownikaString account = req.getparametr("account");String password = req.getParametr("password");String pin = req.getParametr("pin");//sprawdzenie ważności hasła i nazwyif (!allowUser(account, password, pin)) {out.println("<HTML><HEAD><TITLE>Odmowa dostępu</TITLE></HEAD>");out.println("<BODY>Hasło i login są nieważne. <BR>");out.println("Możesz <A HREF=\"/login.html\"> sprobować ponownie</A>");out.println("</BODY></HTML>"); }else {//login ważny, zanotować to w obiekcie sesji.HttpSession session = req.getSession();session.setAttribute ("logon.isDone", account); //zaznaczenie obiektuspróbuj przesłać klienta do strony, na która chciał wejśćtry{String target = (String) session.getAttribute("login.target");if (target != null {res.sendRedirect(target);return; } }catch (Exception ignored){}//nie było możliwości przesłania do target. Przekierowanie do strony startowej.res.sendRedirect ("/");} }protected boolean allowUser (String account, String password, String pin) {return true;//zaufaj każdemu }}

Właściwy sposób kontroli danych w serwlecie jest prosty: serwlet zakłada, że wszystkie dane użytkownika sąważne. Jeśli logowanie się powiedzie, serwlet zachowuje numer rachunku użytkownika w sesji klienta pod nazwąlogon.isDone. Dla chronionych zasobów oznacza to, że klient ma do nich prawo dostępu. Klient zostajeprzekierowany do strony, której adres znajduje się w zmiennej login.target. Jeśli z jakiegokolwiek powoduta operacja się nie powiedzie, serwlet przekieruje użytkownika na stronę główną.

Certyfikaty cyfrowePrawdziwe aplikacje wymagają zabezpieczeń wyższego poziomu niż opisane do tej pory. Wymagają równieżzagwarantowania poufności i integralności informacji oraz bardziej niezawodnego systemu uwierzytelniania. Towłaśnie oferuje technologia certyfikatów cyfrowych.

Główną koncepcją jest system kryptografii oparty o klucze publiczne. W tym systemie każdy uczestnik wymianyinformacji posiada unikatową parę kluczy używanych do kodowania i dekodowania informacji. Jednym z nichjest ogólnie dostępny klucz publiczny, a drugim — tajny klucz prywatny. Aby zademonstrować zasadę działaniasystemu, załóżmy, że Jaś chce przesłać Krzysiowi tajną informację. Jaś znajduje w swoich zasobach kluczpubliczny Krzysia i za jego pomocą koduje informację. Gdy Krzyś otrzyma wiadomość, użyje swojego kluczaprywatnego do jej zdekodowania. Informacja staje się bezwartościowa dla każdego, kto ją przechwyci i nieposiada klucza prywatnego Krzysia.

Metody kodowania wykorzystujące klucze cyfrowe istnieją już od kilku lat i są dość dobrze rozwinięte.Większość z nich działa w oparciu o algorytm RSA stworzony przez Rona Rivesta, Adiego Shamira i Leonarda

Page 196: Java Servlet - Programowanie

Adelmana.* RSA używa dużej ilości liczb pierwszych do generowania unikatowej pary kluczy asymetrycznych(wiadomość zakodowaną przez jeden klucz można zdekodować używając drugiego). Generowane klucze mogąróżnić się długością, zwykle wyrażaną w ilości bitów składających się na klucz. Klucze 1024- lub 2048-bitowe sąwystarczające do zapewnienia bezpiecznej komunikacji RSA.

Ponieważ klucze składają się z tak wielu znaków, niepraktycznym podejściem byłoby zmuszanie użytkownika dowpisywania klucza za każdym razem. Zamiast tego, klucze publiczne są przechowywane na dysku w formiecertyfikatów cyfrowych, a klucze prywatne w zakodowanej formie. Certyfikaty cyfrowe mogą być generowane zapomocą specjalnych programów (takich jak np. pakiet PGP) lub są wydawane przez specjalistyczną firmę. Pliki zcertyfikatami mogą być ładowane przez najbardziej chronione aplikacje (takie jak serwery, przeglądarki iprogramy obsługujące pocztę elektroniczną).

Kryptografia kluczy publicznych rozwiązuje problem poufności, ponieważ cały proces komunikacji jestzakodowany. Metoda ta zapewnia również integralność: w naszym przykładzie Krzyś wie, że wiadomość, którąotrzymał nie została zmodyfikowana (gdyby tak było, nie byłby w stanie jej zdekodować). Nie rozwiązaliśmyjeszcze problemu wierzytelności: Krzyś nie ma pewności, że wiadomość, którą odebrał została wysłana przezJasia. Ten problem rozwiązuje podpis elektroniczny. Ponieważ klucze prywatne i publiczne są asymetryczne, Jaśmoże najpierw użyć własnego klucza prywatnego do zakodowania wiadomości, a następnie zaszyfrowaćinformację jeszcze raz, za pomocą publicznego klucza Krzysia. Gdy Krzyś odbierze wiadomość, zdekoduje jąnajpierw własnym kluczem prywatnym, a następnie kluczem publicznym Jasia. Teraz Krzyś wie, że jedynymnadawcą otrzymanej wiadomości mógł być tylko Jaś, bo wiadomość zakodowana jego kluczem prywatnym możebyć zdekodowana jedynie za pomocą jego klucza publicznego.

Prostszym systemem jest użycie kluczy symetrycznych, czyli jednego klucza kodującego i dekodującego. Kluczeasymetryczne mają dużą zaletę: przy przesyłaniu informacji nie trzeba zapewniać bezpieczeństwa kanałukomunikacyjnego. Mają też ogromną wadę: kodowanie i dekodowanie informacji za pomocą tych kluczywymaga sporej mocy obliczeniowej. Jako kompromis, wiele systemów kryptograficznych korzysta zasymetrycznych kluczy prywatnych i publicznych do wzajemnej identyfikacji i poufnego przekazaniaoddzielnego klucza symetrycznego do kodowania właściwej informacji. Klucz symetryczny jest zwykle tworzonyw oparciu o standard DES.

Rząd Stanów Zjednoczonych ograniczył rozmiar eksportowanych kluczy symetrycznych do długości 56 bitów(co daje około 72*1015 możliwych kombinacji kluczy). Wiadomości zakodowane 56-bitowym kluczem są trudnedo zdekodowania, ale nie ma rzeczy niemożliwych — wyspecjalizowane maszyny potrafią złamać kod w ciągukilku godzin. Wewnątrz USA wiele systemów używa 128-bitowych kluczy DES (około 3.40282*1038

możliwości). Ponieważ nie znaleziono jeszcze sposobu na złamanie kodu informacji zakodowanej kluczem DESw sposób brutalny, przekazywanie wiadomości z wykorzystaniem kluczy o dużych rozmiarach jest bardzobezpieczne.

Pozostaje jeszcze jeden problem: w jaki sposób jeden użytkownik może upewnić się, że ten drugi jest faktycznietym, za kogo się podaje? Jaś i Krzyś znają się, więc Krzyś wierzy, że klucz publiczny dostarczony mu osobiścieprzez Jasia jest prawdziwy**. Z drugiej strony, jeśli np. Małgosia, nie znając wcześniej Jasia, chciałaby muudostępnić swój klucz publiczny, Jaś może podejrzewać, że Małgosia jest w rzeczywistości Markiem. Jeślizałożymy, że Krzyś zna Małgosię, możemy poprosić Krzysia, by za pomocą swojego klucza prywatnegopodpisał klucz publiczny Małgosi. Wówczas, Jaś dostanie klucz Małgosi potwierdzony przez Krzysia, doktórego ma zaufanie.

W rzeczywistości, ta „trzecia strona” potwierdzająca autentyczność nazywana jest Centrum Certyfikacji. Takimcentrum jest np. korporacja VeriSign. Ponieważ VeriSign jest ogólnie znaną organizacją (z powszechnie znanymkluczem publicznym) klucze zweryfikowane przez to centrum można uznać za pewne. VeriSign oferuje kilkaklas identyfikatorów (wraz ze wzrostem numeru klasy rośnie jego poziom zaufania i cena). Certyfikaty pierwszejklasy można otrzymać po wypełnieniu odpowiedniego formularza zamieszczonego w witrynie VeriSign.Certyfikaty wyższych klas są weryfikowane indywidualnie przez pracowników tego centrum certyfikacji, przyużyciu specjalnych metod weryfikacyjnych.

Wybierając centrum autoryzacji należy zwrócić uwagę na jego prestiż. Certyfikaty VeriSign są dołączone doprzeglądarek MS IE i Netscape, więc każdy użytkownik Internetu będzie je akceptował. Poniżej wypisanonajpopularniejsze centra certyfikacji:

• VeriSign (http://www.verisign.com)

* Patent USA nr 4.405.829. Utracił ważność 20 września 2000 r.** Szczerze mówiąc, ludzie nie spotykają się w ciemnej alejce po to, aby wymienić swoje klucze publiczne. Zamiast tego wymieniają kluczew cyfrowy sposób (np. za pomocą email) i rozpoznają klucz po kilku pierwszych znakach.

Page 197: Java Servlet - Programowanie

• Thawte Consulting (http://www.thawte.com)

• Entrust Technologies (http://www.entrust.com)

Więcej informacji na temat certyfikatów cyfrowych znajduje się w książce Gail'a L.Granta (McGraw-Hill)Understanding Digital Signatures, która dostarcza wprowadzenie odpowiednie dla programistów i zwykłychużytkowników. O powiązaniu kryptografii z Javą traktuje książka Jonathana Knudsena Java Cryptography(O'Reilly).

Protokół bezpiecznej transmisji danych (SSL)Protokół SSL zajmuje miejsce pomiędzy protokołem poziomu aplikacji (w tym przypadku HTTP) a protokołemtransportowym niskiego poziomu (w przypadku Internetu — prawie wyłącznie TCP/IP). Protokół tenwykorzystuje klucze publiczne do wymiany kluczy symetrycznych, które służą do zakodowania komunikacjipomiędzy klientem i serwerem. Po raz pierwszy wykorzystano ten protokół w przeglądarce Netscape Navigator1. Od tamtej pory SSL jest standardem bezpieczeństwa komunikacji online i jest podstawą nowego protokołuTLS (który jest tworzony przez IETF). Więcej informacji na temat TLS można uzyskać na stroniehttp://www.ietf,org/rfc/rfc2246.txt.

SSL Version 2.0 jest pierwszą wersją, która zyskała szeroką akceptację. Zawiera ona wsparcie tylko dlacertyfikatów serwerowych, zapewnia uwierzytelnienie serwera, poufność i integralność. Zasady działaniaprotokołu są następujące:

1. Użytkownik łączy się z zabezpieczoną witryną przy użyciu protokołu HTTPS (protokół HTTP iSSL).Adresy URL witryn, które używają protokołu HTTPS, zaczynają się od https: zamiast http:.

2. Serwer podpisuje kluczem prywatnym swój klucz publiczny i przesyła go przeglądarce.

3. Przeglądarka używa klucza publicznego serwera, by sprawdzić, czy osoba podpisująca klucz jest wjego posiadaniu.

4. Przeglądarka upewnia się, czy zaufane centrum certyfikacji podpisało klucz. Jeśli nie, przeglądarkapyta użytkownika, czy klucz można uznać za wiarygodny i postępuje zgodnie z jego zaleceniami.

5. Klient generuje dla tej sesji klucz symetryczny DES, koduje publicznym kluczem serwera i odsyłaserwerowi. Powstały w ten sposób nowy klucz jest użyty do zakodowania transakcji. Kluczsymetryczny jest używany dlatego, że systemy oparte o klucze asymetryczne bardzo obciążająsystem.

Wszystkie opisane tu procedury są niewidoczne dla twórców serwerów i serwletów. Należy po prostu otrzymaćodpowiedni certyfikat serwerowy, zainstalować go i odpowiednio skonfigurować serwer. Informacje przesyłanepomiędzy serwletami i klientami będą od tej pory zakodowane.

Uwierzytelnianie klienta za pomocą SSLNasza skrzynka z narzędziami zabezpieczającymi zawiera już solidne systemy kodowania i uwierzytelnieniaserwera, ale słaby system uwierzytelniania klienta. Oczywiście użycie SSL 2.0 stawia nas w dużo lepszejsytuacji. Serwery wyposażone w SSL mogą jednocześnie używać podstawowych metod uwierzytelnianiaomówionych na początku niniejszego rozdziału, ponieważ nie ma możliwości przechwycenia informacji. Nadaljednak nie mamy dowodu na wiarygodność klienta, bo każdy mógłby zgadnąć lub zawładnąć hasłem i nazwąużytkownika.

Powyższy problem rozwiązano w nowszej wersji SSL 3.0 poprzez zapewnienie obsługi certyfikatów klienckich.Te certyfikaty są zarejestrowane jako klienckie, ale niczym nie różnią się od serwerowych. SSL 3.0 zuwierzytelnieniem klienckim pracuje podobnie do SSL 2.0, ale po uwierzytelnieniu serwera przez klienta, serwerżąda od niego certyfikatu. Klient przesyła podpisany certyfikat i serwer przeprowadza taką samą proceduręuwierzytelniającą, jaka odbyła się po stronie klienta (serwer porównuje certyfikat klienta z biblioteką istniejącychcertyfikatów lub przechowuje certyfikat, aby zweryfikować użytkownika przy kolejnej wizycie). Wieleprzeglądarek zanim wyśle certyfikat, jako zabezpieczenie, wymaga podania hasła przez użytkownika.

Klientowi raz uwierzytelnionemu serwer udostępni chronione zasoby (serwlety lub pliki) tak, jak za pomocąuwierzytelnienia HTTP. Cały proces przebiega w sposób niewidoczny dla użytkownika. Ponadto ten sposóbdostarcza dodatkowy poziom uwierzytelnienia, bo serwer wie, że klient posługujący się certyfikatem JasiaKowalskiego jest w istocie Jasiem Kowalskim (w dodatku może rozpoznać konkretnego Jasia Kowalskiegopoprzez jego unikatowy certyfikat). Wadami certyfikatów klienckich jest konieczność ich otrzymania i instalacji

Page 198: Java Servlet - Programowanie

przez użytkowników. Ponadto serwery muszą zawierać bazę danych z kluczami publicznymi, a przede wszystkimobsługiwać SSL 3.0.

Konfigurowanie ochrony za pomocą SSLAplikacja sieciowa, która wymaga ochrony za pomocą SSL (HTTPS) może o tym powiadomić serwer przyużyciu deskryptora rozmieszczenia. Wymaganie SSL modyfikuje plik web.xml w ten sposób, że znacznik<security-constraint> zawiera znacznik <user-data-constraint> wskazujący wymaganiadotyczące systemu zabezpieczeń. Przykład 8.12 przedstawia ten plik.

Przykład 8.12.Ta kolekcja wymaga bezpiecznego połączenia

<!-- ...itp... --><security-constraint><web-resource-collection><web-resource-name>SecretProtection</web-resource-name><url-pattern>/servlet/SalaryServer</url-pattern><url-pattern>/servlet/secret</url-pattern><url-method>GET</url-method><url-method>POST</url-method>

</web-resource-collection><auth-constraint><role-name>manager</role-name></auth-constraint><user-data-constraint><transport-guarantee> CONFIDENTIAL</transport-guarantee></user-data-constraint></security-constraint><!-- ...itp... -->Dodany znacznik <user-data-constraint> informuje o tym, że dostęp do danych jest zapewnianyużytkownikom w roli manager oraz, że użytkownicy muszą łączyć się używając trybu CONFIDENTIAL.

Znacznik <transport-guaratee> może zawierać jedną z dwóch wartości: INTEGRAL alboCONFIDENTIAL. INTEGRAL wymaga niezmienności danych podczas transmisji. Z kolei CONFIDENTIALwymaga poufności danych. Zastosowanie CONFIDENTIAL pociąga za sobą jednoczesne użycie INTEGRAL.Serwer decyduje, które algorytmy kryptograficzne można zakwalifikować jako INTEGRAL, a które jakoCONFIDENTIAL. Większość powszechnie znanych algorytmów SSL zalicza się do obu grup. Jednakże serwermoże zaliczyć algorytmy wykorzystujące kodowanie 56-bitowe DES do INTEGRAL (informacja nie może byćnaruszona, aby ją zdekodować), a nie do CONFIDENTIAL (bo łatwo złamać taki kod)*. W praktyce pomiędzytymi trybami jest niewielka różnica, a ogólnie gwarantowanym standardem jest CONFIDENTIAL.

Znaczniki <auth-constraint> i <user-data-constraint> mogą występować razem lub osobno. Naprzykład, strona obsługująca kartę kredytową wymaga komunikacji typu CONFIDENTIAL, ale może pozostaćwidoczna dla wszystkich użytkowników.

Konfiguracja uwierzytelnienia SSLSSL 3.0 zyskuje popularność, bo coraz więcej witryn uwierzytelnia użytkowników za pomocą certyfikatówklienckich. To zapewnia automatyczną i bezpieczną metodę identyfikacji użytkownika klienta. W zależności odpoziomu zaufania powiązanego z certyfikatem klienckim, można uważać mechanizm uwierzytelnienia zadostatecznie bezpieczny, by wspomagać podpisywanie prawnie wiążących kontraktów lub nawet głosowanieonline.

* Połączenie klient-serwer poprzez bezpieczną sieć VPN może spotkac się z ograniczeniem CONFIDENTIAL nawet bez użycia SSL.

Page 199: Java Servlet - Programowanie

Można zgadnąć, że uwierzytelnienie oparte o certyfikaty klienckie może być wskazane wewnątrz znacznika<login-config>:

<login-config><auth-method>CLIENT_CERT <!—Klient musi zostać zidentyfikowany za pomocą certyfikatu X.509 --!></auth-method></login-config>

Tak określony znacznik jest informacją dla serwera, że wszystkie procesy uwierzytelnienia dla danej aplikacjibędą wykonywane przy użyciu certyfikatów klienckich zamiast tradycyjnych metod: podstawowej lub w oparciuo formularz. Klient nigdy nie ujrzy strony logującej, mimo że przeglądarka poprosi o hasło do odblokowaniacertyfikatu przed przesłaniem do serwera. Jeśli przeglądarka nie otrzyma certyfikatu klienckiego, dostęp dodanych będzie zabroniony.

Wyszukiwanie informacji uwierzytelnienia SSLPodczas uwierzytelnienia podstawowego i szyfrowanego wszystkie szczegóły dotyczące SSL obsługiwane sąprzez serwer w sposób niewidoczny dla serwletów. Innymi słowy, serwlet nie musi spełniać jakichkolwiekkryteriów, by mieć dostęp do zabezpieczonego serwera! Serwlet może czasami wyszukiwać użytecznychinformacji dotyczących uwierzytelnienia SSL. Na przykład, serwlet może sprawdzić, czy połączenie z klientemjest bezpieczne używając metody isSecure():

public boolean ServletRequest.isSecure()Metoda zwraca true, jeśli serwer uzna połączenie za bezpieczne. Poziom kodowania zależy od implementacjiserwera. Potrzebne detale można znaleźć w dokumentacji serwera. Niestety, serwlet nie może w standardowysposób zażądać właściwego algorytmu kodowania użytego do połączenia, a nawet długości symetrycznegoklucza (40, 56, 128). Taką opcje można ustawić w serwerze jako atrybut o nieokreślonej do tej pory nazwie.Taka cecha jest oczekiwana w Servlet API 2.3, który używa atrybutów żądania:javax.servlet.request.cipher_suite i javax.servlet.request.key_size.

Gdy klient zostanie uwierzytelniony za pomocą CLIENT-CERT metoda getUserPrincipal() zwrócinazwę jego podmiotu. Nazwa ta jest wzięta z pola certyfikatu Distinguished Name.

Za każdym razem, gdy certyfikat kliencki jest wysyłany do serwera, serwlet może otrzymać go jako atrybutżądania. Może to pojawić się podczas zwykłego procesu przesłania z potwierdzeniem SSL 3.0, nawet gdyCLIENT-CERT nie jest określony.

java.security.cert.X509Certificate cert = (java.security.cert.X509Certificate)req.getAttribute(„javax.servlet.request.X509Certificate”);

Dla każdego serwera pracującego na J2SE 1.2 (JDK 1.2) lub obsługującego J2EE 1.2 atrybut żądaniajavax.servlet.request.X509Certyficate zwróci obiektjava.security.cert.X509Certyficate reprezentujący certyfikat X.509v3 (instrukcje konfigurującemożna znaleźć w dokumentacji serwera). Serwery pracujące na JDK 1.1, które są niekompatybilne z J2EE 1.2nie wspierają tego atrybutu, ponieważ JDK 1.1 nie zawiera pakietu java.security.cert. Serwlet zprzykładu 8.13 drukuje łańcuch certyfikatu klienta.

Przykład 8.13Sprawdzanie certyfikatów klienckich

import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class X509Snoop extends HttpServlet {public void doGet (HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {res.setContentType ("text/plain");PrintWriter out = res.getWriter();X509Certificate[] certs = (X509Certificate[])req.getAttribute("javax.servlet.request.X509Certificate");if (certs != null) {for (int i=0; i<certs.length; i++) {out.println("certyfikat klienta [" + i + "] = " + certs[i].tostoring()); } }else {if („https”.equals(req.getScheme())){out.println(„To było HTTPS, ale brak certyfikatu,”+”...”); }else {

Page 200: Java Servlet - Programowanie

out.println(„to nie był HTTPS,”+„wiec żaden certyfikat nie jest możliwy”); } } }}

Zwrócony certyfikat X509Certyficate może być użyty do sprawdzenia ważności, wydawcy, numeruseryjnego, podpisu i tak dalej. Wydruk certyfikatu jako String pokazano w przykładzie 8.14. Pierwszycertyfikat jest kluczem publicznym użytkownika. Drugi z nich to podpis VeriSign potwierdzający autentycznośćpierwszego podpisu.

Przykład 8.14.

Przykładowy certyfikat X509 dla Ramesha Mandavy.Client Certificate [0] =[[Version: V3 Subject: [email protected], CN=Ramesh babu mandava,OU=DigitalID Class 1 –Netscape, OU=Persona Not Validated, OU="www.verisign.com/repository/RPA Incorp. ByRef., LIAB.LTD©98", OU=VeriSign Trust Network, O="VeriSign, INC." Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4Key: com.sun.net.ssl.internal.ssl.JSA_RSAPublicKey@5b5870e3Validity: [From: Tue Oct 10 17:00:00 PDT 2000, To: sun Dec 10 15:59:59 PST 2000] Issuer: CN=VeriSign Class 1 CA Individual subscriber-Persona not Validated,OU="www.verisign.com/repository/RPA incorp. By Ref.,LIAB.LTD(c)98", OU=VeriSignTrust Network, O="VeriSign, Inc." SerialNumber: [ 1ef11638 5ab8aaa1 bfa2b11b3 c0fb9cd9]Certificate Extensions: 4[1]: ObjectId: 2.16.840.1.113730.1.1 Criticality=falseNetscapecertType [SSL client][2]: ObjectId: 2.5.29.32 Criticality=falseExtension unknown: DER encoded OCTET string = 0000: 04 3D 30 3B 30 39 06 0B 60 86 48 01 86 F8 45 01 .=0;09..'.H...E.0010: 07 01 08 30 2A 30 28 06 08 2B 06 01 05 05 07 02 ...0*0(..+......0020: 01 16 1C 68 74 74 70 73 3A 2F 2F 77 77 77 2E 76 ...https:/www.v0030: 65 72 69 73 69 67 6E 2E 63 6F 6D 2F 72 70 61 erisign.com/rpa[3]: ObjectId: 2.5.29.31 Criticality=falseExtension unknown: DER encoded OCTET string = 0000: 04 2C 30 2A 30 28 A0 26 A0 24 86 22 68 74 74 70 .,0*0(.&.$."http0010: 3A 2F 2F 63 72 6C 2E 76 65 72 69 73 69 67 6E 2E ://crl.verisign.0020: 63 6F 6D 2F 63 6C 61 73 73 31 2E 63 72 6C com/class1.crl[4]: ObjectId: 2.5.29.19 Criticality=falseBasiConstraints:[CA:falsePathLen: undefined]]Algorithm: [MD5withRSA]Signature:0000: 5E EC 5C F9 96 D5 3F F6 19 8B 66 0A 46 DE 02 FC ^.\...?...f.F...0010: 52 4E 32 70 5F DA 8B 92 43 F4 19 51 C3 A3 36 7D RN2P^...C...Q..60020: 02 4A 5B 35 B6 76 05 F8 FE C0 4F D7 9C B1 5B BA .J[5.v....O...[.0030: EE 38 A7 98 C5 57 A7 6B 86 B9 B2 A1 4F 25 5F FF .8...W.k....0%_.0040: OB 19 54 86 D7 14 7A F7 97 A1 E8 E7 D3 89 75 B0 ..T...z.......u.0050: 72 4F 4B 77 E4 56 5D B2 40 D2 7E 69 26 77 DD F1 rOkw.V][email protected]&w..0060: E6 31 3D F2 EF 5A 11 22 78 23 47 C2 D6 ED DD 14 .1=..Z."x#G.....0070: 2F E9 2E 46 73 D9 20 72 BF 9B 6C 04 12 0D 68 C7 /..Fs. r..l...h.]Client Certificate [1] = [[Version: V3Subject: CN=VeriSign Class 1 CA Individual Subsciber-Persona Not Validated,OU="www.verisign,com/repository/RPA Incorp. By Ref., LIAB.LTD(c)98", OU=VeriSignTrust Network, O="VeriSign, Inc." Signature Algorithm: MD2withRSA, OID = 1.2.840.113549.1.1.2Key: com.sun.net.ssl.internal.ssl.JSA_RSAPublicKey@9ae870e3 Validity: [From: Mon May 11 17:00:00 PDT 1998, To: Sun May 12 16:59:59 PST 2008] Issuer: OU=Class 1 Public Primary Certification Authority, O="VeriSign, Inc.",C=US SerialNumber: [ d2762e8d 140c3d7d b2a8255d afee0d75 ]

Page 201: Java Servlet - Programowanie

Certificate Extensions: 4[1]: ObjectId: 2.16.840.1.113730.1.1 Criticality=falseNetscapeCerttype [SSL CAS/MIME CA][2]: ObjesctID: 2.5.29.32 Criticality=falseExtension unknown: DER encoded OCTET string = 0000: 04 40 30 3E 30 3C 06 0B 60 86 48 01 86 E8 45 01 .@0>0<..'.H...E.0010: 07 01 01 30 2D 30 2B 06 08 2B 06 01 05 05 07 02 ...0-0+..+......0020: 01 16 1F 77 77 77 2E 76 65 72 69 73 69 67 6E 2E ...www.verisign.0030: 63 6F 6D 2F 72 65 70 6F 73 69 74 6F 72 79 2F 52 com/repository/R0040: 50 41 PA[3]: ObjectId: 2.5.29.15 Criticality=falseKeyUsage [Key_CertSignCrl_Sign][4]: ObjectId: 2.5.29.19 Criticality=falseBasicConstraints:[CA:truePathLen:0]]Algorithm: [MD2withRSA]Signature:0000: 88 B8 37 3B DD DA 94 37 00 AD AA 9F E1 81 01 71 ..7;...7.......q0010: 1E 92 6A 6D 2F F6 F1 9D D3 CA 64 38 DC 1B 98 0C ..jm/.....d8....0020: 07 86 5B 85 15 6A 0F B9 49 85 A4 95 F1 17 7D 67 ..[..j..I......g0030: B4 7F 2D 2C DD 9A 42 9E C3 3E B4 8E AA E5 0B 06 ..-,..B..>......0040: DE F2 56 2A FA 33 C7 BE 19 D7 53 4C C3 BD C8 E3 ..V*.3....SL....0050: 17 B5 A4 49 42 63 EC C2 A6 17 0F 5D 58 1A 49 3C ...Ibc.....]X.I<0060: 90 5C 55 A3 65 20 00 FD 18 20 E5 5F 82 A6 B1 A8 .\U.e... ._....0070: 92 C5 58 6A C1 8D 03 3C EB C3 CD 05 A2 90 AE 6E ..Xj...<.......n]

Page 202: Java Servlet - Programowanie

Rozdział 9. Łączność z bazą

danych

W dzisiejszych czasach trudno znaleźć profesjonalną witrynę, która nie ma powiązania z bazą danych.Administratorzy WWW korzystają z baz danych w wyszukiwarkach internetowych, aplikacjach sklepówinternetowych i programów wysyłających wiadomości online. Współpraca aplikacji sieciowych z bazami danychma swoją cenę: zorientowane bazodanowo witryny internetowe mogą być trudne do tworzenia i często powodująograniczenie wydajności. Korzystanie z baz danych ma jednak więcej zalet niż wad co sprawia, że bazy danychcoraz bardziej „sterują” siecią.

W niniejszym rozdziale przedstawiono relacyjne bazy danych, język zapytań SQL i interfejs JDBC. Serwlety,dzięki długiemu czasowi istnienia, oraz JDBC, dobrze zdefiniowany interfejs łączności z bazami danych, sąskutecznym rozwiązaniem dla administratorów WWW pragnących wykorzystać bazy danych w swych witrynach.Wprawdzie w książce założono, że czytelnik zna Javę, ale ten rozdział zaczyna się krótkim kursemwprowadzającym interfejs programistyczny JDBC.

Największą zaletą serwletów jest ich czas istnienia (szeroko opisany w rozdziale 3,”Cykl Życia Serwletu”), którypozwala na zarządzanie pulą otwartych połączeń. Takie otwarte połączenie pozwala na szybszą komunikacjęaplikacji z bazą danych. Korzystając ze skryptów CGI musimy pamiętać, że przy każdym wywołaniu trzebaponownie otwierać połączenie (co może zająć sporo czasu).

Przewagą serwletów nad skryptami CGI jest niezależność interfejsu JDBC od typów baz danych. Serwletobsługujący np. bazę danych Sybase może korzystać z bazy Oracle po niewielkiej zmianie w pliku właściwościlub modyfikacji kilku linii (zakładając, że dostawca nie określił wywołania konkretnego typu bazy danych).Przykłady w tym rozdziale są napisane w ten sposób, by przedstawić sposób zapewnienia dostępu doróżnorodnych baz danych, łącznie z bazami danych ODBC, takimi jak MS Access, Oracle, czy Sybase.

Page 203: Java Servlet - Programowanie

Serwlety w warstwie pośredniejWarstwa pośrednia służy do połączenia aplikacji klienckich z aplikacjami serwerowymi (np. apletyz bazami danych). Gromadzi głównie serwlety, które wykorzystują bazy danych.

Powinniśmy umieścić warstwę pośrednią pomiędzy aplikacją klienta a naszym krańcowym źródłemdanych, ponieważ oprogramowanie warstwy pośredniej (nazywane middleware) zawiera logikębiznesową. Logika biznesowa oddziela skomplikowane zadania niskiego poziomu (takie jakaktualizacja tabel baz danych) od zadań wysokiego poziomu (np. składanie zamówienia), cosprawia, że operacja wykonania zamówienia jest łatwiejsza i bezpieczniejsza.

Aplikacja klienta, składająca zamówienia bez użycia middleware, musi łączyć się bezpośrednio zserwerem bazy danych, który gromadzi zapisy rozkazów i adekwatnie do nich zmienia pola bazy.

Jakakolwiek zmiana w serwerze (przeniesienie do innej maszyny, zmiana struktury tabel bazydanych, itp.) może spowodować przerwanie połączenia z aplikacją klienta. Jeśli dokonamy zmianyw kliencie (zamierzonej lub przypadkowej), baza danych może błędnie zapisać informacje ozamówieniach złożonych za pośrednictwem aplikacji klienta.

Do Middleware przesyłane są informacje o zamówieniu (np. nazwisko, adres, towar, ilość i numerkarty kredytowej) i za pomocą tych informacji dokonywana jest weryfikacja użytkownikaskładającego zamówienie (np. sprawdzana jest ważność karty kredytowej). Jeśli informacje sąprawdziwe — zostaną wprowadzone do bazy danych. Jeśli baza danych ulegnie zmianie,middleware aktualizuje się bez konieczności modyfikacji aplikacji klienta. Nawet, gdy zamówieniabazy danych są tymczasowo zapisane w postaci pliku jednorodnego, middleware może przekazaćklientowi informacje odczytane z takiego pliku.

Korzystając z middleware można podnieść wydajność systemu poprzez rozproszenie procesuzapisywania informacji w bazie danych na kilka wewnętrznych serwerów. Middleware potrafiwykorzystać przepustowość sieci: zamiast powolnego połączenia klient-serwer, klient możeprzekazać wszystkie informacje middleware, który użyje szybkiego połączenia sieciowego z pulipołączeń i skomunikuje się z bazą danych.

Warstwy pośrednie w sieci są często tworzone przy użyciu serwletów. Serwlety dostarczająodpowiednie sposoby łączenia klientów z wewnętrznym serwerem za pomocą apletów lubformularzy HTML. Klient przesyła żądania do serwletu za pomocą HTTP, a logika biznesowa wserwlecie przekazuje żądanie do wewnętrznego serwera (więcej informacji na temat komunikacjiaplet-serwlet znajduje się w rozdziale 10, „Komunikacja aplet-serwlet”).

Serwlety często używają dodatkowej warstwy pośredniej poza serwerem sieciowym (takiej jakEnterprise Java Beans) do łączenia się z bazą danych. Jeśli przeglądarka prześle formularz HTML zzamówieniem do serwletu, to może on przekształcić tą informację i wykonać wywołanie do EJBinnej maszyny odpowiedzialnej za obsługę wszystkich zamówień — pochodzących zarówno zserwletów, jak i niezależnych programów. W omówionych przypadkach mamy do czynienia zestrukturą czterowarstwową.

Relacyjne bazy danychWe wcześniejszych przykładach omówiono serwlety, które przechowywały dane w postaci pliku umieszczonegona lokalnym dysku. Użycie jednorodnego pliku jest świetnym rozwiązaniem dla małych ilości informacji, alemożna szybko stracić nad nim kontrolę. W miarę wzrostu ilości danych umieszczonych w pliku dostęp do nichstaje się coraz wolniejszy. Wyszukanie odpowiednich informacji może stać się nie lada wyzwaniem: wyobraźmysobie wyszukiwanie w pliku tekstowym nazwisk, miast i adresów e-mail wszystkich naszych klientów. Takiepodejście jest dobre dla nowo otwartej firmy, ale nie sprawdza się w przypadku korporacji obsługującej setkitysięcy klientów. Wyszukanie na przykład listy klientów z Bostonu, których adresy e-mail są zakończoneaol.com z dużego pliku tekstowego stanowiłoby ogromny problem.

Jednym z najlepszych rozwiązań tego problemu jest skorzystanie z systemu zarządzania relacyjnymi bazamidanych (RDBMS). W najprostszej postaci RDBMS umieszcza dane w tabelach. Tabele te podzielone są nawiersze i kolumny podobnie do tabel arkusza kalkulacyjnego. Pomiędzy poszczególnymi wierszami i kolumnamijednej tabeli może zachodzić określona relacja (czyli powiązanie) — stąd termin relacyjne.

Jedna tabela relacyjnej bazy danych może zawierać informacje o klientach, druga o ich zamówieniach, a trzecia oprzedmiotach zamówienia. Poprzez włączenie unikatowych identyfikatorów (powiedzmy, takich jak numery

Page 204: Java Servlet - Programowanie

klientów i zamówień), te trzy tabele mogą być wzajemnie powiązane. Rysunek 9.1 pokazuje, w jaki sposóbwygląda ta relacja.

Dane w tabeli mogą być odczytywane, aktualizowane, dopisywane i kasowane za pomocą poleceń języka SQL.JDBC API języka Java wprowadzony w JDK 1.1 używa pewnej odmiany SQL znanej jako ANSI SQL-2 EntryLevel. W przeciwieństwie do większości języków programowania SQL jest językiem deklaracyjnym: interpreterSQL wykonuje polecenia wpisywane przez użytkownika. Inne języki programowania takie jak C/C++, Javawymagają użycia procedur, czyli określenia kolejnych kroków wykonania pewnego zadania. SQL nie jestszczególnie złożony, ale jest zbyt szerokim tematem do opisu w ramach tej książki. Więcej informacji na tematrelacyjnych baz danych i języka SQL znajduje się w książkach: SQL dla Opornych Allena Tayllora i SQL in aNutshell Kevina i Daniela Kline.

Rysunek 9.1. Powiązane tabele

Najprostszym i najbardziej pospolitym wyrażeniem SQL jest SELECT, które przeszukuje bazę danych i zwracazestaw wierszy pasujących do kryterium. Na przykład, poniższe wyrażenie zaznacza wszystkie wiersze tabeliKLIENCI:

SELECT * FROM KLIENCISłowa kluczowe SQL takie jak SELECT i FROM oraz obiekty takie jak KLIENCI nie są wrażliwe na wielkośćliter. Uruchomienie interpretera SQL SQL*PLUS dla Oracle powinno wygenerować na wyjściu:

KLIENT_ID NAZWISKO TELEFON---------------------------------------------------1 Bob Copier 617-555-12122 Janet Stapler 617 555-12133 Joel Laptop 507 555-71714 Larry Coffee 212 555-6225

Bardziej zaawansowane wyrażenia mogłyby ograniczać szukanie do wybranych kolumn albo według określonychkryteriów, np.:

SELECT ZAMÓWIENIE_ID, KLIENT_ID, SUMA FROM ZAMÓWIENIA WHERE ZAMÓWIENIE_ID = 4

Za pomocą tego wyrażenia zaznaczono kolumny ZAMÓWIENIE_ID, KLIENT_ID i SUMA ze wszystkichrekordów, gdzie w polu ZAMÓWIENIE_ID jest wartość 4. Oto przykładowy rezultat:

ZAMÓWIENIE_ID KLIENT_ID SUMA------------------------------------ 4 1 72.19

Instrukcja SELECT może również łączyć kilka tabel na podstawie zawartości poszczególnych pól. Może to byćzwiązek jeden-do-jednego albo częściej używany jeden-do-wielu, np. jeden klient do kilku zamówień:

SELECT KLIENCI.NAZWISKO, ZAMÓWIENIA.SUMA FROM KLIENCI, ZAMÓWIENIAWHERE ZAMÓWIENIA.KLIENT_ID = KLIENCI.KLIENT_ID AND ZAMÓWIENIA.ZAMÓWIENIE_ID = 4

Powyższa instrukcja spaja tabelę KLIENCI z tabelą ZAMÓWIENIA za pomocą pola KLIENT_ID. Należyzauważyć, że obie tabele posiadają to pole. Zapytanie zwraca informacje z dwóch tabel: nazwę użytkownika,który wykonał zamówienie nr 4 i całościowy koszt zamówienia. Oto przykładowy wynik tej operacji:

NAZWISKO SUMA-------------------------------------Bob Copier 72,19

Język SQL jest również używany do aktualizacji baz danych. Na przykład:INSERT INTO KLIENCI (KLIENT_ID, NAZWISKO, TELEFON) VALUES(5, "Bob Smith", "555 123-3456")UPDATE KLIENCI SET NAZWISKO = "Robert Copier" WHERE KLIENT_ID = 1DELETE FROM KLIENCI WHERE KLIENT_ID = 2

Page 205: Java Servlet - Programowanie

Pierwsza instrukcja tworzy nowy rekord w tabeli KLIENCI, zapełniając pola KLIENT_ID i TELEFONpewnymi wartościami. Druga aktualizuje istniejący rekord, zmieniając pole NAZWISKO określonegoużytkownika. Ostatnia kasuje wszystkie rekordy, gdzie KLIENT_ID = 2. Należy ostrożnie posługiwać się tymiinstrukcjami, a w szczególności instrukcją DELETE. Użycie tej instrukcji bez warunku WHERE usunie wszystkierekordy z tabeli!

JDBC APIWcześniej zakładaliśmy, że czytelnik posiada ogólną wiedze dotyczącą Java API. Ponieważ nawet wprawieniprogramiści Javy mogą mieć małe doświadczenie z bazami danych, ten podpunkt wprowadza JDBC napodstawie najpopularniejszej wersji JDBC 1.2. Pod koniec rozdziału dodano fragment na temat JDBC 2.0.

Jeśli czytelnik po raz pierwszy ma do czynienia z bazami danych, powinien przeczytać książki o ogólnychzałożeniach baz danych i JDBC, takie jak: Database Programming with JDBC and Java George'a Reese'a iJDBC Database Access with Java Grahama Hamiltona, Ricka Cattela, Maydene Fisher. Krótki przegląd znajdujesię w Java Enterprise in a Nutshel Davida Flangana. Oficjalna specyfikacja JDBC znajduje się na stroniehttp://java.sun.com/products/jdbc

JDBC jest interfejsem programowym poziomu SQL, który pozwala wykonywać instrukcje SQL i odczytywaćotrzymane wyniki. API jest zbiorem interfejsów i klas zaprojektowanym tak, aby możliwa była współpraca zjakąkolwiek bazą danych. Rysunek 9.2 pokazuje schemat struktury JDBC.

Rysunek 9.2. Java i baza danych

Sterowniki JDBCJDBC API, znajdujący się w pakiecie java.sql zawiera zestaw klas służących do implementacji określonychinterfejsów. Większa część API jest rozprowadzana jako klasy interfejsów neutralnych dla baz danych, awspółpracę z konkretnymi bazami danych zapewniają interfejsy dostarczone przez producentów baz danych.

Indywidualny system baz danych jest dostępny poprzez sterownik JDBC, który implementuje interfejsjava.sql.Driver. Sterowniki istnieją prawie dla wszystkich popularnych systemów RDBMS, ale niewszystkie są dostępne za darmo. Firma Sun dodaje darmowy sterownik wykorzystujący technikę pomostowąJDBC-ODBC w JDK, aby umożliwić dostęp do danych z baz standardu ODBC, takich jak baza danychMicrosoft Access. Jest to bardzo prosty sterownik, którego można używać w bardzo prostych aplikacjach.Twórcy serwletów powinni w szczególności zwrócić uwagę na to ostrzeżenie, ponieważ jakikolwiek problem wtreści kodu własnego sterownika JDBC-ODBC może zniszczyć cały serwer.

Sterowniki JDBC są dostępne dla większości platform baz danych tworzonych przez wielu producentów. Istniejącztery kategorie sterownika:

• Typ 1: JDBC-ODBC Bridge Driver — sterowniki typu 1 używają technologii pomostowej do łączeniaklienta Javy i serwisu bazy danych ODBC.JDBC-ODBC dostarczony z JDK jest najpopularniejszym

Page 206: Java Servlet - Programowanie

sterownikiem tego typu. Te sterowniki są implementowane przy użyciu kodu własnego i wymagają instalacjidodatkowego oprogramowania po stronie klienta.

• Typ 2: Native-API Partly Java Driver — sterowniki typu 2 są napisane w Javie, ale korzystają z biblioteknapisanych w innych językach. Dla Oracle, biblioteki kodu własnego mogłyby bazować na bibliotekachOCI, które były pierwotnie zaprojektowane dla programistów C/C++. Ponieważ sterowniki typu 2 sązaimplementowane za pomocą kodu własnego, zazwyczaj zapewniają lepszą wydajność niż ich wszystkieodpowiedniki w całości napisane w Javie. Użycie tych sterowników może być jednak ryzykowne: błąd wsekcji kodu własnego sterownika może zniszczyć cały serwer. Sterowniki tego typu wymagają instalacjidodatkowego oprogramowania po stronie klienta.

• Typ 3: Net – Protocoll All-Java Driver — te sterowniki komunikują się zbazami danych za pośrednictwem komponentów pośrednich (ang.middleware) poprzez protokół sieciowy. Komponenty pośredniezapewniają dostęp do różnych baz danych. Sterowniki tegotypu są w całości napisane w Javie i nie wymagają instalacjidodatkowego oprogramowania po stronie klienta, dzięki czemusą wygodnym i bezpiecznym narzędziem zapewniającym apletom iserwletom dostęp do baz danych.

• Typ 4: Native — Protocoll All Java Driver — sterowniki tego typu sąnapisane w całości w Javie i komunikują się bezpośrednio zmaszyną bazodanową (ang. database engine) za pomocąprotokołów sieciowych. Te sterowniki mają bezpośredni dostępdo baz danych bez dodatkowego oprogramowania.

Wykaz obecnie dostępnych sterowników JDBC znajduje się nastronie http://industry.java.sun.com/products/jdbc/drivers.

Połączenie z bazą danychPierwszym krokiem, jaki trzeba wykonać, aby użyć sterownika JDBC do połączenia z bazą danych jestzaładowanie określonej klasy sterownika do aplikacji JVM (Java Virtual Machine). Dzięki temu sterownikbędzie dostępny przy otwieraniu połączenia z bazą danych. Prostym sposobem załadowania klasy sterownika jestużycie metody Class.forName():

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");Załadowany sterownik sam rejestruje się wewnątrz klasy java.sql.DriverManager, jako dostępnysterownik bazy danych.

Następnym krokiem jest użycie klasy DriverManager do otwarcia połączenia z bazą danych, gdzie zapomocą URL określa się żądaną bazę danych. Metodą używaną do otwierania połączenia jestDriverManager.getConnection, która zwraca klasę implementującą interfejsjava.sql.Connection:

Connection con = DriverManager.getConnecton("jdbc:odbc:somedb","user","passwd");

Podany adres URL identyfikuje bazę danych w sposób specyficzny dla określonego sterownika. Różnesterowniki mogą potrzebować różnych informacji zawartych w URL do określenia hosta bazy danych. AdresyURL zazwyczaj zaczynają się od jdbc:subprotokół:subnazwa. Subprotokół określa nazwęwykorzystywanego sterownika, a subnazwa identyfikuje bazę danych. Na przykład, sterownik Oracle JDBC-Thin wymaga URL w formie jdbc:oracle:thin:@dbhost:port:sid, a JDBC-ODBC Bridge używajdbc:odbc:datasourcename:odbcoptions.

Podczas wywołania metody getConnection(), obiekt DriverManager rozpoznaje odpowiedni sterownikza pomocą URL przeszukując bazę dostępnych sterowników. Jeśli wymagany sterownik istnieje w bazie,zostanie użyty do stworzenia obiektu Connection. Poniżej znajduje się fragment kodu, jakiego można użyć wserwlecie do ładowania sterownika bazy danych JDBC-ODBC Bridge i realizacji połączenia:

Connection con = null;try{ // ładujemy (i w ten sposób rejestrujemy) sterownik //możliwość wystąpienia wyjątku ClassNotFoundExceptionClass.forName(„sun.jdbc.odbc.JdbcOdbcDriver”);

Page 207: Java Servlet - Programowanie

//otwieramy połączenie z bazą danych// możliwość wystąpienia wyjątku SQLException con=DriverManager.getConnection("jdbc:odbc:somedb", "user", "passwd");// reszta kodu źródłowego ma tu swoje miejsce}catch(ClassNotFoundException e){ // obsługa wyjątku ClassNotFoundException}catch(SQLException e){// obsługa wyjątku SQLException}finally {// zamknięcie połączenia z bazą danychtry{ if (con!=null) con.close();}catch (SQLException ignored) {}}

Właściwie dostępne są trzy formy metody getConnection(). Najprostszą z nich jest ta, która pobiera tylkoadres URL: getConnection (String url). Ta metoda ma zastosowanie w przypadku, gdy nieprzewidziano konieczności zalogowania się (podania nazwy użytkownika i hasła) lub umieszczenia informacjilogujących w adresie URL. Istnieje jeszcze jedna forma, która pobiera adres URL i obiekt Properties:getConnection(String url, Properties props). Ta metoda zapewnia największą elastyczność.Obiekt Properties (tablica mieszająca zawierająca klucze i wartości typu String) zawierastandardowo nazwę użytkownika i hasło, a także dodatkowe informacje przekazywane odpowiedniemusterownikowi bazy danych. Na przykład, niektóre sterowniki respektują właściwość cacherows, która określajak dużo wierszy ma trafić do pamięci cache w jednostce czasu. Używanie tej metody ułatwia otwarciepołączenia z bazą danych w oparciu o plik z rozszerzeniem .properties.

Sterownik, adres URL i potrzebne do zalogowania się dane mogą być określone w następującym plikusql.properties. Format nazwa=wartość jest formatem standardowym w plikach właściwości Javy:

connection.driver=sun.jdbc.odbc.JdbcOdbcDriverconnection.url=jdbc:odbc:somedbuser=userpassword=passwd

Za pomocą kodu przedstawionego w przykładzie 9.1 otwiera się połączenie z bazą danych używając wartościzgromadzonych wewnątrz pliku sql.properties. Należy zauważyć, że nazwa użytkownika i hasło to informacjastandardowo wymagana do zalogowania się, natomiast właściwości connection.driver iconnection.url są specjalnymi nazwami użytymi w poniższym kodzie do rozpoznania sterownika iodpowiadającego mu URL, a wszelkie dodatkowe własności będą przekazane do wybranego sterownika.

Przykład 9.1.

Użycie odpowiedniego pliku do otwarcia połączenia bazodanowego.// pobierz właściwości połączenia bazodanowegoProperties props = new Properties();InputStream in = new FileInputStream("sql.properties");props.load(in);in.close(); ta instrukcja powinna znaleźć się w bloku finally// załadowanie sterownikaClass.forName(props.getProperty("connection.driver");//otwarcie połączeniacon = DriverManager.getConnection("connection.url"),props);

Utworzony obiekt Properties jest wypełniany wartościami odczytanymi z pliku sql.properties, a następniezawarte w tym pliku właściwości są użyte do otwarcia połączenia z bazą danych. Wykorzystanie plikuwłaściwości pozwala na zmianę wszystkich informacji o połączeniach z bazą danych bez zbędnegoprzekompilowania kodu Javy.

Otwieranie połączenia z serwletuSerwlet może użyć przedstawionego powyżej sposobu ładowania informacji o połączeniu z bazą danych z plikuwłaściwości znajdującego się w katalogu WEB-INF. Metoda getResourceAsStream() pobiera zawartośćtego pliku:

Properties props = new Properties();InputStream in = getServletContext().getResourceAsStream( "/WEB-INF/sql.properities");props.load(in);in.close();

Page 208: Java Servlet - Programowanie

Ponieważ serwlety są najczęściej wdrażane przy użyciu graficznych narzędzi, możemy użyć tych samychnarzędzi do skonfigurowania bazy danych. Jednym ze sposobów osiągnięcia tego celu jest umieszczeniepołączeń z bazą danych wewnątrz serwera JNDI, gdzie połączenia są rozpoznawane przez serwlety za pomocąokreślonych nazw. Ten sposób jest wykorzystywany przez serwlety pracujące wewnątrz serwerów J2EE.Rozdział 12 omawia szczegółowo to zagadnienie. Innym zadaniem, które nie wymaga serwera JNDI, awykorzystuje graficzne narzędzia rozmieszczenia, jest użycie parametrów inicjacji kontekstu do zachowaniainformacji o konfiguracji. Te parametry inicjujące mogą zostać skonfigurowane za pomocą graficznych narzędzirozmieszczeń, a potem można je zapisać w pliku web.xml. (Więcej informacji na ten temat jest w rozdziale 4).

Klasa ContextProperties przedstawiona w przykładzie 9.2 udostępnia parametry inicjujące kontekst jakoobiekt Properties. To pozwala na przekazanie wszystkich par nazwa-wartość metodzieDriverManager.getConnection().

Przykład 9.2.

Klasa ContextPropertiesimport java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class ContextProperties extends Properties { public ContextProperties(ServletContext context) { Enumeration props = context.getInitParameterNames(); while (props.hasMoreElements()) { String name = (String) props.nextElement(); String value = (String) context.getInitParameter(name); put (name,value);}}}

Serwlet otrzymuje informacje o połączeniu za pośrednictwem tej klasy (nie pobiera danych z sql.properties), coprzedstawiono poniżej:

// pobierz kontekst init params jako obiekt propertiesContextProperties props = new ContextProperties(getServletContext());//załaduj sterownikClass.forName(props.getProperty("connection.driver"));// itd.

Wystarczy przypisać parametry inicjujące kontekst konkretnym wartościom bazy danych w sposób ręczny wpliku web.xml lub używając narzędzia rozmieszczenia.

Wykonywanie zapytań SQLAby naprawdę używać bazy danych, musimy poznać sposób wykonywania zapytań. Najprostszym sposobem jestużycie klasy java.sql.Statement. Tego typu obiekty nigdy nie są tworzone bezpośrednio, lecz za pomocąmetody createStatement() obiektu Connection, która dostarcza referencje do utworzonego w danejchwili obiektu typu Statement:

Statement stmt = con.createstatement();Zapytanie zwracające dane może być wykonane przy użyciu metody executeQuery() obiektu Statement.Po wykonaniu tej metody dane zwracane są w java.sql.ResultSet w postaci hermetycznej:

ResultSet rs = stmt.executeQuery("SELECT*FROM KLIENCI");ResultSet jest reprezentacją danych otrzymanych w wyniku wykonania zapytania. Obiekt ten zwraca zakażdym razem jeden wiersz. Użycie metody next() obiektu ResultSet pozwala na przemieszczanie siępomiędzy wierszami danych reprezentowanych przez ResultSet. Istnieje wiele metod pozwalających naodczytywanie danych z danego wiersza obiektu ResultSet. Do najczęściej używanych należą metodygetString() i getObject(), które służą do odczytywania wartości kolumn:

while(rs.next()) { String event = rs.getstring("event"); Object count = (Integer) rs.getObject("count");}

Należy wiedzieć, że obiekt ResultSet jest powiązany ze swym nadrzędnym (rodzicielskim) obiektemStatement. Jeśli obiekt Statement zostanie zamknięty lub wykorzystany do obsługi innego zapytania,wszystkie odpowiadające mu obiekty ResultSet zostaną również automatycznie zamknięte.

Przykład 9.3 pokazuje bardzo prosty serwlet, w którym użyto sterownika Oracle JDBC by wykonać prostezapytanie, zwracające nazwiska i numery telefonu wszystkich pracowników zawartych w tabeli bazy danych.

Page 209: Java Servlet - Programowanie

Zakładamy, że baza danych zawiera tabelę PRACOWNICY z co najmniej dwoma polami, a także tabeleNAZWISKO i TELEFON.

Przykład 9.3.Serwlet JDBC-enabled

import java.io.*;import java.sql.*;import javax.servlet.*;import javax.servlet.http.*;public class DBPhoneLookup extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { Connection con = null; Statement stmt = null; ResultSet rs = null; res.setContentType("text/html");Printwriter out = res.getWriter();try{ //ładujemy sterownik oracle Class.forName("oracle.jdbc.driver.OracleDriver"); // nawiązujemy połączenie z bazą danych // udostępnioną na serwerze dbhost // na porcie 1528 con = DriverManager.getConnection ( "jdbc:oracle:thin:@dbhost:1528:ORCL","user","passwd"); //tworzymy obiekt typu Statement stmt = con.createStatement(); // wykonujemy zapytanie SQL, otrzymujemy wynik w ResultSet rs=stmt.executeQuery("SELECT NAZWISKO, TELEFON FROM PRACOWNICY"); // przedstawiamy otrzymane dane w postaci listy out.println("<HTML><HEAD><TITLE>Książka telefoniczna</TITLE></HEAD>"); out.println("<BODY"); out.println("<UL."); while (rs.next()) { out.println("<LI> + rs.getString("name") + "" + rs.getString("phone")"); } out.println("</UL.>"); out.println("</BODY></HTML>");}catch (ClassNotfoundException e ) { out.println("Nie można zaladowac sterownika bazy danych: "+e.getMessage());}catch (SQLException e) { out.println("SQLException :" + e.getMessage());}finally { // zawsze zamykamy połączenie z bazą danych try { if (con!=null) con.close(); } catch (SQLException ignored){} } }}

Dla każdego obiektu DBPhoneLookup nawiązywane jest połączenie z bazą danych, następnie wykonuje sięzapytanie w celu przeszukania nazwisk i numerów telefonów wszystkich znajdujących się w tabeli pracownikówi wynikowa lista jest dostarczana użytkownikowi.

Obsługa wyjątków SQLDBPhoneLookup obejmuje większość kodu w bloku obsługi wyjątku try-catch. W tym blok wyłapuje dwawyjątki: ClassNotFoundException i SQLException. Pierwszy z nich jest zgłaszany za pomocą metodyClass.forName(), gdy nie można załadować klasy sterownika JDBC. Drugi jest zgłaszany przezktórąkolwiek z metod JDBC, gdy wystąpi problem w trakcie jej wykonania. Obiekty klasy SQLExceptionobok standardowych właściwości klas wyjątków mają dodatkową cechę: mogą tworzyć łańcuchy wyjątków.Klasa SQLException definiuje dodatkową metodę getNextException() pozwalającą na„hermetyzację” dodatkowych obiektów Exception. Nie przedstawiono tego w poprzednim przykładzie, aleużycie tej metody może wyglądać w następujący sposób:

Page 210: Java Servlet - Programowanie

catch (SQL Exception e){ out.println(e.getMessage());while ((e=e.getNextException())!=null){ out.println(e.getMessage());}

}Powyższy kod wyświetla informację z pierwszego wyjątku, a następnie, za pomocą pętli, przechodzi poprzezkolejne wyjątki, wyświetlając informacje o konflikcie związanym z każdym z nich. W praktyce, pierwszywyjątek będzie zawierał najbardziej znaczącą informację.

Zestawy wyników w szczegółachPrzyjrzyjmy się obiektowi ResultSet i obiektowi pokrewnemu ResultSetMetaData. W przykładzie 9.1wiedzieliśmy w jaki sposób wykonać nasze zapytanie, jakiego wyniku należało się spodziewać, więcsformatowaliśmy wyjście w odpowiedni sposób. Jeśli chcielibyśmy wyświetlić wyniki zapytania w tabeli HTML,dobrze byłoby mieć taki program Javy, który tworzy tę tabelę automatycznie korzystając z obiektu ResultSetzamiast pisać kilkakrotnie tą samą część kodu. Dodatkowym atutem takiego programu byłaby możliwość zmianyzawartości tabeli wraz ze zmianą konstrukcji zapytania.

Obiekt ResultSetMetaData przekazuje programowi informacje na temat danego obiektu ResultSet.Możemy wykorzystać tę cechę do budowy obiektu, który będzie generował dynamicznie tabelę HTML zwykorzystaniem ResultSet, tak jak to pokazano w Przykładzie 9.4. Wiele narzędzi Javy wspomagającychHTML ma podobne możliwości, co omówiono w rozdziałach 14 – 18.

Przykład 9.4.

Klasa generująca tabelę HTML z ResultSet przy użyciu ResultSetMetaDataimport java.sql.*;public class HtmlResultSet { private ResultSet rs; public HtmlResultSet(ResultSet rs) { this.rs = rs; } public String toString() { // może być wywołany tylko raz StringBuffer out = new StringBuffer(); // twórzymy tabelę aby wyświetlić zestaw wyników out.append("<TABLE>\n"); try { ResultSetMetaData rsmd = rs.getMetaData(); int numcols = rsmd.getColumnCount(); // Tytuł tabeli z etykietami kolumn zestawów wyników out.append("<TR>"); for (int i = 1; i <= numcols; i++) { out.append("<TH>" + rsmd.getColumnLabel(i)); } out.append("</TR>\n"); while (rs.next()) { out.append("<TR>"); // tworzymy nowy wiersz for (int i = 1; i <= numcols; i++) { out.append("<TD>"); // tworzymy nowy element danych Object obj = rs.getObject(i); if (obj != null) out.append(obj.toString()); else out.append("&nbsp;"); } out.append("</TR>\n"); } // Koniec tabeli out.append("</TABLE>\n"); } catch (SQLException e) { out.append("</TABLE><H1>ERROR:</H1> " + e.getMessage() + "\n"); } return out.toString(); }}

Page 211: Java Servlet - Programowanie

Ten przykład pokazuje w jaki sposób użyć dwóch podstawowych metod pochodzących od obiektuResultSetMetaData: getColumnCount() i getColumnLabel(). Pierwsza z nich zwraca ilośćkolumn obiektu ResultSet, a druga dostarcza nazwę określonej kolumny w oparciu o jej indeks.Indeksowanie w obiektach ResultSet jest oparte na standardzie RDMBS zamiast C++/Java, co oznacza, żeobiekty numerowane są od 1 do n, a nie od 0 do n-1.

W przykładzie użyto metody getObject() obiektu ResultSet, by odczytać zawartość każdej kolumny.Wszystkie metody getXXX() działają zarówno w oparciu o indeksy, jaki i nazwy kolumn. Dostęp do danych wten sposób jest bardziej wydajny i przenośny, jeśli właściwie napiszemy wyrażenie SQL. Używamy tugetObject().toString() zamiast getString(), aby uprościć obsługę pól mających wartość null, coomówiono w następnym podrozdziale.

Tabela 9.1 pokazuje metody Javy, które można użyć do wyszukania najbardziej powszechnych typów danychSQL. Metoda getObject() obiektu ResultSet zwróci obiekt Javy którego typ zależy od typu obiektuSQL, co pokazano w tabeli. Można również użyć innej metody getXXX(). Metody te pokazano w trzeciejkolumnie, wraz ze zwracanymi typami danych. Należy pamiętać, że dostępne typy danych SQL są różne wzależności od bazy danych.

Tabela 9.1.

Metody otrzymywania danych z obiektu ResultSet

Typ danych SQL Typ Java zwracany przezgetObject()

Zalecana alternatywa dogetObject()

BIGINT Long long getlong()BINARY byte[ ] byte[ ] getBytes()BIT Boolean Boolean getBoolean()CHAR String String getString()DATE java.sql.Date java.sql.Date getDate()DECIMAL java.math.BigDecimal java.math.BigDecimal

getBigDecimal()DOUBLE Double double getDouble()FLOAT Double double getDouble()INTEGER Integer int getInt()LONGVARBINARY byte[ ] InputStream

getBinaryStream()LONGVARCHAR String InputStream

getAsciiStream ()InputStreamgetUnicodeStream

NUMERIC java.math.BigDecimal java.math.BigDecimalgetBigDecimal()

REAL Float float getFloat()SMALLINT Integer short getShort()TIME Java.sql.Time java.sql.Time getTime()TIMESTAMP Java.sql.Timestamp java.sql.Timestamp

getTimestamp()TINYINT Integer byte getByte()VARBINARY byte[ ] byte[ ] getBytes()VARCHAR String String getString()

Obsługa pól mających wartość nullPole w bazie danych, podobnie do obiektu w Javie może mieć wartość null, co oznacza, że to pole jestniewypełnione. Obsługa takich pól bazy danych może stanowić pewien problem, bowiem nie jesteśmy w staniestwierdzić, czy uzyskana wartość (0 lub -1 zwrócona np. przez metodę getInt()) jest faktyczną wartościąwpisaną w to pole, czy też jest to informacja, że w dane pole nic nie wpisano. Z tego powodu JDBC zawierametodę wasNull() w obiekcie ResultSet zwracającą true lub false w zależności od tego, czy ostatniaprzeczytana kolumna w bazie danych miała wartość typu null czy nie. To oznacza, że należy wczytać dane z

Page 212: Java Servlet - Programowanie

obiektu ResultSet do konkretnej zmiennej, wywołać metodę wasNull() i w zależności od wynikupostępować dalej. Oto przykład zastosowania tej metody:

int age = rs.getInt( "wiek");if (!rs.wasNull()) out.println("Wiek: " + wiek);

Innym sposobem pozwalającym na odnajdywanie wartości typu null jest użycie metody getObject(). Jeślikolumna ma wartość null, getObject() zawsze zwróci wartość null. Użycie metody getObject()eliminuje konieczność wywołania metody wasNull(), ale czasem lepiej skorzystać z pierwszej metody.

Aktualizacja baz danychWiększość aplikacji bazodanowych, oprócz obsługi zapytań, umożliwia modyfikację rekordów w tabelach bazydanych. Kiedy klient przesyła zamówienie lub dostarcza jakąś informację, dane muszą być wprowadzone dobazy danych. Gdy obsługiwana jest instrukcja SQL UPDATE, INSERT lub DELETE wiadomo, że nie wystąpiobiekt ResultSet i można użyć metodę executeUpdate() obiektu Statement. Zwraca ona liczbęoznaczającą ilość zmodyfikowanych wierszy w wyniku wywołania konkretnego wyrażenia. Tej metody używa sięw następujący sposób:

int count = stmt.executeUpdate("DELETE FROM KLIENCI WHERE KLIENT_ID = 5);

Jeśli wyrażenie SQL może zwrócić obiekt ResultSet lub tylko zmodyfikować odpowiednie pole (czyliwykonać instrukcję typu DELETE, INSERT lub UPDATE) należy skorzystać z metody execute()obiektuStatement. Ta metoda zwraca wartość true, gdy wykonanie wyrażenia SQL utworzyło co najmniej jedenobiekt ResultSet lub false, gdy w wyniku wykonania zapytania zaktualizowano bazę danych.

boolean b = stmt.execute(sql);Metody getResultSet() i getUpdateCount()zapewniają dostęp do wyników wywołania metodyexecute(). Przykład 9.5 demonstruje użycie tych metod w nowej wersji HtmlResultSet, nazwanąHtmlSQLResult, która tworzy tabelę HTML w wyniku wykonania zapytania SQL.

Przykład 9.5.

Klasa generująca tabelę HTML z ResultSet przy użyciu ResultSetMetaDataimport java.sql.*;public class HtmlSQLResult { private String sql; private Connection con; public HtmlSQLResult(String sql, Connection con) { this.sql = sql; this.con = con; } public String toString() { // może być wywołany tylko raz StringBuffer out = new StringBuffer(); // Poniższa linia służy do wyświetlenia komendy SQL na początku tabeli // out.append("Wyniki zapytania SQL: " + sql + "<P>\n"); try { Statement stmt = con.createStatement(); if (stmt.execute(sql)) { //????? ResultSet rs = stmt.getResultSet(); out.append("<TABLE>\n"); ResultSetMetaData rsmd = rs.getMetaData(); int numcols = rsmd.getColumnCount(); // Tytuł tabeli z etykietami kolumn zestawów wyników out.append("<TR>"); for (int i = 1; i <= numcols; i++) out.append("<TH>" + rsmd.getColumnLabel(i)); out.append("</TR>\n"); while (rs.next()) { out.append("<TR>"); // rozpocznij nowy wiersz for (int i = 1; i <= numcols; i++) { out.append("<TD>"); // rozpocznij nowy element danych

Page 213: Java Servlet - Programowanie

Object obj = rs.getObject(i); if (obj != null) out.append(obj.toString()); else out.append("&nbsp;"); } out.append("</TR>\n"); } // Koniec tabeli out.append("</TABLE>\n"); } else { //Tu powinna wystąpić liczba out.append("<B>Records Affected:</B> " + stmt.getUpdateCount()); } } catch (SQLException e) { out.append("</TABLE><H1>ERROR:</H1> " + e.getMessage()); } return out.toString(); } }

Powyższy przykład wykorzystuje metodę execute(), aby wykonać wyrażenie SQL przesłane do konstruktoraHtmlSQLResult. Potem, w zależności od zwróconej wartości, wywołuje getResultSet() lubgetUpdateCount(). Należy zauważyć, że ani getResultSet(), ani getUpdateCount() nie powinnybyć wykonane więcej niż raz w trakcie jednego zapytania.

Użycie gotowych zapytań*

Obiekt PreparedStatement ma podobne właściwości do obiektu Statement. Ważna różnicą jest fakt, żewyrażenia SQL w obiekcie PreparedStatement są wcześniej zinterpretowane przez bazę danych, coprzyspiesza ich wykonanie. Raz skompilowany obiekt PreparedStatement można dostosowywać wzależności od potrzeb poprzez korygowanie wcześniej zdefiniowanych parametrów. Preinterpretowanewyrażenia są użyteczne w aplikacjach, w których wymagane jest wielokrotnie wykonanie tych samych wyrażeńSQL.

Metoda preparedStatement(String) obiektu Connection służy do tworzenia obiektówPreparedStatement. W miejsce znacznika '?' zostaną umieszczone określone wartości, na przykład:

PreparedStatement pstmt = con.prepareStatement( "INSERT INTO ZAMÓWIENIA (ZAMÓWIENIE_ID, KLIENT_ID, TOTAL) VALUES (?,?,?)");// Inny kodpstmt.clearParameters(); // czyści wszystkie poprzednie wartości parametrupstmt.setInt(1, 2); // ustawia ZAMÓWIENIE_IDpstmt.setInt(2, 4); // ustawia KLIENT_IDpstmt.setDouble(3, 53.43); // ustawia TOTALpstmt.executeUpdate(); // wykonuje tak skonstruowane wyrażenie SQL

Metoda clearParametres() usuwa wszystkie wcześniej zdefiniowane wartości parametrów, a metodysetXXX() służą do przypisania właściwych wartości do '?'. Po przypisaniu wartości parametrom należywywołać metodę executeUpdate(), aby utworzyć obiekt PreparedStatement.

Podczas wpisywania tekstu przesłanego przez użytkownika przy użyciu obiektu Statement i dynamicznegoSQL-a, należy uważać, aby przypadkowo nie wprowadzić jakiegoś znaku kontrolnego SQL (takiego jak ' lub ")w odniesieniu do bazy danych. Baza danych Oracle umieszcza łańcuchy tekstowe pomiędzy apostrofami, takwięc próba wstawienia łańcucha John d'Artagan w bazę danych spowoduje wystąpienie błędu SQL:

INSERT INTO MUSKETEERS (NAME) VALUES ('John d'Artagan') Łańcuch w tym przykładzie „kończy się” w dwóch miejscach. Jednym z rozwiązań jest ręczna zamianapojedynczego apostrofu (') na dwa (' '), czyli wykonanie sekwencji unikowej dla Oracle. Takie podejściewymaga unikania znaków specjalnych w sposób ręczny, co kłóci się z zasadą przenośności pisanego kodu. Dużo

* „Prepared statements” to w dosłownym tłumaczeniu „gotowe zapytania”, ale ponieważ te zapytania są interpretowane przedużyciem, lepszym ich określeniem jest „preinterpretowane zapytania”

Page 214: Java Servlet - Programowanie

lepszym rozwiązaniem jest użycie obiektu PreparedStatement i wprowadzanie łańcucha za pomocąmetody setString(), co pokazano poniżej. Metoda ta unika automatycznie znaków specjalnych, gdyzaistnieje taka potrzeba:

PreparedStatement pstmt = con.prepareStatement(" INSERT INTO MUSKETEERS (NAME) VALUES (?)");pstmt.setString (1, "John d'Artagan");pstmt.executeUpdate();

Ponowne użycie obiektów bazy danychWe wprowadzeniu wspomnieliśmy o tym, że czas życia serwletu pozwala na bardzo szybki dostęp do bazydanych. Przy otwieraniu połączenia do bazy danych pojawia się problem wydajności związany z efektem„wąskiego gardła”. Problem ten nie ma wielkiego znaczenia dla aplikacji i apletów, gdyż kilka sekundopóźnienia, potrzebnego na utworzenie obiektu Connection, nie wpływa na funkcjonowanie programu. Wprzypadku serwletów ten problem ma znaczenie, ponieważ nowy obiekt Connection jest tworzony i usuwanyna każde żądanie strony. Na szczęście czas życia serwletu pozwala na ponowne użycie tego samego połączeniadla wielu żądań, nawet konkurujących między sobą.

Ponowne użycie połączeń do baz danychSerwlet może utworzyć wiele obiektów Connection za pomocą metody init() i ponownie ich użyć zapomocą metod service(), doGet() i doPost(). Przykład 9.6 przedstawia zmodyfikowany serwletwyszukujący numery telefonów, w którym najpierw tworzony jest obiekt Connection, a później w trakcieprogramu otwierane jest połączenie z bazą danych. Używa także sterownika Sybase JDBC oraz klasyHtmlSQLResult z przykładu 9.5 do wyświetlania wyników.

Przykład 9.6.

Ulepszony serwlet z numerami telefonów.import java.io.*;import java.sql.*;import javax.servlet.*;import javax.servlet.http.*;public class DBPhoneLookup extends HttpServlet { private Connection con = null; public void init() throws ServletException { try { //załaduj i zarejestruj sterownik Sybase Class.forName("com.sybase.jdbc.SybDriver"); con = DriverManager.getConnection ( "jdbc:sybase:Tds:dbhost:7678","user","passwd");} catch (ClassNotfoundException e ) { throw new UnavailableException ("Nie można zaladowac sterownika bazy danych");}catch (SQLException e) { throw new UnavailableException("Nie można połączyć się z bazą danych");} } public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {res.setContentType("text/html");Printwriter out = res.getWriter();out.println("<HTML><HEAD><TITLE>Książka telefoniczna</TITLE></HEAD>");out.println("<BODY");HtmlSQLResult result = new HtmlSQLResult("SELECT NAME, PHONE FROM EMPLOYEES", con);// Wyświetl wyniki przeszukiwaniaout.println("<H2>Pracownicy:</H2>");out.println(result);out.println("</BODY></HTML>"); }public void destroy() {// Wyczyść try { if (con!=null) con.close();

Page 215: Java Servlet - Programowanie

} catch (SQLException ignored){} } }}

Ponowne użycie przygotowanych wyrażeń.Można w łatwy sposób przyspieszyć wydajność serwletu przez utworzenie z wyprzedzeniem innych obiektówwspółpracujących z bazami danych. Obiekt PreparedStatement idealnie nadaje się do wykonania tegozadania, bo za jego pomocą można wcześniej skompilować wyrażenie SQL. Oszczędza to zaledwie kilkamilisekund, ale taka oszczędność ma znaczenie w witrynach odwiedzanych przez kilkaset tysięcy użytkownikówdziennie.

Współdzielenie obiektów może powodować wystąpienie błędów. Dostęp do obiektu PreparedStatementmoże wymagać wywołania 3 lub 4 metod. Jeśli jeden wątek wywołuje metodę clearParameters()nachwile przed tym, jak kolejny wątek wywoła execute(), wyniki tej drugiej operacji będą błędne. Istniejetakże ograniczenie obsługi tylko jednego zapytania w danym czasie przez obiekt Statement. Rozwiązaniemtego problemu jest synchronizacja tego fragmentu kodu, który używa współdzielonych obiektów, co omówionow rozdziale 3 i pokazano poniżej:

synchronized (pstmt) { pstmt.clearParameters(); pstmt.setInt(1, 2); pstmt.setInt(2, 4); pstmt.setDouble(3, 53.43); pstmt.executeUpdate();}

Niestety, takie rozwiązanie ma również wady. Wprowadzenie bloku synchronizacji na pewnych platformachzajmuje dodatkowy czas, a zsynchronizowane obiekty mogą być użyte tylko przez jeden wątek w danej chwili.Mimo to niektóre serwlety wymagają użycia bloku synchronizacji. Dobrym wyjściem z tej sytuacji jestwcześniejsze utworzenie połączeń z obiektami (takimi jak PreparedStatement), które mogą być szybkoużyte wewnątrz bloków synchronizacji.

Powyższe metody nie mają zastosowania w przypadku serwletów używających interfejsuSigleThreadModel, a ponadto ograniczają wydajność poprzez ładowanie naraz wielu kopii serwletu.

TransakcjeTransakcje są ważnym zagadnieniem związanym ze współczesnymi systemami relacyjnych baz danych. W wieluwitrynach internetowych zachodzi potrzeba posługiwania się rozbudowanymi wyrażeniami i strukturami danych.Spójrzmy na aplikację banku online. Aby wykonać przelew 50000$ pomiędzy dwoma kontami, program musiwykonać operację składającą się z dwóch oddzielnych, ale powiązanych ze sobą akcji: skredytować jedno konto iobciążyć drugie. Wyobraźmy sobie, że z pewnych powodów powiodła się tylko operacja uznania pierwszegokonta, a do obciążenia drugiego konta nie doszło. W wyniku takiego działania na jednym koncie jest o 50000$więcej, a na drugim koncie nic się nie zmieniło.

Błędne wykonanie wyrażenia SQL w tym przykładzie powoduje, że w baza danych zawiera błędne informacjedotyczące ilości środków dostępnych na koncie. Prawdopodobieństwo takiego zdarzenia jest niewielkie, ale jegowystąpienie nie jest wykluczone. Ten rodzaj problemu jest zbliżony do zagadnienia synchronizacji omówionegow rozdziale 3. Tym razem, zamiast zajmować się ważnością danych zgromadzonych w serwlecie, omówimyważność danych zapisywanych w bazie. Prosta synchronizacja nie jest dostatecznym rozwiązaniem omawianegoproblemu, ponieważ z zasobów jednej bazy danych może korzystać wiele serwletów. W aplikacjach bankowychprawdopodobne jest korzystanie z zasobów bazy danych przez wiele aplikacji nie napisanych w Javie. Naszczęście tym problemem zajęto się na długo przed zaistnieniem Javy. Większość głównych systemów RDMBSwspiera koncepcję transakcji. Technologia transakcji pozwala na grupowanie wielu wyrażeń SQL: transakcja tozbiór operacji wykonywanych przez serwer bazodanowy na bazie. Za pomocą systemu RDMBS, obsługującegotę technologię, można rozpocząć transakcję (czyli wykonać określoną liczbę operacji) i przesłać wyniki do bazydanych. Jeśli wykonanie wszystkich operacji wchodzących w skład transakcji jest niemożliwe, cała transakcjajest anulowana i żadne zmiany nie zachodzą w bazie danych. Gdybyśmy naszą aplikację bankową zbudowali woparciu o transakcje, pobranie pieniędzy zostałoby anulowane, gdyby obciążenie konta nie powiodło się.

Transakcja podczas realizacji zadania jest odizolowana od bazy danych. Transakcje są niepodzielne (operacjewchodzące w skład transakcji są traktowane jako logiczna całość). To oznacza, że użytkownicy korzystający z

Page 216: Java Servlet - Programowanie

bazy zawsze mają dostęp do aktualnych, choć nie widzą procesu aktualizacji. Jeśli użytkownik zażąda raportu natemat sprzedanych produktów w trakcie wykonywania transakcji sprzedaży, raport nie uwzględni wyników tejtransakcji.

Użycie transakcji w JDBCObsługa transakcji w JDBC jest realizowana za pomocą obiektu Connection. Nowe połączenia domyślnie sąotwierane w trybie autocommit (automatyczne zatwierdzenie), co oznacza, że każde wyrażenie SQL jesttraktowane jako pojedyncza transakcja, a wyniki wykonania wyrażenia są natychmiast zapisywane w baziedanych. Aby mieć możliwość grupowania wyrażeń SQL w transakcje i samodzielnej kontroli wykonywaniatransakcji, należy wywołać metodę setAutoCommit() (wywoływanej na rzecz obiektu Connection) zparametrem false. Za pomocą metody getAutoCommit()możemy sprawdzić, w jakim trybie realizowanejest połączenie z bazą danych. Funkcja zwróci wartość true, gdy ustawiony jest tryb autocommit. Pozakończeniu wykonywania wszystkich wyrażeń SQL należy wywołać metodę commit(), aby zatwierdzićtransakcję i zapisać wszystkie dokonane zmiany w bazie danych. Jeśli wystąpi błąd, wywołana jest metodarollback(), odwołująca transakcję(powodująca cofnięcie wszystkich zmian).

W przykładzie 9.7 pokazano serwlet używający transakcji do przeprowadzenia podstawowej formy zamówienia.Dla uproszczenia zakładamy istnienie dwóch tabel w bazie danych ODBC: MAGAZYN (tabela zawieraidentyfikator produktu i jego ilość w magazynie) oraz WYSYŁKA (w tej tabeli wpisano identyfikator produktu,numer zamówienia oraz ilość wysłanych produktów). Serwlet korzysta z metody chargeCard(), któraobsługuje wystawianie rachunków i zgłasza wyjątek, jeśli karta kredytowa klienta jest nieważna.

Przykład 9.7.

Zarządzanie zamówieniem za pomocą transakcjiimport java.io.*;import java.sql.*;import javax.servlet.*;import javax.servlet.http.*;public class OrderHandler extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); Connection con = null; try{ Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); con = DriverManager.getConnection("jdbc:odbc:ordersdb","user","passwd"); // włącz tryb transakcji con.setAutoCommit(false);Statement stmt = con.createStatement();stmt.executeUpdate( "UPDATE MAGAZYN SET ILOŚĆ = (ILOŚĆ - 10) WHERE PRODUCTID = 7"); stmt.executeUpdate( "UPDATE WYSYŁKA SET WYSŁANE = (WYSŁANE + 10) WHERE PRODUCTID = 7");chargeCard(); // metoda faktycznie nie istnieje ...con.commit();out.println("Operacja zamówienia zakończona sukcesem! Dziękujemy za współpracę"); } catch(Exception e) { // Jakikolwiek błąd jest podstawą do cofnięcia transakcji try { con.rollback(); } catch (SQLException ignored){} out.println("Operacja zamówienie nie powiodła się. Proszę skontaktowaćsię z obsługą techniczną."); } finally {// wyczyść try { if (con!=null) con.close(); } catch (SQLException ignored){}

Page 217: Java Servlet - Programowanie

}} }

Uwagi do powyższego przykładu: po pierwsze, logika transakcji zamówienia znajduje się w doPost(), dopókiklient nie będzie mógł powtórzyć akcji. Po drugie, celem przykładu jest przedstawienie logiki transakcji, więcdla uproszczenia założono, że użytkownik kupuje 10 sztuk towaru nr 7 i nie wyświetla formularza zawierającegoinformacje o karcie kredytowej i zamówieniu. Ponadto, zgłoszenie jakiegokolwiek wyjątku podczas inicjacjisterownika, łączenia z bazą danych, wykonywania SQL lub obsługi karty kredytowej spowoduje wykonanieskoku do bloku catch(), gdzie wywoływana jest metoda rollback() anulująca całą transakcję.

Optymalizacja transakcjiW poprzednim przykładzie umieściliśmy obiekt Connection wewnątrz metody doPost(). Zrezygnowaliśmywięc ze zwiększonej wydajności aplikacji, jaką daje umieszczenie tego obiektu wewnątrz metody init().Transakcje są ściśle związane z połączeniami, więc połączenia używające transakcji nie mogą byćwspółdzielone. Gdyby połączenia mogły być współdzielone, w serwlecie z poprzedniego przykładu jednocześniemogłyby być wykonane dwa polecenia: pobranie dziesięciu sztuk towaru i zatwierdzenie transakcji (wywołaniemetody commit()). W tabeli MAGAZYN brakowałoby wówczas 10 elementów.

Istnieje kilka możliwości użycia transakcji bez konieczności łączenia się z bazą danych na każde żądanie strony:

• Synchronizacja metody doPost(). Oznacza to, że każdy egzemplarz serwletu zajmujesię tylko jednym żądaniem. Jest to dobry sposób dla rzadko odwiedzanych witryn, alespowalnia działanie po stronie użytkowników, bo każda transakcja musi się zakończyć,zanim następna się rozpocznie. Jeśli wymagane jest częste odświeżanie bazy danych iwprowadzanie nowych danych, opóźnienie działania aplikacji może być niedopuszczalne.

• Tworzenie nowego obiektu Connection dla każdej transakcji. Jeśli wymaga sięuaktualnienia danych tylko raz na kilka tysięcy żądań, to rozwiązanie jest najprostsze.

• Utworzenie zbioru obiektów Connection wewnątrz metody init() i korzystanie znich w zależności od potrzeb, co pokazano na rysunku 9.3. Jest to najefektywniejszametoda rozwiązania tego problemu, ale bez wsparcia obcych klas ten sposób może stać siębardzo skomplikowany.

• Implementacja śledzenia sesji w serwlecie i użycie obiektu HttpSesion doprzechowywania obiektów Connection dla każdego użytkownika. Jest to najlepszerozwiązanie, bo pozwala rozszerzyć transakcję na wiele żądań i wiele serwletów.

Rysunek 9.3. Serwlety korzystające z puli połączeń bazy danych

Pule połączeńZa pomocą puli połączeń możemy podwajać tylko te zasoby, których aktualnie potrzebujemy (w tym wypadkuobiekty Connection). Pula połączeń może w sposób inteligentny zarządzać własnym rozmiarem i upewniaćsię, że każde połączenie jest ważne. Obecnie jest wiele dostępnych pakietów tworzących pule połączeń. Niektórez nich, np. DbConnectionBroker są dostępne za darmo na stronie http://javaexchange.com. Pakiety tetworzą obiekty, których zadaniem jest wydawanie połączeń na żądanie. Są dostępne również sterowniki pulipołączeń (pool drivers), które implementują nowy sterownik JDBC obsługujący połączenia innego (wcześniejzaładowanego) sterownika. Użycie sterownika jest najprostszym sposobem na utworzenie puli połączeń w

Page 218: Java Servlet - Programowanie

serwletach. Sterownik obsługujący pulę połączeń jest bardziej przeciążony niż sterownik obsługujący jednopołączenie.

Przykład 9.8 przedstawia prosty system puli połączeń. Najpierw tworzone są połączenia, które będą otwierane wmiarę potrzeb. Gdy wszystkie połączenia zostaną otwarte i nie będzie już wolnych połączeń, serwlet utworzynowe połączenia.

Nasz klasa ConnectionPool jest w pełni funkcjonalna, ale można stworzyć klasy korzystające z pakietówkomercyjnych służących do tworzenia puli połączeń.

Przykład 9.8.

Klasa ConnectionPoolimport java.sql.*;import java.util.*;public class ConnectionPool { private Hashtable connections = new Hashtable(); private Properties props;

public ConnectionPool(Properties props, int initialConnections) throws SQLException, ClassNotFoundException { this.props = props; initializePool(props, initialConnections); } public ConnectionPool(String driverClassName, String dbURL, String user, String password, int initialConnections) throws SQLException, ClassNotFoundException { props = new Properties(); props.put("connection.driver", driverClassName); props.put("connection.url", dbURL); props.put("user", user); props.put("password", password); initializePool(props, initialConnections); } public Connection getConnection() throws SQLException { Connection con = null; Enumeration con = connections.keys(); synchronized (connections) { while (cons.hasMoreElements()) { con = (Connection)cons.nextElement(); Boolean b = (Boolean)connections.get(con); if (b == Boolean.FALSE) { // Znaleźliśmy nieużywane połączenie. // Należy sprawdzić jego integralność szybkim // wywołaniem setAutoCommit(true). // Do użytku komercyjnego powinno się wykonać //więcej testów, takich jak wykonanie prostego zapytania try { con.setAutoCommit(true); } catch(SQLException e) { // Jeśli jest problem z połączeniem należy je zamienić na nowe connections.remove(con); con = getNewConnection(); } //Zaktualizuj tablicę mieszającą //Zwróć połączenie return con; } } // Jeśli się tu dostaliśmy, to oznacza, że nie ma wolnych połączeń. Należy utworzyć nowe połączenie. //

Page 219: Java Servlet - Programowanie

// con = getNewConnection(); connections.put(con, Boolean.FALSE); return con; } } public void returnConnection(Connection returned) { if (connections.containsKey(returned)) { connections.put(returned, Boolean.FALSE); } } private void initializePool (Properties props, int initialConnections) throws SQLException, ClasNotFoundException { //Ładujemy sterownik Class.forName(props.getProperty("connection.driver")); // Umieszcamy nasz zbiór połączeń w tablicy mieszającej // Wartość FALSE wskazuje na nieużywane połączenie for (int i = 0; i < initialConnections; i++) { Connection con = getNewConnection(); connections.put(con, Boolean.FALSE); } } private Connection getNewConnection() throws SQLException { return DriverManager.getConnection( props.getProperty("connection.url"),props); }}

Klasa ConnectionPool korzysta z tablicy mieszającej (Hashtable) przy użyciu obiektów Connectionjako kluczy i obiektów Boolean jako wartości. Wartości Boolean wskazują, czy połączenie jest używane(true), czy wolne (false). Program wywołuje metodę getConnection() obiektu ConnectionPool,aby wykorzystać obiekt Connection. Połączenie jest zwalniane za pomocą wywołania metodyreturnConnection().Przedstawiony tu model puli połączeń jest bardzo prosty, ale w praktyce częstowymaga się bardziej rozbudowanego modelu puli połączeń, w którym jest możliwość sprawdzenia spójnościpołączenia.

Przykład 9.9 przedstawia ulepszoną wersję serwletu zamówieniowego, który korzysta z puli połączeń.

Przykład 9.9.

Serwlet transakcji zbioru połączeńimport java.io.*;import java.sql.*;import javax.servlet.*;import javax.servlet.http.*;public class OrderHandlerPool extends HttpServlet { private ConnectionPool pool; public void init () throws ServletException { try { pool = new ConnectionPool("oracle.jdbc.driver.Oracledriver", "jdbc:oracle:oci7:orders", "user", "passwd", 5); } catch(Exception e) { throw new UnavailableException("Nie można utworzyć zbioru połączeń"); } } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

Connection con = null; res.setContentType("text/html"); PrintWriter out = res.getWriter();try { con = pool.getConnection(); // Rozpocznij transakcję con.setAutoCommit(false);

Page 220: Java Servlet - Programowanie

Statement stmt = con.createStatement();stmt.executeUpdate( "UPDATE MAGAZYN SET ILOSC = (ILOSC - 10) WHERE PRODUCTID = 7"); stmt.executeUpdate( "UPDATE WYSYLKA SET WYSLANE = (WYSLANE + 10) WHERE PRODUCTID = 7");changeCard(); // metoda faktycznie nie istnieje ...con.commit();out.println("Zamówienie powiodło się! Dziękujemy za współpracę!"); } catch(Exception e) { // Każdy błąd jest podstawą do cofnięcia transakcji try { con.rollback(); } catch (SQLException ignored){} out.println("Zamówienie nie powiodło się. Proszę skontaktować się zobsługą techniczną."); } finally { if (con != null ) pool.returnConnection(con); }} }

Połączenia jako część sesjiŚledzenie sesji, opisane dokładnie w rozdziale 7, pozwala na wykonywanie transakcji w inny sposób. Używającsesji, możemy stworzyć lub przydzielić określone połączenie z bazą danych indywidualnym użytkownikomwitryny internetowej lub aplikacji intranetowej. W przykładzie 9.10 serwlet ConnectionPerClientprzypisuje obiekt Connection każdemu klientowi HttpSession. To powoduje umieszczenie obiektuConnection wewnątrz obiektu ConnectionHolder, który jest odpowiedzialny za zapewnienie cyklu życiapołączenia.

Przykład 9.10.

Skojarzenie Connection z Session. /* Właściwy Serwlet*/ public class ConnectionPerClient extends HttpServlet {public void init() throws ServletException { try { Class.forName("oracle.jdbc.driver.OracleDriver”); } catch (ClassNotfoundException e ) { throw new UnavailableException ("Nie można zaladowac sterownika bazy Oracle");} } public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); HttpSession session = req.getSession(true); Connection con; // zsynchronizuj: Bez tego dwa uchwyty mogą być utworzone dla jednegoklienta. synchronized (session) { // spróbuj otrzymać dla tego klienta uchwyt połączenia ConnectionHolder holder = (ConnectionHolder) session.getAttribute("servletapp.connection"); // stwórz (i przechowuj) nowe połączenie i uchwyt if ( holder = null) { try { holder = new ConnectionHolder(DriverManager.getConnection( "jdbc:oracle:oci7:ordersdb", "user", "passwd")); session.setAttribute("servletapp.connection", holder); } catch (SQLException e) { log(" Nie można otrzymać połączenia db", e); } }

Page 221: Java Servlet - Programowanie

// Pobierz właściwe połączenie od uchwytucon = holder.getConnection(); }// użyj teraz połączeniatry { Statement stmt = con.createStatement(); stmt.executeUpdate( "UPDATE MAGAZYN SET ILOSC = (ILOSC - 10) WHERE PRODUCTID = 7"); stmt.executeUpdate( "UPDATE WYSYLKA SET WYSLANE = (WYSLANE + 10) WHERE PRODUCTID = 7"); // Obsłuż kartę kredytową i potwierdź transakcję w kolejnym serwlecie res.sendRedirect(res.encodeRedirectURL( req.getContextPath() + "/servlet/CreditCardHandler")); } catch(Exception e) { // Jakikolwiek błąd powoduje cofnięcie transakcji try { con.rollback(); session.removeAttribute("servletapp.connection"); } catch (Exception ignored){} out.println("Zamówienie sie nie powiodło. Skontaktuj się z serwisemtechnicznym."); } }}

Zamiast bezpośrednio związania połączenia z sesją, stworzono prostą klasę przechowującą połączenia, któraimplementuje interfejs HttpSessionBindingListener. Takie działanie jest konieczne, gdyż połączenia zbazą danych są jednym z najbardziej ograniczonych zasobów w aplikacji JDBC i trzeba się upewnić, że każdezakończone połączenie zostanie od razu zwolnione. Klasa ta ponadto pozwala na cofnięcie wszystkich niezatwierdzonych zmian. Jeśli użytkownik naszego hipotetycznego sklepu internetowego opuści system przedweryfikacją danych, jego transakcja zostanie automatycznie anulowana po zakończeniu sesji.

Przechowywanie połączeń w sesjach wymaga dokładnej analizy potrzeb aplikacji. Większość prostych serwerówbazodanowych ma możliwość obsługi maksymalnie ok. 100 połączeń; biurkowe bazy danych takie jak MicrosoftAccess mogą obsłużyć nawet mniej żądań.

Serwlet księgi gościAby zrozumieć koncepcję bazy danych, spójrzmy na prawdziwy serwlet. Przykład 9.11 przedstawia kodźródłowy typowego serwletu księgi gości współpracującego z bazą danych. Obsługuje on stronę www, którejgoście mają możliwość wpisywania komentarzy i czytania wpisanych wiadomości przez innych użytkownikówodwiedzających stronę. Na rysunku 9.4 przedstawiono prezentację tej strony. Wszystkie komentarze gości sąwprowadzane do bazy danych. Aby mieć dostęp do informacji zapisanych w bazie, serwlet korzysta z kilkuprzedstawionych w tym rozdziale technik (m.in. z puli połączeń, preinterpretowanych wyrażeń oraz z obiektuContextProperties, służącego do odczytania informacji o konfiguracji bazy danych z parametrówkontekstu inicjującego). Serwlet jest rozszerzony o CacheHttpServlet z rozdziału 4, który optymalizujewydajność wyjścia.

Page 222: Java Servlet - Programowanie

Rysunek 9.4.

Księga gości

Przykład 9.11.

Proszę, Wpisz Się

import java.io.*;import java.sql.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.CacheHttpServlet;public class Guestbook extends CacheHttpServlet { static final String SELECT_ALL = "SELECT name, email, cmt, id FROM guestlist ORDER BY id DESC"; static final String INSERT = "INSERT INTO guestlist (id, name, email, cmt) " + "VALUES (?,?,?,?)"; private long lastModified = 0; // private ConnectionPool pool; // pobierz wskaźnik do puli połączeń public void init() throws ServletException { try { ServletContext context = getServletContext(); synchronized (context) {

Page 223: Java Servlet - Programowanie

// Pula może już istnieje, jako atrybut kontekstu pool = (ConnectionPool) context.getAttribute("pool"); if (pool = null) { // Skonstruuj pulę połączeń używając parametrów kontekstu inicjującego // connection.driver, connection.url, user, password, itd. pool= new ConnectionPool( new ContextProperties(context), 3); context.setAttribute("pool", pool); } } } catch(Exception e) { throw new UnavailableException(" Nie można pobrać puli połączeń z kontekstu: " + e.getMessage()); } }// Wyświetl istniejące w bazie danych komentarze, poproś o wpisanie nowegopublic void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); printHeader(out); printForm(out); printMessages(out); printFooter(out);} // Dodaj nowy komentarz i wyślij go do doGet public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { handleForm (req, res); doGet(req, res); }

private void printHeader (PrintWriter out) { out.println("<HTML><HEAD><TITLE>Księga gości</TITLE></HEAD>"); out.println("<BODY>");}private void printForm (PrintWriter out) { out.println("<FORM METHOD=POST>"); // wysyła do siebie out.println("<B>Prześlij swój komentarz:</B><BR>"); out.println("Twoje nazwisko: <INPUT TYPE=TEXT NAME=name><BR>"); out.println("Twój adres email: <INPUT TYPE=TEXT NAME=email><BR>"); out.println("Komentarz: <INPUT TYPE=TEXT SIZE=50 NAME=comment><BR>"); out.println("<INPUT TYPE=SUBMIT VALUE=\"Wyslij\"><BR>"); out.println("</FORM>"); out.println("<HR>"); } // przeczytaj i wyświetl wiadomości z bazy danych private void printMessages (PrintWriter out) throws ServletException { String name, email, comment; Connection con= null; Statement stmt = null; ResultSet rs = null; try { con = pool.get.Connection(); stmt = con.createStatement(); rs = stmt.executeQuery (SELECT_ALL); while (rs.next()) { name = rs.getString(1); if (rs.wasNull()||name.length()==0) name = "Nieznany użytkownik"; email = rs.getString(2); .if (rs.wasNull()||email.length()==0) name = "Nieznany użytkownik"; .comment = rs.getString(3); if (rs.wasNull()||comment.length()==0) name = "Bez komentarzy"; out.println("<DL>"); out.println("<DT><B>" + name + "</B> (" + email + ") mówi "); out.println("<DD><PRE>" + comment + "</PRE>"); out.println("</DL>"); }

Page 224: Java Servlet - Programowanie

} catch (SQLException e) { throw new ServletException(e); } finally { try { .if (stmt != null) stmt.close(); } catch (SQLException ignored) {} pool.returnConnection(con); }}private void printFooter (PrintWriter out) { out.println("</BODY>");}// zachowaj nowy komentarz w bazie danychprivate HandleForm (HttpServletRequest req, HttpServletResponse res) throws ServletException { String name = req.getParameter("name"); String email = req.getParameter("email"); String comment = req.getParameter("comment"); Connection con = null;PreparedStatement pstmt = null; try { con = pool.getConnection(); //Użyj przygotowanego wyrażenia aby automatycznie unikać znaków specjalnych pstmt = con.prepareStatement(INSERT);long time = System.currentTimeMillis(); pstmt.setString(1, Long.toString(time)); pstmt.setString(2, name); pstmt.setString(3, email); pstmt.setString(4, comment); pstmt.executeUpdate(); } catch (SQLException e) { throw new ServletException(e); } finally { try { if (pstmt != null) pstmt.close(); } catch (SQLException ignored) {} pool.returnConnection(con); } //zauważ,że posiadamy nowo zmodyfikowany czas lastModified = System.currentTimeMillis();}public long getLastModified(HttpServletRequest req) { return lastModified; // wspiera CacheHttpServlet }}

Wyrażenia SQL, umożliwiające dostęp do bazy danych znajdują się w górnej części klasy Guestbook wzmiennych typu static final. Wydzielenie tych wyrażeń pozwala na łatwe dokonywanie zmian aplikacji wprzyszłości.

Metoda init() pobiera pulę połączeń z obiektu ServletContext lub tworzy ją, gdy w obiekcieServletContext nie ma zapisanej puli połączeń. Istniejąca pula połączeń jest zapisana jako atrybutServletContext pod nazwą pool. Jeśli dany atrybut nie istnieje, metoda init() tworzy nową pulępołączeń za pomocą klasy ContextProperties i zapisuje pulę. Pula połączeń nie jest wprawdzie potrzebnatemu serwletowi, bo nie obsługuje on transakcji, więc przepustowość przez pojedynczy obiekt Connectionjest wystarczająca. Użycie puli połączeń pozwala tu na zarządzanie obiektu Connection, który jest używanyjednocześnie przez wiele serwletów.

Metoda doGet() powoduje wyświetlenie nagłówka, formularza HTML z miejscem na wpisanie komentarza iwcześniejszych komentarzy odczytanych z bazy danych. Techniki wykonania bardziej eleganckiej strony HTMLopisane są w dalszych rozdziałach, począwszy od rozdziału 14.

Metoda printMessages() używa obiektu Connection pobranego z puli połączeń, wykonuje zapytanieSELECT_ALL za pomocą obiektu Statement. Na początku każdego wiersza obiektu ResultSet powyższametoda powoduje „dopisanie” znacznika <DL> w formularzu HTML. W końcowym bloku obiekt Connectionjest zwalniany i powraca do puli połączeń.

Metoda doPost() jest wywoływana, gdy użytkownik przesyła komentarze za pomocą formularzagenerowanego za pomocą metody doGet(). Ta metoda wywołuje funkcję handleForm(), która gromadzi

Page 225: Java Servlet - Programowanie

komentarze wewnątrz bazy danych, a następnie przesyła komentarze metodzie doGet(), która powodujewyświetlenie ich na stronie. Metoda handleForm() odczytuje parametry nazwy, adresu email i komentarzaoraz umieszcza je w bazie danych za pomocą instrukcji INSERT obiektu PreparedStatement. Używającobiektów PreparedStatement do zachowywania łańcuchów tekstowych automatycznie unikamy znakówspecjalnych (zabronionych). Oprócz komentarza, w bazie danych znajduje się informacja o dacie wpisanychkomentarzy.

W dolnej części metody handleForm() ustawiany jest parametr lastModified. Pozwala to metodziegetLastModified() zwrócić czas ostatniej aktualizacji bazy danych. Ponieważ ten serwlet jest rozszerzonyo CacheHttpServlet, ostatnia zmodyfikowana funkcja będzie wykorzystana przez superklasę dozarządzania pamięcią wyjścia, która będzie zmieniana tylko po aktualizacji bazy danych.*

Zaawansowane techniki JDBCTeraz, gdy poznaliśmy podstawy, możemy pomówić o kilku zaawansowanych technikach używającychserwletów i JDBC. Najpierw sprawdzimy, w jaki sposób serwlety używają procedur bazy danych. Potemdowiemy się, w jaki sposób serwlety pobierają z bazy danych złożone typy danych, takie jak dane binarne(obrazy, aplikacje, itp.), duże ilości tekstu, a nawet wykonywalny kod manipulujący bazą danych.

Przechowywane proceduryWiększość baz danych posiada pewien rodzaj wewnętrznego języka programowania służącego do tworzenia iprzechowywania procedur działających wewnątrz bazy danych. Utworzone procedury mogą być wywoływaneprzez zewnętrzne aplikacje. Jednym z przykładów jest język PL/SQL dla Oracle. Bazodanowe językiprogramowania są często lepiej przystosowane do wykonania pewnych akcji w bazie danych. Wiele jużistniejących baz danych posiada wiele zgromadzonych procedur gotowych do użycia, więc dobrze byłoby poznaćpewne techniki wykorzystania tych procedur we własnych aplikacjach.

Poniżej przedstawiono kod procedury napisanej w Oracle PL/SQL:

CREATE OR REPLACE PROCEDURE sp_interest(id IN INTEGERBAL IN OUT FLOAT) ISBEGINSELECT balanceINTO balFROM accountsWHERE account_id = id;bal := bal + bal * 0.03;UPDATE accountsSET balance = BALWHERE account_id = id;END;

Ta procedura, oprócz instrukcji SQL, wykonuje tylko pewne obliczenie. Ten program jest łatwy do napisania wSQL (transakcja z wcześniejszego przykładu działa w podobny sposób) i stanowi tylko prosty przykładprocedury przechowywanej. Istnieje kilka powodów, dla których warto używać procedur przechowywanych:

• Przechowywane procedury są wcześniej skompilowane w RDBMS*, więc działają szybciej niżdynamicznie używane wyrażenia SQL.

• Przechowywane procedury są w całości wykonywane w RBDMS, więc zapytania i procesuaktualniania bazy danych mogą odbyć się bez korzystania z sieci.

• Raz napisaną procedurę można użyć w wielu różnych aplikacjach i językach programowania.

• Zmiany w strukturze tabel, na których operują procedury, wymagają modyfikacji kodu tychprocedur. Kod aplikacji korzystającej z danej procedury pozostaje niezmieniony.

* Należy pamiętać, że nie ma możliwości buforowania dla żądań POST. Strona jest odświeżana po każdym przesłanym komentarzu.Gdybyśmy w miejsce komentarza nie wpisali nic, to aplikacja spowoduje ponowne załadowanie całej strony, bo żądanie POST nie zostałozapamiętane. * RDBMS — maszyna bazodanowa

Page 226: Java Servlet - Programowanie

• Wiele starszych baz danych posiada dużą ilość przechowywanych procedur, więc dobrze byłoby towykorzystać.

Procedura Oracle PL/SQL w tym przykładzie pobiera początkowe wartości, np. identyfikator konta i zwracazaktualizowane saldo. Ponieważ każda baza danych ma swoją własną składnię dostępu do przechowywanychprocedur, w JDBC utworzono interfejs pozwalający na korzystanie z przechowywanych procedur niezależnie odrodzaju bazy danych. Ten interfejs to java.sql.CallableStatement. Składnia wywołania procedury niezwracającej wyniku to np. {call procedure_name(?,?)}, a zwracającej wynik to np.{?=callprocedure_name(?,?)}. Parametry wewnątrz nawiasów są opcjonalne.

Sposób użycia klasy CallableStatement jest podobny do sposobu, w jaki używa się klasyPreparedStatement.

CalableStatement cstmt = con.prepareCall("{call sp_interest(?,?)}");cstmt.registerOutParameter(2, java.sql.Types.FLOAT);cstmt.setInt(1, accountID);cstmt.execute();out.println("Zaktualizowane saldo: " + cstmt.getFloat(2));

Ten program tworzy najpierw obiekt CallableStatement poprzez wywołanie metody prepareCall()obiektu Connection. Ponieważ nasza procedura przechowywana ma parametry wyjściowe, używa metodyregisterOutParameter obiektu CallableStatement, by zarejestrować wyjściowe parametry jakowartości typu FLOAT. Program wykonuje procedurę i używa metody getFloat() obiektuCallableStatement aby wyświetlić zaktualizowane saldo. Metody getXXX() interfejsuCallableStatement mają podobne właściwości do metod getXXX()interfejsu ResultSet.

Pliki binarne i księgi, czyli bardzo duże obiektyWiększość baz danych wspomaga obsługę dużych obiektów takich jak łańcuchy tekstowe o dużych rozmiarach(do kilku GB) czy plików binarnych, np. plików z obrazkami (*.gif). W zależności od typu bazy obsługa tychdanych odbywa się w różny sposób, ale metody zapewniane przez JDBC do pobierania dużych obiektów sąstandardowe i mogą być stosowane dla różnych typów baz danych. Metoda getAsciiStream() obiektuResultSet obsługuje duże łańcuchy tekstowe, a getBinaryStream() pracuje na dużych obiektachbinarnych. Są to metody operujące na strumieniach, więc każda z tych metod zwraca obiekt InputStream.

Obsługa dużych obiektów stanowi główne źródło problemów pojawiających się podczas kożystania z JDBC. Wtrakcie projektowania aplikacji należy przetestować sterowniki przy użyciu największych obiektów dla niejprzewidzianych. Sterownik JDBC dla Oracle ma szczególną skłonność do błędnej pracy podczas używaniadużych obiektów.

Oto fragment kodu serwletu, w którym jest pobierany długi łańcuch ASCII. Zakładamy, że reszta serwletu jestjuż stworzona:try {

ResultSet rs = stmt.executeQuery( "SELECT TITLE, SENDER, MESSAGE FROM MESSAGES WHERE MESSAGE_ID = 9"); if (rs.next()) { out.println("<H1>" + rs.getString("title") + "</H1>"); out.println("<B>From:</B> ") + rs.getString("sender") + "<BR>"); BufferedReader msgText = new BufferedReader(rs.getAsciiStream("message"))); while (msgText.ready()) { out.println(msgText.readLine()); } }}catch (SQLException e) {// zdaj relację}

Podczas odczytywania z InputStream, serwlet nie otrzymuje wartości z żadnej innej kolumny zbioruwyników. To bardzo ważne, bo wywołanie każdej innej metody getXXX() obiektu ResultSet zamykapoprzedni strumień InputStream.

Dane binarne mogą być pobrane w ten sam sposób używając ResultSet.getBinaryStream(). W tymwypadku należy ustalić właściwy typ danych i przesłać na wyjście w formie strumienia bajtowego. Przykład 9.12pokazuje serwlet zwracający plik GIF pobrany z bazy danych.

Przykład 9.12.

Wczytywanie binarnego obrazka GIF z bazy danychimport java.io.*;

Page 227: Java Servlet - Programowanie

import java.sql.*;import javax.servlet.*;import javax.servlet.http.*; public class DBGifReader extends HttpServlet { Connection con; public void init() throws ServletException { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver”); con = DriverManager.getConnection("jdbc:odbc:imagedb","user","passwd");} catch (ClassNotfoundException e ) { throw new UnavailableException ("Nie można zaladowac sterownika JdbcOdbc");}catch (SQLException e) { throw new UnavailableException("Nie można połączyć się z db");} } public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try{ res.setContentType("image/gif"); ServletOutputStream out = res.getOutputStream() Statement stmt =con.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT IMAGE FROM PICTURES WHERE PID = " + req.getParameter("PID")); if (rs.next()) { BufferedInputStream gifData = new BufferedInputStream (rs.getBinaryStream("image")); byte[] buf = new byte[4 * 1024]; //bufor 4K int len; while(( len=gifdata.read(buf,0,buf.length))!=-1){ out.write(buf,0,len);}}else { res.sendError(res.SC_NOT_FOUND);}}catch (SQLException e) { // zdaj relację}

Co dalej?Poza metodami zawartymi standardowo w JDBC i technikami przedstawionymi w tym rozdziale istniejądodatkowe interfejsy wspomagające pisanie skomplikowanych aplikacji umożliwiających dostęp do baz danych.Jednym z nich jest interfejs JDBC 2.0, następca omówionego w tym rozdziale JDBC 1.2.

JDBC 2.0 jest szeroko stosowanym narzędziem. Implementacja JDBC 2.0 wymaga bazy danych, która możeobsłużyć zaawansowane metody zawarte w interfejsie oraz sterownika, który zapewnia dostęp do tych metod. Naszczęście, wsparcie JDBC 2.0 jest gwarantowane przez wszystkie serwery J2EE, bo każdy serwer zgodny z J2EEwymaga zapewnienia dostępu do sterownika i bazy danych współpracującej z JDBC2.0. Instrukcja implementacjifirmy Sun zawiera dodatkowo ewaluacyjną wersję bazy danych Cloudscape.

JDBC 2.0 składa się z dwóch części. Jedna z nich znajduje się w pakiecie Javy 2 java.sql i wymaga bazydanych i sterownika zgodnych z JDBC 2.0. Druga część jest umieszczona w pakiecie javax.sql, któryzawiera kilka dodatkowych metod (użycie tych metod nie jest wymagane).

Do udoskonaleń pakietu java.sql należy możliwość modyfikacji i przeglądania zbioru wyników. W obiekcieResultSet kursor można przesuwać do przodu, do tyłu lub można ustawić go na dowolnym rekordzie (winterfejsie JDBC 1.2 można poruszać się tylko o jeden element do przodu). Dodano możliwość obsługi typówdanych SQL3 (BLOB — dla dużych obiektów binarnych, CLOB dla dużych obiektów tekstowych i ARRAY dlatablic).

Opcjonalny pakiet javax.sql zawiera wiele metod szczególnie pomocnych w aplikacjach komercyjnych.Jedną z nich jest wsparcie wyszukiwania JNDI, które pozwala otrzymywać połączenie z bazą danych napodstawie nazwy z serwera JNDI. Kolejną cechą jest wbudowany mechanizm puli połączeń (to bardzo różni

Page 228: Java Servlet - Programowanie

interfejs JDBC 2.0 od JDBC 1.2, ale nie wpływa na działanie aplikacji napisanych pod starszy interfejs). Bardzoważną opcjonalną cechą w JDBC 2.0 jest wspomaganie transakcji rozproszonych. Pozwala to na rozmieszczenietransakcji w różnych oddzielnych bazach danych i aktualizowanie obu baz jedną niepodzielną operacją. Wdodatku, JDBC 2.0 wspiera obiekty RowSet, które „opakowują”wiersze danych ResultSet. Ułatwia tozapamiętywanie danych, zapewnia prosty transfer danych przez sieć oraz standaryzowany dostęp do źródełdanych tabelarycznych (np. do arkusza kalkulacyjnego lub pliku jednorodnego).

Więcej informacji o cechach JDBC 2.0 można znaleźć w książce George'a Reese'a Database Programming withJDBC and Java (O'Reilly) i JDBC API Tutorial and Reference autorstwa Seth White oraz na stronie internetowejhttp://java.sun.com/products/jdbc.

Dla tych, którzy oczekują czegoś więcej niż oferuje JDBC, firma ClearInk sponsoruje otwartą bibliotekę zkodami źródłowymi interfejsu Village, który jest lepszy od JDBC 1.2 i łatwiej współpracuje z bazami danych.Bibliotekę stworzono w oparciu o produkt komercyjny dbKona firmy BEA/WebLogic. Na podstawie Villagestworzono bardziej rozbudowaną wersję o nazwie Town. Oba produkty Village i Town są dostępne na stroniehttp://www.working-dogs.com (jak na razie nie ma żadnych informacji o pakiecie City).

Page 229: Java Servlet - Programowanie

Rozdział 10. Komunikacja aplet-

serwlet

Niniejszy rozdział przedstawia kilka technik, przy pomocy których aplety mogą komunikować się z serwletami.Zaprezentowane zostanie nieco inne podejście, niż to, jakiego można by się spodziewać. Zamiast przyjmować, żeistnieje aplet i serwlet, które muszą się ze sobą porozumieć, przyjęto, że istnieje aplet, który musi porozumieć sięz pewnym elementem serwera, i opisane zostanie, dlaczego czasami elementem tym powinien być serwlet.

Aby rozpocząć, proszę pomyśleć o apletach, które muszą porozumieć się z serwerem. Istnieje kilka dobrychprzykładów. Proszę spojrzeć na aplet administracyjny, który działa na serwerze Java Web Server. Proszępomyśleć, jak on działa — jest wykonywany na kliencie, ale konfiguruje serwer. Aby to uczynić, aplet i serwermuszą być w niemal stałym kontakcie. Inny przykład, proszę spojrzeć na jeden z popularnych apletówpogawędek. Jeden klient coś mówi, a cała reszta to widzi. Jak to działa? Na pewno aplety nie komunikują się zesobą. Zamiast tego, każdy aplet wysyła swoje wiadomości do centralnego serwera, a serwer zajmuje sięuaktualnieniem informacji innych klientów. W końcu, proszę sobie wyobrazić aplet, który śledzi cenę zbioruakcji i dokonuje ciągłego uaktualniania. Jak aplet poznaje aktualne ceny i, co ważniejsze, skąd wie, kiedy się onezmieniają? Odpowiedzią jest, że komunikuje się on z serwerem.

Opcje komunikacjiZainteresowanie handlem akcjami wzrasta razem z indeksami giełdowymi, więc kontynuowany będziehipotetyczny aplet, odpowiadający za akcje. Należy ostrzec, że aplet ten pozostanie hipotetyczny. Zostanie onwykorzystany jedynie jako punkt odniesienia w dyskusji nad kwestiami należącymi do komunikacji aplet-serwlet.Ale proszę się nie martwić, w dalszej części tego rozdziału występuje duża ilość kodu przedstawiająca techniki tuopisane, jedynie w nieco prostszych przykładach.

Ten aplet akcji musi pobierać ceny z jakiegoś serwera. Przyjmując, że jest to zwykły, niegodny zaufania aplet,jest tyko jedno wyjście — z komputera, z którego został pobrany. Każda próba połączenia się z innymkomputerem daje wynik w SecurityException, tak więc należy przyjąć, że aplet pobiera ceny z serwera, zktórego został pobrany6. Pytanie pozostaje — jak aplet i serwer mogą się ze sobą porozumiewać?

Aplety godne i niegodne zaufania

Kiedy aplet Javy zostaje osadzony w stronie WWW, przeglądarka może pobrać go i wykonać automatycznie.Jeżeli się nad tym zastanowić, jest to bardzo niebezpieczna sprawa. Tak więc, aby chronić klienta, JDK 1.0przyjmował, że wszystkie aplety są niegodne zaufania i uruchamiał je pod kontrolą SecurityManager, któryw poważny sposób ograniczał ich funkcjonalność. Na przykład, menedżer bezpieczeństwa zapewniałniemożność apletów do zapisywania informacji w systemie plików użytkownika, odczytywania pewnychwłaściwości systemu, przyjmowania przychodzących połączeń przez porty, czy też uruchamiania połączeńwychodzących z każdym komputerem, który nie był serwerem macierzystym. Własności te zabezpieczałyklienta, ale ograniczały użyteczność apletów.

6 Można zastanawiać się, skąd sam serwer uzyskał informacje na temat cen. Dla celów tego przykładu należy przyjąć, że byłato magia

Page 230: Java Servlet - Programowanie

Konsekwentnie, JDK 1.1 wprowadził koncepcję apletów godnych zaufania — apletów, które mogły działać,jak zwykłe aplikacje z pełnym dostępem do komputera klienta. Aby aplet został uznany za godny zaufania,musi zostać elektronicznie podpisany przez osobę z firmy, której ufa klient (zaznaczonej w przeglądarceużytkownika). Podpis uwierzytelnia pochodzenie apletu i gwarantuje spójność podczas transferu tak, abyklient wiedział, że kod apletu nie został złośliwie zmieniony. Pozwoliło to na tworzenie bardziej użytecznychapletów, ale było podejściem typu wszystko albo nic.

Aby dać klientowi więcej kontroli, JDK 1.2 wprowadził złożony system kontroli dostępu. W tym nowymsystemie, podpisany elektronicznie aplet może być częściowo uznany za godny zaufania i może on otrzymaćpewne możliwości bez uzyskania pełnej kontroli nad systemem. Pozwala to na przyznawanie apletomnieznanego pochodzenia niewielkich przywilejów (takich jak zapis w jednym, konkretnym katalogu), bezdostarczania im możliwości przeglądania twardego dysku klienta. Producenci przeglądarek powoliwprowadzają obsługę nowych wersji JDK, ale na szczęście możliwe jest uaktualnienie JVM przeglądarki przypomocy modułu rozszerzającego Java Plug-in, bezpłatnego produktu dostępnego pod adresemhttp://java.sun.com/products/plugin.

Połączenia przez HTTP i zwykłe portyPrzed stworzeniem JDK 1.1 i serwletów istniały dwie możliwości komunikacji aplet-serwer:

• Połączenie HTTP apletu z programem CGI na serwerze. Aplet działa jak przeglądarka i żąda strony,analizując odpowiedź na własny użytek. Aplet może dostarczać informacji przy pomocy łańcuchazapytania lub danych POST oraz otrzymywać informacje ze zwracanej strony.

• Połączenie przez zwykły port apletu z serwerem nie-HTTP. Serwer nie-HTTP może słuchać na danymporcie i porozumiewać się z apletem przy pomocy dowolnego protokołu, który zostanie wynegocjowany

Każde z tych podejść posiada wady i zalety. Połączenie HTTP apletu z programem CGI działa dobrze znastępujących powodów:

• Jest ono łatwe do napisania. Aplet może korzystać z klas java.net.URL ijava.net.URLConnection w celu zarządzania kanałem komunikacji, a program CGI może zostaćnapisany tak, jak każdy inny.

• Działa nawet w przypadku apletów pracujących za firewallem. Większość firewalli pozwala napołączenia HTTP, a nie pozwala na połączenia przez zwykłe porty.

• Pozwala apletowi Javy na komunikację z programem napisanym w dowolnym języku. Program CGI niemusi być napisany w Javie. Może być to Perl, C, C++, lub każdy inny język.

• Współpracuje z apletami utworzonymi przy pomocy JDK 1.0, tak więc pracuje z wszystkimiprzeglądarkami posiadającymi obsługę Javy.

• Pozwala na bezpieczną komunikację. Aplet może porozumiewać się z bezpiecznym serwerem przypomocy zaszyfrowanego protokołu HTTPS (HTTP + SSL).

• Program CGI może być wykorzystywany przez palety, a także przez przeglądarki. W przypadkuprzykładu akcji, program CGI może wykonywać podwójne zadanie, działając również jako wsparcie dlaserwisu opartego na formularzach HTML. Zwłaszcza wygodne jest wykorzystanie przez apletistniejącego programu CGI.

Jednak połączenie HTTP z programem CGI posiada również pewne wady:

• Jest wolne. Z powodu paradygmatu HTTP żądanie-odpowiedź, aplet i program CGI nie mogąporozumiewać się interaktywnie. Dla każdego żądania i odpowiedzi muszą one tworzyć nowy kanałkomunikacyjny. Dodatkowo istnieje pewne standardowe opóźnienie spowodowane uruchamianiem iinicjalizacją programu CGI w celu obsługi żądania.

• Zazwyczaj wymaga, aby żądania były utworzone w formie okropnej tablicy par nazwa-wartość. Naprzykład, kiedy przykładowy aplet akcji pyta o najwyższy dzienny kurs akcji Sun Microsystems, musizadać pytanie przy pomocy okropnego łańcucha takiego jak akcja=sun&zapytanie=maxdzien.

• Wymaga, aby wszystkie odpowiedzi były sformatowane według arbitralnego, wcześniej uzgodnionegostandardu. Na przykład, kiedy przykładowy aplet akcji otrzymuje odpowiedź zawierającą najwyższydzienny kurs akcji, musi dokładnie znać sposób analizy danych. Czy zwrócona cena rozpoczyna się

Page 231: Java Servlet - Programowanie

znakiem dolara? Czy odpowiedź zawiera również czas wystąpienia ceny maksymalnej? A jeżeli tak,gdzie i w jakim formacie został on podany?

• Jedynie aplet może rozpoczynać komunikację. Program CGI musi biernie czekać na żądanie od apletu,aby mógł zacząć odpowiadać. Jeżeli kurs akcji zmienia się, aplet może się o tym dowiedzieć jedyniewtedy, gdy zada właściwe pytanie.

Aplet i serwlet mogą się również porozumiewać poprzez połączenie zwykłym portem apletu z procesem serweranie-HTTP. Podejście do posiada następujące zalety w stosunku do podejścia opartego na HTTP:

• Pozwala na dwukierunkową stałą komunikację. Aplet i serwlet mogą wykorzystywać ten sam port (anawet kilka portów) w celu interaktywnego porozumiewania się, wysyłania wiadomości w jedną i drugąstronę. Z powodów bezpieczeństwa, aplet musi zawsze rozpoczynać połączenie poprzez połączenie się zportem serwera na komputerze-serwerze, lecz po ustanowieniu połączenia przez port, każda stronamoże zapisywać do portu w dowolnym czasie. Pozwala to apletowi akcji na otrzymywanie uaktualnieńcen akcji, kiedy tylko są one dostępne.

• Pozwala na wykonywanie po stronie serwera bardziej wydajnego programu. Serwer nie-HTTP możebyć utworzony w celu obsługi żądania bezpośrednio bez uruchamiania zewnętrznego programu CGI wcelu wykonania pracy.

Lecz połączenie przez zwykły port posiada również wady w stosunku do podejścia opartego na HTTP:

• Nie działa w przypadku apletów działających za firewallami. Większość firewalli nie pozwala napołączenia przez zwykły port, tak więc nie pozwala na ten typ komunikacji aplet-serwlet. Tak więcmechanizm ten powinien zostać wykorzystywany jedynie wtedy, gdy pewne jest, że aplet nie będzieuruchomiony po drugiej stronie firewalla, na przykład w wypadku aplikacji Intranetu.

• Tworzenie kodu działającego na serwerze może być stosunkowo skomplikowane. Zawsze musi istniećpewien proces (taki, jak serwer kursów akcji) słuchający na dobrze znanym porcie serwera. Tworzenietakiej aplikacji w Javie jest łatwiejsze niż w C++, ale wciąż nie jest proste.

• Może wymagać utworzenia nowego protokołu. Aplet i serwer muszą zdefiniować protokół, którywykorzystają w celu komunikacji. Chociaż protokół ten może być prostszy i bardziej wydajny niżHTTP, często musi zostać specjalnie napisany.

• Serwer nie-HTTP nie może zostać bezpośrednio podłączony do przeglądarki WWW. Przeglądarkiposługują się HTTP; nie mogą komunikować się z serwerem nie-HTTP.

Standardowe historyczne podejście wymagało od apletów porozumiewania się przy pomocy HTTP z programamiCGI na serwerze. Jest to łatwe oraz działa we wszystkich typach przeglądarek, nawet tych pracujących zafirewallami. Wykorzystanie połączenia przez zwykły port zostało generalnie zarezerwowane dla sytuacji, wktórej jest to absolutnie konieczne, takich jak ta, w której aplet i serwer wymagają komunikacji dwukierunkowej.A także, nawet w tym wypadku, często możliwe jest wykorzystanie połączeń HTTP w celu symulacjikomunikacji dwukierunkowej, co ma służyć przejściu przez firewalle, jak przedstawione to będzie wpóźniejszym przykładzie.

Serwlety i serializacja obiektuNiedawne wprowadzenie serwletów Javy i serializacji obiektów tchnęło nowe życie w te tradycyjne technikikomunikacji aplet-serwer. Serwlety zastępują wolno uruchamiające się programy CGI, poprawiając wydajnośćkomunikacji aplet-serwer opartej na HTTP i czyniąc częste połączenia aplet-serwer możliwymi. Chociaż ogólnierzecz biorąc prawdą jest, że aplet i serwlet ciągle wymagają czasu do ponownego otworzenia połączenia dlakażdego żądania i odpowiedzi, aplet nie musi już dłużej czekać, aż serwer uruchomi program CGI w celu obsługikażdego z powtarzających się żądań.

Serializacja obiektów Javy uprościła kwestie związane z formatowaniem odpowiedzi. W związku z tym, żezarówno aplety, jak i serwlety napisane są w Javie, naturalnym jest, że powinny się one komunikować poprzezwymianę obiektów Javy. Na przykład, kiedy hipotetyczny aplet akcji pyta odpowiedni serwlet o najwyższydzienny kurs akcji Suna, może otrzymać CenaAkcji w formie zserializowanego obiektu. Z niego możnapobrać najwyższą wartość jako float oraz czas tej wartości jako Czas. Jest to spójne i dostarcza prostegozabezpieczenia typów. Proszę jednak pamiętać, że serializacja obiektów działa jedynie z apletami pracującymiwewnątrz przeglądarek obsługujących JDK 1.1 i późniejszych.

Page 232: Java Servlet - Programowanie

JDBC, RMI i CORBAJDK 1.1 zawiera dwie dodatkowe własności wywierające wpływ na komunikację aplet-serwlet — JDBC i RMI.API JDBC (Java Database Connectivity — Łączność z Bazami Danych Javy), omówiony w rozdziale 9,„Łączność z bazami danych”, pozwala na połączenie się z relacyjną bazą danych na tym samym lub innymkomputerze. Aplety Javy napisane od wersji JDK 1.1 mogą wykorzystywać JDBC do komunikowania się z baządanych na serwerze. Ta przeznaczona do specjalnych celów komunikacja generalnie nie wymaga komunikacjiaplet-serwlet. Jednak często okazuje się pomocne, aby aplet (szczególnie napisany dla JDK 1.0) nie łączył siębezpośrednio z bazą danych (lub z proxy ba serwerze WWW) a zamiast tego łączył się z serwletemobsługującym łączność z bazą danych zamiast apletu, jak opisano w ramce „Serwlety pośredniczące” w rozdziale9, „Łączność z bazami danych”. Na przykład, aplet który chce wyszukać adres danej osoby może połączyć się zserwletem przy pomocy HTTP, przekazać nazwisko tej osoby wykorzystując parametry HTTP, po czymotrzymać adres jako specjalnie sformatowany łańcuch lub obiekt zserializowany. To zastosowanie komunikacjiaplet-serwlet opiera się w znacznym stopniu na istniejących protokołach takich jak HTTP, tak więc nie zostanietu szerzej opisane.

API RMI (Remote Method Invocation — Zdalne Wywoływanie Metod) pozwala apletowi na wywołanie metodobiektu Javy uruchomionego na serwerze oraz, w pewnych wypadkach, pozwala również obiektowi na serwerzena wywoływanie metod apletu. Zalety wykorzystania RMI w komunikacji aplet-serwer są bardzo znaczące:

• Pozwala on apletom i obiektom serwera na porozumiewanie się przy pomocy eleganckiego,obiektowego paradygmatu wysokiego poziomu. Żądania mogą zostać wykonywane jako wywołaniametod, z przekazaniem zserializowanych parametrów obiektu, kiedy jest to konieczne. Odpowiedzimogą być otrzymywanie jako obiekty zserializowane lub nawet jako odwołania do innych zdalnychobiektów. Lecz nawet używanie słów żądanie i odpowiedź pokazuje na zbytnie przywiązanie do HTTP!Przy korzystaniu z RMI nie ma żadnych żądań i odpowiedzi, jedynie wywołania metod. Patrząc naprzykład apletu akcji, aplet może pobrać maksymalny dzienny kurs akcji Suna poprzez wywołaniesun.pobierzMaxDzien(), gdzie sun jest obiektem Javy istniejącym na serwerze.

• Pozwala on obiektom serwera na wykonywanie odwołań wstecznych do metod apletu. Na przykład,patrząc na aplet akcji, serwer może powiadomić zainteresowane aplety, że cena akcji zmieniła siępoprzez wywołanie applet.update(akcja).

• Możliwe jest przedostanie się przez firewalle (chociaż nie jest to zbyt dobry pomysł, ich obsługa przezobecne przeglądarki jest niedokładna). Warstwa transportująca RMI normalnie opiera się nabezpośrednich połączeniach przez port w celu wykonania swojej pracy. Kiedy jednak apletwykonywane jest za firewallem, komunikacja przez zwykły port zawodzi. W tym przypadku warstwatransportująca RMI może zacząć działać wyłącznie wewnątrz protokołu HTTP7.

Nie odbywa się to jednak bez pewnych kosztów. Wykorzystanie HTTP ma wpływ na wydajność, a paradygmatżądanie-odpowiedź HTTP nie obsługuje odwołań wstecznych. Wady RMI są równie zajmujące:

• Jest on skomplikowany. Komunikacja RMI wykorzystuje specjalne klasy szkieletowe dla każdegozdalnego obiektu, a także wymaga rejestru nazw, w którym klienty mogą otrzymać odwołania do tychzdalnych obiektów.

• Jest on obsługiwany przez niewielką ilość przeglądarek. Z wszystkich popularnych przeglądarekdostępnych w trakcie tworzenia niniejszej książki, jedynie Netscape Navigator 4 (i powyżej) zawierałobsługę RMI. Ani poprzednie wersje przeglądarki Netscape'a, ani żadne wersje Internet ExploreraMicrosoftu nie obsługują RMI bez instalacji odpowiedniego modułu rozszerzającego. (Chociażponieważ klasy RMI to czysta Java, udało się dodać RMI do Internet Explorera jako dodatkowąbibliotekę.)

• Może być on wykorzystywany jedynie przez klienty Javy. Obiekt serwera nie może być współdzielonyprzez przeglądarkę a nawet klienta C++.

Większa ilość informacji na temat programowania RMI dostępna jest w książkach „Java Network Programming”autorstwa Elliotte Rusty Harold (O'Reilly) i „Java Distributed Computing” autorstwa Jima Farleya (O'Reilly).

7 Dokładny opis właściwości systemu potrzebnych aplikacji-klientowi RMI do przejścia przez firewalle znajduje się w 42podpowiedzi Javy Johna D. Mitchela w JavaWorld pod adresem http://www.javaworld.com/javaworld/javatips/jw-javatip42.html (niewspomniane w artykule, a bardzo ważne są również własności socksProxySet, socksProxyHost isocksProxyPort konieczne dla proxy opartych na SOCKS). Wszystkie te właściwości systemu powinny byćautomatycznie ustawiane przez przeglądarkę WWW, lecz niestety obecnie niewiele przeglądarek to robi, pozostawiającswoje aplety bez sposobu określenia właściwych ustawień i bez możliwości wykorzystanie RMI przez firewall.

Page 233: Java Servlet - Programowanie

CORBA (Common Object Request Broker Architecture — Wspólna Architektura Żądań Obiektów) totechnologia podobna do RMI, która pozwala na komunikację pomiędzy obiektami rozproszonymi napisanymi wróżnych językach. Przy pomocy CORBA'y i jej protokołu komunikacyjnego IIOP (Internet Inter-ORB Protocol),klient C++ może porozumiewać się z serwletem Javy. Przedstawienie tej techniki przekracza zakres niniejszejksiążki. Większa ilość informacji dostępna jest pod adresami http://www.omg.org ihttp://java.sun.com/products/jdk/idl.

Podejście hybrydoweTeraz, kiedy przeanalizowane zostały już wszystkie opcje, pytanie pozostaje: jak przykładowy aplet akcjipowinien porozumiewać się ze swoim serwerem cen akcji? Odpowiedź brzmi: to zależy.

Jeżeli można zagwarantować, że wszyscy potencjalni klienci posiadają jego obsługę, elegancja i moc RMI czyniągo idealnym wyborem. Brzmi to jednak tak, jak przyjmowanie, że wszyscy znajomi uwielbiają dowcipy na tematStar Treka. Może to być prawda przy dokładnym wybieraniu przyjaciół (lub klientów), lecz generalnie nie zdarzasię to w prawdziwym świecie.

Kiedy RMI nie jest dostępny, dwukierunkowe możliwości połączenia przez port nie-HTTP sprawiają, żewygląda ono atrakcyjnie. Niestety, ta dwukierunkowa komunikacja staje się nieistniejącą komunikacją, kiedyaplet kończy się na firewallu.

Zawsze jest stary sposób, komunikacja HTTP. Jest ono proste w implementacji i pracuje na każdym kliencie zobsługą Javy. A jeżeli można zagwarantować, że klient obsługuje JDK 1.1 (jest to łatwiejsze dozagwarantowania, niż obsługa RMI przez klientów), można wykorzystać serializację obiektów.

Przypuszczalnie najlepszym rozwiązaniem jest wykorzystanie wszystkich rozwiązań. Java sprawia, że możliwejest połączenie technik komunikacji aplet-serwlet HTTP, nie-HTTP i RMI, umieszczając obsługę ich wszystkichw jednej aplikacji. Dlaczego ktoś miałby chcieć to zrobić? Dlatego, że jest to poręczna technika, kiedy aplet chceporozumieć się przy pomocy RMI lub protokołu nie-HTTP, lecz musi przełączyć się na HTTP, kiedy okaże sięto potrzebne (w przypadkach takich jak znalezienie się przed firewallem). Poprzez zastosowanie tego samegoserwera do obsługi wielu klientów, podstawowa logika serwera i stan serwera mogą być zebrane w jednymmiejscu. Kiedy środowisko jest pod kontrolą można usunąć jeden lub więcej z tych protokołów. Lecz czy nie jestmiło wiedzieć, że nie jest to konieczne?

Dla skomplikowanych aplikacji pracujących na serwerze aplikacji standardowym projektem jest udostępnieniezdalnego obiektu RMI klientom RMI, urządzenia słuchającego na portach klientom portów a serwletu klientomHTTP. Obiekty te wykorzystują wspólnie zbiór logicznych klas biznesowych w celu obsługi żądań klienta(podobnie jak w restauracji z wynosem można zamawiać poprzez telefon, faks lub pocztę elektroniczną). Jednakw pozostałej części niniejszego rozdziału zostanie to nieco uproszczone, i przedstawiona zostanie komunikacjaRMI, przez porty i HTTP obsługiwana przez pojedynczy serwlet. Jeden serwlet, wiele protokołów dostępu.

Serwer godzinyW celu prostego przedstawienia każdej z technik komunikacji, napisany zostanie aplet proszący serwer oaktualną datę i godzinę. Aplet na początku wykorzystuje połączenie HTTP, później połączenie przez port nie-HTTP, a w końcu połączenie RMI. Oczywiście, aplet może normalnie pobierać obecny czas z systemu, naktórym pracuje. Aby nadać temu przykładowi cień praktyczności, można przyjąć, że aplet potrzebuje dokładnegoznacznika czasu dla jakiegoś zdarzenia i nie może polegać na tym, że komputer klienta posiada prawidłowoustawiony zegar.

ApletW całym niniejszym podrozdziale wykorzystane będzie ten sam aplet. Szkielet kodu tego apletu,ApletGodziny jest przedstawiony na przykładzie 10.1. W tym momencie aplet ten po prostu tworzy interfejsużytkownika, na którym wyświetlane będą pobrane przez niego czasy, jak przedstawiono na rysunku 10-1. Wdalszej części tego rozdziału zaimplementowane zostaną metody pobierzDataHttpTekst(),pobierzDataHttpObiekt(), pobierzDataPortTekst(), pobierzDataPortObiekt() ipobierzDataRMIObiekt().

Page 234: Java Servlet - Programowanie

Rysunek 10.1.

Interfejs użytkownika apletu ApletGodzinyProszę zauważyć, że przykłady w niniejszym rozdziale korzystają z kilku metod JDK 1.0, które zostałyzarzucone w JDK 1.1. Zostało to uczynione w celu poprawienie przenośności. Podczas kompilowaniaprzykładów w nowych JDK wyświetlone zostaną ostrzeżenia na temat zarzucenia, mogą one jednak zostaćbezpiecznie zignorowane.

Przykład 10.1.

ApletGodziny, bez ulepszeńimport java.applet.*;import java.awt.*;import java.io.*;import java.util.*;public class ApletGodziny extends Applet { TextField httpTekst, httpObiekt, portTekst, portObiekt, RMIObiekt; Button odswiez; public void init() { // Konstruowanie interfejsu użytkownika setLayout(new BorderLayout()); // Po lewej stronie dodanie etykiet dla różnych mechanizmów komunikacji Panel zachod = new Panel(); zachod.setLayout(new GridLayout(5, 1)); zachod.add(new Label("Tekst HTTP: ", Label.RIGHT)); zachod.add(new Label("Obiekt HTTP: ", Label.RIGHT)); zachod.add(new Label("Tekst portu: ", Label.RIGHT)); zachod.add(new Label("Obiekt portu: ", Label.RIGHT)); zachod.add(new Label("Obiekt RMI: ", Label.RIGHT)); add("Zachod", zachod); // Po prawej utworzenie pól tekstowych wyświetlających otrzymane wartościczasu Panel centrum = new Panel(); centrum.setLayout(new GridLayout(5, 1)); httpTekst = new TextField(); httpTekst.setEditable(false); centrum.add(httpTekst); httpObiekt = new TextField(); httpObiekt.setEditable(false); centrum.add(httpObiekt); portTekst = new TextField();

Page 235: Java Servlet - Programowanie

portTekst.setEditable(false); centrum.add(portTekst); portObiekt = new TextField(); portObiekt.setEditable(false); centrum.add(portObiekt); RMIObiekt = new TextField(); RMIObiekt.setEditable(false); centrum.add(RMIObiekt); add("Centrum", centrum); // Na dole utworzenie przycisku uaktualniającego czas Panel poludnie = new Panel(); odswiez = new Button("Odswież"); poludnie.add(odswiez); add("Poludnie", poludnie); } public void start() { odswiez(); } private void odswiez() { // pobranie i wyświetlenie wartości czasu httpTekst.setText(pobierzDataHttpTekst()); httpObiekt.setText(pobierzDataHttpObiekt()); portTekst.setText(pobierzDataPortTekst()); portObiekt.setText(pobierzDataPortObiekt()); RMIObiekt.setText(pobierzDataRMIObiekt()); } private String pobierzDataHttpTekst() { // Pobranie obecnego czasu przy pomocy opartego na tekście połączenia HTTP return "niedostępny"; } private String pobierzDataHttpObiekt() { // Pobranie obecnego czasu przy pomocy opartego na obiektach połączenia HTTP return "niedostępny"; } private String pobierzDataPortTekst() { // Pobranie obecnego czasu przy pomocy opartego na tekście połączenia z portem //nie-HTTP return "niedostępny"; } private String pobierzDataPortObiekt() { // Pobranie obecnego czasu przy pomocy opartego na obiektach połączenia zportem //nie-HTTP return "niedostępny"; } private String pobierzDataRMIObiekt() { // Pobranie obecnego czasu przy pomocy komunikacji RMI return "niedostępny"; } public boolean obslugaWyjatek(Event zdarzenie) { // Po przyciśnięciu przycisku "Odśwież" odświeżenie ekranu // Wykorzystanie zdarzeń JDK 1.0 w celu zapewnienia maksymalnej przenośności switch (zdarzenie.id) { case Event.ACTION_EVENT: if (zdarzenie.target == odswiez) { odswiez(); return true; } } return false; }}

Aby aplet ten był dostępny do pobrania przez przeglądarkę klienta, musi być umieszczony w macierzystymkatalogu dokumentów serwera, razem z plikiem HTML odwołującym się do niego. Kod HTML może wyglądaćnastępująco:

<HTML><HEAD><TITLE>Aplet godziny</TITLE></HEAD><BODY>

Page 236: Java Servlet - Programowanie

<CENTER><H1>Aplet godziny</H1></CENTER><CENTER><APPLET CODE=ApletGodziny CODEBASE=/ WIDTH=300 HEIGHT=180></APPLET></CENTER></BODY></HTML>

Parametr CODEBASE wskazuje na katalog (z perspektywy klienta), gdzie umieszczony jest plik klasy apletu.Wartość parametru CODEBASE / oznacza, że pliki klasy zostaną pobrane z katalogu macierzystego dokumentówdla domyślnej aplikacji WWW. Jeżeli parametr CODEBASE nie zostanie określony, jego domyślną wartością jestkatalog, w którym znajduje się plik HTML. Przyjmując, że plik HTML nosi nazwę godziny.html, aplet ten jestdostępny pod adresem http://serwer:port/godziny.html, a klasa apletu zostanie pobrana z URL-ahttp://serwer:port/ApletGodziny.class.

Oparta na tekście komunikacja HTTPNa początku omówiona zostanie implementacja podejścia najpopularniejszego — oparta na tekście komunikacjaHTTP.

SerwletAby aplet ApletGodziny mógł pobierać aktualny czas z serwera, musi on porozumiewać się z serwletem,który zwraca aktualny czas. Przykład 10.2 przedstawia taki serwlet. Odpowiada on na wszystkie żądania POST iGET tekstową reprezentacją aktualnego czasu.

Przykład 10.2.

Serwlet SerwletGodziny obsługujący prosty dostęp HTTPimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class SerwletGodziny extends HttpServlet{ public Data getDate() { return new Data(); }

public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter out = odp.getWriter(); wyj.println(getDate().toString()); } } public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { doGet(zad, odp); }}

Powyższa klasa serwletu powinna zostać umieszczona w standardowym dla serwerów miejscu,katalog_kontekstowy/WEB-INF/classes. Zakładając, że są one umieszczone w domyślnym kontekście, sądostępne dla każdej przeglądarki WWW przy pomocy URL-a http://serwer:port/servlet/SerwletGodziny.

Powrót do apletuW tym momencie, aby ApletGodziny mógł porozumiewać się z serwerem, musi zachowywać się tak, jakprzeglądarka i ustanowić połączenie HTTP z URL-em serwletu, jak to przedstawia implementacjapobierzDataHttpTekst() w przykładzie 10.3.

Przykład 10.3.

ApletGodziny pobierający czas przy pomocy HTTPimport java.net.URL; // nowy dodatekimport com.oreilly.servlet.HttpMessage; // klasa wspierająca, przedstawiona dalej private String pobierzDataHttpTekst() { try { // Tworzenie URL-a odnoszącego się do serwletu URL url = new URL(getCodeBase(), "/servlet/SerwletGodziny");

Page 237: Java Servlet - Programowanie

// Tworzenie com.oreilly.servlet.HttpMessage w celu porozumienia się z tymURL-em HttpMessage wiad = new HttpMessage(url); // Wysłanie wiadomości GET serwletowi, bez łańcucha zapytania // Pobranie odpowiedzi jako łańcucha InputStream InputStream in = wiad.sendGetMessage(); // Dołączenie InputStream do DataInputStream DataInputStream wynik = new DataInputStream(new BufferedInputStream(in)); // Odczytanie pierwszej linii odpowiedzi, która powinna być łańcuchową // reprezentacją aktualnego czasu String data = wynik.readLine(); // Zamknięcie InputStream in.close(); // Zwrócenie odczytanego czasu return data; } catch (Exception e) { // Jeżeli nastąpił problem, wynik zapisany w System.out // (zazwyczaj konsoli Javy) i zwrócenie null e.printStackTrace(); return null; } }

Powyższa metoda odczytuje aktualny czas na serwerze przy pomocy opartego na tekście połączenia HTTP. Popierwsze, tworzy obiekt URL, który odwołuje się do serwletu SerwletGodziny pracującego na serwerze.Komputer macierzysty serwera i port dla tego URL-a pochodzi z własnej metody apletu getCodeBase().Gwarantuje to zgodność komputera i portu z danymi o miejscu, z którego został pobrany aplet. Następnie metodatworzy obiekt HttpMessage w celu porozumienia się z tym URL-em. Obiekt ten wykonuje całą ciężką pracę,w tym ustanawianie połączenia. Aplet prosi go o wykonanie żądania GET do SerwletGodziny, po czymodczytuje odpowiedź ze zwróconego InputStream.

Kod HttpMessage jest przedstawiony na przykładzie 10.4. Jest on dość swobodnie wymodelowany napodstawie klasy ServletMessage autorstwa Roda McChesneya.

Przykład 10.4.

Klasa wspierająca HttpMessagepackage com.oreilly.servlet;import java.io.*;import java.net.*;import java.util.*;public class HttpMessage { URL servlet = null; Hashtable headers = null; public HttpMessage(URL servlet) { this.servlet = servlet; } public InputStream sendGetMessage() throws IOException { return sendGetMessage(null); } public InputStream sendGetMessage(Properties args) throws IOException { String argString = ""; // domyślny if (args != null) { argString = "?" + toEncodedString(args); } URL url = new URL(servlet.toExternalForm() + argString); // Wyłączenie buforowania URLConnection con = url.openConnection(); con.setUseCaches(false); // Wysłanie nagłówków sendHeaders(con);

Page 238: Java Servlet - Programowanie

return con.getInputStream(); } public InputStream sendPostMessage() throws IOException { return sendPostMessage(null); } public InputStream sendPostMessage(Properties args) throws IOException { String argString = ""; // default if (args != null) { argString = toEncodedString(args); // notice no "?" } URLConnection con = servlet.openConnection(); // Przygotowanie do wysyłania i odbierania con.setDoInput(true); con.setDoOutput(true); // Wyłączenie buforowania con.setUseCaches(false); // Ominięcie błędu w Netscape'ie con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // Wysłanie nagłówków sendHeaders(con); // Zapisanie argumentów jako danych wysyłanych DataOutputStream out = new DataOutputStream(con.getOutputStream()); out.writeBytes(argString); out.flush(); out.close(); return con.getInputStream(); } public InputStream sendPostMessage(Serializable obj) throws IOException { URLConnection con = servlet.openConnection(); // Przygotowanie do wysyłania i odbierania con.setDoInput(true); con.setDoOutput(true); // Wyłączenie buforowania con.setUseCaches(false); // Ustawienie typu zawartości jako application/x-java-serialized-object con.setRequestProperty("Content-Type", "application/x-java-serialized-object"); // Wysłanie nagłówków sendHeaders(con); // Zapisanie argumentów jako danych wysyłanych ObjectOutputStream out = new ObjectOutputStream(con.getOutputStream()); out.writeObject(obj); out.flush(); out.close(); return con.getInputStream(); } public void setHeader(String name, String value) { if (headers == null) { headers = new Hashtable(); } headers.put(name, value); } // Wysłanie zawartości tablicy asocjacyjnej nagłówków do serwera private void sendHeaders(URLConnection con) { if (headers != null) { Enumeration enum = headers.keys(); while (enum.hasMoreElements()) { String name = (String) enum.nextElement(); String value = (String) headers.get(name); con.setRequestProperty(name, value); } } }

Page 239: Java Servlet - Programowanie

public void setCookie(String name, String value) { if (headers == null) { headers = new Hashtable(); } String existingCookies = (String) headers.get("Cookie"); if (existingCookies == null) { setHeader("Cookie", name + "=" + value); } else { setHeader("Cookie", existingCookies + "; " + name + "=" + value); } } public void setAuthorization(String name, String password) { String authorization = Base64Encoder.encode(name + ":" + password); setHeader("Authorization", "Basic " + authorization); } private String toEncodedString(Properties args) { StringBuffer buf = new StringBuffer(); Enumeration names = args.propertyNames(); while (names.hasMoreElements()) { String name = (String) names.nextElement(); String value = args.getProperty(name); buf.append(URLEncoder.encode(name) + "=" + URLEncoder.encode(value)); if (names.hasMoreElements()) buf.append("&"); } return buf.toString(); }}

Można by się spodziewać, że klasa HttpMessage ustanawia połączenie przez zwykły port do serwera, poczym przechodzi na HTTP. To podejście na pewno działałoby, nie jest jednak konieczne. Klasy wyższegopoziomu java.net.URL i java.net.URLConnection dostarczają już tej funkcjonalności we właściwysposób.

Poniżej przedstawiony jest krótki opis działania HttpMessage. Klasa ta jest zaprojektowana do komunikacji zjednym tylko URL-em, podanym w konstruktorze. Może ona wysyłać do niego wielokrotne żądania GET i/lubPOST, ale zawsze łączy się tylko z jednym URL-em.

Kod wykorzystywany przez HttpMessage do wysyłania wiadomości GET jest stosunkowo prosty. Popierwsze, sendGetMessage() tworzy zakodowany w URL-u łańcuch zapytania z przekazanej listyjava.util.Properties. Dołącza ten łańcuch zapytania do zapisanego URL-a, tworząc nowy obiekt URL. W tymmomencie można by się zdecydować na wykorzystanie tego nowego obiektu (pod nazwą url) w celuporozumienia się z serwerem. Wywołanie url.openStream() zwróciłoby łańcuch InputStreamzawierający odpowiedź. Jednak, niestety dla celów tego przykładu, domyślnie wszystkie połączenia wykonaneprzy pomocy obiektu URL są buforowane. Nie jest to pożądane — zwrócony ma być czas aktualny, a nie czasostatniego żądania. Tak więc HttpMessage musi wyłączyć buforowanie8.

Klasa URL nie obsługuje bezpośrednio tej kontroli niskiego poziomu, tak więc HttpMessage pobieraURLConnection obiektu URL i instruuje go, aby nie wykorzystywał buforowania. Ostatecznie,HttpMessage zwraca InputStream obiektu URLConnection, który zawiera odpowiedź serwletu.

Kod wykorzystywany przez HttpMessage do wysłania żądania POST (sendPostMessage()) jestpodobny. Podstawową różnicą między nimi jest fakt, że ten drugi zapisuje przechowywaną w URL-u informacjebezpośrednio w kodzie żądania. Jest to zgodne z protokołem wysłania informacji przez żądania POST. Innaróżnica to fakt, że HttpMessage obowiązkowo ustawia typ zawartości żądania na application/x-www-form-urlencoded. Informuje to serwer, że zawartość POST zawiera informacje o parametrach.

Należy wspomnieć, że HttpMessage to klasa ogólnego przeznaczenia, służąca do komunikacji HTTP. Niemusi ona być wykorzystywana przez aplety, ani też łączyć się z serwletami. Może z niej skorzystać każdy klientJavy, który musi połączyć się z dowolnymi zasobami HTTP. Jest ona jednak dołączona do pakietucom.oreilly.servlet, ponieważ często okazuje się przydatna w komunikacji aplet-serwlet.

Aby klasa HttpMessage mogła być wykorzystywana przez aplety, musi być udostępniona przez pobranie jejrównolegle do klas apletów. Oznacza to, że musi być ona umieszczona w odpowiednim miejscu w katalogumacierzystym dokumentów serwera. Dla serwera Tomcat, pozycja ta tokatalog_macierzysty/webapps/ROOT/com/oreilly/servlet. Poleca się skopiowanie klasy z

8 Tak właściwie można pozostawić wyłączenie buforowania serwletowi, przez ustawienie nagłówka serwletu Pragma nano-cache, Nie zaszkodzi jednak wyłączenie jej również w aplecie.

Page 240: Java Servlet - Programowanie

miejsca, w którym pakiet com.oreilly.servlet został oryginalnie zainstalowany (zazwyczajkatalog_macierzysty/classes/com/oreilly/servlet).

Proszę zauważyć, że HttpMessage posiada kilka swoich metod, które pozwalają na wysyłanie nagłówkówżądań, cookies i podstawowych informacji uwierzytelniających do serwera jako części żądania. MetodasetHeader(String nazwa, String wartość) dodaje do żądania nagłówek o odpowiedniej nazwie iwartości. Jeżeli nagłówek został ustawiony wcześniej, nowa wartość zastępuje starą. Metoda setCookie(String nazwa, String wartość) dodaje do żądania cookie o odpowiedniej nazwie i wartości.Metoda ta jest szczególnie przydatna przy omijaniu ograniczeń śledzenia sesji dla apletów działających wewnątrzJava Plug-In, jak omówiono w rozdziale 7, „Śledzenie sesji”. Ostatecznie, metoda setAuthorization(String nazwa, String hasło) dodaje dane użytkownika do żądania, pozwalając apletowi na dostępdo stron chronionych hasłem. Wykorzystuje klasę com.oreilly.servlet.Base64Encoder, która niejest przedstawiona w niniejszej książce, lecz jest dostępna pod adresem http://servlets.com. Dla wszystkichpowyższych metod, ustawienia pozostają niezmienione pomiędzy żądaniami, a wywołujący jest odpowiedzialnyza to, aby w nazwie i wartości nie występowały żadne niedozwolone znaki.

Aby nawiązać bezpieczne połączenie z apletu, należy przekazać HttpMessage URL rozpoczynający HTTPS.Jest to tak łatwe! (Proszę tylko nie zapomnieć o tym, że serwer musi mieć włączoną obsługę HTTPS.) Własnośćta działa jednak jedynie w przypadku apletów, ponieważ przeglądarka, w której uruchomiono aplet może pomócw wynegocjowaniu połączenia HTTPS. W przypadku klienta nie będącego apletem konstruktor URL zwróci błądnieznanego protokołu HTTPS. Można to ominąć wykorzystując klasęcom.oreilly.servlet.HttpsMessage dostępną pod adresem http://www.servlets.com. Zawiera onasamodzielną obsługę HTTPS autorstwa Matta Towersa. Artykuł JavaWorld opisujący jej działanie znajduje siępod adresem http://www.javaworld.com/javaworlsd/javatips/jw-javatip96.html.

W tym momencie istnieje już działający aplet odczytujący aktualny czas z serwera przy pomocy opartej natekście komunikacji HTTP aplet-serwer. Po wypróbowaniu jego działania można zobaczyć, że data „TekstHTTP” jest wypełniona, podczas gdy inne są oznaczone jako „niedostępny”.

Oparta na obiektach komunikacja HTTPPo kilku modyfikacjach, można dostosować ApletGodziny tak, aby pobierał aktualny czas jakozserializowany obiekt Data.

SerwletW celu zachowania wstecznej kompatybilności, można zmienić SerwletGodziny tak, aby zwracałzserializowany obiekt jedynie, gdy wykonane zostanie odpowiednie żądanie przez przekazanie parametruformat o wartości obiekt. Odpowiedni kod przedstawiony jest w przykładzie 10.5.

Przykład 10.5.

SerwletGodziny wykorzystujący HTTP w celu pracy na obiekcieimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class SerwletGodziny extends HttpServlet{ public Data getDate() { return new Data(); }

public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { // Jeżeli klient przekaże "format=obiekt" // Wysłanie Data jako obiektu zserializowanego if ("obiekt".equals(zad.getParameter("format"))) { ObjectOutputStream wyj = new ObjectOutputStream(odp.getOutputStream()); wyj.writeObject(getDate()); } // W przeciwnym wypadku wysłanie Data jako zwykłego łańcucha else { PrintWriter wyj = odp.getWriter(); wyj.println(getDate().toString()); } }

Page 241: Java Servlet - Programowanie

public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { doGet(zad, odp); }}

Jak widać na powyższym przykładzie, wysyłanie zserializowanego obiektu Javy jest stosunkowo proste.Technika ta może być wykorzystana w celu wysłania wszystkich prymitywnych typów i/lub obiektów Javy, któreposiadają implementację interfejsu Serializable, włączając w to obiekty Vector, zawierające obiektySerializable. W jednym łańcuchu ObjectOutputStream może zostać zapisana duża ilość obiektów,jeżeli klasa odbierająca obiekty odczytuje je w tej samej kolejności i nadaje im te same typy.

Można zauważyć, że serwlet nie ustawił typu zawartości odpowiedzi w celu wskazania, że jest onazserializowanym obiektem Javy. Powodem tego jest aktualny brak standardowych typów MIMEreprezentujących obiekty zserializowane. Jednak nie ma to znaczenia. Typ zawartości jest jedynie wskaźnikiemdla klienta, jak ma on obsłużyć lub wyświetlić odpowiedź. Jeżeli aplet już przewiduje odbieranie konkretnegozserializowanego obiektu Javy, wszystko działa bez zakłóceń. Jednak czasami warto jest zastosować własne typyMIME (specyficzne dla danej aplikacji), tak aby serwlet mógł wskazać apletowi zawartość jego odpowiedzi.

ApletKod apletu odczytującego zserializowany obiekt Data jest bardzo podobny do kodu odczytującego zwykłytekst. Metoda pobierzDataHttpObiekt() jest przedstawiona na przykładzie 10.6.

Przykład 10.6.

ApletGodziny wykorzystujący HTTP do odczytania obiektuprivate String pobierzDataHttpObiekt() { try { // Tworzenie URL-a odnoszącego się do serwletu URL url = new URL(getCodeBase(), "/servlet/SerwletGodziny"); // Tworzenie com.oreilly.servlet.HttpMessage w celu porozumienia się z tymURL-em HttpMessage wiad = new HttpMessage(url); // Tworzenie listy właściwości Properties w celu podania format=obiekt Properties wlasc = new Properties(); wlasc.put("format", "obiekt"); // Wysłanie wiadomości GET serwletowi, przekazanie „wlasc” jako łańcuchazapytania // Pobranie odpowiedzi jako łańcucha ObjectInputStream InputStream in = wiad.sendGetMessage(wlasc); ObjectInputStream wynik = new ObjectInputStream(in); // Odczytanie obiektu Data z potoku Object obi = wynik.readObject(); Date data = (Date)obi; in.close(); // Zwrócenie łańcuchowej reprezentacji Data return data.toString(); } catch (Exception e) { // Jeżeli nastąpił problem, wynik zapisany w System.out // (zazwyczaj konsoli Javy) i zwrócenie null e.printStackTrace(); return null; }}

Występują dwie różnice pomiędzy tą konkretną metodą i metodą pobierzDataHttpTekst(). Po pierwsze,metoda ta tworzy listę właściwości Properties w celu nadania parametrowi formatu wartości obiektu.Działanie to ma na celu nakazanie serwletowi SerwletGodziny zwrócenia obiektu zserializowanego. Podrugie, nowa metoda odczytuje zwróconą zawartość jako Obiekt, przy pomocy klasy ObjectInputStreami jej metody readObject().

Jeżeli serializowana klasa nie jest częścią Core API Javy (czyli nie jest od razu dostępna apletowi), musi onarównież zostać udostępniona we właściwym miejscu w katalogu macierzystym dokumentów serwera. Aplet możezawsze otrzymywać zserializowane zawartości obiektu, musi jednak pobrać plik jego klasy w celu pełnej jegorekonstrukcji.

Page 242: Java Servlet - Programowanie

W tym momencie aplet może odczytywać aktualny czas przy pomocy zarówno opartej na tekście, jak i naobiektach, komunikacji HTTP. Po wypróbowaniu przykładu (przy pomocy przeglądarki WWW lub przeglądarkiapletów obsługującej JDK 1.1) można dostrzec, że pola „Tekst HTTP” i „Obiekt HTTP” zostały wypełnione.

Przesyłanie zserializowanego obiektu lub plikuPrzed przejściem do następnych metod, należy opisać jeszcze jedną (dotychczas nieomówioną) metodę z klasyHttpMessage — sendPostMessage(Serializable). Metoda ta pomaga apletowi wysłaćzserializowany obiekt do serwletu metodą POST. Taki transfer obiektu nie jest szczególnie użyteczny wopisywanym tu przykładzie (i raczej do niego nie pasuje), lecz zostanie wspomniany, ponieważ może okazać sięużyteczny, gdyby aplet musiał wysłać skomplikowane struktury danych na serwer. Na przykład, aplet możewysłać obiekt Date, obiekt Person lub tablicę HighScore zawierającą obiekty Date i Person. Apletmoże nawet wysłać dokument XML, jako dokument JDOM (http://jdom.org) lub jako zwykły plik. Proszęjedynie pamiętać, że przy wysyłaniu pliku powinno wysłać się zawartość jako obiekt byte[], nie File,ponieważ obiekt File przechowuje jedynie nazwę pliku, a nie jego zawartość. Przykład 10.7 zawiera kodmetody sendPostMessage(Serializable).

Przykład 10.7

Wysyłanie obiektu zserializowanego// Wysyła zserializowany obiekt przy pomocy żądania POST.// Ustawia typ zawartości na application/x-java-serialized-objectpublic InputStream sendPostMessage(Serializable obj) throws IOException { URLConnection con = servlet.openConnection(); // Przygotowanie do wysyłania i odbierania con.setDoInput(true); con.setDoOutput(true); // Wyłączenie buforowania con.setUseCaches(false); // Ustawienie typu zawartości jako application/x-java-serialized-object con.setRequestProperty("Content-Type", "application/x-java-serialized-object"); // Wysłanie nagłówków sendHeaders(con); // Zapisanie argumentów jako danych wysyłanych ObjectOutputStream wyj = new ObjectOutputStream(con.getOutputStream()); wyj.writeObject(obj); wyj.flush(); out.close(); return con.getInputStream();}

Aplet wykorzystuje sendPostMessage(Serializable) tak, jak wykorzystuje sendPostMessage(Properties). Poniżej przedstawiony jest kod apletu wysyłający wszystkie napotkane wyjątki do serwletu9:

catch (Exception w) { URL url = new URL(getCodeBase(), "/servlet/DziennikWyjatkow"); HttpMessage wiad = new HttpMessage(URL); InputStream = wiad.sendPostMessage(e);}

Tymczasem serwlet otrzymuje wyjątek Exception przez swoją metodę doPost(), w następujący sposób:ObjectInputStream obiin = new ObjectInputStream(zad.getInputStream());Object obi = obiin.readObject();Exception w = (Exception) obi;

Serwlet może otrzymywać typ wysłanego obiektu jako podtyp (druga część typu zawartości). Proszę zauważyć,że powyższa metoda sendPostMessage(Serializable) pobiera tylko jeden obiekt w danym czasieoraz, że pobiera jedynie obiekty Serializable (to znaczy, żadnych prymitywnych typów).

Komunikacja przez portTeraz opisane zostaną sposoby komunikacji apletu i serwletu przy pomocy połączeń portów nie-HTTP.

9 Zazwyczaj kiedy aplet wywoła nieobsługiwany wyjątek, przeglądarka zapisuje ścieżkę do wyjątku w konsoli Javy. Ten kodzawiera aplet naprawdę wyrzucający wyjątek na serwer, gdzie może być przechowywany w dzienniku zdarzeń. W dziennikutym zapisywane są ścieżki, mogące być później przeglądane przez twórcę apletu

Page 243: Java Servlet - Programowanie

SerwletRola serwletu w tej technice komunikacji ogranicza się do pasywnego słuchania. Z powodów bezpieczeństwa,jedynie aplet może nawiązać połączenie przez zwykły port. Generalnie, serwlet musi być skonfigurowany dorozpoczęcia nasłuchu począwszy od metody init(), a skończywszy na metodzie destroy(). Pomiędzy tymidwoma metodami, powinien utworzyć wątek obsługujący dla każdego otrzymanego połączenia z klientem.

W przypadku połączenia HTTP, te trudne w obsłudze szczegóły są zarządzane przez serwer WWW. Serwer tenprowadzi nasłuch przychodzących żądań HTTP i odpowiednio je rozsyła, wywołując odpowiednio metodyserwletu service(), doGet() lub doPost(). Jednak kiedy serwlet nie wykorzystuje komunikacji HTTP,serwer WWW nie może dostarczyć żadnej pomocy. Właściwie to serwlet działa jako swój własny serwer i wzwiązku z tym musi zarządzać połączeniami przez port samodzielnie.

Może to brzmieć nieco zastraszająco. Prawda jest taka, że można stworzyć superklasę serwletu, która przejmiena siebie szczegóły związane z zarządzaniem połączeń przez port. Klasa ta, nazwana DaemonHttpServlet,może być wykorzystana przez każdy serwlet, który ma zostać udostępniony poprzez połączenie przez port nie-HTTP10.

DaemonHttpServlet rozpoczyna nasłuch żądań klienta w swojej metodzie init(), a kończy na metodziedestroy(). W międzyczasie, dla każdego odebranego połączenia wywołuje abstrakcyjną metodęhandleClient(Socket). Metoda ta powinna zostać zaimplementowana przez każdy serwlet, który jestpodklasą DaemonHttpServlet.

Przykład 10.8 przedstawia sposób, w jaki SerwletGodziny wykorzystuje klasę DaemonHttpServlet iimplementuje handleClient() w celu stania się dostępnym przez połączenie przez port nie-HTTP.

Przykład 10.8.

Serwer HTTPimport java.io.*;import java.net.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.DaemonHttpServlet;public class SerwletGodziny extends DaemonHttpServlet { public Date getDate() { return new Date(); } public void init() throws ServletException { // Jak poprzednio, nie ma potrzeby wywoływania super.init() // z bezargumentowej metody init, trzeba jednak wywołać super.init(config) ze // starszej metody init(ServletConfig). Szczegóły można znaleźć w rozdziale 3. } public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { // Jeżeli klient przesłał "format=obiekt", // to wysłanie daty jako obiektu zserializowanego if ("obiekt".equals(zad.getParameter("format"))) { ObjectOutputStream wyj = new ObjectOutputStream(odp.getOutputStream()); wyj.writeObject(getDate()); } // W przeciwnym wypadku wysłanie daty jako zwykłego łańcucha ASCII else { PrintWriter wyj = odp.getWriter(); wyj.println(getDate().toString()); } } public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { doGet(zad, odp); }

10 Nazwa daemon (demon) został wprowadzona w odniesieniu do demonów Uniksa, programów działających w tle iobsługujących konkretne zdarzenia. W jaki sposób otrzymały one nazwę demonów? Według słownika „New Hacker'sDictionary”, początkowo pochodziło „ze znaczenia mitologicznego, później zracjonalizowanego do akronimu 'Disk AndExecution Monitor' (Monitor Dysku i Wykonania)”.

Page 244: Java Servlet - Programowanie

public void destroy() { // W tym miejscu, inaczej niż poprzednio, po przejęciu destroy() należywywołać // super.destroy() super.destroy(); } // Obsługa połączenia klienta przez port poprzez dodanie wątku PolaczenieGodziny public void handleClient(Socket klient) { new PolaczenieGodziny(this, klient).start(); }}class PolaczenieGodziny extends Thread { SerwletGodziny serwlet; Socket klient; PolaczenieGodziny(SerwletGodziny serwlet, Socket klient) { this.serwlet = serwlet; this.klient = klient; setPriority(NORM_PRIORITY - 1); } public void run() { try { // Odczytanie pierwszej linii wysłanej przez klienta, jako tekstu Latin-1 BufferedReader in = new BufferedReader( new InputStreamReader( klient.getInputStream(), "ISO-8859-1")); String line = in.readLine(); // Jeżeli brzmiała ona „Obiekt”, zwrócenie Data jako obiektuzserializowanego if ("obiekt".equals(line)) { ObjectOutputStream wyj = new ObjectOutputStream(klient.getOutputStream()); wyj.writeObject(serwlet.getDate()); wyj.close(); } // W przeciwnym wypadku wysłanie Data jako zwykłego łańcucha else { // Owinięcie PrintStream dookoła OutputStream portu Socket PrintStream wyj = new PrintStream(klient.getOutputStream()); wyj.println(serwlet.getDate().toString()); wyj.close(); } // Należy zapewnić zamknięcie połączenia klient.close(); } catch (IOException w) { serwlet.log("Wyjątek IOException podczas obsługi żądania klienta", w); } catch (Exception w) { serwlet.log("Wyjątek Exception podczas obsługi żądania klienta", w); } }}

Klasa SerwletGodziny pozostała w większej części niezmieniona w porównaniu z poprzednią formą.Główną różnicą polega na tym, że jest ona teraz rozszerzeniem klasy DaemonHttpServlet i wykorzystujemetodę handleClient(Socket), która tworzy nowy wątek PolaczenieGodziny. Ten egzemplarzPolaczenieGodziny ponosi odpowiedzialność za obsługę konkretnego połączenia przez port.

PolaczenieGodziny działa w sposób opisany poniżej. Kiedy zostaje utworzone, zapamiętuje odwołanie doSerwletGodziny, tak więc może wywołać metodę serwletu getDate() i odwołanie do portu Socket, wcelu zapewnienia komunikacji z klientem. PolaczenieGodziny ustawia swój priorytet o jeden poziom niżejod normalnego, aby wskazać, że połączenie to może zaczekać, kiedy inne wątki wykonują ważniejszą pracę.

Bezpośrednio po utworzeniu wątku PolaczenieGodziny, SerwletGodziny rozpoczyna wątek,powodując wywołanie swojej metody run(). W tej metodzie, PolaczenieGodziny porozumiewa się zklientem przy pomocy nienazwanego (ale na pewno nie będącego HTTP) protokołu. Rozpoczyna od odczytaniapierwszej linii wysłanej przez klienta. Jeżeli wartość tej linii wynosi obiekt, zwraca on aktualny czas jakozserializowany obiekt Data. Jeżeli linia ta zawiera dowolną inną wartość, zwraca aktualny czas w postacizwykłego łańcucha. Kiedy kończy swoją działalność, zamyka połączenie.

Page 245: Java Servlet - Programowanie

SuperklasaNiskiego poziomu zarządzanie portem jest wykonywane przez klasę DaemonHttpServlet. Ogólnie rzeczbiorąc, klasa ta może być zastosowana bez modyfikacji, warto jest jednak zrozumieć jej wnętrze. Jaj kodprzedstawiony w przykładzie 10.9. Proszę zauważyć, że również ona jest napisana według Servlet API 2.0 w celuzapewnienia wstecznej kompatybilności.

Przykład 10.9.

Superklasa DaemonHttpServletpackage com.oreilly.servlet;import java.io.*;import java.net.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public abstract class DaemonHttpServlet extends HttpServlet { protected int DEFAULT_PORT = 1313; // nie określony ostatecznie private Thread daemonThread; public void init(ServletConfig config) throws ServletException { super.init(config); // rozpoczęcie wątku demona try { daemonThread = new Daemon(this); daemonThread.start(); } catch (Exception e) { log("Problem starting socket server daemon thread" + e.getClass().getName() + ": " + e.getMessage()); } } // Zwraca port połączenia, na którym ten serwlet będzie nasłuchiwał // Serwlet może określić port na trzy sposoby — przy pomocy parametru init //socketPort, przez określenie zmiennej DEFAULT_PORT przed wywołaniem super.init(), //lub przez przejecie implementacji tej metody protected int getSocketPort() { try { return Integer.parseInt(getInitParameter("socketPort")); } catch (NumberFormatException e) { return DEFAULT_PORT; } } abstract public void handleClient(Socket client);

public void destroy() { try { daemonThread.stop(); daemonThread = null; } catch (Exception e) { log("Problem stopping server socket daemon thread: " + e.getClass().getName() + ": " + e.getMessage()); } }}// Praca ta wykonywana jest przez klasę pomocniczą tak, aby podklasyDaemonHttpServlet // może bez problemów zdefiniować swoją własną metodę run()class Daemon extends Thread { private ServerSocket serverSocket; private DaemonHttpServlet servlet; public Daemon(DaemonHttpServlet servlet) { this.servlet = servlet; } public void run() { try { // Stworzenie portu serwera akceptującego połączenia serverSocket = new ServerSocket(servlet.getSocketPort()); }

Page 246: Java Servlet - Programowanie

catch (Exception e) { servlet.log("Problem establishing server socket: " + e.getClass().getName() + ": " + e.getMessage()); return; } try { while (true) { // Po otrzymaniu połączenia, wywołanie metody handleClient() serwera. // Proszę zauważyć, że metoda ta jest blokująca. Na serwlet spada // odpowiedzialność utworzenia wątku obsługującego długo trwającepołączenia. try { servlet.handleClient(serverSocket.accept()); } catch (IOException ioe) { servlet.log("Problem accepting client's socket connection: " + ioe.getClass().getName() + ": " + ioe.getMessage()); } } } catch (ThreadDeath e) { // Kiedy wątek zostaje zabity, zamknięcie portu serwera try { serverSocket.close(); } catch (IOException ioe) { servlet.log("Problem closing server socket: " + ioe.getClass().getName() + ": " + ioe.getMessage()); } } }}

Metoda init() DaemonHttpServlet tworzy i rozpoczyna nowy wątek Daemon, który odpowiada zanasłuchiwanie połączeń przychodzących. Metoda destroy() zatrzymuje wątek. Sprawia to że imperatywemstaje się, żeby każdy serwlet będący podklasą DaemonHttpServlet wywoływał super.init() isuper.destroy(), jeżeli serwlet ten wykorzystuje własne metody init() i destroy(). (Serwletnapisany według Servlet API 2.1 i wykorzystujący bez argumentów init() nie musi wywoływaćsuper.init(), lecz musi wywołać super.destroy().)

Wątek Daemon rozpoczyna działalność utworzeniem ServerSocket w celu nasłuchiwania na konkretnymporcie połączenia. Numer portu jest określany przez wywołanie metody serwletu getSocketPort(). Wartośćprzez nią zwracana to wartość parametru inicjacji SocketPort, lub, w wypadku nieistnienia tego parametru,obecna wartość zmiennej DEFAULT_PORT. Serwlet może zdecydować się na przejęcie implementacjigetSocketPort, jeżeli jest to konieczne.

Po utworzeniu ServerSocket, wątek Daemon czeka na nadchodzące żądania przy pomocy wywołaniaserverSocket.accept(). Metoda jest blokująca — zatrzymuje wykonywanie tego wątku do czasupołączenia się przez klienta. Kiedy się to dzieje, metoda accept() zwraca obiekt Socket, który wątekDaemon natychmiast przekazuje do metody serwletu handleClient(). Ta metoda zazwyczaj tworzy wątekobsługujący i natychmiast powraca, powodując przejście wątku Daemon w stan gotowości do przyjęcianastępnego połączenia.

Oczyszczenie portu jest tak samo ważne, jak jego konfiguracja. Należy upewnić się, że port połączenia działa takdługo jak serwlet, ale nie dłużej. W tym momencie metoda destroy() DaemonHttpServlet wywołujemetodę stop() wątku Daemon. Wywołanie to nie zatrzymuje jednak natychmiast wątku Daemon. Powodujejedynie wystąpienie wyjątku ThreadDeath wątku Daemon w jego aktualnym miejscu wykonania. WątekDaemon przejmuje ten wyjątek i zamyka port połączenia.

Przy stworzeniu serwletu działającego jako serwer nie-HTTP mogą wystąpić dwa problemy. Po pierwsze,jedynie jeden serwlet w jednym czasie może nasłuchiwać na konkretnym porcie. Sprawia to, że absolutniekonieczne staje się, aby każdy serwlet-demon wybierał swój własny port połączenia — poprzez ustawienieswojego parametru inicjacji socketPort, ustawiając zmienną DEFAULT_PORT przed wywołaniemsuper.init(config) lub bezpośrednim przejęciem getSocketPort(). Po drugie, serwlet-demon musibyć załadowany na serwer i wywołana musi być jego metoda init(), zanim będzie on mógł przyjmowaćprzychodzące połączenia nie-HTTP. W związku z tym należy nakazać serwerowi ładowanie go podczas startu,lub upewnić się, że jest on zawsze dostępny przez HTTP, zanim można będzie uzyskać do niego dostępbezpośredni.

Page 247: Java Servlet - Programowanie

ApletKod apletu łączącego się z serwletem przy pomocy komunikacji nie-HTTP, przede wszystkim metodypobierzDataPortTekst() i pobierzDataPortObiekt(), jest przedstawiony w przykładzie 10.10.

Przykład 10.10.

ApletGodziny pobierający czas przy pomocy połączenia przez zwykły port.import java.net.socket; // Nowy dodatekstatic final int DEFAULT_PORT=1313; // Nowy dodatekprivate int getSocketPort() { try { return Integer.parseInt(getParameter("socketPort")); } catch (NumberFormatException w) { return DEFAULT_PORT; }}private String pobierzDataPortTekst() { InputStream in = null; try { // Ustanowienie połączenia przez port z serwletem Socket port = new Socket(getCodeBase().getHost(), getSocketPort()); // wyświetlenie pustej linii, oznaczającej chęć pobrania czasu jako zwykłegotekstu PrintStream wyj = new PrintStream(port.getOutputStream()); wyj.println(); wyj.flush();

// Odczytanie pierwszej linii odpowiedzi // Powinna ona zawierać aktualny czas in = port.getInputStream(); DataInputStream wynik = new DataInputStream(new BufferedInputStream(in)); String data = wynik.readLine(); // Zwrócenie otrzymanego łańcucha return data; } catch (Exception w) { // Jeżeli wystąpił problem, wyświetlenie w System.out // (zazwyczaj konsoli Javy) i zwrócenie null w.printStackTrace(); return null; } finally { // Zawsze zamknięcie połączenia // Poniższy kod jest wykonywany niezależnie od wykonania poprzednich działań if (in != null) { try { in.close(); } catch (IOException ignored) { } } }} private String pobierzDataPortObiekt() { InputStream in = null; try { // Ustanowienie połączenia przez port z serwletem Socket port = new Socket(getCodeBase().getHost(), getSocketPort()); // Wyświetlenie linii "obiekt", wskazującej chęć pobrania Data jako obiektu // zserializowanego PrintStream wyj = new PrintStream(port.getOutputStream()); wyj.println("obiekt"); wyj.flush(); // Stworzenie ObjectInputStream odczytującego odpowiedź in = port.getInputStream(); ObjectInputStream wynik = new ObjectInputStream(new BufferedInputStream(in)); // Odczytanie obiektu i zapamiętania go jako Data Object obi = wynik.readObject(); Date data = (Date)obi; // Zwrócenie łańcuchowej reprezentacji otrzymanej Data return data.toString(); } catch (Exception w) { // Jeżeli wystąpił problem, wyświetlenie w System.out

Page 248: Java Servlet - Programowanie

// (zazwyczaj konsoli Javy) i zwrócenie null w.printStackTrace(); return null; } finally { // Zawsze zamknięcie połączenia // Poniższy kod jest wykonywany niezależnie od wykonania poprzednich działań if (in != null) { try { in.close(); } catch (IOException ignored) { } } }}

W obu powyższych metodach, aplet rozpoczyna działanie przez utworzenie obiektu Socket wykorzystywanegodo komunikacji z serwerem. Aby go utworzyć, musi znać nazwę komputera i numer portu, na którym nasłuchujeserwlet. Określenie komputera jest proste — musi to być ten sam komputer, z którego został on pobrany,dostępny przy pomocy wywołania getCodeBase().getHost(). Określenie portu jest trudniejsze, jako żezależy wyłącznie od serwletu, z którym łączy się aplet. Aplet ten wykorzystuje metodę getSocketPort() wcelu określenia tego. Przedstawiona powyżej implementacja getSocketPort() zwraca wartość parametruapletu socketPort lub (jeżeli parametr ten nie jest podany) wartość zmiennej DEFAULT_PORT.

Po ustanowieniu połączenia przez port, aplet wykorzystuje nienazwany protokół w celu porozumienia się zserwerem. Protokół ten wymaga, aby aplet wysłał jedną linię w celu wskazania, czy aktualny czas wysyłany wodpowiedzi miał formę tekstu, czy obiektu. Jeżeli linia ta zawiera słowo obiekt, aplet otrzymuje obiekt. Jeżelizawiera cokolwiek innego, otrzymuje zwykły tekst. Po wysłaniu tej linii, aplet może odczytać odpowiedź wewłaściwy sposób.

Aplet i serwlet mogą kontynuować porozumiewanie się przy pomocy tego portu. Jest to jedna z głównych zaletniestosowania komunikacji HTTP. Jednak w tym przypadku, aplet otrzymał pożądane informacje i może poprostu zamknąć połączenie. Zamknięcie to jest wykonywane w bloku finally. Umieszczenie zamknięcia wtym miejscu zapewnia koniec połączenia niezależnie od tego, czy try spowoduje dowolny wyjątek, czy nie.

Po dodaniu dwóch powyższych metod aplet jest niemal kompletny. Po uruchomieniu go w tym momencie,wynikiem będzie wyświetlenie dat we wszystkich polach poza „Obiekt RMI”.

Komunikacja RMIWe wcześniejszej części tego rozdziału powiedziano, że jednym z powodów nie wykorzystywania komunikacjiRMI jest jej skomplikowanie. Chociaż jest to prawda, jest również prawdą, że przy pomocy innej superklasyserwletu, kod wymagany, aby serwlet był dostępny przez komunikację RMI, może być aż śmiesznie prosty. Popierwsze, dokładnie opisany zostanie proces nadawania serwletowi właściwości obiektu zdalnego. Następnie, poudowodnieniu prostoty tego działania wyjaśniona zostanie cała praca mająca miejsce w tle.

SerwletWszystkie obiekty zdalne RMI muszą wykorzystywać specyficzny interfejs, Interfejs ten wykonuje dwa działania— deklaruje, które metody obiektu zdalnego mają zostać udostępnione zdalnym klientom, oraz rozszerzainterfejs Remote w celu wskazania, że jest to interfejs obiektu zdalnego. W przypadku przykładowego serwletuSerwletGodziny, można stworzyć interfejs SerwerGodziny przedstawiony w przykładzie 10.11.

Przykład 10.11. Interfejs SerwerGodzinyimport java.util.Date;import java.rmi.Remote;import java.rmi.RemoteException;public interface SerwerGodziny extends Remote { public Date getDate() throws RemoteException;}

Powyższy interfejs deklaruje udostępnianie przez SerwletGodziny klientom zdalnym metody getDate().Proszę zauważyć, że podpis getDate() został nieco zmieniony — wywołuje ona teraz RemoteException. Dlakażdej metody udostępnionej przez RMI musi być zadeklarowane wywołanie wyjątku. Chociaż sama metoda niemusi go wywoływać, może on być wywołany przez system w celu wskazania błędu w usługach sieciowych.

Kod SerwletGodziny pozostaje w przeważającej części niezmieniony w porównaniu ze swoją wersjąoryginalną. Właściwie jedyne zmiany to aktualna implementacja SerwerGodziny i rozszerzaniecom.oreilly.servlet.RemoteHttpServlet, superklasy pozwalającej temu serwletowi na pozostanieniezmienionym. Serwlet wykorzystuje również metodę destroy(), która wywołuje super.destroy().

Page 249: Java Servlet - Programowanie

Prawdą jest, że metoda ta jest właściwie bezużyteczna w niniejszym przykładzie, ale przypomina o tym, że każdametoda destroy() zaimplementowana w zdalnym serwlecie musi wywoływać super.destroy() w celudostarczenia metodzie destroy() obiektu RemoteHttpServlet możliwości zakończenia komunikacjiRMI. Przykład 10.12 przedstawia nowy kod SerwletGodziny.

Przykład 10.12.

SerwletGodziny obsługuje teraz dostęp RMIimport java.io.*;import java.net.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.RemoteDaemonHttpServlet; // Nowy dodatekpublic class SerwletGodziny extends RemoteDaemonHttpServlet // Nowy dodatek implements SerwerGodziny { // Nowy dodatek // Pojedyncza metoda z SerwerGodziny // Uwaga: klauzula na temat wywoływania nie jest tu potrzebna public Data getDate() { return new Data(); } public void init(ServletConfig konfig) throws ServletException { super.init(konfig); // Miejsce na dodatkowy kod } public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { // Jeżeli klient prześle "format=obiekt", to wysłanie Data jako obiektu // zserializowanego if ("obiect".equals(zad.getParameter("format"))) { ObjectOutputStream wyj = new ObjectOutputStream(odp.getOutputStream()); wyj.writeObject(getDate()); } // W przeciwnym wypadku wysłanie Data jako zwykłego łańcucha ASCII else { PrintWriter wyj = odp.getWriter(); wyj.println(getDate().toString()); } } public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { doGet(zad, odp); } public void destroy() { // Po przejęciu destroy() należy wywołać super.destroy() super.destroy(); }

W taki właśnie sposób tworzy się zdalny obiekt serwletu. Kompilacja zdalnego obiektu serwletu jest taka sama,jak dla każdego innego serwletu, z jednym dodatkowym krokiem. Po skompilowaniu kodu źródłowego serwletunależy skompilować klasę serwletu przy pomocy kompilatora RMI rmic. Kompilator RMI pobiera plik klasyobiektu zdalnego i tworzy szkieletową i końcową wersję klasy. Klasy te pracują w tle w celu umożliwieniakomunikacji RMI. Nie należy przejmować się szczegółami, trzeba jednak wiedzieć, że końcówka pomagaklientowi w wywoływaniu metod na zdalnym obiekcie, a szkielet pomaga serwerowi w obsłudze tych wywołań.

Wykorzystywanie rmic jest podobne do stosowania javac. W przypadku tego przykładu można skompilowaćSerwletGodziny przy pomocy następującego polecenia:

% rmic SerwletGodzinyProszę zauważyć, że rmic należy dostarczyć nazwę klasy Javy — nie pliku — do skompilowania. W związku ztym, jeżeli kompilowany serwlet jest częścią pakietu powinien zostać podany rmic jakonazwa.pakietu.NazwaSerwletu. Program rmic może pobierać ścieżkę klasy do poszukiwania przypomocy parametru –classpath, a także katalog docelowy dla plików szkieletu i końcówki przy pomocyparametru –d.

Po wykonaniu powyższego polecenia rmic, powinny ukazać się dwa nowe pliki klas —SerwletGodziny_Stub.class (końcówka) i SerwletGodziny_skel.class (szkielet). Poniżej

Page 250: Java Servlet - Programowanie

przedstawione zostanie ich zastosowanie. Po pierwsze, należy pamiętać, że nie jest konieczne ponowneuruchamianie kompilatora RMI za każdym razem, kiedy modyfikowany jest kod zdalnego serwletu. Jest tak,ponieważ klasy szkieletu i końcówki są wbudowane w interfejs serwletu, a nie w implementację tego interfejsu.W związku z tym, należy zregenerować go jedynie po modyfikacji interfejsu SerwerGodziny (lub interfejsudo niego równoważnego).

Teraz następuje ostatni krok w tworzeniu zdalnego serwletu — kopiowanie kilku plików klas do katalogumacierzystego dokumentów serwera, z którego to miejsca mogą być one pobrane przez aplet. Pobrane muszązostać dwa pliki klas — końcówka SerwletGodziny_Stub.class i zdalny interfejs klasy SerwerGodziny.class.Klient (w tym przypadku aplet) potrzebuje końcówki w celu wykonania swojej części komunikacji RMI, a samakońcówka wykorzystuje interfejs zdalnej klasy. Proszę pamiętać, że serwlet również wykorzystuje te klasy, więcnależy skopiować je do katalogu macierzystego dokumentów serwera oraz pozostawić je w ścieżce klasserwera11. Rysunek 10.2 przedstawia pozycje wszystkich plików serwera.

Rysunek 10.2.

To jest koniec! Jeżeli postępuje się według powyższych instrukcji możliwe jest utworzenie w krótkim czasiedziałającego zdalnego serwletu. Poniżej przedstawiona jest klasa RemoteHttpServlet i opisane działaniazachodzące w tle.

SuperklasaObiekt zdalny musi wykonać dwa działania, aby przygotować się do komunikacji RMI — musi sięwyeksportować i zarejestrować. Kiedy obiekt zdalny eksportuje siebie, zaczyna nasłuchiwanie na porcie,oczekując na przychodzące wywołania metod. Kiedy obiekt zdalny rejestruje się, podaje serwerowirejestrującemu swoją nazwę i numer portu tak, aby klient mógł go zlokalizować (a zwłaszcza poznać numer jegoportu) i porozumieć się z nim. Te dwa zadania są obsługiwane przez klasę RemoteHttpServlet,przedstawioną w przykładzie 10.13.

11 Zarządzanie wieloma plikami klas może stać się poważnym utrudnieniem przy tworzeniu oprogramowania. W systemieUniksowym można wykorzystać łącza symboliczne w celu ułatwienia tego zadania. W każdym systemie, można zastosowaćbardziej ogólne rozwiązanie — zmienić ścieżkę klas serwera tak, aby zawierała onakatalog_macierzysty/webapps/ROOT/classes. (Proszę zauważyć, że nie jest to WEB_INF/classes). W tym miejscu należypozostawić klasy końcówki i szkieletu. Następnie serwer może je odnaleźć w nowej ścieżce klas, a baza kodu apletu możezostać ustawiona na /classes, aby aplet również mógł je odnaleźć.

Page 251: Java Servlet - Programowanie

Przykład 10.13.

Superklasa RemoteHttpServletpackage com.oreilly.servlet;import java.io.*;import java.net.*;import java.rmi.*;import java.rmi.server.*;import java.rmi.registry.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public abstract class RemoteHttpServlet extends HttpServlet implements Remote { protected Registry registry; public void init(ServletConfig config) throws ServletException { super.init(config); try { // Eksportowanie siebie UnicastRemoteObject.exportObject(this); // Rejestrowanie siebie bind(); } catch (RemoteException e) { log("Problem binding to RMI registry: " + e.getMessage()); } }

public void destroy() { // Wyrejestrowanie siebie unbind(); } // Zwraca nazwę pod którą obiekt zostanie zarejestrowany protected String getRegistryName() { // Pierwszą wybieraną nazwą jest parametr inicjacji "registryName" String name = getInitParameter("registryName"); if (name != null) return name; // Drugim wyborem jest nazwa tej klasy return this.getClass().getName(); } // Zwraca port na którym nasłuchuje (lub powinien nasłuchiwać) serwerrejestrujący protected int getRegistryPort() { // Pierwszym wybieranym portem jest parametr inicjacji "registryPort" try { return Integer.parseInt(getInitParameter("registryPort")); } // Drugim wyborem jest domyślny port rejestracji (1099) catch (NumberFormatException e) { return Registry.REGISTRY_PORT; } } protected void bind() { // Próba znalezienia właściwego działającego już rejestru try { registry = LocateRegistry.getRegistry(getRegistryPort()); registry.list(); // Verify it's alive and well } catch (Exception e) { // Couldn't get a valid registry registry = null; } // Jeżeli go nie ma, należy go utworzyć // (Ekwiwalent uruchomienia "rmiregistry") if (registry == null) { try { registry = LocateRegistry.createRegistry(getRegistryPort()); } catch (Exception e) { log("Could not get or create RMI registry on port " + getRegistryPort() + ": " + e.getMessage()); return; } }

Page 252: Java Servlet - Programowanie

// Jeżeli program dotarł do tego miejsca, to istnieje prawidłowy rejestr // Teraz należy zarejestrować ten egzemplarz serwletu w tym rejestrze // „Rebind” podmienia inne obiekty wykorzystujące tą nazwę try { registry.rebind(getRegistryName(), this); } catch (Exception e) { log("Could not bind to RMI registry: " + e.getMessage()); return; } } protected void unbind() { try { if (registry != null) registry.unbind(getRegistryName()); } catch (Exception e) { log("Problem unbinding from RMI registry: " + e.getMessage()); } }}

Osoby, które wcześniej wykorzystywały lub czytały o RMI prawdopodobnie napotkały zdalne obiektyrozszerzające klasę java.rmi.server.UnicastRemoteObject. Jest to standardowy — i właściwiepolecany — sposób tworzenia zdalnego obiektu. Jednak klasa RemoteHttpServlet nie jest rozszerzeniemUnicastRemoteObject — rozszerza ona HttpServlet. Jak wiadomo, Java nie obsługujewielodziedziczenia. Oznacza to, że RemoteHttpServlet musi wybrać, czy jest rozszerzeniemUnicastRemoteObject, czy HttpServlet — nawet, jeżeli potrzebuje funkcjonalności obu klas. Jest totrudny wybór. Niezależnie od tego, której klasy nie rozszerza, musi po prostu reimplementować ją samodzielnie.Ostatecznie rozszerzono HttpServlet, ponieważ łatwiej jest napisać ponownie funkcjonalnośćUnicastRemoteObject niż HttpServlet.

To ponowne napisanie wymaga, aby klasa RemoteHttpServlet wykonywała dwa działania, których niemusiałaby wykonywać, jeżeli byłaby rozszerzeniem UnicastRemoteObject. Musi ona zadeklarować, żerozszerza interfejs Remote. Wszystkie obiekty zdalne muszą wykorzystywać ten interfejs, ale zazwyczajpoprzez rozszerzanie UnicastRemoteObject, klasa otrzymuje to za darmo. Wysiłek związany zsamodzielnym wykonaniem tego działania nie jest duży, jako że interfejs Remote właściwie nie definiujeżadnych metod. Obiekt deklaruje, że implementuje interfejs Remote, aby być traktowany jako obiekt zdalny.

Drugim działaniem, jakie musi wykonać RemoteHttpServlet jest ręczne wyeksportowanie siebie.Zazwyczaj jest to wykonywane automatycznie przez konstruktora UnicastRemoteObject(). Wykonanietego bez konstruktora nie jest jednak problemem. Klasa UnicastRemoteObject posiada statyczną metodęexportObject(Remote), którą każdy obiekt Remote może wykorzystać w celu wyeksportowania siebie.RemoteHttpServlet wykorzystuje tę metodę i eksportuje siebie przy pomocy poniższej pojedynczej linii:

UnicastRemoteObject.exportObject(this);Powyższe dwa kroki, implementacja Remote i eksportowanie siebie są wykonywane przezRemoteHttpServlet, ponieważ nie jest ona rozszerzeniem UnicastRemoteObject12.

Pozostała część kodu RemoteHttpServlet wykonuje rejestrowanie i wyrejestrowanie siebie w rejestrzeRMI. Jak powiedziano wcześniej, serwer rejestrujący RMI działa jako miejsce, w którym klienty mogązlokalizować obiekty serwera. Zdalny obiekt (obiekt serwera) rejestruje siebie w rejestrze pod konkretną nazwą.Klienty mogą więc wyszukać ten obiekt według nazwy w rejestrze. W takim razie, aby być dostępny dlaklientów, serwlet musi odnaleźć (lub stworzyć) serwer rejestrujący i zarejestrować się w tym serwerze podkonkretną nazwą. W języku specjalistycznym nosi to nazwę dowiązania do rejestru. RemoteHttpServletwykonuje to dowiązanie przy pomocy swojej metody bind(), wywoływanej wewnątrz metody init().

Metoda bind() wykorzystuje dwie metody wspierające, getRegistryPort() i getRegistryName()w celu określenia portu, na którym powinien działać serwlet i nazwy, pod którą powinien on zostaćzarejestrowany. W przypadku przykładowej implementacji, port jest określany przy pomocy parametru inicjacjiregistryPort, lub posiada domyślną wartość 1099. Nazwa jest pobierana z parametru inicjacjiregistryName lub posiada domyślną nazwę klasy serwletu — w tym przypadku SerwletGodziny.

12 Aby być całkowicie poprawnym, należy wykonać jeszcze dodatkowe działania. Według dokumentacjijava.rmi.remoteUnicastRemoteObject, „Jeżeli UnicastRemoteObject nie jest rozszerzana, klasaimplementująca musi przejąć odpowiedzialność za poprawną semantykę metod hashCode, equals i toStringdziedziczonych z klasy Object tak, aby zachowywały się one poprawnie w przypadku obiektów zdalnych”. Wedługdokumentacji java.rmi.remoteRef, „Metody te powinny zagwarantować, że końcówki obiektów zdalnych będąposiadać ten sam kod asocjacyjny (w celu obsługi obiektów zdalnych jako kluczy w tablicach asocjacyjnych).”Implementacja mechanizmów obsługujących tę gwarancję jest stosunkowo trudna i, według autorów, nie zawsze koniecznado komunikacji aplet-serwlet; w związku z tym podjęto ryzyko wykorzystania RemoteHttpServlet

Page 253: Java Servlet - Programowanie

Poniżej opisana jest dokładniej metoda bind(). Rozpoczyna ona działanie przez wykorzystanie następującegokodu, próbując odnaleźć właściwy działający już rejestr.

registry = LocateRegistry.getRegistry(getRegistryPort());registry.list();

Pierwsza linia próbuje odnaleźć rejestr pracujący na danym porcie. Druga prosi rejestr o listę jego obecniezarejestrowanych obiektów. Jeżeli oba wywołania zakończą się sukcesem, wynikiem będzie prawidłowy rejestr.Jeżeli dowolne wywołanie spowoduje wyjątek Exception, metoda bind() dowiaduje się, że nie istniejeprawidłowy rejestr i samodzielnie go tworzy. Wykonuje to działanie przy pomocy następującej linii kodu:

registry = LocateRegistry.createRegistry(getRegistryPort());Po wykonaniu tych działań metoda bind() powinna znaleźć lub utworzyć serwer rejestrujący. Jeżeli pobranierejestru nie powiodło się, a nie powiodło się też jego tworzenie, powraca ona i serwlet pozostajeniezarejestrowany. Następnie RemoteHttpServlet dowiązuje się do rejestru przy pomocy poniższej liniikodu:

registry.rebind(getRegistryName(), this);Wykorzystuje ona metodę Registry.rebind() zamiast metody Registry.bind() aby wskazać, że todowiązanie powinno zastąpić wszystkie poprzednie dowiązania wykorzystujące tę nazwę. Dowiązanie to trwadopóki serwlet nie zostaje zniszczony, w którym to miejscu metoda destroy() RemoteHttpServletwywołuje jego metodę unbind(). Kod wykorzystywany przez unbind() w celu zniszczenia dowiązania dorejestru jest niezwykle prosty:

if (registry != null) registry.unbind(getRegistryName());Po prostu prosi ona rejestr o zniszczenie dowiązania do swojej nazwy.

Wskazówka!!!!

W którym miejscu uruchomić rejestr?

Szeroko akceptowanym sposobem uruchamiania serwera rejestrującego RMI jest samodzielny program Javyrmiregistry. Polecane jest jednak opuszczenie rmiregistry i pozwolenie RemoteHttpServlet nasamodzielne utworzenie rejestru. Jest to łatwiejsze i bardziej wydajne. Pierwszy serwlet korzystający zrejestru może go utworzyć. Z powodu uruchamiania rejestru wewnątrz serwletu, rejestr pracuje przy pomocytej samej JVM, co serwlet. Pozwala to na wykorzystanie tylko jednej wirtualnej maszyny Javy dla serwera,wszystkich jego serwletów (obiektów zdalnych) i rejestru. Niektóre serwery aplikacji na starcie uruchamiająrejestr wewnątrz własnej JVM.

Możliwe jest również całkowite ominięcie uruchomienia rejestru, przy pomocy serwletów. Klient możepołączyć się ze specjalnym serwletem przy pomocy HTTP i zażądać danego obiektu zdalnego (przy pomocyparametru), a serwlet może zwrócić mu dane odpowiedzi jako zserializowaną końcówkę obiektu zdalnego —podobnie jak zwraca te dane rejestr. Zastosowanie serwletu zamiast rmiregistry pozwala serwletowi nazaimplementowanie polityki bezpieczeństwa ograniczającej prawa przeglądania obiektów, pozwalaserwletowi na nadanie każdemu klientowi odwołania do różnych obiektów zdalnych oraz (ponieważ serwletmoże tworzyć zdalny obiekt w ramach obsługi żądań) powoduje, że obiekt zdalny jest dostępny nawet przedjego utworzeniem.

Proszę zauważyć, że zdalny serwlet musi zostać załadowany na swój serwer, a jego metoda init() musi zostaćwywołana, zanim będzie on gotowy do komunikacji RMI. Podobnie jak w przypadku serwletu-demona, należynakazać serwerowi ładowanie go przy uruchamianiu lub upewnić się, że jest on zawsze dostępny przez HTTPzanim będzie dostępny bezpośrednio.

ApletTeraz należy odwrócić uwagę od serwera i skupić się na kliencie. Kod wykorzystywany przez ApletGodzinydo wywołania metody GetDate() nowego SerwletGodziny() jest przedstawiony w przykładzie 10.14.

Przykład 10.14.

ApletGodziny pobierający czas przy pomocy RMIimport java.rmi.*; // Nowy dodatekimport java.rmi.registry.*; // Nowy dodatekprivate String getRegistryHost() {

Page 254: Java Servlet - Programowanie

return getCodeBase().getHost();} private int getRegistryPort() { try { return Integer.parseInt(getParameter("registryPort")); } catch (NumberFormatException w) { return Registry.REGISTRY_PORT; }} private String getRegistryName() { String nazwa = getParameter("registryName"); if (nazwa == null) { nazwa = "SerwletGodziny"; // domyślnie } return nazwa;} private String pobierzDataRMIObiekt() { try { Registry rejestr = LocateRegistry.getRegistry(getRegistryHost(), getRegistryPort()); SerwerGodziny godziny = (SerwerGodziny)rejestr.lookup(getRegistryName()); return godziny.getDate().toString(); } catch (ClassCastException w) { System.out.println("Otrzymany obiekt to nie SerwerGodziny: " + w.getMessage()); } catch (NotBoundException w) { System.out.println(getRegistryName() + " nie dowiązany: " + w.getMessage()); } catch (RemoteException w) { System.out.println("Wyjątek zdalny: " + w.getMessage()); } catch (Exception w) { System.out.println("Problem z pobraniem odwołania do SerwerGOdziny: " + w.getClass().getName() + ": " + w.getMessage()); } return null;}

Pierwsze trzy metody to metody wspomagające. getRegistryHost() zwraca komputer, na którym powinienpracować serwer rejestrujący. Musi to zawsze być komputer, z którego pobrany został aplet.getRegistryPort() zwraca port, na którym powinien nasłuchiwać serwer rejestrujący. Zazwyczaj jest todomyślny port rejestru 1099, chociaż może on zostać przejęty przy pomocy parametru registryPort.getRegistryName() zwraca nazwę, pod którą serwlet powinien zostać zarejestrowany. Jej domyślnawartość to SerwletGodziny, lecz może ona zostać przejęta przy pomocy parametru registryName.

Właściwe poszukiwanie zdalnego obiektu serwletu i wywołanie jego metody getDate() następuje wponiższych trzech liniach metody pobierzDataRMIObiekt():

Registry rejestr = LocateRegistry.getRegistry(getRegistryHost(), getRegistryPort());SerwerGodziny godziny = (SerwerGodziny)rejestr.lookup(getRegistryName());return godziny.getDate().toString();

Pierwsza linia odnajduje rejestr dla danego komputera i danego portu. Druga linia wykorzystuje ten rejestr dowyszukania zdalnego obiektu zarejestrowanego pod daną nazwą, wysyłając ten obiekt do obiektuSerwerGodziny. Trzecia linia wywołuje metodę getDate() tego obiektu i otrzymuje w odpowiedzizserializowany obiekt Data. Następnie, w tej samej linii, zwraca reprezentację łańcuchową (String) tegoobiektu Data.

Pozostała część metody pobierzDataRMIObiekt() obsługuje mogące wystąpić wyjątki. Przejmuje onawyjątek ClassCastException, jeżeli wywoływany obiekt to nie SerwerGodziny,NotBoundException jeżeli rejestr nie posiada obiektu zarejestrowanego pod tą nazwą, aRemoteException w przypadku, gdy występuje błąd w usługach sieciowych. Przejmuje także ogólny wyjątekException, w przypadku wystąpienia innych problemów.

Można się zastanawiać, dlaczego ApletGodziny wykorzystuje Registry.lookup(String) zamiastjava.rmi.Naming.lookup(String) do pobrania swojego odwołania do zdalnego serwletu. Nie ma kutemu żadnego powodu — to tylko sprawa osobistego gustu. Aplet ten pracowałby podobnie po wymianie dwóchpierwszych linii pobierzDataRMIObiekt() na następujący kod:

SerwerGodziny godziny = (SerwerGodziny)Naming.lookup("rmi:// + getRegistryHost() + ":" + getRegistryPort() +

Page 255: Java Servlet - Programowanie

"/" + getRegistryName());To jest koniec piątej i ostatniej metody ApletGodziny. Proszę uruchomić teraz aplet. Czy wszystkie pola sąwypełnione? Nie powinny być. Pola odpowiadające komunikacji przez port powinny być puste. Proszęprzypomnieć sobie, że podczas zmiany SerwletGodziny na obiekt zdalny, usunięta została obsługakomunikacji przez port. Zostanie ona ponownie umieszczona poniżej.

W pełni funkcjonalny serwletPotrzebny jest pojedynczy serwlet dostępny przez komunikację HTTP, nie-HTTP i RMI. Serwlet tego typu możebyć rozszerzeniem nowej superklasy, com.oreilly.servlet.RemoteDaemonHttpServlet,wykorzystującym możliwości opisane dotychczas dla RemoteHttpServlet i DaemonHttpServlet.

Poniżej przedstawiony jest kod deklarujący ten w pełni funkcjonalny serwlet:import java.io.*;import java.net.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.RemoteDaemonHttpServlet;public class SerwletGodziny extends RemoteDaemonHttpServlet implements SerwerGodziny { public Data getDate() { return new Data(); } // Pozostała część bez zmian

Powyższy kod jest niemal identyczny jak przykład 10.8. Jest to po prostu tamten przykład napisany ponownie,aby zadeklarować, że rozszerza on RemoteDaemonHttpServlet i implementuje SerwerGodziny.

Kod superklasy RemoteDaemonHttpServlet również jest niemal identyczny jak RemoteHttpServlet.Występują jedynie dwie zmiany — rozszerza on DaemonHttpServlet zamiast HttpServlet, a jegometoda destroy() jako pierwsza wywołuje super.destroy():

package com.oreilly.servlet;import java.io.*;import java.net.*;import java.rmi.*;import java.rmi.server.*;import java.rmi.registry.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;

public abstract class RemoteDaemonHttpServlet extends DaemonHttpServlet implements Remote { public void destroy() { super.destroy(); unbind(); } // Pozostała część bez zmian

W tym momencie ApletGodziny może połączyć się z poprawionym zdalnym serwerem-demonem i utworzyćpełny wynik przedstawiony wcześniej na rysunku 10.1.

Serwer pogawędekPrzykład serwera godziny z poprzedniego podrozdziału przedstawiał wady i zalety stosowania wszystkich trzechtechnik komunikacji aplet-serwer. Nie korzystał jednak z zalet utrzymywania połączenia w przypadku połączeniaprzez port. Nie przedstawiał również prostoty komunikacji RMI i elegancji wywołań wstecznych RMI (w którychserwlet może wywoływać metody apletu). Nie przedstawiał poważnego powodu, dla którego jeden serwletpowinien obsługiwać wszystkie techniki komunikacji — nie istniał powód dla którego należało utrzymywaćskomplikowany kod w jednym miejscu. Tak więc przed zakończeniem dyskusji na temat komunikacji aplet-serwlet przedstawiony zostanie przykład bardziej skomplikowany — serwer pogawędek (chat),zaimplementowany jako serwlet, który obsługuje klientów łączących się przez HTTP, porty nie-HTTP i RMI.

Ten serwer pogawędek zostanie utworzony przy pomocy wszystkich trzech technik komunikacji, aby mógłkorzystać z zalet najlepszego, najbardziej wydajnego rozwiązania dla każdego klienta. Na przykład, kiedy klient

Page 256: Java Servlet - Programowanie

obsługuje RMI, serwlet może być traktowany jako obiekt zdalny, a (gdzie to możliwe) może traktować apletrównież jako zdalny obiekt. Kiedy klient nie obsługuje HTTP, ale obsługuje bezpośrednie połączenia przez port,serwer pogawędek może skorzystać z trwałości portów i łączyć się z klientem przy pomocy protokołu portu niebędącego HTTP. I, oczywiście, jeżeli inne techniki zawiodą, serwer pogawędek wykorzystać HTTP. Nie jest topolecane, ponieważ HTTP jako protokół bezstanowy wymaga od klienta aktywności w celu dokonaniauaktualnień. Lecz dla wielu klientów HTTP jest jedynym możliwym rozwiązaniem.

Serwer pogawędek jest implementowany jako pojedyncza klasa z pojedynczym egzemplarzowaniem, ponieważposiada on dużą ilość związanych ze sobą danych i sporą ilość kodu, który musiałby być powtarzany. Podzieleniego na trzy klasy, jedną dla każdego protokołu, wymagałoby nadmiernej komunikacji między nimi i trzykrotnegopowtarzania podstawowego kodu serwera pogawędek. Implementacja serwera pogawędek jako serwletudostarcza prostej metody udostępnienia jednego obiektu przy pomocy wszystkich trzech technik komunikacji.Jako że jest on serwletem HTTP, posiada wbudowaną obsługę HTTP. A dzięki temu, że jest rozszerzeniem klasyRemoteDaemonHttpServlet, może również łatwo uzyskać możliwość obsługi portów nie-HTTP ikomunikacji RMI.

Proszę zauważyć, że chociaż kod zostanie przedstawiony w całości, nie zostanie wyjaśniona każda pojedynczalinia. Spowodowałoby to powiększenie niniejszego rozdziału poza rozsądny rozmiar, jeżeli się to już nie stało.Tak więc zostaną wyjaśnione kwestie ściśle związane z komunikacją aplet-serwlet, a analiza kodu i zrozumieniewszystkich szczegółów będą pozostawione Czytelnikowi.

ProjektRysunek 10.3 przedstawia aplet pogawędek w działaniu. Proszę zauważyć, że wykorzystuje on duży elementTextArea w celu wyświetlenia toczącej się konwersacji. Mały element TextArea poniżej służyużytkownikowi do wpisywania i wysyłania jednolinijkowych wiadomości. Kiedy dowolny użytkownik wpiszewiadomość zostaje ona wysłana do serwera i rozprowadzona na różne sposoby do innych klientów.

Rysunek 10.3.

Aplet pogawędek w działaniu

Klienci HTTP wysyłają swoje wiadomości na serwer przy pomoc metody HTTP POST. Aplet pobiera nowąwiadomość z elementu TextInput, kiedy użytkownik naciska Enter, koduje wiadomość w URL-u i wysyła jądo serwletu jako parametr wiadomości. Jest to działanie bardzo proste. Nieco bardziej skomplikowany jestsposób zarządzania pobieraniem wiadomości od innych użytkowników przez klienta pogawędek HTTP.Wykorzystuje on metodę HTTP GET do odebrania każdej wiadomości, występuje jednak pewien problem: niewie on, kiedy pojawia się nowa wiadomość do odebrania. Jest to problem związany z paradygmatemjednokierunkowej komunikacji żądanie/odpowiedź. Klient musi albo od czasu do czasu wysłać jakąś wiadomośćw celu uaktualnienia, lub symulować komunikację dwukierunkową przy pomocy serii blokujących żądań GET.Oznacza to, że klient pogawędek inicjuje żądanie GET, które działa blokująco, dopóki serwer nie zdecyduje, żenadszedł czas na zwrócenie jakiejś informacji. W niniejszym przykładzie zaimplementowana zostanie tasymulowana komunikacja dwukierunkowa.

Page 257: Java Servlet - Programowanie

Klienci uzyskujący dostęp przez port, w celu zachowania spójności, wysyłają swoje wiadomości w identycznysposób jak klienci HTTP, przy pomocy metody HTTP POST. Mogą oni wysyłać swoje wiadomości przy pomocypołączeń przez zwykły port, lecz daje to jedynie minimalny zysk w wydajności, który, przynajmniej w tymprzypadku, nie przeważa zwiększonej złożoności. Klienci korzystający ze zwykłych portów wykorzystują jejednak do odbierania wiadomości od innych użytkowników. W tym przypadku symulowana łącznośćdwukierunkowa zostaje zastąpiona przez komunikację prawdziwą. Kiedy dowolna nowa wiadomość jestodbierana przez serwlet, jest wysyłana bezpośrednio z serwletu do klientów korzystających z portów przypomocy opartych na zwykłym tekście połączeń przez port.

Klienci pogawędek RMI wykonują swoje żądania POST i GET przy pomocy wywołań metod. Aby wysłać nowąwiadomość, aplet po prostu wywołuje metodę zdalnego serwletu nadajWiadomosc(String). Aby odebraćnowe wiadomości, może zastosować jedną z dwóch opcji. Może wywołać blokującą serwlet metodępobierzNastepnaWiadomosc() lub, przy pomocy wywołań wstecznych, może poprosić serwer owywołanie swojej własnej metody ustawNastepnaWiadomosc(String) za każdym razem, kiedy nadanazostaje nowa wiadomość. W niniejszym przykładzie zastosowana zostanie technika wywołań wstecznych.

Pierwszym z wszystkich powyższych apletów jest aplet-dyspozytor. Pozwala on użytkownikowi na wybórtechniki komunikacji aplet-serwlet (HTTP, port lub RMI), któryą chce on wykorzystać i, w oparciu o jegowybór, generuje stronę zawierającą właściwy aplet. Prawdą jest, że pojedynczy aplet mógłby obsługiwaćwszystkie te trzy techniki i automatycznie wybierać między nimi w oparciu o środowisko uruchomieniowe, leczdziałanie takie w tym miejscu jedynie niepotrzebnie skomplikowałoby przykład. Serwlet-dyspozytor podajerównież apletowi nazwę jego użytkownika, zostanie to jednak opisane później.

SerwletPełne wydruki interfejsu SerwerPogaw i klasy SerwletPogaw, która go wykorzystuje są przedstawione wprzykładach 10.15 i 10.16.

Przykład 10.15.

Interfejs SerwerPogaw, wykorzystywany przez SerwletPogawimport java.rmi.Remote;import java.rmi.RemoteException;public interface SerwerPogaw extends Remote { public String pobierzNastepnaWiadomosc() throws RemoteException; public void nadajWiadomosc(String message) throws RemoteException; public void dodajKlient(KlientPogaw klient) throws RemoteException; public void usunKlient(KlientPogaw klient) throws RemoteException;}

Przykład 10.16.

W pełni funkcjonalny serwer-serwlet pogawędekimport java.io.*;import java.net.*;import java.rmi.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.RemoteDaemonHttpServlet;public class SerwletPogaw extends RemoteDaemonHttpServlet implements SerwerPogaw { // zrodlo działa jako dystrybutor nowych wiadomości ZrodloWiadomosci zrodlo = new ZrodloWiadomosci(); // portKlienci przechowuje odwołania do wszystkich klientów łączących się przez // porty Vector portKlienci = new Vector(); // rmiKlienci przechowuje odwołania do wszystkich klientów łączących się przezRMI Vector rmiKlienci = new Vector(); // doGet() zwraca następną wiadomość. Blokuje dopóki nie ona nie wystąpi. public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter();

Page 258: Java Servlet - Programowanie

// Zwrócenie następnej wiadomości (blokowanie) wyj.println(pobierzNastepnaWiadomosc()); } // doPost() przyjmuje nową wiadomość i wysyła ją do wszystkich // aktualnie nasłuchujących klientów łączących się przez HTTP i porty. public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { // Przyjęcie wiadomości jako parametru „wiadomosc” String wiadomosc = zad.getParameter("wiadomosc"); // Wysłanie jej do wszystkich nasłuchujących klientów if (wiadomosc != null) nadajWiadomosc(wiadomosc); // Ustawienie kodu stanu aby zaznaczyć, że nie będzie odpowiedzi odp.setStatus(odp.SC_NO_CONTENT); } // pobierzNastepnaWiadomosc() zwraca następną nową wiadomość. // Blokuje dopóki ona nie wystąpi. public String pobierzNastepnaWiadomosc() { // Stworzenie pojemnika na wiadomości czekającego na nową wiadomość ze źródła // wiadomości return new PojemnikWiadomosc().pobierzNastepnaWiadomosc(zrodlo); } // nadajWiadomosc() informuje wszystkich aktualnie nasłuchujących klientów żejest // nowa wiadomość. Powoduje odblokowanie wszystkich wywołań do // pobierzNowaWiadomosc(). public void nadajWiadomosc(String wiadomosc) { // Wysłanie wiadomości do wszystkich klientów HTTP poprzez przekazaniewiadomości do // źródła wiadomości zrodlo.wyslijWiadomosc(wiadomosc); // Bezpośrednie wysłanie wiadomości do wszystkich klientów połączonych przezport Enumeration enum = portKlienci.elements(); while (enum.hasMoreElements()) { Socket klient = null; try { klient = (Socket)enum.nextElement(); PrintStream wyj = new PrintStream(klient.getOutputStream()); wyj.println(wiadomosc); } catch (IOException w) { // Problem z klientem, zamknięcie i oddalenie go try { if (klient != null) klient.close(); } catch (IOException ignored) { } portKlienci.removeElement(klient); } } // Bezpośrednie wysłanie wiadomości do wszystkich klientów RMI enum = rmiKlienci.elements(); while (enum.hasMoreElements()) { KlientPogaw klientPogaw = null; try { klientPogaw = (KlientPogaw)enum.nextElement(); klientPogaw.ustawNastepnaWiadomosc(wiadomosc); } catch (RemoteException w) { // Problem z komunikacją z klientem, zamknięcie go usunKlient(klientPogaw); } } } protected int getSocketPort() { // Nasłuchiwanie na porcie 2428 (proszę spojrzeć na telefon, dlaczego) return 2428; } public void obslugaKlient(Socket klient) { // Nowy klient łączący się przez port. Dodanie go do listy. portKlienci.addElement(klient); }

Page 259: Java Servlet - Programowanie

public void dodajKlient(ChatClient klient) { // Nowy klient RMI. Dodanie go do listy. rmiKlienci.addElement(klient); } public void usunKlient(ChatClient klient) { // Usunięcie wybranego klienta z listy. rmiKlienci.removeElement(klient); }}// ZrodloWiadomosc działa jako źródło nowych wiadomości.// Klienci zainteresowani przyjmowaniem nowych wiadomości mogą obserwować tenobiektclass ZrodloWiadomosc extends Observable { public void wyslijWiadomosc(String wiadomosc) { setChanged(); notifyObservers(wiadomosc); }}// PojemnikWiadomosc działa jako odbierający nowe wiadomości.// Nasłuchuje źródła.class PojemnikWiadomosci implements Observer { String wiadomosc = null; // ustawienie według uaktual() I odczytanie według // pobierzNastepnaWiadomosc() // Wywoływany przez źródło wiadomości kiedy odbiera nową wiadomość synchronized public void uaktual(Observable o, Object arg) { // odebranie nowej wiadomości wiadomosc = (String)arg; // Obudzenie czekającego wątku notify(); } // Pobranie następnej wiadomości wysłanej ze źródła wiadomości synchronized public String pobierzNastepnaWiadomosc(ZrodloWiadomosci zrodlo) { // Powiedz źródłu, że ma powiadamiać o nowych wiadomościach zrodlo.addObserver(this); // Czekanie aż metod uaktual() odbierze wiadomość while (wiadomosc == null) { try { wait(); } catch (Exception ignored) { } } // Powiedz źródłu żeby przestało informować o nowych wiadomościach zrodlo.deleteObserver(this); // Zwrócenie otrzymanej wiadomości // ale wcześniej wyczyszczenie zmiennej egzemplarza wiadomości // aby uaktual() i pobierzNastepnaWiadomosc mogły zostać wywołane ponownie. String wiadomoscKopia = wiadomosc; wiadomosc = null; return wiadomoscKopia; }}

Metody pobierzNastepnaWiadomosc() i nadajWiadomosc(String wiadomosc) są najbardziejinteresującymi częściami SerwletPogaw. Metoda pobierzNastepnaWiadomosc zwraca następnąwiadomość, kiedy ona nadejdzie, blokując port dopóki jej nie ma. Aby umożliwić to blokowanie, wykorzystujeona klasy ZrodloWiadomosc i PojemnikWiadomosc. Bez wchodzenia w zbytnie szczegóły tych dwóchklas, można powiedzieć, że serwlet konstruuje nowy PojemnikWiadomosc i prosi pojemnik o pobranienastępnej wiadomości ze źródła. Aby to wykonać, pojemnik rejestruje się jako obserwator źródła i wywołujewait() w celu blokowania. Kiedy źródło otrzymuje nową wiadomość, pojemnik (będący obserwatorem) jestpowiadamiany o zmianie przy pomocy wywołania jego metody uaktual(). Metoda PojemnikWiadomoscuaktual() zapamiętuje ostatnią wiadomość ze źródła w swojej zmiennej wiadomości, po czym wywołujenotify(). Powoduje to odblokowanie metody pobierzNastepnaWiadomosc() i zwróceniewiadomości.

Metoda nadajWiadomosc() powiadamia wszystkich klientów, kiedy występuje nowa wiadomość. KlientówHTTP poprzez wysłanie wiadomości do ZrodloWiadomosci, innych klientów bezpośrednio poprzez przeglądanielist klientów. Dla każdego klienta połączonego przez port, wyświetla wiadomość do portu klienta. Dla każdegoklienta RMI, wywołuje metodę ustawNastepnaWiadomosc(String). Jest to omawiane wcześniejwywołanie wsteczne. Jeżeli, w dowolnym momencie, występuje problem z klientem RMI lub łączącym się przezport, usuwa tego klienta z listy.

Page 260: Java Servlet - Programowanie

Dwie listy, portKlienci i rmiKlienci, zyskują elementy, kiedy klienci nadają wiadomość serwletowi.Kiedy klient łączy się przez port, wywoływana jest metoda serwletu obslugaKlient(Socket), a nowyklient jest dodawany do Vector portKlienci. Klienci RMI muszą dodać się do listy sami poprzezwywołanie metody serwletu dodajKlient(klientPogaw).

Metody doGet() i doPost() SerwletPogaw są właściwie małymi obwódkami metodpobierzNastepnaWiadomosc() i nadajWiadomosc(). Obwódka doGet() jest tak cienka, że niemalprzezroczysta — doGet() wysyła jako odpowiedź jakikolwiek łańcuch String, który zostaje zwrócony przezpobierzNastepnaWiadomosc(). Obwódka doPost() jest nieco bardziej widoczna, Pobiera onawysłaną wiadomość z parametru formularza danych POST wiadomosc, nadaje wiadomość poprzezprzekazanie jej metodzie nadajWiadomosc() oraz ustawia kod stanu odpowiedzi na SC_NO_CONTENT, abywskazać, że w odpowiedź nie posiada żadnej zawartości. Właściwie wykonywanie żądania GET jestrównoznaczne z wywołaniem pobierzNastepnaWiadomosc(), a wykonywanie żądania POST jestrównoznaczne z wywołaniem nadajWiadomosc(). Jedyny problem jest taki, że w projekcie HTTP istniejemały odstęp czasu pomiędzy otrzymaniem wiadomości przez klienta i zażądaniem przez niego nowej, co możespowodować ominięcie pewnych wiadomości w sytuacji dużego obciążenia. Lepszy projekt (pozostawionyCzytelnikowi) wykorzystywałby obiekt HttpSession klienta w celu utworzenia kolejki oczekującychwiadomości.

Można zauważyć, że port połączenia, na którym nasłuchuje SerwletPogaw to 2428. Przejmowanie metodygetSocketPort() w sposób taki, w jaki czyni to SerwletPogaw to prosty sposób ustawienia portupołączenia bez korzystania z parametru inicjacji.

Aplet HTTPKod pierwszego apletu, apletu pogawędek HTTP, jest przedstawiony w przykładzie 10.17.

Przykład 10.17.

Klient pogawędek wykorzystujący komunikację HTTPimport java.applet.*;import java.awt.*;import java.io.*;import java.net.*;import java.util.*;import com.oreilly.servlet.HttpMessage;public class ApletPogawHTTP extends Applet implements Runnable { TextArea tekst; Label etykieta; TextField wpis; Thread watek; String uzyt; public void init() { // Sprawdzenie, czy aplet został pobrany bezpośrednio z systemu plików. // Jeżeli tak, wyjaśnienie użytkownikowi, że musi on być pobrany z serwera wcelu // komunikacji z serwletami tego serwera URL kodbazy = getCodeBase(); if (!"http".equals(kodbazy.getProtocol())) { System.out.println(); System.out.println("*** Ups! ***"); System.out.println("Ten aplet musi być pobrany z serwera WWW."); System.out.println("Proszę spróbować ponownie, tym razem ładując plikHTML"); System.out.println("zawierający ten serwlet jako"); System.out.println("\"http://serwer:port/plik.html\"."); System.out.println(); System.exit(1); // Działa jedynie z przeglądarką apletów // Przeglądarki wyświetlają błąd i kontynuują } // Pobranie nazwy tego użytkownika z parametru apletu ustawionego przezserwlet // Można o to po prostu spytać użytkownika, ale jest to przedstawienie formy // komunikacji serwlet->aplet. uzyt = getParameter("user"); if (uzyt == null) uzyt = "anonymous"; // konfiguracja interfejsu użytkownika...

Page 261: Java Servlet - Programowanie

// Na górze duże pole TextArea przedstawiające całą rozmowę. // Poniżej opisane TextField przyjmujące wpisy tego użytkownika. tekst = new TextArea(); tekst.setEditable(false); etykieta = new Label("Powiedz coś: "); wpis = new TextField(); wpis.setEditable(true); setLayout(new BorderLayout()); Panel panel = new Panel(); panel.setLayout(new BorderLayout()); add("Centrum", tekst); add("Dol", panel); panel.add("Lewo", etykieta); panel.add("Centrum", wpis); } public void start() { watek = new Thread(this); watek.start(); } String pobierzNastepnaWiadomosc() { String nastepnaWiadomosc = null; while (nastepnaWiadomosc == null) { try { URL url = new URL(getCodeBase(), "/servlet/SerwletPogaw"); HttpMessage wiad = new HttpMessage(url); InputStream in = wiad.sendGetMessage(); DataInputStream dane = new DataInputStream( new BufferedInputStream(in)); nastepnaWiadomosc = dane.readLine(); } catch (SocketException w) { // Niemożliwe połączenie z komputerem macierzystym, zgłoszenie ioczekiwanie // przed ponowną próbą System.out.println("Połączenie z komputerem niemożliwe: " + w.getMessage()); try { Thread.sleep(5000); } catch (InterruptedException ignored) { } } catch (FileNotFoundException w) { // Serwlet nie istnieje, zgłoszenie i oczekiwanie System.out.println("Zasoby nie odnalezione: " + w.getMessage()); try { Thread.sleep(5000); } catch (InterruptedException ignored) { } } catch (Exception w) { // Inny problem, zgłoszenie i oczekiwanie System.out.println("Ogólny wyjątek: " + w.getClass().getName() + ": " + w.getMessage()); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } } } return nastepnaWiadomosc + "\n"; } public void run() { while (true) { tekst.appendText(pobierzNastepnaWiadomosc()); } } public void stop() { watek.stop(); watek = null; } void nadajWiadomosc(String wiadomosc) { wiadomosc = uzyt + ": " + wiadomosc; // Na początku dodanie nazwy użytkownika try { URL url = new URL(getCodeBase(), "/servlet/SerwletPogaw"); HttpMessage wiad = new HttpMessage(url); Properties wlasc = new Properties(); wlasc.put("wiadomosc", wiadomosc); wiad.sendPostMessage(wlasc); } catch (SocketException w) { // Niemożliwe połączenie z komputerem macierzystym, zgłoszenie izaprzestanie // nadawania

Page 262: Java Servlet - Programowanie

System.out.println("Niemożliwe połączenie z komputerem: " + w.getMessage()); } catch (FileNotFoundException w) { // Serwlet nie istnieje, zgłoszenie i zaprzestanie nadawania System.out.println("Zasoby nie odnalezione: " + w.getMessage()); } catch (Exception w) { // inny problem, zgłoszenie i zaprzestanie nadawania System.out.println("Ogólny wyjątek: " + w.getClass().getName() + ": " + w.getMessage()); } } public boolean obslugaWyjatek(Event wyjatek) { switch (wyjatek.id) { case Event.ACTION_EVENT: if (wyjatek.target == wpis) { nadajWiadomosc(wpis.getText()); wpis.setText(""); return true; } } return false; }}

Powyższy aplet posiada dwie takie same metody robocze jak SerwletPogaw —pobierzNastepnaWiadomosc() i nadajWiadomosc(). Jego metodapobierzNastepnaWiadomosc() pobiera następną wiadomość od serwletu. Jest ona wywoływana w celuuaktualnienia pola TextArea. Działa wykorzystując HttpMessage do wykonania żądania GET dla serwletu,po czym interpretuje pierwszą linie odpowiedzi jako następną nową wiadomość. Metoda apletunadajWiadomosc() wysyła wiadomość do serwletu w celu udostępnienia jej pozostałym klientom. Metoda tajest wywoływana w metodzie apletu obslugaWyjatek() za każdym razem, kiedy użytkownik naciśnie Enterw elemencie TextInput. Działa podobnie do pobierzNastepnaWiadomosc(). WykorzystujeHttpMessage do wykonania żądania POST, przekazując tekst z TextInput jako parametr wiadomosc i niezajmuje się odczytaniem odpowiedzi.

Aplet łączący się przez portJedyną różnicą pomiędzy łączącym się przez port ApletPogawPort i opartym na HTTP ApletPogawHTTPjest przeprojektowana metoda pobierzNastepnaWiadomosc(). Metoda ta przedstawiona jest wprzykładzie 10.18.

Przykład 10.18.

Klient pogawędek wykorzystujący połączenie przez zwykły portstatic final int PORT = 2428;DataInputStream potokSerwer;String pobierzNastepnaWiadomosc() { String nastepnaWiadomosc = null; while (nastepnaWiadomosc == null) { try { // Połączenie z serwerem jeżeli nie nastąpiło ono wcześniej if (potokSerwer == null) { Socket s = new Socket(getCodeBase().getHost(), PORT); potokSerwer = new DataInputStream( new BufferedInputStream( s.getInputStream())); } // Odczytanie linii nastepnaWiadomosc = potokSerwer.readLine(); } catch (SocketException w) { // Połączenie z komputerem niemożliwe, zgłoszenie i odczekanie przed kolejnąpróbą System.out.println("Połączenie z komputerem niemożliwe: " + w.getMessage()); potokSerwer = null; try { Thread.sleep(5000); } catch (InterruptedException ignored) { } } catch (Exception w) { // Inny problem, zgłoszenie i odczekanie przed kolejną próbą System.out.println("Ogólny wyjątek: " + w.getClass().getName() + ": " + w.getMessage()); try { Thread.sleep(1000); } catch (InterruptedException ignored) { }

Page 263: Java Servlet - Programowanie

} } return nastepnaWiadomosc + "\n";}

Powyższa metoda odczytuje nadane wiadomości z portu, który podłączony jest do serwletu pogawędek.Wykorzystuje ona prosty protokół portu — cała zawartość to zwykły tekst, jedna wiadomość na linię. Popierwszym wywołaniu tej metody ustanawia ona połączenie z portem po czym wykorzystuje je do pobraniaDataInputStream, przy pomocy którego może odczytywać z portu jedną linię za każdym razem. Przykażdym ponownym wywołaniu ponownie wykorzystuje ona ten sam potok i po prostu zwraca następnąprzeczytaną linię. Jeżeli kiedykolwiek wystąpi wyjątek SocketException, ponownie ustanawia onapołączenie.

Aplet RMIKod interfejsu KlientPogaw jest przedstawiony w przykładzie 10.19. Aplet pogawędek oparty na RMI, którywykorzystuje ten interfejs jest przedstawiony w przykładzie 10.20.

Przykład 10.19.

Interfejs KlientPogaw, wykorzystywany przez ApletPogawRMIimport java.rmi.Remote;import java.rmi.RemoteException;public interface KlientPogaw extends Remote { public void pobierzNastepnaWiadomosc(String message) throws RemoteException;}

Przykład 10.20.

Klient pogawędek wykorzystujący komunikację RMIimport java.applet.*;import java.awt.*;import java.io.*;import java.net.*;import java.rmi.*;import java.rmi.registry.*;import java.rmi.server.*;import java.util.*;public class ApletPogawRMI extends Applet implements KlientPogaw { TextArea tekst; Label etykieta; TextField wpis; Thread watek; String uzyt; SerwerPogaw serwerPogaw; private int pobierzRejestrPort () { try { return Integer.parseInt(getParameter("port")); } catch (NumberFormatException ignored) { return Registry.REGISTRY_PORT; } } private String pobierzRejestrNazwa() { String nazwa = getParameter("nazwa"); return (nazwa == null ? "SerwletPogaw" : nazwa); } // Zwraca odwołanie do zdalnej pogawędki serwer/serwlet // Próbuje wyjść, jeżeli wystąpi problem private SerwerPogaw pobierzSerwerPogaw() { try { Registry rejestr = LocateRegistry.getRegistry(getCodeBase().getHost(), pobierzRejestrPort()); Object obi = rejestr.lookup(pobierzRejestrNazwa()); return (SerwerPogaw)obi; } catch (java.rmi.UnknownHostException w) { // Nieznany komputer rejestru, próba wyjścia System.out.println("Komputer nieznany, URL: " + w.getMessage()); System.exit(1); } catch (NotBoundException w) { // Odnalezienie obiektu niemożliwe, próba wyjścia System.out.println("Nazwa niedowiązana: " + w.getMessage()); System.exit(1);

Page 264: Java Servlet - Programowanie

} catch (ClassCastException w) { // Obiekt to nie SerwerPogaw, próba wyjścia System.out.println(pobierzRejestrNazwa() + " to nie SerwerPogaw:" + w.getMessage()); System.exit(1); } catch (RemoteException w) { // Ogólny problem RMI, próba wyjścia System.out.println("Wyjątek zdalny: " + w.getMessage()); System.exit(1); } catch (Exception w) { // Inny problem, próba wyjścia System.out.println("Ogólny wyjątek: " + w.getClass().getName() + ": " + w.getMessage()); System.exit(1); } return null; // zwraca null, jeżeli exit() nie powiedzie się } // Dodanie siebie jako klienta serwera pogawędek // Proszę zauważyć, że rejestr RMI nie jest potrzebny private void rejestrujSerwerPogaw(SerwerPogaw serwer) { try { UnicastRemoteObject.exportObject(this); serwer.dodajKlient(this); } catch (RemoteException w) { // Ogólny problem RMI, próba wyjścia System.out.println("Wyjątek zdalny: " + w.getMessage()); System.exit(1); } catch (Exception w) { // Inny problem, próba wyjścia System.out.println("Ogólny wyjątek: " + w.getClass().getName() + ": " + w.getMessage()); System.exit(1); } } public void init() { // Sprawdzenie, czy aplet został pobrany bezpośrednio z systemu plików. // Jeżeli tak, wyjaśnienie użytkownikowi, że musi on być pobrany z serwera wcelu // komunikacji z serwletami tego serwera URL kodbazy = getCodeBase(); if (!"http".equals(kodbazy.getProtocol())) { System.out.println(); System.out.println("*** Ups! ***"); System.out.println("Ten aplet musi być pobrany z serwera WWW."); System.out.println("Proszę spróbować ponownie, tym razem ładując plikHTML"); System.out.println("zawierający ten serwlet jako"); System.out.println("\"http://serwer:port/plik.html\"."); System.out.println(); System.exit(1); // Działa jedynie z przeglądarką apletów // Przeglądarki wyświetlają błąd i kontynuują } // Pobranie zdalnego serwera pogawędek serwerPogaw = pobierzSerwerPogaw(); // Zarejestrowanie siebie jako jednego z klientów rejestrujSerwerPogaw(serwerPogaw); // Pobranie nazwy tego użytkownika z parametru apletu ustawionego przezserwlet // Można o to po prostu spytać użytkownika, ale jest to przedstawienie formy // komunikacji serwlet->aplet. uzyt = getParameter("user"); if (uzyt == null) uzyt = "anonymous"; // konfiguracja interfejsu użytkownika... // Na górze duże pole TextArea przedstawiające całą rozmowę. // Poniżej opisane TextField przyjmujące wpisy tego użytkownika. tekst = new TextArea(); tekst.setEditable(false); etykieta = new Label("Powiedz coś: "); wpis = new TextField(); wpis.setEditable(true);

Page 265: Java Servlet - Programowanie

setLayout(new BorderLayout()); Panel panel = new Panel(); panel.setLayout(new BorderLayout()); add("Centrum", tekst); add("Dol", panel); panel.add("Lewo", etykieta); panel.add("Centrum", wpis); } String pobierzNastepnaWiadomosc() { String nastepnaWiadomosc = null; while (nastepnaWiadomosc == null) { try { nastepnaWiadomosc = serwerPogaw.pobierzNastepnaWiadomosc(); } catch (RemoteException w) { // Wyjątek zdalny, zgłoszenie i odczekanie przed następną próbą System.out.println("wyjątek zdalny:" + w.getMessage()); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } } } return nastepnaWiadomosc + "\n"; } public void ustawNastepnaWiadomosc(String wiadomosc) { tekst.appendText(wiadomosc + "\n"); } void nadajWiadomosc(String wiadomosc) { wiadomosc = uzyt + ": " + wiadomosc; // Dodanie na początku nazwy klienta try { serwerPogaw.nadajWiadomosc(wiadomosc); } catch (RemoteException w) { // Wyjątek zdalny zgłoszenie i zaprzestanie nadawania System.out.println("Wyjatek zdalny: " + w.getMessage()); } catch (Exception w) { // Inny wyjątek, zgłoszenie i zaprzestanie nadawania System.out.println("Ogólny wyjątek: " + w.getClass().getName() + ": " + w.getMessage()); } } public boolean obslugaWyjatek(Event wyjatek) { switch (wyjatek.id) { case Event.ACTION_EVENT: if (wyjatek.target == wpis) { nadajWiadomosc(wpis.getText()); wpis.setText(""); return true; } } return false; }}

Implementacje pobierzNastepnaWiadomosc() i nadajWiadomosc() są najprostsze z możliwych.Muszą one jedynie wywołać metody zdalnego serwletu o tych samych nazwach. Lecz ich prostota ma swoją cenę— bardziej skomplikowany kod konfiguracji. Konkretnie, metoda init() musi teraz wywołać stosunkowodługą (ale zrozumiałą w tym momencie) pobierzSerwerPogaw() w celu pobrania odwołania do zdalnegoserwera pogawędek.

Po dokładniejszym przyjrzeniu się apletowi ApletPogawRMI można dostrzec, że właściwie nie wykorzystujeon swojej metody pobierzNastepnaWiadomosc(). Zamiast tego, prosi serwlet o wywołanie metodypobierzNastepnaWiadomosc() za każdym razem, kiedy nadana zostaje nowa wiadomość.ApletPogawRMI wykonuje to żądanie w swojej metodzie init(), kiedy wywołujerejestrujSerwerPogaw(SerwerPogaw). Metoda ta eksportuje aplet jako zdalny obiekt, po czymwywołuje metodę serwletu addClient() przekazując jej odwołanie do siebie. Następnie metoda serwletunadajWiadomosc() wysyła wywołanie zwrotne do apletu za każdym razem, kiedy pojawia się nowawiadomość.

Podczas stosowania wywołań zwrotnych na własną rękę, należy pamiętać o opisanych wcześniej podstawach.Należy uruchomić kompilator RMI rmic na zdalnym aplecie w celu utworzenia jego klas końcówki i szkieletu.

Page 266: Java Servlet - Programowanie

Należy również upewnić się, że serwer posiada pliki ApletPogawRMI.class i KlientPogaw.classgdzieś w swojej ścieżce klas.

Należy uważać także na fakt, ze współczesne przeglądarki są o wiele bardziej restrykcyjne w kwestii pozwalaniana wywołania zwrotne niż przeglądarki wykorzystywane w przeszłości i często właściwość ta jest w nichwyłączona. W takim wypadku można zobaczyć jedynie zakodowane komunikaty o błędach, a czasami po prostupusty ekran. Wywołania zwrotne mogą zostać włączone poprzez dołączenie do apletu cyfrowego podpisu(działanie to różni się w zależności od przeglądarki) i nadanie podpisanym apletom dodatkowych uprawnień lubprzez uzyskanie dostępu do apletu przy pomocy HTTPS z zaufanego serwera. Generalnie, może to być uważaneza kolejną wadę RMI i kolejny powód, by wykorzystywać HTTP do komunikacji aplet-serwer.

DyspozytorOstatni przykład kodu w niniejszym rozdziale, serwlet DyspozytorPogaw przedstawiony jest w przykładzie10.21. Serwlet ten posiada dwa obowiązki. Po pierwsze, kiedyn jest wywoływany bez żadnych parametrówżądania, wyświetla on przyjazną stronę powitalną, zapytującą użytkownika o wersje apletu, której chciałby onużyć, jak przedstawiono na rysunku 10.4. Po drugie, kiedy wywoływany jest z parametrem żądania, wyświetlastronę zawierającą właściwy aplet, jak przedstawiono wcześniej na rysunku 10.3. Proszę pamiętać, że URLwykorzystywany do uzyskania dostępu do serwletu-dyspozytora powinien zawierać prawdziwą nazwę serwera,nie localhost, w celu uniknięcia problemów bezpieczeństwa RMI.

Rysunek 10.4.

Strona powitalna dyspozytora pogawędek

Przykład 10.21.

Powitalny serwlet-dyspozytorimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class DyspozytorPogaw extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws IOException, ServletException { odp.setContentType("text/html"); if (!zad.getParameterNames().hasMoreElements()) { // Nie było parametrów żądania. Wyświetlenie strony powitalnej. wyswietlStronaPowitalna(zad, odp); } else { // Wystapił przynajmniej jeden parametr żądania. // Wyświetl stronę apletu. wyswietlApletStrona(zad, odp); } } // Strona powitalna wita użytkownika i wyświetla formularz, w którym użytkownikmoże // wybrać metodę komunikacji aplet-serwlet private void wyswietlStronaPowitalna(HttpServletRequest zad, HttpServletResponse odp) throws IOException { PrintWriter wyj = odp.getWriter(); String ja = zad.getServletPath(); wyj.println("<HTML>"); wyj.println("<HEAD><TITLE>"); wyj.println("Witamy w Absurdalnie Prostej Pogawędce"); wyj.println("</TITLE></HEAD>"); wyj.println(); wyj.println("<BODY>"); wyj.println("<H1> Witamy w Absurdalnie Prostej Pogawędce </H1>"); wyj.println(); wyj.println("Proszę wybrać formę komunikacji:"); wyj.println("<UL>"); wyj.println(" <LI><A HREF=\"" + ja + "?metoda=http\">http</A>"); wyj.println(" <LI><A HREF=\"" + ja + "?metoda=port\">port</A>"); wyj.println(" <LI><A HREF=\"" + ja + "?metoda=rmi\">rmi</A>"); wyj.println("</UL>"); wyj.println("</BODY></HTML>"); }

Page 267: Java Servlet - Programowanie

// Strona apletu wyświetla aplet pogawędek. private void wyswietlApletStrona(HttpServletRequest zad, HttpServletResponse odp) throws IOException { PrintWriter wyj = odp.getWriter(); wyj.println("<HTML>"); wyj.println("<HEAD><TITLE>Absurdalnie Prosta Pogawędka</TITLE></HEAD>"); wyj.println("<BODY>"); wyj.println("<H1> Absurdalnie Prosta Pogawędka </H1>"); String metoda = zad.getParameter("metoda"); String uzyt = zad.getRemoteUser(); String aplet = null; if ("http".equals(metoda)) { aplet = "ApletPogawHTTP"; } else if ("port".equals(metoda)) { aplet = "ApletPogawPort"; } else if ("rmi".equals(metoda)) { aplet = "ApletPogawRMI"; } else { // Brak podanej metody lub metoda nieprawidłowa. // Wyjaśnienie użytkownikowi oczekiwań. wyj.println("Przepraszamy, ten serwlet potrzebuje parametru <TT>metoda</TT>" + "o jednej wartości jednej z poniższych: " + "http, port, rmi"); return; } // Wyświetlenie kodu HTML wyświetlającego aplet. // Wybranie kodu apletu oparte na parametrze metoda. // Dostarczenie parametru użytkownik, jeżeli użytkownik jest znany. wyj.println("<APPLET CODE=" + aplet + " CODEBASE=/ " + "WIDTH=500 HEIGHT=170>"); if (uzyt != null) wyj.println("<PARAM NAME=uzyt VALUE=\"" + uzyt + "\">"); wyj.println("</APPLET>"); wyj.println("</BODY></HTML>"); }}

W powyższym kodzie nie występuje nic zaskakującego. Właściwie kod ten powinien wyglądać odświeżającoprosto po przykładzie SerwletPogaw. Przykład ten przedstawia jednak ostatnią formę komunikacji aplet-serwlet — generowane przez serwlet parametry apletu. Przy pomocy tej techniki serwlet generuje stronęzawierającą aplet i przekazuje apletowi informacje poprzez manipulację znacznikami apletu <PARAM>. Każdainformacja, jaką serwlet chce przesłać nowemu apletowi, może być wysłana w ten właśnie sposób. Wpowyższym przykładzie, serwlet wysyła nazwę zwróconą przez odp.getRemoteUser(). W innymprzykładzie serwlet mógłby przekazać apletowi jego typ przeglądarki poprzez wysłanie mu łańcucha zwróconegoprzez req.getHeader("User-Agent"). Lub, aby okazać się bardziej pomocnym, serwlet mógłbywykorzystać bazę danych w celu określenia możliwości przeglądarki i przekazać apletowi dokładne informacje.Mógłby nawet przekazać apletowi wiadomość z pytaniem, czy przeglądarka obsługuje komunikację RMI.

Page 268: Java Servlet - Programowanie

Rozdział 11. Współpraca

serwletów

Serwlety pracujące wspólnie na jednym serwerze mogą porozumiewać się ze sobą na kilka sposobów. Istniejądwa główne typy współpracy serwletów:

• Dzielenie informacji — dwa lub więcej serwletów dzieli pewien zbiór zasobów. Na przykład, zbiórserwletów zarządzających sklepem online może współdzielić zbiór inwentarza sklepu lub połączenie zbaza danych. Specjalnym przypadkiem dzielenia informacji jest śledzenie sesji (proszę zobaczyćrozdział 7, „Śledzenie sesji”)

• Dzielenie kontroli — dwa lub więcej serwletów dzieli kontrolę żądania. Na przykład, jeden serwletmoże otrzymywać żądanie, ale pozwalać innemu serwletowi na przejęcie części lub całejodpowiedzialności za jego obsługę

W przeszłości (przed pojawieniem się Servlet API 2.1) wymieniona zostałaby inna technika współpracy —manipulacja bezpośrednia. W tej technice serwlet może otrzymać odwołanie do innego poprzez metodęgetServlet() i wywoływać jego metody. Ta metoda współpracy nie jest już obsługiwana; metodagetServlet() została wyłączona i zdefiniowana tak, by zwracała null w przypadku Servlet API 2.1 ipóźniejszych. Powód — serwlet może zostać zniszczony przez serwer WWW w dowolnym momencie, tak więcnic poza serwerem nie powinno posiadać bezpośredniego odwołania do serwletu. Wszystkie działania, któremogły być wykonywane przy pomocy getServlet() mogą być wykonane w sposób lepszy i bezpieczniejszyprzez alternatywy opisane w niniejszym rozdziale.

Dzielenie informacjiSerwlety często współpracują poprzez dzielenie pewnych informacji. Informacje te mogą być informacjami ostanie, współdzielonymi zasobami, źródłem zasobów, lub czymkolwiek innym. W Servlet API 2.0 iwcześniejszych nie istniały wbudowane mechanizmy, przy pomocy których serwlety mogły dzielić informacje, aw pierwszym wydaniu niniejszej książki (napisanej według Servlet API 2.0) musiały zostać przedstawione pewnekreatywne sposoby pracy, między innymi umieszczanie informacji na liście właściwości System! Na szczęściesposoby te nie są już potrzebne, ponieważ począwszy od Servlet API 2.1 rozszerzona została klasaServletContext, która działa na zasadzie składnicy współdzielonych informacji.

Współdzielenie przy pomocy ServletContextSerwlet odczytuje ServletContext swojej aplikacji WWW przy pomocy wywołaniagetServletContext(). Serwlet może wykorzystywać kontekst tak, jakby był on zmienną typuHashtable lub Map, przy pomocy następujących metod:

public void ServletContext.setAttribute(String nazwa, Object o)public Object ServletContext.getAttribute(String nazwa)public Enumeration ServletContext.getAttributeNames()public void ServletContext.removeAttribute(String nazwa)

Page 269: Java Servlet - Programowanie

Metoda setAttribute() dowiązuje obiekt pod podaną nazwą. Każde istniejące dowiązanie o takiej samejnazwie zostaje usunięte. Nazwy atrybutów powinny stosować tę samą konwencję, co nazwy pakietów, w celuuniknięcia nadpisywania siebie. Nazwy pakietów java.*, javax.* i com.sun.* są zarezerwowane.

Metoda getAttribute() odczytuje obiekt dowiązany pod podaną nazwą lub zwraca null, jeżeli pakiet takinie istnieje. Wywołanie może również odczytywać specyficzne dla danego serwera, zakodowane atrybuty (naprzykład javax.servlet.context.tempdir), jak opisano w rozdziale 4, „Pobieranie informacji”.

Metoda getAttributeNames() zwraca zmienną typu Enumeration, która zawiera nazwy wszystkichdowiązanych atrybutów lub pustą Enumeration, jeżeli nie istnieją żadne dowiązania.

Metoda removeAttribute() usuwa obiekt dowiązany pod daną nazwą lub nie wykonuje żadnego działania,jeżeli atrybut nie istnieje. Dobrym pomysłem jest usuwanie niepotrzebnych już atrybutów w celu zredukowaniawykorzystania pamięci.

Zastosowanie kontekstu do sprzedawania pizzyJako zabawny przykład, proszę wyobrazić sobie zbiór serwletów sprzedających pizzę i dzielących ofertę dnia.Serwlet administracyjny mógłby ustawiać ofertę dnia w sposób przedstawiony w przykładzie 11.1.

Przykład 11.1.

Pizzeria Italiaimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class UstawiaOferta extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); ServletContext kontekst = getServletContext(); kontekst.setAttribute("com.pizzeria.oferta.pizza", "Cappricciosa"); kontekst.setAttribute("com.pizzeria.oferta.dzien", new Date()); wyj.println("Pizza — oferta dnia została ustawiona."); }}

Następnie każdy inny serwlet na serwerze może uzyskać dostęp do oferty i wyświetlić ją przy pomocy koduprzedstawionego w przykładzie 11.2.

import java.io.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class pobierzOferta extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter(); ServletContext kontekst = getServletContext(); String pizza = (String) context.getAttribute("com.pizzeria.oferta.pizza"); Date dzien = (Date) context.getAttribute("com.pizzeria.oferta.dzien"); DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); String dzisiaj = df.format(dzien); wyj.println("Oferta dnia(" + dzisiaj + ") to pizza: " + pizza); }}

Page 270: Java Servlet - Programowanie

Współdzielenie z innym ServletContextWykorzystanie ServletContext do współdzielenia informacji daje dodatkowy efekt w postaci posiadaniaprzez każdą aplikację WWW swojego własnego składu informacji. Nie istnieje ryzyko przypadkowej kolizjinazw lub nawet kolizji nazw z tej samej aplikacji uruchomionej dwukrotnie na serwerze. Jednak czasamiinformacja musi być dzielona pomiędzy dwoma kontekstami WWW. W tej sytuacji można postąpić na dwasposoby. Po pierwsze, wykorzystać zewnętrzny skład informacji taki, jak plik bądź bazę danych, lub, po drugie,wykorzystać specjalne punkty zaczepienia w celu bezpośredniego dostępu do innego kontekstu. Kwestia tazostanie opisana później.

Serwlet może otrzymać uchwyt do innego kontekstu na tym samym serwerze przy pomocy punktu zaczepieniagetContext() w swoim własnym kontekście:

public ServletContext ServletContext.getContext(String sciezkauri)Metoda zwraca ServletContext zawierający podaną ścieżkę URI. Podana ścieżka musi być absolutna(rozpoczynająca się od /) i jest interpretowana według katalogu macierzystego dokumentów serwera. Metoda tapozwala serwletowi na uzyskanie dostępu do kontekstu poza jego własnym. W środowisku wrażliwym nabezpieczeństwo lub rozpowszechnianym (proszę zobaczyć rozdział 12, „Serwlety korporacyjne i J2EE”),kontener serwletów może zwrócić null dla wszystkich ścieżek.

Aby to zademonstrować, proszę wyobrazić sobie, że serwlet poza dostępem do pizzerii musi uzyskać dostęp dooferty dnia. Proszę także przyjąć, że kontekst pizzerii znajduje się w katalogu /pizzeria. W końcu proszę założyć,że kontrola bezpieczeństwa serwera (nie dopuszczająca do komunikacji międzykontekstowej) została wyłączona.Pod tymi warunkami następujący kod pozwala dowolnemu serwletowi na serwerze na odczytanie ofert dnia zaplikacji pizzerii:

ServletContext mojKontekst = getServletContext();ServletContext innyKontekst = mojKontekst.getContext("/pizzeria/indeks.httml");String pizza = innyKontekst.getAttribute("com.pizzeria.oferta.pizza");Date dzien = (Date)innyKontekst.getAttribute("com.pizzeria.oferta.dzien");

Aktualnie jedyną metodą pobierania odwołania do innego kontekstu jest wyszukiwanie ścieżki, tak więcpowyższy przykład nie będzie działał, jeżeli pizzeria przeniesie się do innego URL-a. W przyszłości możepojawić się sposób na pobieranie odwołania według nazwy.

Kwestie mechanizmu ładowania klasJeżeli obiekt umieszczony w kontekście nie jest obiektem standardowym, dzielenie obiektu pomiędzykontekstami staje się o wiele trudniejsze z powodu kwestii związanych z mechanizmem ładowania klas. Proszępamiętać, że każda aplikacja WWW posiada swoje klasy w WEB-INF, ładowanym przez inny mechanizmładowania klas. Proszę również pamiętać, że mechanizm ładowania klas jednej aplikacji nie umie zlokalizowaćklas umieszczonych w innej aplikacji. Nieszczęśliwy wynik — obiekt, którego plik .class istnieje jedyniewewnątrz WEB-INF/classess lub WEB-INF/lib nie może być w łatwy sposób wykorzystany przez żadną innąaplikację. Każda próba wywołania obiektu według jego właściciela daje wynik w postaci błęduNoClassDefFoundError.

Rozwiązanie w postaci skopiowania pliku .class do obu aplikacji również nie działa. Klasy ładowane przez różneaplikacje nie są tą samą klasą, nawet jeżeli ich definicje są identyczne. Próba wykonania tego daje w efekciejedynie zamianę błędu NoClassDefFoundError na wyjątek ClassCastException.

Istnieją trzy możliwe sposoby obejścia tego problemu. Po pierwsze, skopiowanie pliku .class współdzielonegoobiektu do standardowej ścieżki klas serwera (w przypadku serwera Tomcat jest tokatalog_macierzysty_serwera/classes). Pozwala to na odnalezienie klasy przez podstawowy mechanizmładowania klas współdzielony przez wszystkie aplikacje. Po drugie, uniknięcie wywoływania zwracanegoobiektu i wywoływanie jego metod przy pomocy refleksji (techniki, w której klasa Javy może przeglądać imanipulować sobą w trakcie uruchomienia). Nie jest to tak eleganckie, ale pomaga, jeżeli nie posiada się pełnejkontroli nad serwerem. Po trzecie, wywołanie zwracanego obiektu do interfejsu, który deklaruje pożądanemetody i umieszczenie interfejsu w standardowej ścieżce klas serwera. Każda klasa oprócz interfejsu możepozostać w pojedynczych aplikacjach WWW. Wymaga to przeprogramowania klasy dzielonego obiektu, alepomaga w przypadkach, w których implementacja musi pozostać w swoich aplikacjach WWW.

Dzielenie kontroliW celu bardziej dynamicznej współpracy, serwlety mogą dzielić kontrole żądania. Po pierwsze, serwlet możeprzekazać całe żądanie wykonując pewne wstępne przetwarzanie, po czym przekazać żądanie innemuelementowi. Po drugie, serwlet może dołączyć do swojej odpowiedzi pewną zawartość generowaną przez inny

Page 271: Java Servlet - Programowanie

składnik, właściwie tworząc programowe dołączenie po stronie serwera. Patrząc pojęciowo, jeżeli myśli się ostronie wynikowej takiej, jak ekran, przekazanie daje innemu serwletowi pełną kontrole nad ekranem, podczasgdy dołączanie wyświetla jedynie część zawartości w pewnym miejscu ekranu.

Ta możliwość delegowania daje serwletom większą elastyczność i pozwala na lepsze rozdzielanie. Przy pomocydelegowania, serwlet może skonstruować swoją odpowiedź jako zbiór zawartości tworzonych przez różneskładniki serwera WWW. Funkcjonalność ta jest szczególnie ważna dla JavaServer Pages, w której to techniceczęsto zdarza się, że jeden serwlet przetwarza żądanie, po czym przekazuje je stronie JSP w celu dokończeniazachowania spójności (proszę zobaczyć rozdział 18, JavaServer Pages).

Pobieranie dyspozytora żądańW celu zapewnienia obsługi delegowania, Servlet API 2.1 wprowadził interfejsjavax.servlet.RequestDispatcher. Serwlet pobiera egzemplarz RequestDispatcher wykonującmetodę getRequestDispatcher() na pożądanym obiekcie. Metoda ta zwraca RequestDispatcher,który działa jako dyspozytor dla elementu (serwletu, JSP, pliku statycznego itp.) odnalezionego w podanejścieżce URI:

public RequestDispatcher ServletRequest.getRequestDispatcher(String sciezka)Przekazywana ścieżka może być ścieżką względną, chociaż nie powinna sięgać poza aktualny kontekst serwletu.Można wykorzystać opisaną w poprzednim podrozdziale metodę getContext() w celu sięgnięcia pozaaktualny kontekst. Nie istnieje sposób rozciągnięcia dyspozycji na kontekst znajdujący się na innym serwerze.Jeżeli ścieżka rozpoczyna się od /, to jest interpretowana jako ścieżka względna wobec katalogu macierzystegoaktualnego kontekstu, jeżeli ścieżka zawiera łańcuch zapytanie, parametry dodawane są na początku zbioruparametrów odbierającego elementu. Metoda zwraca null, jeżeli kontener serwletów nie jest w stanie zwrócićRequestDispatcher, niezależnie od powodu.

Co ciekawe, istnieje metoda o tej samej nazwie w klasie ServletContext:public RequestDispatcher ServletContext.getRequestDispatcher(String sciezka)

Różnica pomiędzy tymi metodami polega na tym, że wersja znajdująca się w ServletContext(wprowadzona w API 2.1) przyjmuje jedynie URL-e absolutne (rozpoczynające się ukośnikiem), a wersja wServletRequest (wprowadzona w API 2.2) akceptuje URL-e zarówno absolutne, jak i względne. W związkuz tym nie ma powodu, aby stosować metodę znajdującą się w ServletContext. Istnieje ona jedynie zpowodów historycznych i może być uważana za wyłączoną, choć decyzja o jej oficjalnym wyłączeniu jeszcze nienastąpiła.

Możliwe jest również pobranie RequestDispatcher dla zasobu określonego przy pomocy nazwy, a nieścieżki, przy pomocy metody getNamedDispatch(), znajdującej się w ServletContext:

public RequestDispatcher ServletContext.getNamedDispatcher(String nazwa)Pozwala to na zarządzanie zasobami, które niekoniecznie są dostępne publicznie. Serwletom (a także stronomJSP) można nadawać nazwy poprzez deskryptor aplikacji WWW, jak opisano w rozdziale 3, „Cykl życiaserwletów”. Metoda ta zwraca null, jeżeli kontekst nie może zwrócić dyspozytora, niezależnie od powodu.

RequestDispatcher posiada dwie metody, forward() i include(). Metoda forward() przekazuje całeżądanie innemu elementowi. Metoda include() dodaje wynik pracy innego elementu do odpowiedziwywołującego serwletu, pozostawia jednak kontrolę w jego rękach.

Dyspozycja przekazaniemMetoda forward() przekazuje żądanie od serwletu do innego zasobu na serwerze. Metoda pozwala jednemuserwletowi na wykonanie wstępnego przetwarzania żądania, a innemu elementowi na wygenerowanieodpowiedzi. Inaczej niż sendRedirect(), forward() działa jedynie wewnątrz serwera, a klient nie jest wstanie rozpoznać, że nastąpiło przekazanie. Informacja może zostać przekazana delegatowi przy pomocydołączonego łańcucha zapytania lub przy pomocy atrybutów żądania ustawionych przy pomocy metodysetAttribute(). Przykład 11.3 przedstawia serwlet wykonujący poszukiwanie, po czym przekierowującyjego wyniki innej stronie, która je wyświetla.

Przykład 11.3.

Wewnętrzny mechanizm wyszukiwarkiimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;

Page 272: Java Servlet - Programowanie

public class LogikaSzukaj extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { // Typ zawartości ani urządzenie wyświetlające nie jest określone // Pobranie łańcucha do poszukiwań String szukaj = zad.getParameter("szukaj"); // Utworzenie URL-i zawierających atrybut żądania String[] wyniki = pobierzWyniki(szukaj); // Określenie wyników jako atrybutu żądania zad.setAttribute("wyniki", wyniki); // Przekazanie do strony wyświetlającej String wyswietl = "/servlet/wyswietlSzukaj"; RequestDispatcher dyspozytor = zad.getRequestDispatcher(wyswietl); dyspozytor.forward(zad, odp); } // W prawdziwym zastosowaniu metoda ta wywoływałaby właściwą logikę wyszukiwarki // i zwracałaby większą ilość informacji na temat każdego wyniku niż zwykły URL String[]pobierzWyniki(String szukaj) { return new String[] { "http://www.abc.com", "http://www.xyz.com" }; }}

Zadaniem tego serwletu jest bycie mózgiem wyszukiwarki online. Wykonuje on poszukiwania tekstu podanego wparametrze szukaj, po czym przechowuje wynikowe URL-e w atrybucie żądania wyniki. Następnie serwletprzekazuje żądanie do elementu wyświetlającego. Powyżej został on sztywno zakodowany jako /servlet/wyswietlSzukaj, ale ścieżka może być utworzona tak, aby mogła podlegać zmianom zależnie odpreferencji użytkownika co do języka, kolorów witryny, tego, czy jest on użytkownikiem początkującym, czyzaawansowanym, itp.

Zasady, według których działać musi serwlet przekazujący, są stosunkowo rygorystyczne:

• Może on ustawiać nagłówki i kod stanu, ale nie może wysyłać klientowi właściwej odpowiedzi (jest ozadanie drugiego elementu). Konsekwentnie, metoda forward() musi zostać wywołana przedzatwierdzeniem odpowiedzi.

• Jeżeli odpowiedź została już zatwierdzona, wywołanie forward() powoduje wystąpienie wyjątkuIllegalStateException.

• Jeżeli odpowiedź nie została zatwierdzona, lecz w buforze odpowiedzi istnieje zawartość, bufor zostajeautomatycznie wyczyszczony — jest to część przekazywania.

• Dodatkowo, nie jest możliwe wysyłanie nowych obiektów żądania i odpowiedzi. Metoda forward()musi zostać wywołana z tymi samymi obiektami żądania i odpowiedzi, które zostały przekazanemetodzie usługowej serwletu wywołującego, a forward() musi zostać wywołana wewnątrz tegosamego wątku obsługującego.

Element odbierający może zostać napisany tak, jak każdy inny element, jak przedstawia to przykład 11.4.

Przykład 11.4.

Fronton wyszukiwarkiimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class WyswietlSzukaj extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); // Pobranie wyników przeszukiwania z atrybutu żądania String[] wyniki = (String[]) zad.getAttribute("wyniki"); if (wyniki == null) { wyj.println("Brak wyników."); wyj.println("Czy przypadkiem serwlet nie został wywołany bezpośrednio?"); } else {

Page 273: Java Servlet - Programowanie

wyj.println("Wyniki:"); for (int i = 0; i < wyniki.length; i++) { wyj.println(wyniki[i]); } } wyj.println(); wyj.println("URI żądania: " + zad.getRequestURI()); wyj.println("Ścieżka kontekstu: " + zad.getContextPath()); wyj.println("Ścieżka serwletu: " + zad.getServletPath()); wyj.println("Informacje o ścieżce: " + zad.getPathInfo()); wyj.println("Łańcuch zapytania: " + zad.getQueryString()); }}

Kiedy serwlet ten zostanie wypróbowany, można zauważyć, że uaktualniona została informacja o ścieżce. Niejest ona taka, jak oryginalna informacja o żądaniu; zamiast tego pasuje do ścieżki wykorzystywanej do dotarciado dyspozytora:

URI żądania: /servlet/WyswietlSzukajŚcieżka kontekstu:Ścieżka serwletu:/ servlet/WyswietlSzukajInformacje o ścieżce: nullŁańcuch zapytania: null

Powód tego uaktualnienia jest taki, że jeżeli wykonane zostaje przekazanie serwletowi, serwlet ten powinienotrzymać tę samą informację o ścieżce, jaką otrzymałby, gdyby został wywołany bezpośrednio; od tej poryposiada on pełną kontrolę nad żądaniem. Jest to również powód, dla którego ważne jest, żeby bufor odpowiedzizostał wyczyszczony przed jego wywołaniem i żaby odpowiedź nie została zatwierdzona.

Dyspozycja według nazwyJednym z problemów występujących przy dyspozycji według ścieżki jest fakt, że jeżeli element docelowy jestudostępniony elementom serwera pod pewną ścieżką, jest również pod tą samą ścieżką dostępny klientom. Dlabezpieczeństwa można rozważyć uczynienie komponentu niedostępnym publicznie (na przykład przezwyłączenie logiki wywołania /servlet) i dyspozycję żądań według nazwy zamiast według ścieżki. Umożliwia tometoda getNamedDispatcher(). Logika dyspozycji w przykładzie 11-3 może zostać zmieniona nanastępującą:

// Przekazanie do strony wyświetlającej String wyswietl = "wyswietlSzukaj"; RequestDispatcher dyspozytor = zad.getNamedDispatcher(wyswietl); dyspozytor.forward(zad, odp);

Przy korzystaniu z getNamedDispatcher() należy pamiętać o fakcie, że ponieważ nie jest wykorzystanaścieżka URI, nie mogą zostać dołączone żadne łańcuchy zapytania i nie występuje uaktualnianie ścieżki.

Przekazanie czy przekierowanieCzy lepiej jest zastosować forward(), czy sendRedirect()? Obie metody posiadają swoje zastosowania.forward() działa najlepiej, kiedy jeden z elementów musi wykonać pewną logikę biznesową, po czympodzielić się wynikami z innym elementem, a sendRedirect() działa najlepiej wtedy, gdy klient powinienzostać przekierowany z jednej strony na inną. Kuszące jest zastosowanie forward() zamiastsendRedirect() w przypadku prostych przekierowań, ponieważ forward() pracuje wewnątrz serwera idziała szybciej od sendRedirect(), który wymaga bardzo okrężnej komunikacji z klientem. Niestety,powoduje to problemy ze względną obsługą URL-i, jak przedstawiono w przykładzie 11.5, serwlecie, któryprzekazuje żądanie do strony głównej witryny.

Przykład 11.5.

Bezpośrednia podróż do strony głównejimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class PrzekazStronaGlowna extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { RequestDispatcher dyspozytor = zad.getRequestDispatcher("/indeks.html"); dyspozytor.forward(zad, odp); }}

Page 274: Java Servlet - Programowanie

Jeżeli wykorzysta się serwer Tomcat w celu uzyskania dostępu do powyższego serwletu pod URL-emhttp://localhost:8080/servlet/PrzekazStronaGlowna, można zauważyć, że wszystkie obrazki na stronie sąuszkodzone. Dzieje się tak dlatego, że strona ta zawiera znaczniki <IMG> z względnymi ścieżkami do plikówobrazków. Podczas, gdy sendRedirect() powiadomiłaby klienta o tym, że źródłem pliku jest katalogmacierzysty dokumentów serwera, forward() nie przekazuje tej wiadomości. W związku z tym nie działajążadne względne URL-e. Metoda sendRedirect() ułatwia również dyspozycję do zasobów w innychkontekstach, ponieważ nie jest w tym przypadku konieczne wyszukiwanie getContext(). Ogólnie poleca sięwykorzystywane sendRedirect(), gdzie to tylko możliwe, a forward() tylko wtedy, gdy jest tokonieczne.

Dyspozycja dołączaniaMetoda include() RequestDispatcher dołącza zawartość zasobu do aktualnej odpowiedzi. Pozwala nacoś, co mogłoby być nazwane programistycznym dołączaniem po stronie serwera. Różni się to od forward(),ponieważ wywołujący serwlet utrzymuje kontrolę nad odpowiedzią i może dołączyć zawartość zarówno przed,jak i po zawartości dołączonej. Przykład 11.16 przedstawia serwlet, który dołącza egzemplarz z katalogu doaktualnej odpowiedzi.

Przykład 11.6.

Dołączanie egzemplarza z kataloguimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class Ksiazki extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter(); wyj.println("<HTML><HEAD><TITLE>Witamy</TITLE></HEAD>"); wyj.println("<BODY>"); // Wyświetlenie egzemplarza z katalogu wyj.println("Proszę nacieszyć się tym pięknem:"); RequestDispatcher dyspozytor = zad.getRequestDispatcher("/servlet/Egzemplarz?egzemplarz=0596000405"); dyspozytor.include(zad, odp); wyj.println("A ponieważ lubimy Cię, te książki są o 20% tańsze!"); wyj.println("</BODY></HTML>"); }}

Podobnie jak w przypadku forward(), informacje mogą być przesyłane do wywołanego zasobu przy pomocydołączonego łańcucha zapytania lub przy pomocy atrybutów żądania ustawionych za pomocą metodysetAttribute(). Wykorzystanie atrybutów zamiast parametrów umożliwia przekazywanie obiektów zamiastprostych łańcuchów, jak przedstawiono w przykładzie 11.7.

Przykład 11.7.

Dołączanie kilku egzemplarzy z kataloguimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class Ksiazki extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter(); wyj.println("<HTML><HEAD><TITLE>Witamy</TITLE></HEAD>"); wyj.println("<BODY>"); // Wyświetlenie egzemplarza z katalogu RequestDispatcher dyspozytor = zad.getRequestDispatcher("/servlet/Egzemplarz");

Page 275: Java Servlet - Programowanie

wyj.println("Proszę nacieszyć się tym pięknem:"); zad.setAttribute("egzemplarz", Ksiazka.pobierzKsiazka("0596000405")); dyspozytor.include(zad, odp); // Usunięcie atrybutu „egzemplarz” po jego wykorzystaniu zad.removeAttribute("egzemplarz"); wyj.println("A może ta:"); zad.setAttribute("egzemplarz", Ksiazka.pobierzKsiazka("0395282659")); dyspozytor.include(zad, odp); wyj.println("A ponieważ lubimy Cię, te książki są o 20% tańsze!"); wyj.println("</BODY></HTML>"); }}// prosta klasa Ksiazkaclass Ksiazka { String isbn; String tytul; String autor; private static Ksiazka JSERWLET = new Ksiazka("0596000405", "Java Serwlet — programowanie", "Hunter"); private static Ksiazka HOBBIT = new Ksiazka("0395282659", "Hobbit", "Tolkien"); // Symulacja wyszukiwania w bazie danych public static Ksiazka pobierzKsiazka(String isbn) { if (JSERWLET.pobierzISBN().equals(isbn)) { return JSERWLET; } else if (HOBBIT.pobierzISBN().equals(isbn)) { return HOBBIT; } else { return null; } } private Ksiazka(String isbn, String tytul, String autor) { this.isbn = isbn; this.tytul = tytul; this.autor = autor; } public String pobierzISBN() { return isbn; } public String pobierzTytul() { return tytul; } public String pobierzAutor() { return autor; }}

W powyższym przykładzie zamiast wysyłać jedynie ISBN, serwlet przekazuje kompletny obiekt Ksiazka. Wprawdziwym zastosowaniu, informacja o książce pochodziłaby z wyszukiwania ISBN w bazie danych. Powyżejpara książek została po prostu sztywno zakodowana.

Serwlet Egzemplarz może otrzymywać atrybut żądania egzemplarz poprzez wywołaniezad.getAttribute("egzemplarz"), jak przedstawiono w przykładzie 11.8.

Przykład 11.8.

Wyświetlanie egzemplarza z katalogu.import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class Egzemplarz extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { // Typ zawartości nie jest ustawiany

Page 276: Java Servlet - Programowanie

PrintWriter wyj = odp.getWriter(); Ksiazka ksiazka = (Ksiazka) zad.getAttribute("egzemplarz"); wyj.println("<BR>"); if (ksiazka != null) { wyj.println("<I>" + ksiazka.pobierzTytul() + "</I>"); wyj.println(" autorstwa " + ksiazka.pobierzAutor()); } else { wyj.println("<I>Nie znaleziono książki</I>"); } wyj.println("<BR>"); }}

Zawartość dołączonego serwletu może zostać umieszczona w dowolnym miejscu strony. W związku z tym nieposiada ona możliwości zmiany kodu stanu ani nagłówków HTTP wysłanych w odpowiedzi. Każda próbadokonania zmiany jest ignorowana.

Inaczej niż w przypadku forward(), elementy ścieżki i parametry żądania pozostają niezmienione wporównaniu z elementami serwletu wywołującego. Jeżeli dołączony składnik wymaga dostępu do elementówswojej własnej ścieżki, może on je odczytać przy pomocy następujących preasygnowanych atrybutów żądania:

javax.servlet.include.request_urijavax.servlet.include.context_pathjavax.servlet.include.servlet_pathjavax.servlet.include.path_infojavax.servlet.include.query_string

Parametry żądania i odpowiedzi muszą być tymi samymi obiektami, które zostały przekazane metodzie usługserwletu wywołującego, a include() musi być wywołana wewnątrz tego samego wątku obsługującego.

Dołączane zasoby muszą wykorzystywać mechanizm wyjścia, pasujący do mechanizmu wywołującego. Jeżelimechanizm wywołujący wykorzystuje PrintWriter, dołączane zasoby muszą również wykorzystywaćPrintWriter. Jeżeli mechanizm wykorzystuje OutputStream, dołączane zasoby również musząwykorzystywać OutputStream. Jeżeli mechanizmy nie pasują do siebie, dołączony serwlet powoduje wyjątekIllegalStateException. Jeżeli jest to możliwe, należy się upewnić, co do stosowania PrintWriterdla każdego wyświetlanego tekstu, a nie stanie się to problemem.

Page 277: Java Servlet - Programowanie

Rozdział 12. Serwlety

korporacyjne i J2EE

Niniejszy rozdział opisuje serwlety korporacyjne. Termin korporacyjny (enterprise) jest w obecnych czasachmocno związany z Javą, co jednak oznacza? Według starszego wydania słownika „American HeritageDictionary” słowo enterprise posiada trzy znaczenia:

1. Podejmowanie działań, zwłaszcza o dużej skali lub ryzyku

2. Biznes

3. Gotowość na ryzyko, inicjatywa

Jest to zaskakująco bliska definicja tego, co próbują powiedzieć ludzie mówiący o korporacyjnej Javie ikorporacyjnych serwletach. Można połączyć tradycyjne definicje w celu utworzenia definicji nowoczesnej:

1. Gotowość do obsługi działań biznesowych o dużej skali

Innymi słowy, serwlety korporacyjne to serwlety zaprojektowane do obsługi zorientowanych biznesowo witrynWWW o dużej skali — witryny o wysokim obciążeniu i niezawodności wiążą się z dodatkowymi wymaganiamina temat skalowalności, rozkładu ładunku, obsługi błędów i integracji z innymi technologiami Java 2, EnterpriseEdition (J2EE).

Odkąd serwlety zaczęły być coraz bardziej popularne i profesjonalne, a także kontenery serwletów stały siębardziej niezawodne i wszechstronne, coraz większa liczba witryn biznesowych tworzona jest przy pomocyserwletów. Tworzenie serwletów dla takich witryn różni się od tworzenia ich dla witryn tradycyjnych, i w tymrozdziale omówione zostaną specjalne wymagania i możliwości serwletów korporacyjnych.

Dystrybucja ładunkuW przypadku witryn o wysokiej wydajności i/lub niezawodności, często korzystna jest dystrybucja zawartości iprzetwarzania witryny pomiędzy większą ilością serwerów wspierających. Dystrybucja ta pozwala kilkuserwerom na współdzielenie ładunku, co zwiększa ilość równoczesnych żądań, które mogą zostać obsłużone,oraz pozwala na przetrwanie i działanie witryny nawet wtedy, gdy zawiedzie jeden konkretny składnik.

Dystrybucja nie jest polecana dla każdej witryny. Tworzenie i utrzymanie witryny dystrybuowanej może być wznaczący sposób trudniejsze niż wykonanie tych samych czynności w przypadku witryny samodzielne, orazbardziej kosztowne w kwestii sprzętu dzielącego ładunek i/lub wymagań programów. Dystrybucja nie przynosirównież znaczącej poprawy wydajności, jeżeli serwer nie znajduje się pod maksymalnym obciążeniem. Powystąpieniu problemu z wydajnością, często najłatwiej jest „rzucić sprzęt na problem” poprzez instalacjępojedynczego wysokowydajnego komputera, a nie podzielić ładunek pomiędzy dwoma komputerami oniewystarczającej wydajności.

Istnieje jednak wiele witryn, które wymagają skalowalności poza osiągami dowolnego pojedynczego komputera ipoziomu niezawodności niemożliwego do uzyskania na pojedynczym komputerze. W witrynach tych należyzastosować dystrybucję.

Page 278: Java Servlet - Programowanie

Jak być dystrybuowalnymWymagania programistyczne dla serwletu dystrybuowalnego są dużo bardziej restrykcyjne niż wymaganiaodnoszące się do innych serwletów. Serwlet dystrybuowalny musi zostać napisany według konkretnych zasadtak, aby różne egzemplarze serwletu mogły być wykonywane na wielu serwerach wspierających. Każdyprogramista, który przyjmuje, że istnieje tylko jedna kopia serwletu, jeden kontekst serwletu, jedna wirtualnamaszyna Javy i jeden system plików, może sprawić sobie poważne problemy.

Aby zrozumieć, jak serwlety mogą być dystrybuowane, proszę spojrzeć na technologię Enterprise JavaBeans(EJB), model elementów po stronie serwera stworzony w celu implementacji dystrybuowanych obiektówbiznesowych, który jest sercem J2EE. EJB jest zaprojektowany od początku jako zbiór obiektówdystrybuowalnych. ELB implementuje logikę biznesową i pozwala kontenerowi (zwykle serwerowi), na którympracuje, na uruchamianie usług zarządzających takich, jak transakcje, trwałość, współbieżność i bezpieczeństwo.EJB może być dystrybuowany pomiędzy większą ilością komputerów wspierających i może być przenoszonypomiędzy komputerami przy pomocy kontenera. Aby wykorzystywać ten model dystrybucji, EJB musi spełniaćścisły zestaw zasad opartych na specyfikacji, mówiący, co może on robić, a czego nie13.

Serwlety nie posiadają takiego opartego na specyfikacji zestawu zasad. Wynika to z ich pochodzenia jakofrontowych elementów po stronie serwera, wykorzystywanych do komunikacji z klientem i wywoływaniadystrybuowanych EJB, podczas, gdy same serwlety nie są przeznaczone do dystrybucji. Jednak w przypadkuwitryn o dużym obciążeniu lub witryn, które wymagają wysokiej niezawodności, również serwlety muszą byćdystrybuowane. Przypuszcza się, że przyszłe wersje Servlet API będą zawierać ściślejszą definicję implementacjidystrybuowalnych kontenerów serwletów.

Poniżej podano utworzone przez autorów niniejszej książki skrótowe zasady tworzenia serwletów, które mająbyć umieszczane w środowisku dystrybuowalnym:

• Należy pamiętać, że różne egzemplarze serwletu mogą pracować w różnych JVM i/lubkomputerach. W związku z tym zmienne egzemplarza i zmienne statyczne nie powinny byćwykorzystywane do przechowywania stanu. Każdy stan powinien być przechowywany w obiekciezewnętrznym, takim jak baza danych lub EJB (do poszukiwań można zastosować nazwę serwletu).

• Należy pamiętać, że w każdym JVM i/lub komputerze mogą istnieć różne kopieServletContext. W związku z tym kontekst nie powinien być wykorzystywany doprzechowywania stanu aplikacji. Każdy stan powinien być przechowywany w obiekciezewnętrznym, takim jak baza danych lub EJB (do poszukiwań można zastosować nazwę serwletu).

• Należy pamiętać, że dowolny obiekt umieszczony w HttpSession powinien mieć możliwośćprzeniesienia do (lub uzyskania dostępu z) innego komputera. Na przykład, obiekt możewykorzystywać java.io.Serializable. Proszę pamiętać, że ponieważ sesje mogą poruszaćsię, zdarzenie usunięcia dowiązania sesji może wystąpić w innym komputerze niż zdarzenie jejdowiązania!

• Należy pamiętać, że pliki nie muszą występować na wszystkich komputerach wspierających. Wzwiązku z tym powinno się unikać wykorzystywania pakietu java.io w celu uzyskania dostępudo pliku i zamiast tego wykorzystać mechanizm getServletContext().getResource()— lub upewnić się, że wszystkie wykorzystywane pliki zostały umieszczone na wszystkichkomputerach wspierających.

• Należy pamiętać, że synchronizacja nie jest globalna i działa jedynie w przypadku lokalnej JVM.

Aplikacja WWW, której elementy spełniają powyższe zasady może zostać oznaczona jako dystrybuowalna, a tooznaczenie pozwala serwerowi na uruchomienie jej na kilku serwerach wspierających. Znak dystrybuowalnościjest umieszczany w deskryptorze web.xml jako pusty znacznik <distribuable/> umieszczony pomiędzyopisem aplikacji i jej parametrami kontekstu:

<web-app> <description> Wszystkie serwlety i strony JSP są gotowe do dystrybucji </description> <distribuable/> <context-param> <!--...--> </context-param>

13 Większa ilość informacji na temat Enterprise JavaBeans dostępna jest pod adresem http://java.sun.com/products/ejb i wksiążce „Enterprise JavaBeans” autorstwa Richarda Monson-Haefela (O'Reilly).

Page 279: Java Servlet - Programowanie

</web-app>Aplikacje z definicji nie są dystrybuowalne w celu zapewnienia niedoświadczonym programistom serwletówmożliwości tworzenia ich bez potrzeby martwienia się o dodatkowe zasady związane z dystrybucją. Zaznaczenieaplikacji jako dystrybuowalnej niekoniecznie oznacza, że aplikacja zostanie podzielona pomiędzy kilkomaróżnymi komputerami. Wskazuje to jedynie, że aplikacja ma możliwość bycia dystrybuowaną. Należy myśleć otym jako o wystawionym przez programistę świadectwie aplikacji.

Serwery nie wymuszają większości podanych powyżej zasad dystrybucji aplikacji. Na przykład, serwlet możewykorzystywać zmienne egzemplarza i statyczne, a także przechowywać obiekty w swoim ServletContextoraz uzyskiwać bezpośredni dostęp do plików przy pomocy pakietu java.io. Od programisty zależyzabezpieczenie tych własności przed nadużyciem. Jedynym działaniem, jakie może podjąć serwer jest wywołaniewyjątku IllegalArgumentException, jeżeli obiekt dowiązany do HttpSession nie jest implementacjąjava.io.Serializable (a nawet to działanie jest opcjonalne, ponieważ, jak zostanie to opisano później,serwer kompatybilny z J2EE musi pozwalać na przechowywanie w sesji dodatkowych typów obiektów).

Wiele stylów dystrybucjiDystrybucja serwletów (często nazywana klastrowaniem) jest opcjonalną własnością kontenera serwletów, akontenery serwletów obsługujące klastrowanie mogą wykonywać je na kilka różnych sposobów. Istnieją czterystandardowe architektury, wymienione poniżej, od najprostszej do najbardziej zaawansowanej.

1. Brak klastrowania. Wszystkie serwlety wykonywane są wewnątrz jednej wirtualnej maszyny Javy, aznacznik <distributable/> jest właściwie ignorowany. Projekt jest prosty, jednak działapoprawnie w przypadku standardowych witryn. W ten sposób działa samodzielny serwer Tomcat.

2. Obsługa klastrowania, brak sesji wędrujących i unikania błędów. Serwlety w aplikacji WWWzaznaczone jako <distributable/> mogą być wykonywane na kilku komputerach. Żądanianiezwiązane z sesjami są dystrybuowane losowo (z zachowaniem równomiernego obciążenia). Żądaniasesji są „lepkie” i przywiązane do konkretnego serwera wspierającego, na którym rozpoczęły działanie.Dane sesji nie przemieszczają się pomiędzy komputerami, co posiada zaletę taką, że sesje mogąprzechowywać nie przenoszone dane (nie-Serializable) oraz wadę taką, że sesje nie mogą byćprzenoszone do niewykorzystywanych serwerów, a serwer może załamać się z powodu uszkodzonejsesji. Architektura ta wykorzystywana jest przez Apache/JServ i Apache/Tomcat. Sesje są przywiązanedo konkretnego komputera poprzez mechanizm, w którym łącznik mod_jserv/mod_jk będącyczęścią Apache'a wykorzystuje część identyfikatora sesji w celu wskazania, który wspierający JServ lubTomcat jest właścicielem sesji. Wykorzystanych może być również kilka egzemplarzy Apache'a,obsługujących sprzęt lub oprogramowanie rozkładające obciążenie.

3. Obsługa klastrowania i sesji wędrujących, brak unikania błędów. Architektura ta pracuje podobnie jakpoprzednia, poza tym, że sesje mogą przenosić się z jednego serwera do drugiego w celu lepszegorozłożenia obciążenia. Aby uniknąć kwestii współbieżności, każda wędrówka sesji posiada gwarancjęwystąpienia pomiędzy żądaniami klienta. Specyfikacja serwletów mówi, że „wewnątrz aplikacjioznaczonej jako dystrybuowalna wszystkie żądanie będące częścią sesji mogą być obsługiwane jedynieprzez pojedynczą maszynę wirtualną w jednym czasie”. Wszystkie obiekty umieszczone w sesji, któremają być przenoszone muszą wykorzystywać java.io.Serializable lub mieć możliwośćprzenoszenia w pewien inny sposób.

4. Obsługa klastrowania, sesji wędrujących i unikania błędów. Serwer wykorzystujący tę architekturęposiada dodatkową możliwość powielania zawartości sesji tak, że załamanie pojedynczego elementuniekoniecznie niszczy sesje klienta. Wyzwaniem tej architektury jest koordynacja wydajnego przepływuinformacji. Architekturę tę wykorzystuje większość wysokowydajnych serwerów.

Szczegóły implementacji klastrowania różnią się między serwerami i są miejscem rywalizacji poszczególnychproducentów serwerów. Proszę spojrzeć do dokumentacji własnego serwera w celu uzyskania szczegółów natemat obsługiwanego poziomu klastrowania. Inną przydatną własnością jest trwałość sesji, czyli zapisywanie wtle informacji sesji na dysk lub do bazy danych, co pozwala informacjom na przetrwanie załamań i ponownychuruchomień serwera.

Integracja z J2EEW poprzednich częściach książki serwlety były wykorzystywane jako samodzielna technologia utworzona nastandardowej podstawie Javy. Serwlety posiadają także inne życie, w którym działają jako integralna część

Page 280: Java Servlet - Programowanie

czegoś, co nazywane jest Java 2, Enterprise Edition, w skrócie J2EE14. J2EE 1.2 zbiera razem kilka interfejsówpracujących po stronie serwera, w tym Servlet API 2.2, JSP 1.1, EJB, JavaMail, Java Messaging Service (JMS),Java Transactions (JTA), CORBA, JDBC, Java API for XML Parsing (JAXP) i Java Naming and DirectoryInterface (JNDI). J2EE integruje te elementy w coś więcej niż prostą sumę części poprzez zdefiniowanie sposobuwspółpracy tych technologii, ich wykorzystywania siebie nawzajem oraz dostarczania zaświadczeń, że konkretneserwery aplikacji są zgodne z J2EE, co oznacza, że obsługują wszystkie potrzebne usługi, a także mechanizmyich integracji.

Podział pracy w J2EEJ2EE rozbija tworzenie aplikacji korporacyjnych na sześć różnych ról. Oczywiście pojedyncza osoba możepełnić więcej niż jedną rolę, albo też kilka osób może pracować wspólnie nad zadaną rolą.

• Dostawca produktów J2EE — producent systemu operacyjnego, systemu baz danych, serwera aplikacjii/lub serwera WWW. Dostawca produktów dostarcza implementacji interfejsów J2EE i narzędzi dotworzenia i zarządzania aplikacją.

• Dostawca elementów aplikacji — autor serwletów aplikacji, EJB i innego kodu, a także ogólnejzawartości takiej jak HTML. (Innymi słowy, Czytelnik tej książki.)

• Monter aplikacji — pobiera elementy aplikacji i (przy pomocy narzędzi dostarczonych przez dostawcęproduktów) nadaje im formę odpowiednią do wdrożenia. Częścią tej funkcji jest utworzenie opisuzewnętrznych zależności aplikacji, które mogą zmieniać się w zależności od wdrożenia, takich jak bazadanych bądź informacje na temat logowania.

• Wdrożeniowiec — pobiera wyniki pracy montera i (przy pomocy narzędzi dostarczonych przezdostawcę produktów) instaluje, konfiguruje i uruchamia aplikację. Zadanie konfiguracji wymagadostosowania produktu do zewnętrznych zależności wymienionych przez montera.

• Administrator systemu — konfiguruje i administruje infrastrukturę sieci, utrzymując pracę aplikacji.

• Dostawca narzędzi — Tworzy narzędzia wspierające wdrażanie J2EE, poza tymi, które są dostarczoneprzez dostawcę produktów.

Podział pracy pomiędzy dostawcą elementów, monterem i wdrożeniowcem ma wpływ na zachowanieprogramisty serwletów w roli dostawcy zawartości. Zwłaszcza należy zaprojektować kod tak, aby uczynićzewnętrzne zależności jasnymi dla montera, a poza tym powinno się wykorzystać mechanizmy pozwalającewdrożeniowcowi na dostosowanie się do tych zależności bez konieczności modyfikacji plików otrzymanych odmontera. Oznacza to, że żaden wdrożeniowiec nie edytuje pliku web.xml! Dlaczego nie? Ponieważ aplikacjeJ2EE są wmontowane w pliki archiwa Enterprise Archive (.ear), w których plik web.xml aplikacji WWW jestjedyną częścią, której nie można edytować.

Brzmi to o wiele trudniej, niż tak naprawdę wygląda. J2EE dostarcza standardowego mechanizmu w celuosiągnięcia tego wyłączenia przy pomocy JNDI i kilku specjalnych znaczników w deskryptorze web.xml. JNDIto mechanizm wyszukiwania obiektów, sposób na związanie ich z konkretnymi ścieżkami i późniejszegoodnalezienia przy pomocy danych ścieżek. Można myśleć o tym mechanizmie jak o rejestrze RMI, poza tym, żejest on bardziej ogólny poprzez obsługę dostępu do wielu usług, włączając w to LDAP i NIS (a nawet, właściwie,rejestr RMI!). Monter deklaruje zewnętrzne zależności w web.xml przy pomocy specjalnych znaczników,wdrożeniowiec dostosowuje aplikacje do tych zależności przy pomocy właściwych serwerowi narzędzi, apodczas uruchomienia kod Javy wykorzystuje interfejs JNDI w celu uzyskania dostępu do zasobówzewnętrznych — umieszczonych tam przez serwer zgodny z J2EE. Wszystkie cele zostają wypełnione — kodJavy pozostaje przenośny pomiędzy serwerami zgodnymi z J2EE, a wdrożeniowiec może dostosować się dozależności zewnętrznych bez konieczności modyfikowania plików otrzymanych od montera. W tym miejscupozostawiono nawet wystarczającą elastyczność, która pozwala producentom serwerów na rywalizację wimplementacji standardu.

Pozycje środowiskoweParametry inicjacji kontekstu są użyteczne dla serwletów, ale w modelu J2EE istnieje z nimi problem— każdazmiana wartości parametru wymaga modyfikacji pliku web.xml. Zamiast wartości parametrów, które mogązmienić się w trakcie wdrażania lepiej jest zastosować pozycje środowiskowe, wskazywane przez znaczniki<env-entry>. Znacznik <env-entry> może zawierać znaczniki <description> (opis), <env-

14 Większość ludzi wymawia J2EE jako J-2-E-E, ale specjaliści z firmy Sun mówią po prostu „jah-too-ee”

Page 281: Java Servlet - Programowanie

entry-name> (nazwa), <env-entry-value> (wartość) i <env-entry-type> (typ). Poniższy <env-entry> określa, czy aplikacja powinna umożliwiać wysyłanie kodu PIN przy pomocy poczty elektronicznej:

<env-entry> <description>Wysyłanie kodu PIN pocztą</description> <env-entry-name>pocztaPIN</env-entry-name> <env-entry-value>false</env-entry-value> <env-entry-type>java.lang.Boolean</env-entry-type> <!--PNK--></env-entry>

Znacznik <description> wyjaśnia wdrożeniowcowi cel tej pozycji. Jest on opcjonalny, lecz warto goumieszczać. <env-entry-name> jest wykorzystywany przez kod Javy jako część wyszukiwania JNDI.<env-entry-value> definiuje domyślną wartość przekazywaną wdrożeniowcowi. On także jest opcjonalny,lecz wart umieszczenia. <env-entry-type> definiuje domyślną pełną nazwę klasy (PNK) pozycji. Typ możebyć jednym z następujących — String, Byte, Short, Integer, Long, Boolean, Double lub Float(wszystkie z ich pełną kwalifikacją java.lang). Typ pomaga wdrożeniowcowi w zorientowaniu się, czegospodziewa się serwer. Powyższe znaczniki mogą wydawać się znajome osobom, którym nieobcy jest deskryptorEJB, posiadają one identyczne nazwy i semantykę.

Kod Javy może odczytać wartości <env-entry> przy pomocy JNDI:Context poczKontekst = new InitialContext();Boolean pocztaPIN = (Boolean) poczKontekst.lookup("java:comp/env/pocztaPIN");

Wszystkie pozycje umieszczane są przez serwer w kontekście java:comp/env. Osoby nie znające JNDImogą o nim myśleć jako o bazie URL-a lub katalogu w systemie plików. Kontekst java:comp/env mawłasności tylko do odczytu i jest unikatowy dla aplikacji WWW, tak więc jeżeli dwie różne aplikacje WWWzdefiniują taką samą pozycję środowiskową, pozycje te nie kolidują. Skrót nazwy kontekstu oznacza componentenvironment (środowisko elementów).

Przykład 12.1 przedstawia serwlet wyświetlający wszystkie jego pozycje środowiskowe, wykorzystując interfejsJNDI do przeglądania kontekstu java:comp/env.

Przykład 12.1.

Przeglądanie kontekstu java:comp/envimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import javax.naming.*;public class PrzeglPozSrod extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); try { Context poczKontekst = new InitialContext(); NamingEnumeration enum = poczKontekst.listBindings("java:comp/env"); // Wykorzystywane są metody JDK 1.2; można to zrobić, ponieważ J2EE wymagaJDK 1.2 while (enum.hasMore()) { Binding wiazanie = (Binding) enum.next(); wyj.println("Nazwa: " + wiazanie.getName()); wyj.println("Typ: " + wiazanie.getClassName()); wyj.println("Wartość: " + wiazanie.getObject()); wyj.println(); } } catch (NamingException w) { w.printStackTrace(wyj); } }}

Przyjmując poprzednią pozycję web.xml, serwlet wygenerowałby:Nazwa: pocztaPINTyp: java.lang.BooleanWartość: false

Proszę pamiętać, że serwer, który nie obsługuje J2EE niekoniecznie obsługuje powyższe znaczniki, ani żadneinne opisywane w tym podrozdziale.

Page 282: Java Servlet - Programowanie

Odwołania do elementów EJBJeśli obiekt w pozycji środowiskowej to element EJB, należy wykorzystać specjalny znacznik <ejb-ref>.Daje on serwletom możliwość obsługi EJB przy pomocy abstrakcyjnej nazwy. Wdrożeniowiec zapewniadostępność odpowiedniego elementu w trakcie uruchamiania w oparciu o ograniczenia podane w znaczniku<ejb-ref>. Znacznik ten może zawierać znaczniki <description>, <ejb-ref-name>, <ejb-ref-type>, <home>, <remote> i <ejb-link>. Poniżej przedstawiony jest typowy <ejb-ref>:

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote></ejb-ref>

Powyższe znaczniki posiadają również swoje odpowiedniki w EJB, i właściwie przykład ten jest zapożyczony zksiążki „Enterprise JavaBeans” autorstwa Richarda Monson-Haefela (O'Reilly). <ejb-ref-name> nadajenazwę do poszukiwań JNDI. Poleca się (chociaż nie jest to konieczne) umieszczenie nazwy w podkontekścieejb/, co sprawia, że pełna ścieżka do elementu to java:comp/env/ejb/KabinaGlowny. <ejb-ref-type> musi posiadać wartość Entity lub Session (są to typy elementów EJB15). Natomiast element<home> określa pełną nazwę klasy interfejsu właściwego EJB, podczas gdy element <remote> określa PNKinterfejsu zdalnego EJB.

Serwlet może odczytać odwołanie do elementu Kabina przy pomocy następującego kodu:InitialContext poczKontekst = new InitialContext();Object odw = poczKontekst.lookup("java:comp/env/ejb/KabinaPocz");KabinaPocz pocz = (KabinaPocz) PortableRemoteObject.narrow(odw, KabinaPocz.class);

Jeżeli monter tworzący plik web.xml chce umieścić w odwołaniu EJB konkretny element EJB, informacja tamoże zostać przekazana wdrożeniowcowi przy pomocy opcjonalnego elementu <ejb-link>. Element <ejb-link> powinien odwoływać się do <ejb-name> komponentu EJB zarejestrowanego w deskryptorze EJB wtej samej aplikacji J2EE. Wdrożeniowiec posiada możliwość skorzystania z sugestii bądź opuszczenia jej.Poniżej przedstawiona jest uaktualniona pozycja web.xml:

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote> <ejb-link>KabinaElement</ejb-link></ejb-ref>

Odwołania do zewnętrznych fabryk zasobówOstatecznie, w przypadkach, w których pozycja środowiskowa to fabryka zasobów, wykorzystuje się znacznik<resource-ref>. Fabryka to obiekt, który na żądanie tworzy inne obiekty. Fabryka zasobów tworzy obiektyzasobów, takie jak połączenia z bazami danych lub kolejki wiadomości.

Znacznik <resource-ref> może zawierać znaczniki <description>, <res-ref-name>, <res-type> i <res-auth>. Poniżej przedstawiony jest typowy znacznik <resource-ref>:

<resource-ref> <description>Podstawowa baza danych</description> <res-ref-name>jdbc/podstawBD</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>CONTAINER</res-auth></resource-ref>

W tym przypadku <description> również służy jak wsparcie dla wdrożeniowca i jest opcjonalny, alepolecany. <res-ref-name> zawiera nazwę do poszukiwań JNDI. Poleca się, lecz nie jest to wymagane, abyfabryki zasobów były umieszczane w podkontekście, który określa typ zasobów:

• jdbc/ dla fabryki JDBC javax.sql.DataSource• jms/ dla javax.jms.QueueConnectionFactory lub

javax.jms.TopicConnectionFactory JMS

• mail/ dla fabryki JavaMail javax.mail.Session

15 Specyfikacja Servlet API 2.2 głosi, że „element ejb-ref-type zawiera przewidywany typ klasy Javy dla EJBstanowiącego cel odwołania”. Jest to potwierdzony błąd. Właściwy cel jest taki, jak wymieniony powyżej.

Page 283: Java Servlet - Programowanie

• url/ dla fabryki java.net.URLElement <res-type> określa PNK fabryki zasobów (a nie utworzonego zasobu). Typy fabryk na powyższejliście to typy standardowe. Serwer posiada możliwość obsługi dodatkowych typów; nie mogą zostać zastosowanefabryki użytkownika. Specyfikacja nadchodzącej wersji 1.3 J2EE zawiera mechanizm „łącznika” służący dorozszerzania tego modelu o fabryki zdefiniowane przez użytkownika.

<res-auth> służy do przekazania serwerowi informacji, kto jest odpowiedzialny za uwierzytelnianie.Znacznik ten może posiadać dwie wartości — CONTAINER lub SERVLET. Jeżeli jego wartość wynosiCONTAINER, wtedy kontener serwletów (serwer J2EE) obsługuje uwierzytelnianie przed dowiązaniem fabrykido JDNI, przy pomocy danych dostarczonych przez wdrożeniowca. Jeżeli wynosi SERVLET, uwierzytelnieniemusi zostać przeprowadzone programowo przez serwlet. Poniższy kod przedstawia ten mechanizm:

InitialContext poczKontekst = new InitialContext();DataSource zrodlo = (DataSource) poczKontekst.lookup("java:comp/env/jdbc/podstawBD");// Jeżeli "CONTAINER"Connection pol1 = zrodlo.getConnection();// Jeżeli "SERVLET"Connection pol2 = zrodlo.getConnection("user", "password");

Powyższe znaczniki również posiadają swoje odpowiedniki w deskryptorze EJB. Jedyną różnicą jest fakt, że wEJB dwie możliwe wartości <res-auth> to Container i Application (proszę zauważyć niewyjaśnionąróżnicę w wielkości liter).

Dystrybucja serwletów w środowisku J2EEOstatnia różnica pomiędzy serwletami w środowisku samodzielnym i serwletami w środowisku J2EE związanajest z niewielką zmianą w zasadach dystrybucji sesji. Podczas gdy standardowy serwer WWW musi obsługiwaćw sesji jedynie obiekty java.io.Serializable aplikacji dystrybuowalnej, serwer zgodny z J2EE, któryobsługuje dystrybuowalny kontener serwletów musi również obsługiwać kilka dodatkowych typów obiektów:

• każdy javax.ejb.EJBObject• każdy javax.ejb.EJBHome• każdy javax.transaction.UserTransaction• javax.naming.Context dla java:comp/env

Wszystkie powyższe interfejsy to interfejsy, które nie wykorzystują Serializable. W celu przeniesienia tychobiektów kontener może wykorzystać swój własny mechanizm, który może (ale nie musi) być oparty naserializacji. Dodatkowe typy klas mogą być obsługiwane w zależności od serwera, ale powyższe to jedyne typy,których obsługa jest gwarantowana.

Page 284: Java Servlet - Programowanie

Rozdział 13. Internacjonalizacja

Pomimo swojej nazwy, sieć WWW musi przebyć jeszcze długą drogę, zanim będzie mogła być uważana zacałkowicie światową. Oczywiście, kable doprowadzają zawartość sieci do prawie wszystkich krajów świata.Jednak, aby zawartość sieci była naprawdę międzynarodowa, musi ona być możliwa do odczytania przez każdąosobę ją odbierającą — własność rzadko dziś spotykana, gdy większość stron WWW posiada jedynie wersjęangielską.

Sytuacja zaczyna się jednak zmieniać. Wiele największych witryn WWW posiada obszary zaprojektowane dlajęzyków innych niż angielski. Na przykład, strona główna firmy Netscape jest dostępna w języku angielskim podadresem http://home.netscape.com/index.html, natomiast w wersji francuskojęzycznej pod adresemhttp://home.netscape.com/fr/index.html, a dla osób znających inne języki pod wieloma innymi URL-ami.

Serwery WWW mogą również obsługiwać rozwiązanie pojedyncze, w którym pojedynczy URL może zostaćwykorzystany do przeglądania tej samej zawartości w różnych językach, których wybór zależny jest odpreferencji klienta. To, który język zostanie wyświetlony jest uzależnione od konfiguracji przeglądarki16. Chociażtechnika ta daje wrażenie, że następuje dynamiczne tłumaczenie, tak naprawdę serwer dysponuje po prostukilkoma specjalnie nazwanymi wersjami statycznego dokumentu.

Podczas gdy te techniki działają dobrze w przypadku statycznych dokumentów, nie rozwiązują one jednakproblemu internacjonalizacji i lokalizacji zawartości dynamicznej. Jest to temat niniejszego rozdziału. Opisanezostaną tu sposoby wykorzystania możliwości internacjonalizacji dodanych w JDK1.1. przez serwlety w celuprawdziwego rozszerzenia sieci WWW na cały świat.

Po pierwsze należy omówić terminologię. Internacjonalizacja (Internationalization — słowo często miłosiernieskracane do I18N, ponieważ rozpoczyna się od I, kończy na N, a pomiędzy nimi jest 18 liter) jest zadaniemuczynienia programu na tyle elastycznym, aby można go było uruchomić w każdym miejscu. Lokalizacja(Localization, często skracana do L10N) to proces ustawiania programu tak, aby działał w konkretnym miejscu.Większa część tego rozdziału opisuje internacjonalizację serwletów. Lokalizacja zostanie opisana jedynie naprzykładach dat, godzin, liczb i innych obiektów, które domyślnie obsługuje Java.

Języki zachodnioeuropejskieNa początku zostanie opisany sposób wyświetlania przez serwlet strony utworzonej w językuzachodnioeuropejskim, takim jak angielski, hiszpański, niemiecki, francuski, włoski, holenderski, norweski,fiński lub szwedzki. W przykładzie wyświetlony zostanie napis „Witaj świecie!” po hiszpańsku, jakprzedstawiono to na rysunku 13.1.

16 Wiele starszych przeglądarek nie obsługuje jednak dostosowywania języków. Na przykład, własność ta zostaławprowadzona po raz pierwszy w przeglądarkach Netscape Navigator 4 i Microsoft Internet Explorer 4.

Page 285: Java Servlet - Programowanie

Rysunek 13.1.

En Español — ¡HolaMundo!

Proszę zauważyć zastosowanie specjalnych znaków ñ i ¡. Znaki takie jak te, chociaż praktycznie nieobecne wjęzyku angielskim, są przypisane do języków zachodnioeuropejskich. Serwlety posiadają dwa sposobygenerowania takich znaków — poprzez encje znakowe HTML i kody ucieczkowe Unicode.

Encje znakowe HTMLHTML 2.0 wprowadził możliwość wyświetlania specyficznych sekwencji znaków na stronie HTML jako znakupojedynczego. Sekwencje te, nazywane encjami znakowymi, rozpoczynają się znakiem &, a kończą średnikiem(;). Encje znakowe mogą mieć nazwy, jak i wartości numeryczne. Na przykład, nazwana encja &ntilde;przedstawia ñ, a &iexcl; przedstawia ¡. Kompletna lista znaków specjalnych i ich nazw jest podana w dodatkuA, „Encje znakowe”. Przykład 13.1 przedstawia serwlet wykorzystujący nazwane encje w celu wyświetlenia„Witaj świecie” po hiszpańsku.

Przykład 13.1.

Powitanie dla posługujących się językiem hiszpańskim, przy pomocy nazwanych encji znakowychimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class WitajHiszpania extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter(); odp.setHeader("Content-Language", "es"); wyj.println("<HTML><HEAD><TITLE>En Espa&ntilde;ol</TITLE></HEAD>"); wyj.println("<BODY>"); wyj.println("<H3>En Espa&ntilde;ol:</H3>"); wyj.println("&iexcl;Hola Mundo!"); wyj.println("</BODY<>/HTML>"); }}

Można zauważyć, że oprócz wykorzystywania encji znakowych powyższy serwlet nadaje swojemu nagłówkowiContent-Language wartość es. Nagłówek Content-Language jest wykorzystywany do określaniajęzyka następujących po nim encji. W tym przypadku serwlet wykorzystuje nagłówek do wskazania klientowi, żestrona jest napisana w języku hiszpańskim (Español). Większość klientów ignoruje tę informację, lecz dodobrego tonu należy jej wysyłanie. Języki są zawsze przedstawiane przy pomocy dwuliterowych skrótówzapisanych małymi literami. Kompletna lista jest dostępna w standardzie ISO-639 pod adresemhttp://www.ics.uci.edu/pub/ietf/http/related/iso639.txt.

Encje znakowe mogą być również określane przy pomocy liczb. Na przykład, &#241; przedstawia ñ, a &#161;przedstawia ¡. Liczba odnosi się do dziesiętnej wartości znaku w standardzie ISO-8859-1 (Latin-1), któryzostanie opisany w dalszej części niniejszego rozdziału. Kompletna lista wartości numerycznych encjiznakowych również znajduje się w dodatku E. Przykład 13-2 przedstawia serwlet WitajHiszpaniazmieniony tak, aby wykorzystywał encje numeryczne.

Przykład 13.2.

Powitanie dla posługujących się językiem hiszpańskim, przy pomocy numerycznych encji znakowych

Page 286: Java Servlet - Programowanie

import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class WitajHiszpania extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter(); odp.setHeader("Content-Language", "es"); wyj.println("<HTML><HEAD><TITLE>En Espa&#241;ol</TITLE></HEAD>"); wyj.println("<BODY>"); wyj.println("<H3>En Espa&#241;ol:</H3>"); wyj.println("&#161;Hola Mundo!"); wyj.println("</BODY<>/HTML>"); }}

Niestety, z wykorzystaniem encji znakowych wiąże się jeden poważny problem — działają one jedynie zestronami HTML. Jeżeli wynikiem serwletu nie jest kod HTML, strona będzie wyglądać podobnie doprzedstawionej na rysunku 13.2. Aby obsługiwać wyniki nie będące HTML, należy wykorzystać kodyucieczkowe Unicode.

Rysunek 13.2.

Nie do końca hiszpański

Kody ucieczkowe UnicodeW Javie znaki, łańcuchy i identyfikatory są złożone wewnętrznie z 16-bitowych (2-bajtowych) znaków Unicode2.0. Unicode został wprowadzony przez Unicode Consortium, które opisuje standard w następujący sposób(proszę zobaczyć http://unicode.org):

Światowy standard znaków Unicode to system kodowania znakówzaprojektowany w celu obsługi wymiany, przetwarzania iwyświetlania tekstów pisanych w różnych językachwspółczesnych. Dodatkowo, obsługuje on klasyczne ihistoryczne teksty w wielu językach pisanych.

W swojej aktualnej wersji (2.0) standard Unicode zawiera 38,885 osobno oznaczonych znakówodziedziczonych ze wspieranych skryptów. Znaki te umożliwiają obsługę głównych języków pisanych wAmerykach, Europie, Bliskim Wschodzie, Afryce, Indiach, Azji i rejonie Pacyfiku.

Większa ilość informacji na temat Unicode jest dostępna pod adresem http://www.unicode.org, a także w książce„The Unicode Standard, Version 2.0” (Addison-Wesley). Proszę zauważyć, że pomimo wprowadzenia Unicode3.0, Java wciąż obsługuje wersję 2.0.

Wykorzystanie przez Javę Unicode jest bardzo ważne dla niniejszego rozdziału, ponieważ oznacza ono, żeserwlet może wewnętrznie przedstawiać właściwie każdy znak w powszechnie stosowanym języku pisanym. 16-bitowe znaki Unicode są przedstawiane w 7-bitowym kodzie źródłowym US-ASCII przy pomocy kodówucieczkowych Unicode o postaci \uxxxx, gdzie xxxx oznacza sekwencję czterech cyfr w formacieszesnastkowym. Kompilator Javy traktuje każda sekwencję ucieczkową jako pojedynczy znak.

Nieprzypadkowo i dla zachowania wygody pierwsze 256 znaków Unicode (\u0000 do \u00ff) są równe z256 znakami ISO 8859-1 (Latin-1). Tak więc znak ñ może zostać opisany jako \u00f1, a znak ¡ jako \u00a1.Kompletna lista sekwencji ucieczkowych Unicode dla znaków ISO-8859-1 jest również zawarta w dodatku.Przykład 13.2 przedstawia WitajHiszpania przepisany przy pomocy kodów ucieczkowych Unicode.

Page 287: Java Servlet - Programowanie

Przykład 13.2.

Powitanie dla posługujących się językiem hiszpańskim, przy pomocy kodów ucieczkowych Unicodeimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class WitajHiszpania extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); odp.setHeader("Content-Language", "es"); wyj.println("En Espa\u00f1ol:"); wyj.println("\u00a1Hola Mundo!"); }}

Wynik powyższego serwletu wyświetla się prawidłowo, kiedy wykorzystywany jako część strony HTML lubkiedy wykorzystywany jest do wyświetlania zwykłego tekstu.

Hołdowanie lokalnym zwyczajomTeraz wiadomo już jak wykorzystywać encje znakowe HTML lub kody ucieczkowe Unicode do wyświetlaniaznaków w językach zachodnioeuropejskich. Pozostaje pytanie, co powiedzieć przy pomocy tych języków?Generalnie, problem tłumaczenia najlepiej pozostawić odpowiedniemu zespołowi odpowiedzialnemu zalokalizację, Jednak w niektórych przypadkach Java dostarcza pewnej pomocy.

Na przykład, jeżeli oprócz powiedzenia „Witaj świecie” przykładowy serwlet powinien wyświetlać aktualną datęw formacie naturalnie rozumianym przez odbiorcę. To, co mogłoby być skomplikowanym problemem zformatowaniem jest tak naprawdę stosunkowo proste, ponieważ JDK 1.1 posiada wbudowaną obsługę lokalizacjidynamicznych obiektów takich jak daty i godziny.

Sztuczka polega na zastosowaniu egzemplarza java.text.DateFormat odpowiedniego dla docelowegoodbiorcy. Obiekt DateFormat może przekonwertować Date do odpowiednio zlokalizowanego obiektuString. Na przykład, znacznik czasu utworzony w języku angielskim jako „February 16, 1998 12:36:18 PMPST” zostanie zapisany po hiszpańsku jako „16 de febrero de 1998 12:36:18 GMT-8:00”.

Obiekt DateFormat jest tworzony przy pomocy metody fabryki, która przyjmuje style formatowania (krótki,średni, długi, pełny) oraz obiektu java.util.Locale, który identyfikuje docelowego odbiorcę (amerykańskiangielski, podstawowy chiński itp.). Najpopularniejszy konstruktor Locale pobiera dwa parametry —dwuliterowy skrót języka zapisany małymi literami (jak opisano wcześniej) oraz dwuliterowy kod kraju zapisanydużymi literami zdefiniowany przez ISO-3166 (dostępny pod adresem http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html). Pusty łańcuch kodu kraju wskazuje na kraj domyślny dla danego języka.

Przykład 13.4 przedstawia serwlet WitajHiszpania wykorzystujący obiekt DateFormat w celuwyświetlenia aktualnego czasu w formacie naturalnie rozumianym przez odbiorców posługujących się językiemhiszpańskim.

Przykład 13.4.

Powitanie dla posługujących się językiem hiszpańskim, łącznie ze zlokalizowaną datąimport java.io.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class WitajHiszpania extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); odp.setHeader("Content-Language", "es"); Locale lokal = new Locale("es", ""); DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); fmt.setTimeZone(TimeZone.getDefault());

Page 288: Java Servlet - Programowanie

wyj.println("En Espa\u00f1ol:"); wyj.println("\u00a1Hola Mundo!"); wyj.println(fmt.format(new Date())); }}

Powyższy serwlet na początku tworzy obiekt Locale przedstawiający ogólne środowisko jako hiszpańskie.Następnie wykorzystuje ten Locale do utworzenia egzemplarza DateFormat, który formatuje daty w językuhiszpańskim. Następnie ustawia on strefę czasową na strefę domyślną (strefę czasową serwera). Dzieje się tak,ponieważ obiekt DateFormat formatuje swoje czasy w sposób taki, aby pasowały one do strefy czasowej, wktórej znajduje się domyślny klient, w tym przypadku Hiszpanii. Ponieważ serwlet ten nie może być pewnym,czy podejmuje właściwe decyzje, zamienia on wartość domyślną i ustawia strefę czasową tak, aby pasowała doserwera. Oczywiście lepsze byłoby ustawienie strefy czasowej tak, aby pasowała ona do miejsca, w którymznajduje się klient, ale nie jest to aktualnie możliwe bez dodatkowych informacji dostarczanych przezużytkownika. Ostatecznie, po wypowiedzeniu swojego „Witaj świecie”, serwlet wyświetla prawidłowosformatowana datę i godzinę. Wynik działania powyższego skryptu jest przedstawiony na rysunku 13.3.

Rysunek 13.3.

Hola Mundo con Tiempo

Powyższy przykład stanowi jedynie bardzo krótki podgląd możliwości dynamicznego formatowania zawartościJavy. Osoby zainteresowane bardziej skomplikowanym formatowaniem powinny obejrzeć kilka przydatnych klasznajdujących się w pakiecie java.text. Szczególnie należy zwracać uwagę na te będące rozszerzeniemjava.text.Format.

Języki spoza Europy ZachodniejTeraz omówione zostanie tworzenie przez serwlet strony w języku spoza Europy Zachodniej, takim jak rosyjski,czeski, litewski czy japoński. Aby pojąć sposoby pracy związane z tymi językami, należy najpierw poznać pewnemechanizmy zachodzące poza ekranem w poprzednich przykładach.

KodowanieNa początku sytuacja zostanie omówiona z punktu widzenia przeglądarki. Proszę wyobrazić sobie spełnianiepracy przeglądarki. Wykonuje się żądanie HTTP dla pewnego URL-a i otrzymuje odpowiedź. Odpowiedź ta wnajprostszym znaczeniu nie jest niczym więcej niż długą sekwencją bajtów. Ale w jaki sposób można siędowiedzieć, jak wyświetlić tę odpowiedź?

Popularnym i właściwie domyślnym sposobem jest przyjęcie, że każdy bajt reprezentuje jeden z 256 możliwychznaków, a następnie przyjęcie, że znak, który bajt reprezentuje może zostać określony przez wyszukanie wartościbajtu w pewnej tabeli. Domyślna tabela jest zdefiniowana przez standard ISO-8859-1, noszący także nazwęLatin-1. Zawiera on odwzorowanie typu bajt-do-znaku dla najpopularniejszych znaków stosowanych w językachzachodnioeuropejskich. Tak więc domyślnie przeglądarka może otrzymywać sekwencję bajtów i konwertować jądo sekwencji znaków zachodnioeuropejskich.

Co natomiast należy uczynić, jeżeli pragnie się otrzymać tekst, który nie jest napisany w językuzachodnioeuropejskim? Należy pobrać długą sekwencję bajtów odpowiedzi i zinterpretować ją w inny sposób,przy pomocy innego odwzorowania sekwencja-bajtów-do-znaku. Mówiąc językiem technicznym, należyzastosować inne kodowanie17. Istnieje nieskończona ilość potencjalnych kodowań. Na szczęście, najczęściejstosuje się jedynie kilkadziesiąt z nich.

17 Kodowanie (charset — odwzorowanie sekwencja-bajtów-do-znaku) to nie to samo co zestaw znaków (character set). Pełnewyjaśnienie można znaleźć w dokumencie RFC 2278 pod adresem http://www.ietf.org/rfc/rfc2278.txt.

Page 289: Java Servlet - Programowanie

Niektóre kodowania wykorzystują znaki jednobajtowe w sposób podobny do ISO-8859-1, chociaż z innymodwzorowaniem bajt-do-znaku. Na przykład, ISO-8859-5 definiuje odwzorowanie bajt-do-znaku dla znakówcyrylicy (alfabetu rosyjskiego), a ISO-8859-8 definiuje odwzorowanie dla alfabetu hebrajskiego18.

Inne kodowania wykorzystują znaki wielobajtowe, w których jeden znak może być przedstawiany przez więcejniż jeden bajt. Najczęściej zdarza się to w przypadku języków zawierających tysiące znaków, takich jak chiński,japoński czy koreański — często nazywanych po prostu CJK. Kodowania wykorzystywane do wyświetlania tychjęzyków to między innymi Big5 (chiński), Shift_JIS (japoński) i EUC-KR (koreański). Tabela zawierająca językii odpowiadające im kodowania znajduje się w dodatku F, „Kodowania”.

Oznacza to, że jeżeli znane jest kodowanie, w którym zapisana została odpowiedź, można określić sposóbinterpretacji otrzymanych bajtów. Pozostaje jedno pytanie — jak określić kodowanie? Można wykonać to nadwa sposoby. Po pierwsze, można zażądać ujawnienia kodowania przez użytkownika. W przeglądarce NetscapeNavigator 4 można to wykonać poprzez opcję menu View → Encoding, w Netscape Navigator 6 poprzez View→ Character Coding. W przeglądarce Microsoft Internet Explorer 4 poprzez Widok → Czcionki, a MicrosoftInternet Explorer 5 poprzez Widok → Kodowanie. Podejście to często wymaga, aby użytkownik wypróbowałkilku kodowań, zanim obraz zacznie wyglądać sensownie. Drugą możliwością jest określenie kodowania przezserwer (lub serwlet) w nagłówku Content-Type. Na przykład, następująca wartość Content-Type:

text/html; charset=ISO-8859-5wskazuje, że wykorzystywane kodowanie to ISO-8859-5. Niestety, niektóre starsze przeglądarki mogą błędniezinterpretować dodanie kodowania do nagłówka Content-Type.

Tworzenie wyników zakodowanychKiedy znana jest już zasada działania kodowań z perspektywy przeglądarki, można wrócić do perspektywyserwletu. Rola serwletu w tym działaniu jest następująca:

1. Wybranie kodowania i ustawienie go dla serwletu.

2. Wybranie PrintWriter dla tego kodowania.

3. Wyświetlenie znaków, które mogą zostać wyświetlone przy pomocy tego kodowania.

Przykład 13.5 przedstawia serwlet wyświetlający „Witaj świecie” oraz aktualną datę i godzinę w językurosyjskim.

Przykład 13.5.

Powitanie dla użytkowników rosyjskichimport java.io.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class WitajRosja extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain; charset=ISO-8859-5"); PrintWriter wyj = odp.getWriter(); odp.setHeader("Content-Language", "ru"); Locale lokal = new Locale("ru", ""); DateFormat pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("Po rosyjsku (cyrylica):"); wyj.print("\u0417\u0434\u0440\u0430\u0432\u0441\u0442"); // Witaj świecie wyj.println("\u0432\u0443\u0439, \u041c\u0438\u0440"); }}

Rysunek 13.4 przedstawia wynik uruchomienia przykładu 13-5.

18 Warto zapamiętać, że dla prawie wszystkich kodowań wartości bajtów pomiędzy dziesiętnym 0 i 127 przedstawiająstandardowe znaki US-ASCII, co pozwala na dodanie angielskiego tekstu do strony utworzonej w prawie dowolnym języku.

Page 290: Java Servlet - Programowanie

Rysunek 13.4.

Royjskie powitanie

Powyższy serwlet rozpoczyna działanie od ustawienia typu zawartości na text/plain oraz kodowania naISO-8859-5. Następnie wywołuje odp.getWriter() tak, jak zazwyczaj — poza tym, że w tym przypadkuwynik tej metody otrzymuje specjalny PrintWriter. Ten PrintWriter koduje cały wynik działaniaserwletu przy pomocy kodowania ISO-8859-5., ponieważ to kodowanie zostało wymienione w nagłówkuContent-Type. Tak więc druga linia jest równoznaczna poniższej:

PrintWriter wyj = new PrintWriter( new OutputStreamWriter(odp.getOutputStream(), "ISO-8859-5"), true);

Należy również zapamiętać, że wywołanie odp.GetWriter() może spowodować wyjątekUnsupportingEncodingException, jeżeli kodowanie nie zostanie rozpoznane przez Javę19, lubIllegalStateException, jeżeli getOutputStream() został wywołany wcześniej w tym samymżądaniu.

Następnie serwlet tworzy obiekt Locale przy pomocy języka ru w celu przedstawienia ogólnego rosyjskiegośrodowiska, po czym tworzy pasujący do niego DateFormat. Na końcu wyświetla po rosyjsku wyrażenierównoznaczne z „Witaj świecie”, przy pomocy kodów ucieczkowych Unicode, po czym wyświetla aktualną datęi godzinę.

Aby powyższy serwlet mógł działać, ścieżka klas serwera musi zawierać klasy sun.io.CharToByte lub ichekwiwalent. Poza tym, aby znaki rosyjskie (lub innego języka) były wyświetlane poprawnie w przeglądarce, musiona obsługiwać dane kodowanie i mieć dostęp do potrzebnych czcionek w celu wyświetlenia kodowania.

Większa ilość informacji na temat możliwości internacjonalizacji przeglądarki Netscape Navigator jest dostępnapod adresem http://home.netscape.com/eng/intl/index.html. Większa ilość informacji na temat możliwościinternacjonalizacji przeglądarki Microsoft Internet Explorer jest dostępna pod adresemhttp://www.microsoft.com/ie/intlhome.htm.

Odczyt i zapis wyników zakodowanychRęczne wprowadzanie setek lub tysięcy kodów ucieczkowych Unicode w plikach źródłowych Javy może byćczynnością bardzo powolną. Łatwiejszym sposobem jest utworzenie serwletu przy pomocyzinternacjonalizowanego edytora i zapisanie pliku przy pomocy odpowiedniego kodowania. Jeżeli kodowanie tojest rozpoznawane przez Javę, źródło może być skompilowane przy pomocy niemal każdego nowoczesnegokompilatora Javy. Na przykład, przy pomocy kompilatora javac dołączonego do JDK, plik źródłowy serwletuzakodowany przy pomocy ISO-8859-5, powinien zostać skompilowany w następujący sposób:

javac –encoding ISO-8859-5 WitajRosja.javaPlik źródłowy WitajRosja.java powinien wyglądać niemal identycznie jak ten przedstawiony w przykładzie 13.5,z jedyną różnicą taką, że kody ucieczkowe Unicode mogą zostać zastąpione przez oryginalne znaki rosyjskie.Jeżeli plik ten zostanie otwarty przy pomocy rosyjskiego edytora tekstu, pomiędzy cudzysłowami wwyj.println() można będzie zobaczyć znaki rosyjskie, W dowolnym innym edytorze znaki te — i zależnieod kodowania nawet cały plik — wyglądałby jak zbiór śmieci. Co interesujące, zawartość pliku .class jestidentyczna niezależnie od tego, czy kompilowano go przy pomocy kodów ucieczkowych Unicode, czyzakodowanych plików źródłowych.

Inną opcją, przydatną, jeżeli programista nie zna języka, w którym utworzona została strona wynikowa, jestutworzenie serwletu przy pomocy standardowego kodu ASCII, ale odczytanie zlokalizowanego tekstu zzakodowanego pliku. Na przykład, jeżeli rosyjski tekst „Witaj świecie” został zapisany przez osobę z zespołulokalizującego w pliku o nazwie WitajSwiecie.ISO-8859-5, przy pomocy kodowania ISO-8859-5, wtedy serwletmoże odczytać ten plik i wysłać zawartość do przeglądarki przy pomocy kodowania ISO-8859-5, jakprzedstawiono w przykładzie 13.6.

19 W niektórych wczesnych wersjach Javy może ona w niektórych sytuacjach błędnie wywoływać wyjątekIllegalArgumentException, jeżeli kodowanie nie zostanie rozpoznane.

Page 291: Java Servlet - Programowanie

Przykład 13.6.

Wysyłanie zlokalizowanego wyniku odczytanego z plikuimport java.io.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class WitajRosjaCzytanie extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain; charset= ISO-8859-5"); PrintWriter wyj = odp.getWriter(); odp.setHeader("Content-Language", "ru"); Locale lokal = new Locale("ru", ""); DateFormat pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("Po rosyjsku:"); try { FileInputStream fis = new FileInputStream(zad.getRealPath("/WitajSwiecie.ISO-8859-5")); InputStreamReader isr = new InputStreamReader(fis, "ISO-8859-5"); BufferedReader czytanie = new BufferedReader(isr); String linia = null; while ((linia = czytanie.readLine()) != null) { wyj.println(linia); } } catch (FileNotFoundException w) { // Brak powitania } wyj.println(pelny.format(new Date())); }}

Powyższy serwlet jest właściwie konwerterem kodowań znaków. Odczytuje on tekst WitajSwiecie.ISO-8859-5zakodowany w standardzie ISO-8859-5, i wewnętrznie konwertuje go do Unicode. Następnie wyświetla ten samtekst poprzez konwersję go z Unicode do ISO-8859-5.

Większa ilość językówTeraz należy podnieść nieco poprzeczkę i spróbować czegoś, co stało się możliwe dopiero niedawno. Napisanyzostanie serwlet zawierający kilka języków na tej samej stronie. Właściwie serwlet taki został już utworzony.Poprzedni przykład, WitajRosja, zawierał zarówno tekst angielski/polski, jak i rosyjski. Można jednakzauważyć, że jest to przypadek specjalny. Dodanie do strony tekstu angielskiego jest prawie zawsze możliwe, zpowodu wygodnego faktu, że prawie wszystkie kodowania zawierają 128 znaków US-ASCII. W bardziejogólnym przypadku, kiedy tekst na stronie składa się z różnych języków, a żadne z wymienionych poprzedniokodowań nie zawiera wszystkich potrzebnych znaków, konieczna jest inna technika.

UCS-2 i UTF-8Najlepszym sposobem tworzenia strony zawierającej większa ilość języków jest wyświetlenie klientom 16-bitowych znaków Unicode. Istnieją dwa popularne sposoby wykonania tego działania — UCS-2 i UTF-8. UCS-2(Universal Character Set, 2-byte form — Uniwersalny Zestaw Znaków, forma dwubajtowa) wysyła znakiUnicode w czymś, co może być nazwane ich naturalnym formatem, 2 bajty na znak. Wszystkie znaki, włączającw to znaki US-ASCII, wymagają dwóch bajtów. UTF-8 (UCS Transformation Format, 8-bit form — FormatTransformacji UTF, forma 8-bitowa) to kodowanie o zmiennej długości. Przy pomocy UTF-8, znak Unicode jestzmieniany w swoją 1-, 2- lub 3-bajtową reprezentacją. Generalnie, UTF-8 jest bardziej wydajne niż UCS-2,ponieważ może zakodować znak z zestawu US-ASCII przy pomocy zaledwie jednego bajtu. Z tego powoduzastosowanie w sieci WWW UTF-8 mocno przewyższa UCS-2. Większa ilość informacji na temat UTF-8 jestdostępna w dokumencie RFC 2279 pod adresem http://www.ietf.org/rfc/rfc2279.txt.

Przed przejściem dalej należy zapamiętać, że obsługa UTF-8 nie jest jeszcze gwarantowana. Netscape po razpierwszy wprowadził obsługę kodowania UTF-8 w przeglądarce Netscape Navigator 4, a Microsoft w InternetExplorer 4.

Page 292: Java Servlet - Programowanie

Tworzenie UTF-8Przykład 13.7 przedstawia serwlet wykorzystujący UTF-8 do wyświetlenia „Witaj świecie!” i aktualnego czasu(w lokalnej strefie czasowej) po angielsku, hiszpańsku, niemiecku, czesku i rosyjsku.

Przykład 13.7.

Serwletowa wersja kamienia z Rosettyimport java.io.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils;public class WitajRosetta extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { Locale lokal; DateFormat pelny; try { odp.setContentType("text/plain; charset=UTF-8"); PrintWriter wyj = odp.getWriter(); lokal = new Locale("en", "US"); pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("American English:"); wyj.println("Hello World!"); wyj.println(pelny.format(new Date())); wyj.println(); lokal = new Locale("es", ""); pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("En Espa\u00f1ol:"); wyj.println("\u00a1Hola Mundo!"); wyj.println(pelny.format(new Date())); wyj.println(); lokal = new Locale("cs", ""); pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("\u010ceski:"); wyj.println("Halo svete!"); wyj.println(pelny.format(new Date())); wyj.println(); lokal = new Locale("de", ""); pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("Deutsch:"); wyj.println("Hallo Welt!"); wyj.println(pelny.format(new Date())); wyj.println(); lokal = new Locale("fr", ""); pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("Fran\u00e7ais:"); wyj.println("Salut le monde!"); wyj.println(pelny.format(new Date())); wyj.println(); lokal = new Locale("ru", ""); pelny = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); wyj.println("Po rosyjsku (cyrylica):"); wyj.print("\u0417\u0434\u0440\u0430\u0432\u0441\u0442"); wyj.println("\u0432\u0443\u0439, \u041c\u0438\u0440"); wyj.println(pelny.format(new Date()));

Page 293: Java Servlet - Programowanie

wyj.println(); } catch (Exception w) { log(ServletUtils.getStackTraceAsString(w)); } }}

Rysunek 13.5 przedstawia stronę wyświetlaną przez powyższy serwlet.

Rysunek 13.5.

Prawdziwe witaj świecie

Aby powyższy serwlet działał tak, jak opisano, serwer musi obsługiwać JDK w wersji 1.1.6 lub późniejszej.Poprzednie wersje Javy wyświetlają wyjątek UnsupportedEncodingException podczas próby pobraniaPrintWriter, a strona pozostaje pusta. Problemem jest brakujący alias kodowania. Java posiadała obsługękodowania UTF-8 od wprowadzenia JDK 1.1. Niestety JDK wykorzystywał nazwę kodowania UTF8, podczasgdy przeglądarki spodziewały się nazwy UTF-8. Tak więc kto miał rację? Nie było to jasne do początków 1998,kiedy IANA (Internet Assigned Numbers Authority — Komisja Przyznawania Numerów Internetowych)zadeklarowała, że preferowaną nazwą będzie UTF-8. (Proszę zobaczyć http://www.isi.edu/in-notes/iana/assignments/character-sets). Niewiele później JDK 1.1.6 dodał UTF-8 jako alternatywny alias dlakodowania UTF8. W celu zachowania maksymalnej przenośności pomiędzy wersjami Javy można wykorzystaćbezpośrednio nazwę UTF8 w następującym kodzie:

odp.setContentType("text/plain; charset=UTF-8");PrintWriter wyj = new PrintWriter( new OutputStreamWriter(odp.getOutputStream(), "UTF8"), true);

Również klient musi posiadać obsługę kodowania UTF-8 oraz mieć dostęp do wszystkich potrzebnych czcionek.W przeciwnym wypadku część wyświetlanych danych może być niepoprawna.

Dynamiczna negocjacja językaTeraz poprzeczka zostanie podniesiona jeszcze wyżej (przypuszczalnie do maksymalnej wysokości) poprzezserwlet, który zmienia swoją zawartość tak, aby pasowała ona do preferencji językowych klienta. Pozwala to nawykorzystanie jednego URL-a przez użytkowników mówiących różnymi językami.

Page 294: Java Servlet - Programowanie

Preferencje językoweIstnieją dwie metody, przy pomocy których serwlet może poznać preferencje językowe klienta. Po pierwsze,przeglądarka może wysłać tę informacje jako część swojego żądania. Nowsze przeglądarki, począwszy odNetscape Navigator 4 i Microsoft Internet Explorer 4, pozwalają użytkownikom na określenie ich preferowanychjęzyków. W przeglądarce Netscape Navigator 4 i 6 jest to wykonywane w menu Edit → Preferences →Navigator → Languages. W Microsoft Internet Explorer 4 jest to wykonywane w menu Widok → Opcjeinternetowe → Ogólne → Języki; Internet Explorer 5 przesuwa tę opcje z menu Widok do Narzędzia.

Przeglądarka wysyła preferencje językowe użytkownika przy pomocy nagłówka HTP Accept-Language.Wartość tego nagłówka określa język lub języki, które preferuje otrzymywać klient. Proszę zauważyć, żespecyfikacja HTTP pozwala na zignorowanie tych preferencji. Wartość nagłówka Accept-Languagewygląda podobnie do następującej linii kodu:

en, fr, de, pl, cs, zh-TWOznacza to, że klient zna język angielski, francuski, niemiecki, polski, czeski i chiński w wersji tajwańskiej.Konwencja głosi, że języki wymieniane są w kolejności preferencji. Każdy język może również zawieraćwartość q, która wskazuje, na skali od 0.0 do 1.0, siłę tej preferencji. Domyślna wartość q wynosi 1.0(maksymalna preferencja). Wartość nagłówka Accept-Language zawierająca wartości q wyglądanastępująco:

en, fr;q=0.8, de;q=0.7, pl;q=0.3, cs;q=0.2, zh-TW;q=0.1Powyższa wartość nagłówka znaczy mniej więcej to samo, co ta w poprzednim przykładzie.

Drugą metodą odczytywania przez serwlet preferencji językowych klienta jest zapytanie. Na przykład, serwletmoże wygenerować formularz zapytujący klienta, jaki język preferuje. Następnie może zapamiętać i wykorzystaćodpowiedź, na przykład stosując techniki śledzenia sesji omówione w rozdziale 7, „Śledzenie sesji”.

Preferencje kodowaniaOprócz nagłówka HTTP Accept-Language, przeglądarka może wysyłać nagłówek Accept-Charset,który określa, jakie kodowania rozpoznaje. Wartość nagłówka Accept-Charset może wyglądać następująco:

iso-8859-1, utf-8Powyższa wartość wskazuje, że przeglądarka zna kodowania ISO-8859-1 i UTF-8. Jeżeli Accept-Charsetnie jest ustawiony, lub jego wartość zawiera gwiazdkę (*), można przyjąć, że klient przyjmuje wszystkiekodowania. Proszę zauważyć, że aktualna przydatność tego nagłówka jest ograniczona — wysyłają go tylkoniektóre przeglądarki, a i one zazwyczaj wysyłają gwiazdkę.

Pakiety zasobówWykorzystując Accept-Language (i w niektórych przypadkach Accept-Charset), serwlet może określićjęzyk, w którym mówi do konkretnego klienta. Ale jak serwlet może efektywnie zarządzać kilkomazlokalizowanymi wersjami strony? Można w tym celu wykorzystać wbudowaną w Javę obsługę pakietówzasobów.

Pakiet zasobów przechowuje zbiór zlokalizowanych zasobów odpowiednich dla danej lokalizacji. Na przykład,pakiet zasobów dla lokalizacji francuskiej może zawierać francuskie tłumaczenie wszystkich zdań wyświetlanychprzez serwlet. Następnie, kiedy serwlet określi, że preferowanym językiem klienta jest francuski, możezaładować ten pakiet zasobów i wykorzystać zapamiętane zdania. Wszystkie pakiety zasobów to rozszerzeniajava.util.ResourceBundle. Serwlet może załadować pakiet zasobów przy pomocy statycznej metodyResourceBundle.getBundle():

public static final ResourceBundle ResourceBundle.getBundle(String nazwaPakietu, Locale lokal)

Serwlet może pobierać zdania z pakietu zasobów przy pomocy metody ResourceBundle getString():Public final String ResourceBundle.getString(String klucz)

Pakiet zasobów może zostać utworzony na klika sposobów. W przypadku serwletów najbardziej przydatnątechniką jest umieszczenie specjalnego pliku właściwości, który zawiera przetłumaczone zdania, w ścieżce klasserwera. Plik powinien zostać nazwany w specjalny sposób, według wzoru nazwapakietu_jezyk.properties lubnazwapakietu_język_kraj.properties. Na przykład, można wykorzystać Wiadomosci_fr.properties dla pakietufrancuskiego lub Wiadomosci_zh_TW.properties dla pakietu chińsko-tajwańskiego. Plik powinien zawierać znakiUS-ASCII w następującej formie:

nazwa1=wartosc1nazwa1=wartosc1...

Page 295: Java Servlet - Programowanie

Każda linia może również zawierać puste miejsca i kody ucieczkowe Unicode. Informacje zawarte w tym plikumogą zostać automatycznie pobrane przy pomocy metody getBundle().

Wyświetlanie odpowiednich informacjiPrzykład 13.8 przedstawia wykorzystanie nagłówków Accept-Language i Accept-Charset orazpakietów zasobów w serwlecie, który wyświetla „Witaj świecie” każdemu klientowi w języku przez niegopreferowanym. Poniżej umieszczono przykładowy plik pakietu zasobów dla języka angielskiego, który należynazwać WitajBabel_en.properties i umieścić w miejscu przeszukiwanym przez mechanizm ładowania klas (takimjak WEB-INF/classes):

powitanie=Hello worldPoniżej przedstawiono pakiet zasobów dla języka rosyjskiego, przechowywany w WitajBabel_ru.properties:

powitanie=\u0417\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439,\u041c\u0438\u0440

Poniższy serwlet WitajBabel wykorzystuje klasę com.oreilly.servlet.LocaleNegotiator, którazawiera logikę czarnej skrzynki określającą, jakie wartości Locale, ResourceBundle i kodowanie zostanąużyte. Jej kod przedstawiony zostanie w następnym podrozdziale.

Przykład 13.8.

Serwletowa wersja wieży Babelimport java.io.*;import java.util.*;import java.text.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.LocaleNegotiator;import com.oreilly.servlet.ServletUtils;public class WitajBabel extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { try { String nazwaPakietu = "WitajBabel"; String akceptJezyk = zad.getHeader("Accept-Language"); String akcepKod = zad.getHeader("Accept-Charset"); LocaleNegotiator negocjator = new LocaleNegotiator(nazwaPakietu, akceptJezyk, akceptKod); Locale lokal = negocjator.getLocale(); String kodowanie = negocjator.getCharset(); ResourceBundle pakiet = negocjator.getBundle(); // może być równy null odp.setContentType("text/plain; charset=" + kodowanie); odp.setHeader("Content-Language", lokal.getLanguage()); odp.setHeader("Vary", "Accept-Language"); PrintWriter wyj = odp.getWriter(); DateFormat fmt = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, lokal); if (pakiet != null) { wyj.println("Po " + lokal.getDisplayLanguage() + ":"); wyj.println(pakiet.getString("powitanie")); wyj.println(fmt.format(new Date())); } else { wyj.println("Nie znaleziono pakietu."); } } catch (Exception w) { log(ServletUtils.getStackTraceAsString(w)); } }}

Powyższy serwlet rozpoczyna działanie od ustawienia nazwy pakietu, który chce wykorzystać, po czym pobieraswoje nagłówki Accept-Language i Accept-Charset. Tworzy LocaleNegotiator, przekazuje muinformacje i szybko pyta go, jakie Locale, ResourceBundle i kodowanie powinien wykorzystać. Proszęzauważyć, że serwlet może porzucić zwrócone kodowanie na rzecz kodowania UTF-8. Proszę także pamiętać, że

Page 296: Java Servlet - Programowanie

UTF-8 nie jest obsługiwane w tak szerokim zakresie, jak kodowania zwracane przez LocaleNegotiator.Następnie serwlet ustawia swoje nagłówki — jego nagłówek Content-Type określa kodowanie, Content-Language określa lokalny język, a nagłówek Vary wskazuje klientowi (jeżeli by się tym przejmował), żeserwlet ten może wyświetlać różną zawartość w zależności od nagłówka klienta Accept-Language.

Kiedy nagłówki zostaną ustawione, serwlet wyświetla informacje. Po pierwsze pobiera PrintWriter w celudopasowania kodowań. Następnie wyświetla — w domyślnym języku, zazwyczaj po angielsku — w jakim językuzostanie wyświetlone powitanie. Następnie pobiera i wyświetla odpowiednie powitanie z pakietu zasobów. Nakońcu wyświetla datę i godzinę odpowiednią dla lokalizacji klienta. Jeżeli wartość pakietu zasobów wynosinull, jak zdarza się to, kiedy nie ma pakietu zasobów pasującego do preferencji klienta, serwlet po prostuinformuje, że pakiet zasobów nie mógł zostać odnaleziony.

Klasa LocaleNegotiatorKod klasy LocaleNegotiator jest przedstawiony w przykładzie 13.9. Jej klasa wspomagająca,LocaleToCharsetMap, jest przedstawiona w przykładzie 13.10. Osoby, których nie interesuje negocjacjalokalizacji mogą opuścić ten podrozdział.

LocaleNegotiator działa poprzez skanowanie preferencji językowych klienta w poszukiwaniu dowolnegojęzyka, dla którego występuje odpowiadający mu pakiet zasobów. Kiedy go znajduje, wykorzystujeLocaleToCharsetMap w celu określenia kodowania. Jeżeli wystąpi dowolny problem, próbuje powrócić doamerykańskiego angielskiego. Logika ignoruje preferencje kodowania klienta.

Najbardziej skomplikowanym aspektem kodu LocaleNegotiator jest konieczność obsługi nieszczęśliwegozachowania ResourceBundle.getBundle(). Metoda getBundle() próbuje działać inteligentnie.Jeżeli nie może ona odnaleźć pakietu zasobów, który dokładnie pasuje do określonej lokalizacji, próbujeodnaleźć taki, który jest podobny. Dla celów tego przykładu problemem jest fakt, że getBundle() uważa zapodobny pakiet zasobów dla domyślnej lokalizacji. Tak więc podczas przeglądania języków klienta ciężko jestokreślić, kiedy występuje dokładne dopasowanie pakietu zasobów, a kiedy nie. Sposobem na to jest, po pierwszepobranie awaryjnego pakietu zasobów, po czym wykorzystanie odwołania do niego w celu określenia, czyznaleziono dokładne dopasowanie. Logika ta zawarta jest w metodzie getBundleNoFallback().

Przykład 13.9.

Klasa LocaleNegotiatorpackage com.oreilly.servlet;import java.io.*;import java.util.*;import com.oreilly.servlet.LocaleToCharsetMap;public class LocaleNegotiator { private ResourceBundle chosenBundle; private Locale chosenLocale; private String chosenCharset; public LocaleNegotiator(String bundleName, String languages, String charsets) { // Określenie wartości domyślnych: // Język angielski, kodowanie ISO-8859-1 (Latin-1), pakiet angielski Locale defaultLocale = new Locale("en", "US"); String defaultCharset = "ISO-8859-1"; ResourceBundle defaultBundle = null; try { defaultBundle = ResourceBundle.getBundle(bundleName, defaultLocale); } catch (MissingResourceException e) { // Nie odnaleziono domyślnego pakietu. Działanie bez zabezpieczenia. } // Jeżeli klient nie określi przyjmowanych języków, można zatrzymać domyślne if (languages == null) { chosenLocale = defaultLocale; chosenCharset = defaultCharset; chosenBundle = defaultBundle; return; // szybkie wyjście }

Page 297: Java Servlet - Programowanie

//Wykorzystanie znaczników do oddzielenia akceptowanych języków StringTokenizer tokenizer = new StringTokenizer(languages, ","); while (tokenizer.hasMoreTokens()) { // Pobranie następnego przyjmowanego języka // (Język może wyglądać tak: "en; wartoscq=0.91") String lang = tokenizer.nextToken(); // Pobranie lokalizacji dla danego języka Locale loc = getLocaleForLanguage(lang); // Pobranie pakietu dla tej lokalizacji. Nie należy pozwolić na dopasowanieinnych // języków! ResourceBundle bundle = getBundleNoFallback(bundleName, loc); // Zwrócony pakiet wynosi null, jeżeli nie znaleziono doapsowania. W tymprzypadku // nie można wykorzystać tego języka, ponieważ serwlet go nie zna. if (bundle == null) continue; // przejście do następnego języka // Odnalezienie kodowania, które można wykorzystać do wyświetlenia językatej // lokalizacji String charset = getCharsetForLocale(loc, charsets); // Zwrócone kodowanie wynosi null, jeżeli nie znaleziono doapsowania. W tym // przypadku nie można wykorzystać tego języka, ponieważ serwlet nie może go // zakodować if (charset == null) continue; // on to the next language // Jeżeli w tym miejscu, to nie ma problemów z tym językiem. chosenLocale = loc; chosenBundle = bundle; chosenCharset = charset; return; // koniec } // Brak dopasowania, zostaje domyślny chosenLocale = defaultLocale; chosenCharset = defaultCharset; chosenBundle = defaultBundle; } public ResourceBundle getBundle() { return chosenBundle; } public Locale getLocale() { return chosenLocale; } public String getCharset() { return chosenCharset; } private Locale getLocaleForLanguage(String lang) { Locale loc; int semi, dash; // Odcięcie wszystkich wartości q, które mogą następować po średniku if ((semi = lang.indexOf(';')) != -1) { lang = lang.substring(0, semi); } // Obcięcie wolnych miejsc lang = lang.trim(); // Utworzenie Locale z języka. Myślnik może oddzielać język od kraju if ((dash = lang.indexOf('-')) == -1) { loc = new Locale(lang, ""); // Brak myślnika, brak kraju } else { loc = new Locale(lang.substring(0, dash), lang.substring(dash+1)); } return loc; } private ResourceBundle getBundleNoFallback(String bundleName, Locale loc) {

Page 298: Java Servlet - Programowanie

// Po pierwsze pobranie pakietu awaryjnego — pakietu, który zostanie pobrany,jeżeli // getBundle() nie może odnaleźć bezpośredniego doapsowania. Pakiet ten będzie // porównywany z pakietami dostarczanymi przez następne wywołania getBundle()w celu // wykrycia, czy getBundle() znalazła bezpośrednie dopasowanie. ResourceBundle fallback = null; try { fallback = ResourceBundle.getBundle(bundleName, new Locale("bogus", "")); } catch (MissingResourceException e) { // Nie odnaleziono pakietu awaryjnego } try { // Pobranie pakietu dla określonej lokalizacji ResourceBundle bundle = ResourceBundle.getBundle(bundleName, loc); // Czy pakiet różni się od pakietu awaryjnego? if (bundle != fallback) { // Prawdziwe dopasowanie! return bundle; } // Tak więc pakiet jest taki sam, jak pakiet awaryjny. // To ciągle może być dopasowanie, ale tylko wtedy, gdy lokalny język pasujedo // języka domyślnej lokalizacji. else if (bundle == fallback && loc.getLanguage().equals(Locale.getDefault().getLanguage())) { // Inny sposób na dopasowanie return bundle; } else { // Brak dopasowania, kontynuacja poszukiwań } } catch (MissingResourceException e) { // Brak pakietu dla tej lokalizacji } return null; // Brak dopasowania } protected String getCharsetForLocale(Locale loc, String charsets) { // Uwaga — ta metoda ignoruje kodowania określone przez klienta return LocaleToCharsetMap.getCharset(loc); }}

Przykład 13.10.

Klasa LocaleToCharsetMappackage com.oreilly.servlet;import java.util.*;public class LocaleToCharsetMap { private static Hashtable map; static { map = new Hashtable(); map.put("ar", "ISO-8859-6"); map.put("be", "ISO-8859-5"); map.put("bg", "ISO-8859-5"); map.put("ca", "ISO-8859-1"); map.put("cs", "ISO-8859-2"); map.put("da", "ISO-8859-1"); map.put("de", "ISO-8859-1"); map.put("el", "ISO-8859-7"); map.put("en", "ISO-8859-1"); map.put("es", "ISO-8859-1"); map.put("et", "ISO-8859-1"); map.put("fi", "ISO-8859-1"); map.put("fr", "ISO-8859-1"); map.put("hr", "ISO-8859-2"); map.put("hu", "ISO-8859-2"); map.put("is", "ISO-8859-1"); map.put("it", "ISO-8859-1"); map.put("iw", "ISO-8859-8");

Page 299: Java Servlet - Programowanie

map.put("ja", "Shift_JIS"); map.put("ko", "EUC-KR"); // Wymaga JDK 1.1.6 map.put("lt", "ISO-8859-2"); map.put("lv", "ISO-8859-2"); map.put("mk", "ISO-8859-5"); map.put("nl", "ISO-8859-1"); map.put("no", "ISO-8859-1"); map.put("pl", "ISO-8859-2"); map.put("pt", "ISO-8859-1"); map.put("ro", "ISO-8859-2"); map.put("ru", "ISO-8859-5"); map.put("sh", "ISO-8859-5"); map.put("sk", "ISO-8859-2"); map.put("sl", "ISO-8859-2"); map.put("sq", "ISO-8859-2"); map.put("sr", "ISO-8859-5"); map.put("sv", "ISO-8859-1"); map.put("tr", "ISO-8859-9"); map.put("uk", "ISO-8859-5"); map.put("zh", "GB2312"); map.put("zh_TW", "Big5"); } public static String getCharset(Locale loc) { String charset; // Próba dopasowania pełnej nazwy (może zawierać kraj) charset = (String) map.get(loc.toString()); if (charset != null) return charset; // Jeżeli pełna nazwa nie pasuje, to może to być tylko język charset = (String) map.get(loc.getLanguage()); return charset; // może wynosić null }}

Lokalizacje dostarczane przez systemPocząwszy od Servlet API 2.2 serwlet może pobierać i wykorzystywać preferowane lokalizacje klienta przypomocy kilku prostych metod. ServletRequest posiada nową metodę getLocale(), która zwraca obiektLocale wskazujący na najbardziej preferowaną przez klienta lokalizację. W przypadku serwletów HTTPpreferencja oparta jest na nagłówku Accept-Language. Istnieje również metoda getLocales(), którazwraca Enumeration obiektów Locale wskazując wszystkie akceptowane przez klienta lokalizacje,rozpoczynając od najbardziej preferowanej. Metodom tym towarzyszy dołączona do ServletResponsemetoda setLocale(Locale loc), która pozwala serwerowi na określenie lokalizacji odpowiedzi. Metodata automatycznie ustawia nagłówek Content-Language oraz wartość kodowania Content-Type. MetodasetLocale() powinna zostać wywołana zaraz po setContentType(), a przed getWriter()(ponieważ modyfikuje typ zawartości i wpływa na tworzenie PrintWriter). Na przykład:

public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); Locale lokal = zad.getLocale(); odp.setLocale(lokal); PrintWriter out = odp.getWriter(); // Wyświetlenie wyników w oparciu o lokal.getLanguage()}

Metody te pozwalają na odwoływanie się do kodu, ale nie dostarczają pomocy w określaniu, czy dana Localejest obsługiwana przez aplikację WWW. W tym celu konieczna jest dodatkowa logika taka, jakLocaleNegotiator.

Formularze HTMLZarządzanie formularzami HTML wymaga pewnej ilości dodatkowej pracy i kilku sztuczek, kiedy obsługuje sięzawartość zlokalizowaną. Aby zrozumieć problem, proszę wyobrazić sobie następującą sytuację. FormularzHTML jest wysyłany jako część strony rosyjskiej. Prosi on użytkownika o jego nazwisko, które on wprowadzajako łańcuch rosyjskich znaków. Jak to nazwisko jest wysyłane do serwletu? I, co ważniejsze, jak serwlet możeje odczytać?

Odpowiedź na pierwsze pytanie jest taka, że wszystkie dane formularzy HTML są wysyłane jako sekwencjabajtów. Bajty te to zakodowana reprezentacja oryginalnych znaków. W przypadku języków

Page 300: Java Servlet - Programowanie

zachodnioeuropejskich, kodowanie jest domyślne, ISO-8859-1, z jednym bajtem na znak. W przypadku innychjęzyków mogą występować inne kodowania. Przeglądarki kodują dane formularzy przy pomocy tych samychkodowań, które zostały wykorzystane w przypadku strony zawierającej formularz. Tak więc jeżeli wspomnianawcześniej rosyjska strona została zakodowana przy pomocy ISO-8859-5, wysyłane dane formularza równieżzostaną zakodowane przy pomocy ISO-8859-5. Proszę zauważyć, że jeżeli strona nie określa kodowania, iużytkownik musi samodzielnie wybrać kodowanie ISO-8859-5 w celu przeglądania, duża część przeglądarekwyśle dane przy pomocy ISO-8859-120. Generalnie, zakodowany łańcuch bajtów zawiera dużą ilość specjalnychbajtów, które muszą zostać zakodowane w URL-u. Jeżeli przyjmie się, że rosyjski formularz wysyła nazwiskoużytkownika przy pomocy żądania GET, odpowiedni URL może wyglądać następująco:

http://serwer:port/servlet/ObslugaNazwisk?nazwisko=%8CK%8C%B4%90%B3%8E%9F

Odpowiedź na drugie pytanie, czyli jak serwlet odczytuje zakodowane informacje, jest nieco bardziejskomplikowana. Serwlet posiada dwie opcje wyboru. Po pierwsze, serwlet może pozostawić dane formularza wich surowej formie zakodowania, traktując je jak sekwencję bajtów — z każdym bajtem obrzydliwiezakodowanym jako znak w łańcuchu parametrów. Taktyka ta jest użyteczna jedynie wtedy, gdy serwlet nie musimanipulować danymi i może być pewny, że dane te zostaną wyświetlone jedynie temu samemu użytkownikowiwykorzystującemu to samo kodowanie. Drugą metodą jest konwersja przez serwlet danych formularza z ichpoczątkowego formatu do przyjaznego Javie łańcucha Unicode. Pozwala to serwletowi na swobodną manipulacjętekstem i wyświetlanie go przy pomocy innych kodowań. Jednak z metodą tą związany jest pewien problem.Aktualnie przeglądarki nie dostarczają żadnych informacji wskazujących na kodowanie wykorzystane w danychformularza. W przyszłości własność ta może zostać dodana (można na przykład wykorzystać w tym celunagłówek Content-Type w żądaniu POST), ale aktualnie to serwlet jest odpowiedzialny za śledzenie tejinformacji.

Ukryte kodowanieSzeroko akceptowaną techniką śledzenia kodowania wysłanych danych formularza jest wykorzystanie ukrytegopola formularza zawierającego kodowanie21. Jego wartość powinna zostać ustawiona na kodowanie strony, którago zawiera. Następnie każdy serwlet odbierający formularz mógłby odczytać wartość pola kodowania i wiedzieć,jak zdekodować wysłane dane formularza.

Przykład 13.11 przedstawia tą technikę w przypadku generatora formularza, który ustawia kodowanie tak, abypasowało ono do kodowania strony. Poniżej znajduje się angielski pakiet zasobów, który może zostać dołączonydo serwletu, przechowany jako FormKod_en.properties:

tytul=FormKodnaglowek=<h1>Charset Form</h1>tekst=Enter text:

Poniżej znajduje się pakiet rosyjski, przechowywany jako FormKod_ru.properties:tytul=FormKodnaglowek=<h1>\u0424\u043e\u0440\u043c\u0443\u043b\u044f\u0440</h1>tekst=\u041d\u0430\u043f\u0438\u0448\u0438 \u0442\u0435\u043a\u0441\u0442

Przykład 13.11.

Zapisywanie kodowania w ukrytym polu formularzaimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.LocaleNegotiator;import com.oreilly.servlet.ServletUtils;public class FormKod extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { try { String nazwaPakiet = "FormKod"; String akceptJezyk = zad.getHeader("Accept-Language"); String akceptKod = zad.getHeader("Accept-Charset"); LocaleNegotiator negocjator =

20 Większa ilość informacji na temat internacjonalizacji HTML i formularzy HTML jest dostępna w dokumencie RFC 2070pod adresem http://www.ietf.org.rfc/rfc2070.txt.21 Ukryte pola formularzy zostały wprowadzone w rozdziale 7, w którym były wykorzystywane do śledzenia sesji.

Page 301: Java Servlet - Programowanie

new LocaleNegotiator(nazwaPakiet, akceptJezyk, akceptKod); Locale lokal = negocjator.getLocale(); String kodowanie = negocjator.getCharset(); ResourceBundle pakiet = negocjator.getBundle(); // może wynosić null odp.setContentType("text/html; charset=" + kodowanie); odp.setHeader("Content-Language", lokal.getLanguage()); odp.setHeader("Vary", "Accept-Language"); PrintWriter wyj = odp.getWriter(); if (pakiet != null) { wyj.println("<HTML><HEAD><TITLE>"); wyj.println(pakiet.getString("tytul")); wyj.println("</TITLE></HEAD>"); wyj.println("<BODY>"); wyj.println(pakiet.getString("naglowek")); wyj.println("<FORM ACTION=/servlet/AkcjaKod METHOD=GET>"); wyj.println("<INPUT TYPE=HIDDEN NAME=kodowanie VALUE=" + kodowanie + ">"); wyj.println(pakiet.getString("tekst")); wyj.println("<FONT FACE='Czar'>"); wyj.println("<INPUT TYPE=TEXT NAME=tekst>"); wyj.println("</FORM>"); wyj.println("</BODY></HTML>"); } else { wyj.println("Nie odnaleziono pakietu."); } } catch (Exception w) { log(ServletUtils.getStackTraceAsString(w)); } }}

Wynik uruchomienia wersji rosyjskiej jest przedstawiony na rysunku 13.6.

Rysunek 13.6.

Formularz rosyjski, z tekstem wpisanym przez użytkownika

Serwlet odpowiedzialny za obsługę wysłanego formularza jest przedstawiony w przykładzie 13-12. Serwlet tenodczytuje wysłany tekst i dokonuje jego konwersji na Unicode, po czym wyświetla znaki przy pomocykodowania UTF-8. Dodatkowo, wyświetla on również otrzymany łańcuch jak łańcuch kodów ucieczkowychUnicode, pokazując, że należałoby dane wpisać w pliku źródłowym Javy lub pakiecie zasobów, aby otrzymaćidentyczny wynik. Pozwala to serwletowi na działanie jako oparty na WWW tłumacz kodowania początkowegona łańcuch Unicode.

Przykład 13.12.

Otrzymywanie kodowania w ukrytym polu formularzaimport java.io.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;

Page 302: Java Servlet - Programowanie

public class AkcjaKod extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { try { odp.setContentType("text/plain; charset=UTF-8"); PrintWriter wyj = odp.getWriter(); String kodowanie = zad.getParameter("kodowanie"); // Pobranie parametru tekstu String tekst = zad.getParameter("tekst"); // Teraz konwersja go z tablicy bajtów do tablicy znaków. // Wykonanie tego przy pomocy kodowania wysłanego w ukrytym polu. // Traktowanie oryginalnych wartości jako surowych 8-bitowych bajtówwewnątrz //łańcucha BufferedReader reader = new BufferedReader( new InputStreamReader(new StringBufferInputStream(tekst), kodowanie)); tekst = reader.readLine(); wyj.println("Otrzymane kodowanie: " + kodowanie); wyj.println("Otrzymany tekst: " + tekst); wyj.println("Otrzymany tekst (Unicode): " + doLancuchUcieczkiUnicode(tekst)); } catch (Exception w) { w.printStackTrace(); } } public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { doGet(zad, odp); } private static String doLancuchUcieczkiUnicode(String lan) { // utworzony według kodu w java.util.Properties.save() StringBuffer buf = new StringBuffer(); int dlug = lan.length(); char zn; for (int i = 0; i < dlug; i++) { zn = lan.charAt(i); switch (zn) { case '\\': buf.append("\\\\"); break; case '\t': buf.append("\\t"); break; case '\n': buf.append("\\n"); break; case '\r': buf.append("\\r"); break; default: if (zn >= ' ' && zn <= 127) { buf.append(zn); } else { buf.append('\\'); buf.append('u'); buf.append(doHeks((zn >> 12) & 0xF)); buf.append(doHeks((zn >> 8) & 0xF)); buf.append(doHeks((zn >> 4) & 0xF)); buf.append(doHeks((zn >> 0) & 0xF)); } } } return buf.toString(); } private static char doHeks(int polbajtu) { return hexCyfra[(polbajtu & 0xF)]; } private static char[] hexCyfra = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };}

Przykładowy wynik przedstawiony jest na rysunku 13.7.

Page 303: Java Servlet - Programowanie

Rysunek 13.7.

Obsługa rosyjskiego formularza

Najbardziej interesującą częścią powyższego serwletu jest fragment, który otrzymuje i konwertuje wysłany tekst:tekst = new String(tekst.getBytes("ISO-8859-1"), kodowanie);

Wywołanie tekst.getBytes("ISO-8859-1") dokonuje konwersji tekstu do jego surowego formatu byte.Chociaż wartość parametru jest zwracana jako String, nie jest to prawdziwy łańcuch. Każdy znak w Stringnaprawdę przechowuje jeden bajt zakodowanego tekstu, wymagając tej specjalnej konwersji. Otaczającykonstruktor String tworzy następnie String z surowych bajtów przy pomocy kodowania określonego w polukodowania. Nie wygląda to zbyt ładnie, ale działa. Bardziej eleganckim rozwiązaniem jest klasacom.oreilly.servlet.ParameterParser opisana w rozdziale 19, „Informacje dodatkowe”.

Page 304: Java Servlet - Programowanie

Rozdział 14. Szkielet Tea

W tym momencie można przejść do omawiania opartych na serwletach szkieletów służących do tworzeniazawartości stron WWW. Technika JSP zostanie omówiona później, ponieważ jest on najbardziej skomplikowanąalternatywą tworzenia zawartości. Jako pierwsza omówiona zostanie Tea. TeaServlet (znana powszechnie jakoTea) to produkt Walt Disney Internet Group (WDIG) — dawniej GO.com — rozwijany przez wiele lat wewnątrzfirmy w celu wspomożenia tworzenia witryn o wysokim obciążeniu takich jak ESPN.com, NFL.com, Disney.com,DisneyLand.com, Movies.com, ABC.com i GO.com. Niedawno produkt ten został udostępniony jako OpenSource w nadziei, że inni uznają Tea za narzędzie przydatne i pomogą w jego rozwoju. Strategia ta posiada sens— dzielenie się narzędziami w nadziei, że inni pomogą je wyostrzyć. Tu omówiona zostanie TeaServlet 1.1.0,dostępna pod adresem http://opensource.go.com. Wymaga ona Servlet API 2.1 i JDK 1.2 lub późniejszych.Licencja TeaServlet jest oparta na licencji Apache'a, jednej z najmniej restrykcyjnych i przez to atrakcyjnych dlaprogramistów, ponieważ według niej można wykorzystywać Tea do tworzenia nowych produktów i witryn bezudostępniania tych produktów jako Open Source. Ten model licencji wykorzystywany jest przez wszystkieprojekty Apache, włączając w to serwery Apache i Tomcat.

Tea została zaprojektowana dla zadań prowadzonych przez małe zespoły programistów i producentówtechnicznych. Role programisty to tworzenie „aplikacji” napisanych w Javie i zainstalowanych w TeaServlet.Producent tworzy i utrzymuje ostateczny wygląd dynamicznych stron WWW poprzez tworzenie szablonów Tea,które wywołują funkcje dostarczone przez aplikacje programisty. Na przykład, w witrynie ESPN, jedenprogramista tworzy aplikację służącą do zarządzania statystykami zespołów, inny (pracujący niezależnie) tworzyaplikację służącą do zarządzania statystykami zawodników, a producent techniczny umieszcza dane w sieci przypomocy szablonów Tea. Właściwie kilku producentów pracujących niezależnie może wykorzystywać te samedane wspierające w celu utworzenie stron ukierunkowanych na różnych użytkowników — działanie, które firmaWDIG wykonała w swoich witrynach włączając w to Disney.com, Movies.com, ESPN.com i GO.com. Szablonytworzone są w języku Tea w celu wymuszenia całkowitego oddzielenia zawartości od prezentacji.

Język TeaTea to język programowania zaprojektowany do formatowania tekstu. Posiada on silnie zaznaczone typy, musibyć kompilowany i został zaprojektowany do pracy w środowisku opartym na Javie. Środowiskiem tym jestTeaServlet, narzędzie wykorzystujące Tea do tworzenia stron WWW i umożliwiające dostęp do mechanizmówserwletów, z których wywoływane są pliki szablonów Tea22.

Głównym celem języka Tea jest wymuszenie oddzielenia zawartości i prezentacji, bez poświęcaniapodstawowych konstruktów programistycznych. Do czasu pisania tej książki, Tea została utworzona napodstawie czterech ograniczeń:

• Dane i typy danych nie mogą być tworzone bezpośrednio. Są one odczytywane.

• Odczytane dane nie mogą zostać bezpośrednio modyfikowane w żaden sposób.

• Szablon nie może w bezpośredni sposób uszkadzać swojego środowiska.

• Dostarczana jest jedynie minimalna ilość konstruktów programistycznych.

22 Chociaż język Tea został zaprogramowany do formatowania tekstu, właściwie możliwa jest kontrola szablonu Tea nadklasą aplikacji tworzącą rysunek i łatwo jest stworzyć wspólnie dynamiczny obrazek. Inaczej niż pozostałe narzędzia, którezostaną omówione, szablony Tea mogą wyświetlać również dane binarne, tak samo jako znakowe.

Page 305: Java Servlet - Programowanie

Ograniczenia te dodatkowo chronią system przed uszkodzonymi bądź błędnymi szablonami, co jest własnościąniezwykle pożądaną w przypadku witryn stale przyjmujących dużą ilość szablonów, jak zdarza się to wpopularnych witrynach informacyjnych. Projekt ten jest wygodny również dla firm hostingowych, które musządawać klientom możliwość tworzenia stron dynamicznych, ale nie chcą, aby żaden konkretny błąd klienta mógłwpłynąć negatywnie na pozostałą część serwera.

Ale czy kolejny język, zwłaszcza język, który potrafi o wiele mniej niż Java jest tak naprawdę potrzebny? TeaTemplate Language Manual (Podręcznik Języka Szablonów Tea) odpowiada na to pytanie:

Tea jest wynikiem wielu lat doświadczenia z innymi mechanizmami tworzenia stron WWW. Większośćopartych WWW aplikacji rozpoczyna się od znaczników HTML osadzonych w kodzie, niezależnie odtego, czy jest to C, Perl, czy Java. Podejście to jest odpowiednie dla małych lub początkującychprojektów, ponieważ nie wymaga dużej ilości programowania.

Ponieważ zmiany w formatowaniu strony mogą zdarzać się często, a twórcy nie chcą ich dokonywać,nieodwołalnie przesuwają się oni w stronę wykorzystywania pewnego rodzaju szablonowegomechanizmu wymiany elementów. Każdy element jest po prostu obszarem zablokowanym dla łańcucha,który zawiera dane utworzone przez aplikację. Te systemy szablonów dalej ewoluują w stronę obsługispecjalnych konstrukcji służących do formatowania tabeli, formularzy i prostej logiki warunkowej.

Podczas wprowadzania konstrukcji programistycznych do szablonu, podstawową trudnością jeststworzenie czegoś, co będzie posiadać wystarczającą moc, a zarazem będzie proste i bezpieczne. Jeżelijest to zbyt potężne, wtedy kompletne aplikacje mogą być tworzone przez szablony. Jeżeli jest zbytsłabe, wtedy w aplikacji musi być zawarte formatowanie HTML. Jeżeli nie jest proste lub bezpieczne,wtedy programiści aplikacji musza sami stworzyć szablony.

Inaczej niż w przypadku istniejących języków osadzanych w czymś podobnym do ASP lub JSP, Tea jestjęzykiem zaprojektowanym specjalnie do tego, aby spełniać wymagania systemu szablonów. Jest onbezpieczny, prosty, wydajny i potężny.

Każdy egzemplarz Tea jest zintegrowany ze specjalnym serwletem. Serwlet ten daje szablonom Teakontrolę nad tworzeniem strony, zachowując silne powiązania z aplikacją wspierającą utworzoną przezprogramistę Javy. Podczas gdy serwlet ten dostarcza funkcjonalności podobnej do JSP, Tea wymuszapoprawne rozdzielenie model-widok ze względu na międzynarodowe ograniczenia języka. Chociaż jestto także sugerowany model separacji w JSP, nie może on być w tamtym przypadku wymuszony.Dodatkowo szablony Tea nie obsługują własności programów, które mogą zostać wykorzystanenieodpowiedzialnie. Modyfikacje nie muszą przechodzić przez ścisłą korektę i fazę testów, która jestwymagana w przypadku JSP.

Każda osoba pracująca nad projektem powinna mieć dostęp do narzędzi pozwalających na pracę zmaksymalną wydajnością, a Tea wykonuje swoją część pracy poprzez umożliwienie programiściewykonywania dokładnie tego, co jest mu potrzebne, tak łatwo jak to tylko możliwe, i żadnej dodatkowejpracy ponad to. Nawet w przypadku projektów tworzonych przez początkujących programistówwykorzystanie Tea posiada swoje zalety. Narzędzie wspomaga nabywanie dobrych praktykprogramistycznych i ułatwia utrzymanie aplikacji.

Czytelnicy niniejszej książki powinni również pamiętać, że Tea nie jest językiem zaprojektowanym dlaprogramistów, ale dla producentów technicznych. Jest językiem prostszym niż Java, bezpieczniejszym, ale taksamo wydajnym.

PoczątkiAby zapoznać się z Tea, można przyjrzeć się kilku samodzielnym szablonom. Szablony te nie wykorzystujążadnych wspierających „aplikacyjnych” klas Javy, i w związku z tym nie mają wielkich możliwości działania.Poniżej przedstawiono pierwszy z szablonów:

<% template ProstaStrona() %>To jest prosta strona, która nie robi praktycznie nic.

Powyższy szablon po prostu wyświetla „To jest prosta strona, która nie robi praktycznie nic.” każdemu, ktouzyska do niej dostęp. Szablony złożone są z obszarów kodu i tekstu. Obszary kodu są ograniczane znakami <% i%> (nie są wymagane żadne inne znaki ograniczające). Tekst poza obszarem kodu jest wyświetlany przezszablon bez żadnego formatowania, właśnie dlatego powyższy szablon po prostu wyświetla proste zdanie.

Aby uruchomić szablon, należy po pierwsze zapamiętać go w pliku o nazwie ProstaStrona.tea. Podobnie jak wklasach Javy, nazwa pliku musi być taka sama jak nazwa szablonu zadeklarowana przez konstruktora (łącznie z

Page 306: Java Servlet - Programowanie

wielkością liter), a plik musi posiadać rozszerzenie .tea. Miejsce, w którym szablon powinien zostać zapisany,jest konfigurowalne. Poleca się katalog będący podkatalogiem WEB-INF, taki jak WEB-INF/szablony.

Następnym etapem przed uruchomieniem aplikacji jest instalacja samej Tea. Należy pobrać dystrybucję dostępnąpod adresem http://opensource.go.com i wypełnić instrukcje instalacji w niej zawarte — umieścić plikTeaServlet.jar w ścieżce klas serwera WWW. Następnie należy dokonać edycji deskryptora web.xml aplikacjiWWW w celu zarejestrowania TeaServlet pod nazwą tea z parametrem inicjacji określającym pozycjęinformacji o konfiguracji. Należy również skonfigurować zasadę odwzorowania przedrostków tak, że tea/*wywołuje serwlet tea. Przykład dodatku do web.xml jest przedstawiony w przykładzie 14.1.

Przykład 14.1.

Instalacja TeaServlet<!-- ... --><servlet> <servlet-name> tea </servlet-name> <servlet-class> com.go.teaservlet.TeaServlet </servlet-class> <init-param> <param-name> properties.file </param-name> <param-value> <!—Należy dokonać edycji tak, aby wskazywało na bezwzględną ścieżkę do--> <!—pliku właściwości--> /tomcat/webapps/teatime/WEB-INF/TeaServlet.properties </param-value> </init-param></servlet><servlet-mapping> <servlet-name> tea </servlet-name> <url-pattern> /tea/* </url-pattern></servlet-mapping><!-- ... -->

Jedyną informacją, którą należy dostosować, jest ścieżka do pliku TeaServlet.properties. Powinna być to ścieżkabezwzględna. Plik TeaServlet.properties definiuje zachowanie TeaServlet. Może być on stosunkowo długi,dlatego, że plik web.xml wskazuje na zewnętrzny plik zamiast bezpośrednio zawierać informacje konfiguracyjne.Pełne omówienie pliku właściwości nastąpi później, a teraz wykorzystany będzie najprostszy z możliwych plikTeaServlet.properties:

# TeaServlet.properties## Określenie miejsca szablonów Teatemplate.path = /tomcat/webapps/herbatka/WEB-INF/szablony

Proszę upewnić się co do umieszczenia pliku właściwości w miejscu określonym w pliku web.xml. Ostatnimkrokiem jest ponowne uruchomienie serwera tak, aby mógł odczytać nowy plik web.xml (w przypadku niektórychserwerów nie jest to konieczne).

Kiedy TeaServlet zostanie właściwie zainstalowany, dostęp do ProstaStrona można uzyskać w miejscu takim jakhttp://localhost:8080/herbatka/tea/ProstaStrona. Część ścieżki /herbatka określa aplikację WWW, do którejuzyskiwany jest dostęp. Następna część /tea jest tak sama, jak odwzorowanie przedrostków podane w web.xmlherbatka, co powoduje uruchomienie TeaServlet w celu obsłużenia żądania. Szablony mogą również zostaćumieszczone w podkatalogach pod template.path, w którym to przypadku wywoływane są przy pomocyścieżki zawierającej podkatalogi takie jak /herbatka/tea/podkat/ProstaStrona.

Informacja o żądaniuTworzenie statycznej zawartości wiąże się z dużą ilością pracy, tak więc teraz utworzone zostanie coś bardziejdynamicznego. Konstruktor szablonu może przyjmować dowolną ilość parametrów, których wartości sąautomatycznie przekazywane z parametrów żądania (poprzez łańcuch zapytania i dane POST). Parametry temogą być typu String, Integer, Float, Double i każdego innej podklasy java.lang.Number.Konwersja do typów numerycznych następuje automatycznie. Jeżeli nie zostanie przekazany żaden parametr o

Page 307: Java Servlet - Programowanie

danej nazwie, lub konwersja typu nie powiedzie się, szablon otrzymuje wartość parametru null. Szablonprzedstawiony w przykładzie 14-2 wykorzystuje swój konstruktor do przyjęcia parametru o nazwie nazwisko.

<% template ProstaStrona(String nazwisko) %>To jest prosta strona, która nie robi praktycznie nicpoza wyświetleniem nazwiska: <% nazwisko ?>

Jeżeli uzyska się dostęp do szablonu jako herbatka/tea/ProstaStrona?imię=Janek, na stronie wyświetlonezostanie „Janek”. Jeżeli dostęp do szablonu nastąpi bez parametru nazwisko, na stronie wyświetlone zostanie„null”. Przykład 14.2 przedstawia szablon sprawdzający ten parametr.

Przykład 14.2.

Szablon sprawdzający nazwiska<% template ProstaStrona(String nazwisko) %>To jest prosta strona, która nie robi praktycznie nicpoza wyświetleniem nazwiska: <% nazwisko ?><P><%if (nazwisko == null) { "Nie podano nazwiska"}if (name == "Sędzia") { "Proszę wstać"}%>

Powyższy nowy szablon sprawdza , czy nazwisko wynosi null i, jeżeli tak, wyświetla użytkownikowiostrzeżenie. Szablon sprawdza również, czy podane nazwisko brzmi Sędzia i , jeżeli tak, traktuje użytkownikaw specjalny sposób. Ważne jest, by zauważyć dwa elementy porównania Sędzia — po pierwsze wartościłańcuchów w Tea mogą zostać sprawdzone pod kątem równości przy pomocy operatora == (jak to jest wprzypadku wszystkich obiektów). Oznacza to, że nie jest konieczne wyjaśnianie producentowi technicznemu celumetody .equals(). Po drugie, łańcuch null może być porównany z innym łańcuchem bez ryzyka wystąpieniawyjątku NullPointerException, co mogłoby się zdarzyć w przypadku .equals().

Sięganie głębiejInne informacje o żądaniu są dostępne przy pomocy TeaServlet. Na przykład, szablon Tea może uzyskaćbezpośredni dostęp do nagłówków i cookies żądania, a także do parametrów. Przykład 14.3 przedstawia szablonTea przeglądający informacje o żądaniu. W praktycznych przypadkach, rzadkością jest konieczność uzyskaniaprzez szablon Tea bezpośredniego dostępu do tych informacji, zamiast tego można wykorzystać aplikację, któramoże dostarczyć przetworzoną wersję informacji.

Przykład 14.3.

Szablon przeglądający informacje o żądaniu<% template Przegl() %><HTML><HEAD><TITLE>Przeglądanie</TITLE></HEAD><BODY><% zadanie = getRequest() // Typ obiektu jest narzucony %><H1>Różne informacje</H1>QueryString: <% zadanie.queryString %> <BR>RemoteUser: <% zadanie.remoteUser %> <BR><H1>Informacje o parametrach</H1><%foreach (paramName in zadanie.parameters.names) { paramName ": " zadanie.parameters[paramName] "<BR>"}%><H1>Informacja o atrybutach żądania</H1><%foreach (attribName in zadanie.attributes.names) { attribName ': ' zadanie.attributes[attribName] '<BR>'}%>

<H1>Header Informacja o nagłówkach</H1><%foreach (headerName in zadanie.headers.names) {

Page 308: Java Servlet - Programowanie

headerName ': ' zadanie.headers[headerName] '<BR>'}%><h1>Informacja o cookies</h1><%foreach (cookie in request.cookies.all) { cookie.name ': ' cookie.value '<BR>'}%>Wartość cookie identyfikatora sesji wynosi<% zadanie.cookies["JSESSIONID"].value %>.<% // Rzeczy niemożliwe do wykonania w Tea... %><%/*"<H1>Odczyt z sesji użytkownika</H1>"zadanie.session["moze"] = "popracuje pozniej""<H1>Ustawienie atrybutu</H1>"zadanie.attributes["nie"] = "będzie pracował""<H1>Konfiguracja cookie</H1>"// Manipulacje odpowiedzią poprzez funkcje aplikacji*/%></BODY></HTML>

Powyższy przykład może powiedzieć dużo o Tea. Po pierwsze, zmienne lokalne (takie jak zadanie) nie musząbyć deklarowane według typu obiektu. Posiadają one silnie zaznaczony typ, ale przydzielanie typu jest ukryte. Wjęzyku istnieje słowo kluczowe isa pozwalające na obsługę kwestii związanych z tworzeniem zmiennych, jeżelijest to konieczne.

Wykorzystane zostało wyrażenie foreach, które obsługuje często występujące zadanie wykonywania pętli nadzestawem obiektów. Jest to elastyczna konstrukcja, która może działać zarówno na tablicach, jak i na każdychinnych obiektach będących rozszerzeniem java.util.Collection. Wyrażenie foreach obsługujerównież działanie według zakresu i wstecz. Na przykład:

foreach (licznik in 1 .. 10 reverse) { licznik '<BR>' }wyświetla odliczanie począwszy od 10. Wyrażenie foreach obsługuje w Tea wszystkie zadania związane zpętlami, nie występują wyrażenia for i while. (W związku z tym w Tea nie występują żadne pętlenieskończone.)

Wyrażenia w Tea nie muszą kończyć się średnikiem, jak to jest w przypadku Javy, ani też nie muszą występowaćw osobnych liniach. Zamiast tego, kompilator Tea wykorzystuje skonstruowaną uważnie gramatykę Tea w celuobliczenia końca wyrażeń! Średniki są obsługiwane w celu umożliwienia rzadkich okoliczności, w którychśrednik jest konieczny do uniknięcia dwuznaczności wyrażeń i jako ułatwienie dla programistów Javy, którzyinstynktownie dodają średniki.

Zazwyczaj nie ma potrzeby wykorzystania w Tea operatora łączenia łańcuchów. Obecność łańcuchów w„podwójnych cudzysłowach” i 'pojedynczych cudzysłowach' wewnątrz kodu Tea dostarcza wystarczającychwskazówek, w którym miejscu powinno wystąpić łączenie. W celu uzyskania bardziej zaawansowanego łączenianależy wykorzystać znak &. Znak + nie jest wykorzystywany, ponieważ jego nadmierne stosowanie powodujeniejasności.

Komentarze w Tea tworzone są według standardów Javy, w których /* i */ ograniczają komentarze o długościwiększej niż jedna linia, a // rozpoczyna komentarz jednoliniowy. Komentarze te nie są wysyłane do klienta,jeżeli pamięta się o umieszczeniu ich wewnątrz bloku kodu Tea.

W powyższym przykładzie wykorzystano długi komentarz w celu zademonstrowania działań, które nie mogązostać wykonane przez Tea — odczyt z sesji użytkownika, ustawianie atrybutów żądania, konfiguracja cookie iwłaściwie wysyłanie czegokolwiek w żądaniu. Zdolność do uzyskania dostępu do sesji może zostać dodana wpóźniejszym czasie jako część procesu projektowania Open Source tworzącego Tea. Nie jest ona obecnieobsługiwana tylko dlatego, że WDIG wewnętrznie stosuje inny mechanizm śledzenia sesji. Inne własnościcelowo nie są obsługiwane i muszą zostać wykonane przez kod Javy w aplikacji wspierającej, z powodówzałożeń projektowych, które głoszą, że „otrzymane dane nie mogą być modyfikowane w żaden sposób”.

Administracja TeaSzkielet TeaServlet wyświetla stronę WWW (napisaną oczywiście w Tea) służącą jako centrum administracyjne,przy pomocy której administrator witryny może zarządzać wszystkimi szablonami Tea i wspierającymi klasamiaplikacji. Ekran administratora umożliwia przeglądanie aktualnie załadowanych szablonów i uruchomionych

Page 309: Java Servlet - Programowanie

aplikacji, a także wykorzystanie funkcji udostępnionych przez te aplikacje w stylu Javadoc. Umożliwia równieżdostęp do dziennika zdarzeń dla szablonów, pozwalając administratorowi przeglądanie wszystkich dziennikówlub ich wybranych części, w oparciu o szablon i poziom dziennika (debug, info, warn lub error). Ekranadministracyjny dostarcza również mechanizmu służącego do kontrolowanego przeładowywania szablonów,które zostanie szczegółowo omówione w dalszej części. Rysunek 14.1 przedstawia przykładową stronęwyświetlającą szablony.

Rysunek 14.1.

Administrowaniezaładowanymiobecnieszablonami

W celu uruchomienia aplikacji administracyjnej TeaServlet należy wcześniej skonfigurować plikTeaServlet.properties. Przykładowa konfiguracja przedstawiona jest w przykładzie 14.4.

Przykład 14.4. Początkowy plik TeaServlet.properties# TeaServlet.properties# Określenie lokalizacji szablonów Tea (plików .tea)template.path = /tomcat/webapps/herbatka/WEB-INF/szablony# Określenie opcjonalnej ścieżki do miejsca, W którym system będzie zapisywał # skompilowane szablony jako pliki .classtemplate.classes = /tomcat/webapps/herbatka/WEB-INF/klasySzablony# Określenie opcjonalnego domyślnego szablonu, który zostanie załadowany, jeżelinie # poda się innegotemplate.default = Indeks

# Określenie wspierających aplikacji, które zostaną załadowane do systemuTeaServletapplications { "System" { # SystemApplication dostarcza wsparcia administracyjnego dla TeaServlet class = com.go.teaservlet.AdminApplication init { # klucz bezpieczeństwa dla strony administracyjnej admin.key = admin admin.value = true } } "Inne" { class = WiecejNaTematAplikacjiPozniej } # Dla przykładu 14-8 "NarzedziaAp" { class = NarzedziaAp init { narzedziaPlik = /tomcat/webapps/herbatka/WEB-INF/narzedzia.xml } }}# Określenie, które wiadomości aplikacji są umieszczane w dzienniku zdarzeńlog.debug = truelog.info = truelog.warn = truelog.error = true

Page 310: Java Servlet - Programowanie

Plik TeaServlet.properties konfiguruje system TeaServlet podobnie jak plik web.xml konfiguruje aplikacjęWWW. Plik ten podaje TeaServlet lokalizację szablonów oraz gdzie powinny zostać umieszczone skompilowaneklasy szablonów tak, aby mogły być przechowywane podczas ponownych uruchomień serwera. Zawiera takżeinformacje, które aplikacje powinny być załadowane dla wsparcia szablonów oraz który poziom wiadomościdziennika zdarzeń powinien zostać domyślnie zastosowany. Każda aplikacja musi zostać zarejestrowana wTeaServlet.properties przy pomocy określonego pliku klasy oraz opcjonalnie określonymi parami parametrównazwa-wartość.

Struktura TeaServlet.properties jest podobna do tradycyjnego pliku java.util.Properties, z kilkomapoważnymi ulepszeniami — zachowano kolejność elementów, cudzysłowy (pojedyncze lub podwójne) mogązostać wykorzystane do zdefiniowania kluczy, w których osadzone są spacje, a własności mogą zostaćzagnieżdżone przy pomocy zakręconych nawiasów. Przykład 14.4 mógłby zostać napisany jako zwykły plikwłaściwości Javy, jak przedstawiono poniżej (chociaż po odczytaniu go przez java.util.Propertiesstracony zostałby porządek elementów). Plik ten mógłby zostać napisany przy pomocy XML, chociaż dlaprostych zastosowań takich jak to XML byłby niepotrzebnym utrudnieniem.

template.path = /tomcat/webapps/herbatka/WEB-INF/szablonytemplate.classes = /tomcat/webapps/herbatka/WEB-INF/klasySzablonyapplications.System.class = com.go.teaservlet.AdminApplicationapplications.System.admin.key = adminapplications.System.admin.value = trueapplications.Inne.class = WiecejNaTematAplikacjiPozniejlog.debug = truelog.info = truelog.warn = truelog.error = true

Po dokonaniu edycji pliku TeaServlet.properties i ponownym uruchomieniu serwera, system jest gotowy dodziałań administracyjnych. Domyślnym położeniem aplikacji administracyjnej (zakładając, że została onaumieszczona w kontekście /herbatka) jesthttp://localhost:8080/herbatka/tea/system/teaservlet/Admin?admin=true. Wyrażenie ?admin=true działa jakoprymitywne zabezpieczenie. Nazwa i wartość muszą zgadzać się z wartościami admin.key/admin.value zpliku TeaServlet.properties. Tak naprawdę nic by się nie stało, jeżeli człowiek z zewnątrz uzyskałby dostęp dostron administracyjnych, ale pozostawienie frontowych drzwi otwartych nie jest mądre, tak więc istniejeprymitywny zamek. Przypuszczalnie społeczność Open Source szybko poprawi mechanizm bezpieczeństwa.

Najczęstszym zastosowaniem dla aplikacji administracyjnej jest ponowne ładowanie plików szablonów.Szablony nie są przeładowywane podczas otwierania, jak strony JSP. Zmiany w szablonach nie są widoczne,dopóki nie zostanie naciśnięty guzik „Reload Changes” („Przeładuj zmiany”) na ekranie administratoraTemplates (Szablony). Kompilowanie szablonów tylko na żądanie jest bardziej wydajne niż sprawdzanieznacznika czasu podczas żądania oraz pozwala także na wystąpienie wyraźniejszego kroku „opublikowanie”aktywnej witrynie. Aplikacja administracyjna nie przyjmuje zmian, jeżeli szablonu nie uda się skompilować,pozostawiając poprzednią wersję, tak więc klienci absolutnie nie mogą zobaczyć błędu kompilacji. Wszystkiebłędy występujące podczas kompilacji zostają wyświetlone przez aplikację administracyjną, jak przedstawionona rysunku 14.2.

Page 311: Java Servlet - Programowanie

Rysunek 14.2.

Obszerne i precyzyjnewiadomości o błędachkompilacji

Proszę zauważyć, że lokalizacja błędów i ich opis jest są absolutnie precyzyjne, TeaServlet może to uczynić,ponieważ szablony Tea są kompilowane bezpośrednio do kodu bajtowego Javy! Nie jest tworzony żadenpośredni plik .java, a w związku z tym nie jest konieczne zastosowanie żadnego kompilatora Javy.Kompilowanie szablonów bezpośrednio do kodu bajtowego jest wykonywane przy pomocy technik JIT iHotSpot w celu osiągnięcia maksymalnej wydajności, ale inaczej niż w przypadku stron JSP, kompilator pracujebezpośrednio na źródle utworzonym przez człowieka i w związku z tym potrafi lepiej zdiagnozować błędy bezpośredniczącego pliku rozmywającego bezpośredniość. Poza tym, Tea nie wymaga dołączenia kompilatora Javydo serwera WWW, który jest potrzebny w przypadku JSP. Wymaganie to sprawia problemy związane zlicencjonowaniem. Tea może również prekompilować szablony, umożliwiając dystrybucję szablonów bez ichźródeł.

Skompilowane szablony: szansa dla biznesu

Uwaga!!!!

Możliwość dystrybucji prekompilowanych szablonów Tea tworzy nowy, interesujący model biznesowy.Producent tworzący aplikację WWW przy pomocy Tea może sprzedawać aplikację WWW bez udostępnianiaoryginalnego kodu źródłowego szablonów. Potrzebna jest jedynie biblioteka Tea, która posiada możliwośćprzenoszenia pomiędzy serwerami i może być dołączana do aplikacji. Możliwość udostępniania produktu bezkodów źródłowych może spowodować, że niektórzy producenci utworzą nowe aplikacje WWW na sprzedaż.

Proszę zauważyć, że JSP również obsługuje prekompilowanie stron. Niestety, kod bajtowy tworzony przezJSP jest często uzależniony od klas związanych z danym kontenerem, a tworzona klasa musi być zgodna zzależną od kontenera konwencją nadawania nazw (proszę zobaczyć przykład 18-2 z rozdziału 18,„JavaServer Pages”). Własność ta powoduje przywiązanie prekompilowanych stron JSP do konkretnegokontenera.

Tea elegancko obsługuje również błędy czasu wykonania. Wyjątki, które wystąpią wewnątrz Tea zawierająścieżki, w których znajdują się numery linii szablonów Tea, na przykład:

2001/10/23 19:29:12.105 GMT> java.lang.NullPointerException: at com.go.teaservlet.template.ProstaStrona.execute(ProstaStrona.tea:5) at java.lang.reflect.Method.invoke(Native Method) at co.go.tea.runtime.TemplateLoader$TemplateImpl.execute(TemplateLoader.java,Compiled Code) at com.go.teaservlet.TeaServlet.processTemplate(TeaServlet.java, CompiledCode) at com.go.teaservlet.TeaServlet.doGet(TeaServlet.java:238) <itd.>

Page 312: Java Servlet - Programowanie

Wiadomości takie jak powyższa mogą być przeglądane przy pomocy aplikacji administracyjnej TeaServlet.Kiedy występują, klient domyślnie otrzymuje stronę błędu o kodzie stanu 500, celowo bez dodatkowychinformacji w celu zachowania bezpieczeństwa.

Tea posiada również własność „strażnik wyjątków”. Własność ta pozwala Tea na kontynuowanie przetwarzaniastrony podczas zapisywania błędu w dzienniku. Tea tworzy wszystkie części strony, które może omijającniewielką część, na którą wpływ ma wyjątek. Dla wielu witryn strona, której brakuje niewielkiej części jestlepsza niż błąd serwera zwracany użytkownikowi.

Zastosowania TeaJak dotychczas omawiane były samotne szablony Tea, które są bardzo słabe — celowo słabe. Ale szablony Teasą jak frontowi żołnierze piechoty — kiedy samotni, słabi, ale kiedy w zespole z zaawansowanym wsparciemlotniczym, niezwykle potężni. Poniżej opisano sposoby uczynienia szablonów Tea potężniejszymi.

Szablony mogą uzyskać dostęp do każdej funkcji z dowolnej „aplikacji” zainstalowanej w TeaServlet poprzezplik TeaServlet.properties. (Każda aplikacja WWW może posiadać inny plik TeaServlet.properties i w związku ztym inny zestaw zainstalowanych aplikacji.) Szablony wywołują funkcję poprzez wymienienie w bloku kodunazwy funkcji, po której następuje para nawiasów. Funkcje mogą przyjmować parametry i zwracać wartości:

<% lista = pobierzRekordy() %><% usunRekord(8086) %>

Ekran administracyjny TeaServlet wyświetla listę wszystkich dostępnych funkcji z wszystkich zainstalowanychaplikacji. Jeżeli dwie aplikacje deklarują tę samą funkcję, szablon może poprzedzić wywołanie funkcji nazwąaplikacji w celu uniknięcia niejasności:

<% lista = ApListaRekordów.pobierzRekordy() %>Istnieje zbiór klas narzędziowych dostępnych dla wszystkich szablonów utworzonych w celu wspomożenia wpodstawowych zadaniach przetwarzania tekstu. Poniższe podrozdziały wymieniają niektóre z najczęściejwywoływanych funkcji narzędziowych.

Przetwarzanie tekstuPoniżej przedstawiono niektóre popularne funkcje przetwarzające tekst.Date currentDate()

Zwraca aktualną datę.void dateFormat (String format)

Zmienia format wszystkich dat wyświetlanych w późniejszym czasie przez ten szablon.void numberFormat (String format)

Zmienia format wszystkich liczb wyświetlanych w późniejszymczasie przez ten szablon (wyłączając liczby zawartedosłownie w łańcuchach).

void nullFormat (String format)

Przydziela łańcuch, który zostanie wykorzystany zamiast nullpodczas wyświetlania zmiennej o wartości null.

void setLocale(String jezyk, String kraj)

Zmienia lokalizację dla wszystkich dat, liczb itp.wyświetlanych w późniejszym czasie przez ten szablon.

boolean startsWith(String lan, String przedrostek)

Zwraca true, jeżeli łańcuch rozpoczyna się od określonegoprzedrostka.

Page 313: Java Servlet - Programowanie

boolean endsWith(String lan, String przyrostek)

Zwraca true, jeżeli łańcuch kończy się określonymprzyrostkiem.

String subString(String lan, int poczIndeks, int konIndeks)

Zwraca część łańcucha od indeksu początkowego do końcowego(wyłączając indeksy).

String trim (String lan)Zwraca łańcuch bez początkowych i końcowych pustych miejsc

int[] find(String lan, String szuka)int[] find(String lan, String szuka, int odIndeks)

Zwraca indeksy, w których łańcuch zawiera poszukiwany String,z opcjonalnym indeksem początkowym.

String replace(String zrodlo, String wzor, String zamiana)String replace(String zrodlo, String wzor, String zamiana int odIndeks)String replace(String zrodlo, String wzor, String zamiana int odIndeks int

doIndeks)

Zamienia wszystkie dane dokładnie pasujące do wzoru na podanyłańcuch zamiany, z opcjonalnym indeksem początkowym ikońcowym.

Obsługa zawartościPoniżej wymieniono popularne metody obsługujące zawartość.void InsertFile (string nazwapliku)

Wstawia zawartość danego pliku do wyświetlanej strony. Niewstawia nic, jeżeli plik nie może zostać odczytany.Nazwa pliku może być względna lub bezwzględna. Jeżelibezwzględna, ścieżka jest odnogą katalogu macierzystegoszablonów (zazwyczaj katalogu macierzystego dokumentówserwera WWW).

void InsertURL (string url)

Wstawia zawartość danego URL-a do wyświetlanej strony. Niewstawia nic, jeżeli URL nie może zostać odczytany. URLmoże być względny lub bezwzględny.

boolean fileExists(String nazwapliku)

Zwraca true, jeżeli plik o podanej nazwie istnieje.boolean URLExists(String url)

Page 314: Java Servlet - Programowanie

Zwraca true, jeżeli podany URL istnieje.

Obsługa żądań/odpowiedziDodatkowo przedstawiono kilka często wykorzystywanych metod żądań/odpowiedzi.com.go.teaservlet.HttpContext.Request getRequest()com.go.teaservlet.HttpContext.Request getRequest(String kodowanie)

Zwraca obiekt zawierający informacje o żądaniu, z opcjonalnymkodowaniem pozwalającym na automatyczną konwersjęwartości parametrów.

void setContentType(String typZawartosci)

Ustawia typ zawartości odpowiedzi.void setHeader(String nazwa, String wartosc)

Ustawia nagłówek odpowiedzi.void setStatus(int ks)

Ustawia kod stanu odpowiedzi.void sendRedirect(String url)

Przekierowuje żądanie do danego URL-a.void sendError(int ks)

void sendError(int ks, String wiadomosc)

Wysłanie strony z błędem o danym kodzie stanu z opcjonalnąwiadomością o błędzie.

Szablon w przykładzie 14.5 demonstruje sposób wywoływania klas narzędziowych przez szablon w celuwyświetlenia danych procentowych dla klikania na reklamy danego dnia. Data i procenty formatowane są w stylufrancuskim.

Przykład 14.5.

Francuskie klikanie<%template klik() %><HTML<HEAD><TITLE>Testowanie funkcji wbudowanych w Tea</TITLE></HEAD><BODY><%// Ustawienie lokalizacji. Nie zmienia się ona dla wszystkich danych wyświetlanych// później przez szablonset locale ("fr", "CA") // Quebec// Określenie formatowania dat I liczbdateFormat("d MMMM yyyy")numberFormat("#0,00%")// Pewne fałszywe daneklik- 485.0dostep = 7673.0%><H2><% currentDate %></H2>Le pourcentage: <% klik/dostep %></BODY></HTML>

Szablon wyświetla dane podobne do poniższych, z datą i liczbami właściwymi dla kogoś, kto mieszka wQuebecu:

Page 315: Java Servlet - Programowanie

23 octobre 2001Le pourcentage: 6,32%

Tworzenie aplikacji TeaTeraz przedstawiony zostanie sposób tworzenia własnej aplikacji Tea. Jest to łatwe — należy po prostu napisaćklasę aplikacji i klasę kontekstu. Wszystkie klasy aplikacji są rozszerzeniem interfejsucom.go.teaservlet.Application. Podobnie jak serwlet, interfejs Application posiada metodyinit() i destroy() obsługujące kwestie związane z okresem trwałości. Zamiast metody service()Application posiada parę metod createContext() i getContextType(). MetodacreateContext() zwraca obiekt kontekstu, którego metody zostają udostępnione szablonom jako funkcje.Metoda getContextType() zwraca java.lang.Class obiektu zwracanego przez createContext()w celu ułatwienia sprawdzania typu.

Poniższe dwa przykłady przedstawiają prostą aplikację próbującą na różne sposoby określić nazwę użytkownika.Klasa NazwaAp to klasa aplikacji (przykład 14.6); klasa NazwaKontekst to obiekt kontekstu tworzony przezNazwaAp i udostępniany szablonom (przykład 14.7).

Przykład 14.6.

Klasa NazwaApimport com.go.teaservlet.*;import javax.servlet.*;public class NazwaAp implements Application { // Zarówno init(ApplicationConfig) jak i destroy() muszą być zaimplementowaneponieważ // zostały zadeklarowane w interfejsie Application interface. Mogą pozostaćpuste. public void init(ApplicationConfig konfig) throws ServletException { } // Tworzenie kontekstu dostarcza funkcji dostępnych z szablonów. public Object createContext(ApplicationRequest zadanie, ApplicationResponse odpowiedz) { // Często przekazuje się żądanie i odpowiedź, nawet jeżeli nie sąwykorzystywane, // ponieważ mogą być wykorzystane później return new NameContext(zadanie, odpowiedz); } // Ta metoda musi zostać zaimplementowana, by zwracać klasę obiektu zwracanegoprzez // createContext() public Class getContextType() { return NameContext.class; } public void destroy() { }}

TeaServlet wywołuje metodę init() jeden raz, kiedy aplikacja zostaje załadowana po raz pierwszy, w celudania jej możliwości przeprowadzenia inicjacji. Wywołuje jeden raz destroy() podczas kończenia pracy wcelu dania aplikacji możliwości wyczyszczenia. Metoda init() przyjmuje parametr ApplicationConfig,który jest podinterfejsem ServletConfig z trzema dodatkowymi metodami — getLog(), getName() igetProperties() — służącymi do odczytywania dziennika zdarzeń, nazwy i właściwości inicjacji.

TeaServlet wywołuje metodę createContext() przed obsługą żądań i udostępnia wszystkie metodyzwracanego obiektu kontekstu jako funkcje żądanego szablonu Tea. Jeżeli każdy użytkownik musi posiadać swójwłasny kontekst, może to być w łatwy sposób osiągnięte przy pomocy standardowego śledzenia sesji w metodziecreateContext(). Metoda CreateContext() przyjmuje jako parametry ApplicationRequest iApplicationResponse. Są to podinterfejsy HttpServletRequest i HttpServletResponse zdodatkiem kilku metod Tea.

Przykład 14.7.

Klasa NazwaKontekstimport com.go.teaservlet.*;import javax.servlet.*;import javax.servlet.http.*;

Page 316: Java Servlet - Programowanie

public class NazwaKontekst { ApplicationRequest zadanie; ApplicationResponse odpowiedz; String nazwa; public NazwaKontekst(ApplicationRequest zadanie, ApplicationResponse odpowiedz) { this.zadanie = zadanie; this.odpowiedz = odpowiedz; } public String pobierzNazwa() { // Jeżeli nazwa wcześniej określona, zwrócenie jej if (nazwa != null) { return nazwa; } // Próba określenia nazwy użytkownika nazwa = zadanie.getRemoteUser(); // Jeżeli nazwa logowania nie jest dostępna, próba odczytania parametru if (nazwa == null) { nazwa = zadanie.getParameter("nazwa"); } // Jeżeli nazwa nie jest dostępna jako parametr, próba sesji if (nazwa == null) { nazwa = (String) zadanie.getSession().getAttribute("nazwa"); } // Jeżeli nazwy nie ma w sesji, próba cookie if (nazwa == null) { Cookie[] cookies = zadanie.getCookies(); for (int i = 0; i < cookies.length; i++) { if (cookies[i].pobierzNazwa().equals("nazwa")) { nazwa = cookies[i].getValue(); } } } // Jeżeli nazwy nie ma też w sesji, poddanie się return nazwa; }}

Metoda pobierzNazwa() próbuje określić nazwę użytkownika-klienta poprzez przeszukanie danychlogowania, listy parametrów, danych sesji i cookies żądania. Zwraca null, jeżeli odnalezienie informacji onazwie nie jest możliwe. Szablony uzyskują dostęp do nazwy przez wywołanie funkcji pobierzNazwa().Proszę zauważyć, że kontekst określa nazwę klienta w metodzie pobierzNazwa(), a nie w konstruktorze. Jestto sztuczka mająca na celu poprawienie wydajności. Jeżeli logika znajdowałaby się w konstruktorze, byłabywykonywana podczas obsługi każdego żądania, niezależnie od tego, czy szablon wywoływałbypobierzNazwa(), czy nie.

Uczynienie aplikacji NazwaAp dostępną wymaga następującego krótkiego dodatku do plikuTeaServlet.properties:

"NazwaAp" { class = NazwaAp}

Można sprawdzić, czy aplikacja została prawidłowo załadowana przez przejrzenie łącza Applications (Aplikacje)aplikacji administracyjnej TeaServlet23. Po załadowaniu tej aplikacji każdy szablon może wyświetlić nazwęklienta przy pomocy funkcji pobierzNazwa():

<% template nazwy() %><%setContentType("text/plain")nullFormat("Nieznane")%>

23 Jeżeli klasy aplikacji nie zostały odnalezione nawet jeżeli wygląda na to, że powinny były, powodem może być kwestiazwiązana z mechanizmem ładowania klas. Problemy mogą wystąpić, jeżeli TeaServlet.jar i klasy aplikacji są ładowane przezinne mechanizmy. Należy albo umieścić obie w ścieżce klas systemu, gdzie zostaną odnalezione przez początkowymechanizm, albo umieścić obie w katalogu WEB-INF, gdzie zostaną odnalezione przez mechanizm ładowania klas aplikacjiWWW. (Proszę umieścić TeaServlet.jar w katalogu WEB-INF/lib, a klasy aplikacji w WEB-INF/classes.) Proszę takżepamiętać, że z tych samych powodów klasy wspierające nie są ładowane ponownie nawet po wybraniu Reload All (Przeładujwszystko) ze strony administracyjnej TeaServlet.

Page 317: Java Servlet - Programowanie

Nazwa użytkownika to <%pobierzNazwa() %>

Aplikacja „Narzędzia”Określanie nazwy użytkownika nie jest zbyt realistycznym zastosowaniem, tak więc opisane teraz zostanie cośbliższego rzeczywistości. Utworzona zostanie aplikacja, która wyświetla listę różnych dostępnych narzędzi dotworzenia zawartości (aplikacja mniej więcej taka, jakiej potrzebuje Servlets.com). Informacje o narzędziachbędą pochodzić z pliku XML, chociaż równie dobrze mogłyby pochodzić z bazy danych. Przykład 14.8przedstawia klasę NarzedziaAp.

Przykład 14.8.

Centrum aplikacji „Narzędzia”import com.go.teaservlet.*;import com.go.trove.log.*;import java.sql.Timestamp;import java.util.*;import javax.servlet.*;public class NarzedziaAp implements Application { private Log dziennik; private Narzedzie[] narzedzia; public void init(ApplicationConfig konfig) throws ServletException { // Dziennik zdarzeń dla wydarzeń specyficznych dla tej aplikacji dziennik = konfig.getLog(); // Pobranie danych o narzędziach w metodzie init() dla zachowania prostoty String narzedziaPlik = konfig.getInitParameter("narzedziaPlik"); if (narzedziaPlik == null) { throw new ServletException( "Plik danych o narzędziach musi zostać określony jako parametr inicjacji narzedziaPlik"); } dziennik.debug("Pobieranie narzędzi z " + narzedziaPlik); try { narzedzia = Narzedzie.pobierzNarzedzia(narzedziaPlik); if (nazredzia.length == 0) { dziennik.warn("Nie znaleziono narzędzi w " + narzeziaPlik); } } catch (Exception w) { dziennik.error(w); throw new ServletException(e); } } public Object createContext(ApplicationRequest zadanie, ApplicationResponse odpowiedz) { return new NarzedziaKontekst(zadanie, odpowiedz, this); } public Class getContextType() { return NarzedziaKontekst.class; } public void destroy() { } public Narzedzie[] pobierzNarzedzia() { // Zazwyczaj „aplikacja” utrzymywałaby lub miałaby dostęp do wcześniejutworzonej // bazy danych. Tutaj dla zachowania prostoty zastosowano plik XML return narzedzia; } public Narzedzie[] pobierzNarzedzie(String stan) { // Zwracane tylko narzędzi o określonym stanie // (wysłane, żyje, odrzucone, lub martwe) List lista = new LinkedList(); for (int i = 0; i < narzedzia.length; i++) { if (narzedzia[i].pobierzZnacznikStanu().equalsIgnoreCase(stan)) { lista.add(narzedzia[i]); } } return (Narzedzie[]) lista.toArray(new Narzedzie[0]); }

Page 318: Java Servlet - Programowanie

}Metoda init() NarzedziaAp wykorzystuje przekazany jej ApplicationConfig w celu odczytania izapisania odwołania do dziennika zdarzeń, następnie wykorzystuje konfig w celu pobrania parametru inicjacjinarzedziaPlik. Wartość tego parametru musi zostać określona w pliku TeaServlet.properties:

"NarzedziaAp" { class = NarzedziaAp init { narzedziaPlik = /tomcat/webapps/herbatka/WEB-INF/narzedzia.xml }}

Metoda init() wywołuje następnie pobierzNarzedzia() w celu pobrania informacji o narzędziach zpliku XML do tablicy narzędzi. Plik XML powinien wyglądać podobnie do przykładu 14.9.

<?xml version="1.0"?><narzedzia> <narzedzie id="1"> <nazwa>JavaServer Pages</nazwa> <domURL>http://java.sun.com/products/jsp</domURL> <komentarz>JavaServer Pages (JSP) to technologia utworzona przez Sun Microsystems, bliskozwiązana z serwletami. Tak jak w przypadku serwletów, Sun udostępnia specyfikacjęJSP i inni producenci rywalizują swoimi implementacjami standardu. To, że jestdziełem firmy Sun, stawia JSP w bardzo uprzywilejowanej pozycji i jeżeli JSProzwiązywałaby wystarczającą ilość problemów użytkowników, przypuszczalniezagarnęłaby rynek jeszcze przed pojawieniem się alternatyw. Jednak zadziwiającoduża liczba użytkowników jest rozczarowana JSP i jej alternatywy zyskująpopularność. </komentarz> <znacznikStanu>ŻYJE</znacznikStanu> <czasUtworzenia>1998-03-17 00:00:00.000</czasUtworzenia> <czasModyfikacji>1999-12-16 00:00:00.000</czasModyfikacji> </narzedzie> <narzedzie id="2"> <nazwa>Tea</nazwa> <domURL>http://opensource.go.com</domURL> <komentarz>Tea to nowy produkt Open Source Walt Disney Internet Group, tworzony wewnętrznieprzez lata w celu rozwiązania problemów z tworzeniem witryn takich jak ESPN.com.Tea jest podobna do JSP, ale pozbawiona jej wielu wad, i już posiada ogromną ilośćnarzędzi wspierających. </komentarz> <znacznikStanu>ŻYJE</znacznikStanu> <czasUtworzenia>2000-07-12 00:00:00.000</czasUtworzenia> <czasModyfikacji>2000-07-12 00:00:00.000</czasModyfikacji> </narzedzie> <narzedzie id="3"> <nazwa>WebMacro</nazwa> <domURL>http://jakarta.apache.org</domURL> <komentarz>WebMacro to mechanizm szablonów utworzony przez firmę Semiotek jako część projektuShimari, włączony teraz do Apache Jakarta Project. WebMacro było wykorzystywane wkomercyjnych witrynach takich jak AltaVista.com, zintegrowane w szkieletach OpenSource takich jak Turbine i Melati oraz wykorzystywane w znanych projektach OpenSource takich jak JetSpeed. </komentarz> <znacznikStanu>ŻYJE</znacznikStanu> <czasUtworzenia>1998-11-19 00:00:00.000</czasUtworzenia> <czasModyfikacji>2000-08-31 00:00:00.000</czasModyfikacji> </narzedzie> <narzedzie id="4"> <nazwa>Element Construction Set</nazwa> <domURL>http://java.apache.org/ecs</domURL> <komentarz>Pakiet Element Construction Set (ECS) pochodzący z Java Apache Project to zbiórklas utworzonych na podstawie htmlKona, produktu WebLogic (teraz BEA Systems). ECSposiada wiele ograniczeń, ale rozwiązuje konkretny zestaw problemów. </komentarz> <znacznikStanu>ŻYJE</znacznikStanu> <czasUtworzenia>1999-03-31 00:00:00.000</czasUtworzenia> <czasModyfikacji>2000-06-16 00:00:00.000</czasModyfikacji> </narzedzie> <narzedzie id="5"> <nazwa>XMLC</nazwa> <domURL>http://xmlc.enhydra.org</domURL> <komentarz>XMLC wykorzystuje XML w celu osiągnięcia niemal tej samej mocy, co ECS, bez wielujego ograniczeń. Technika ta została utworzona przez Lutris jako część ich serweraOpen Source — Enhydra Application Server, może być stosowana jako osobny element. </komentarz>

Page 319: Java Servlet - Programowanie

<znacznikStanu>ŻYJE</znacznikStanu> <czasUtworzenia>1998-10-11 00:00:00.000</czasUtworzenia> <czasModyfikacji>2000-03-09 00:00:00.000</czasModyfikacji> </narzedzie> <!—itd. --></narzedzia>

Aplikacja odczytuje powyższy plik przy pomocy interfejsu JDOM, interfejsu Open Source służącego do odczytu,zapisu i manipulacji XML przy pomocy Javy. Jeżeli zamiast tego wyniki powinny być odczytywane z bazydanych, można to uczynić po prostu zamieniając SAXBuilder JDOM (który tworzy Document z pliku lubpotoku przy pomocy analizatora SAX) na ResultSetBuilder JDOM (który tworzy Document zjava.sql.ResultSet, klasy przekazanej JDOM).24 W trakcie pisania niniejszej książki, JDOM ciągle był w fazietworzenia. Niniejszy przykład wykorzystuje JDOM Beta 5. W celu pracy z ostateczną wersją JDOM mogą sięokazać konieczne drobne zmiany.

Metoda createContext() konstruuje nawy egzemplarz NarzedziaKontekst. KodNarzedziaKontekst jest przedstawiony w przykładzie 14.10.

Przykład 14.10.

Kontekst zawierający wszystkie dostępne funkcjeimport com.go.teaservlet.*;public class NarzedziaKontekst { ApplicationRequest zadanie; ApplicationResponse odpowiedz; NarzedziaAp ap; public NarzedziaKontekst(ApplicationRequest zadanie, ApplicationResponse odpowiedz, NarzedziaAp ap) { this.zadanie = zadanie; this.zadanie = zadanie; this.ap = ap; } public Narzedzie[] pobierzNarzedzia() { return ap.pobierzNarzedzia(); } public Narzedzie[] pobierzNarzedzia(String stan) { return ap.pobierzNarzedzia(stan); }}

Metody publiczne w kontekście są funkcjami udostępnianymi szablonom Tea. W tym miejscu metody odwołująsię z powrotem do aplikacji, w można powiedzieć dobrym stylu, ponieważ konteksty powinny być tak małe, jakto jest tylko możliwe; stan powinien być przechowywany w aplikacji. Klasa Narzedzie sama działa jakomagazyn informacji Narzedzie. Jej kod przedstawiony jest w przykładzie 14.11.

Przykład 14.11.

Klasa pobierająca i przechowująca dane Narzedzieimport java.io.*;import java.sql.*;import java.util.*;import org.jdom.*;import org.jdom.input.*;public class Narzedzie { // Dane na temat tego rekordu narzedzie public int id; public String nazwa; public String domURL; public String komentarz; public String znacznikStanu; public Timestamp czasUtworzenia; public Timestamp czasModyfikacji; // Tea może uzyskać dostęp jedynie do podstawowych własności, potrzebne są więc // specjalne metody dostępu public int pobierzId() { return id; } public String pobierzNazwa() { return nazwa; } public String pobierzDomURL() { return domURL; }

24 Jason Hunter jest jednym z twórców JDOM, razem z Brettem McLaughlinem. Większa ilość informacji na temat JDOMdostępna jest pod adresem http://jdom.org.

Page 320: Java Servlet - Programowanie

public String pobierzKomentarz() { return komentarz; } public String pobierzZnacznikStanu() { return znacznikStanu; } public Timestamp pobierzCzasUtworzenia() { return czasUtworzenia; } public Timestamp pobierzCzasModyfikacji() { return czasModyfikacji; }

public int pobierzCzasUtworzeniaDni() { return (int) ((System.currentTimeMillis() - czasUtworzenia.getTime()) / (24 * 60 * 60 * 1000)); // milisekundy w dniu } public int pobierzCzasModyfikacjiDni() { return (int) ((System.currentTimeMillis() - czasModyfikacji.getTime()) / (24 * 60 * 60 * 1000)); // milisekundy w dniu }

// Idealnie użyto by metod takich jak te, ale Tea pozwala jedynie na dostęp // do własności obiektu. Nie będą one widoczne.public boolean czyNowy(int dni) { return pobierzCzasUtworzeniaDni() < dni; } public boolean czyUaktualniony(int dni) { return pobierzCzasModyfikacjiDni () < dni; }

public static Narzedzie[] pobierzNarzedzia(String plikNarzedzia) throwsException { // Odczytanie danych narzędzi z pliku XML zawierającego elementy <narzedzie> // Wykorzystanie interfejsu JDOM by uprościć to działanie (http://jdom.org) List obiektyNarzedzia = new LinkedList(); SAXBuilder budowa = new SAXBuilder(); Document dokument = budowa.build(new File(plikNarzedzia)); Element root = dokument.getRootElement(); List elementyNarzedzia = root.getChildren("narzedzie"); Iterator i = elementyNarzedzia.iterator(); while (i.hasNext()) { Element narzedzie = (Element) i.next(); Narzedzie n = new Narzedzie(); n.id = narzedzie.getAttribute("id").getIntValue(); n.nazwa = narzedzie.getChild("nazwa").getTextTrim(); n.domURL = narzedzie.getChild("domURL").getTextTrim(); n.komentarz = narzedzie.getChild("komentarz").getTextTrim(); n.znacznikStanu = narzedzie.getChild("znacznikStanu").getTextTrim(); n.czasUtworzenia = Timestamp.valueOf( narzedzie.getChild("czasUtworzenia").getTextTrim()); t.czasModyfikacji = Timestamp.valueOf( narzedzie.getChild("czasModyfikacji").getTextTrim()); obiektyNarzedzia.add(n); } return (Narzedzie[]) obiektNarzedzia.toArray(new Narzedzie[0]); }}

Proszę zauważyć, że chociaż klasa powyższa posiada publiczne zmienne przechowujące jej stan, to nie są onewidoczne przez szablony Tea. Szablony Tea mają dostęp jedynie do funkcji zadeklarowanych w zainstalowanychkontekstach i do podstawowych własności obiektów zwracanych przez te funkcje. Dlatego klasa Narzedzieposiada różne metody dostępu. Narzedzie posiada również dwie metody ułatwiające, które zwracają ilość dniod utworzenia i modyfikacji rekordu. Można by pomyśleć o wykorzystaniu metody isNewWithin(intdni) lub isUpdateWithin(int dni). Jednak szablon nie posiada dostępu do tych metod, ponieważ nienależą one do podstawowych własności obiektu.

Przykład 14.12 przedstawia prosty fronton szablonu dla tej aplikacji. Nosi on nazwę widoknarz1 i przyjmujeopcjonalny parametr stan, przy pomocy którego można ograniczyć wyświetlane narzędzia do posiadającychokreślony stan. Przykładowa strona wyświetlana przez ten szablon jest przedstawiona na rysunku 14.3.

Przykład 14.12.

Prosty widok narzędzi<% template widoknarz1(String stan) %><% if (stan == null) { narzedzia = pobierzNarzedzia("zyje"); }

Page 321: Java Servlet - Programowanie

else { narzedzia = pobierzNarzedzia(stan) }%><% foreach (narzedzie in narzedzia) { %> <HR SIZE=2 ALIGN=LEFT> <H3> <% narzedzie.nazwa %> <% if (narzedzie.czasUtworzeniaDni < 45) { '<FONT COLOR=#FF0000><B> (Nowość!) </B></FONT>' } else if (narzedzie.czasModyfkacjiDni < 45) { '<FONT COLOR=#FF0000><B> (Uaktualnienie!) </B></FONT>' } %> </H3> <A HREF="<% narzedzie.domURL %>"><% narzedzie.domURL %></A><BR> <% tool.komentarz %><% } %>

Rysunek 14.3.

Lista narzędzi bez dodatków

Jak można dostrzec, na powyższym rysunku nie ma żadnych ozdobników (nagłówka, stopki, paska bocznego)koniecznych na profesjonalnej stronie. Dekoracje te można dodać przez wywołanie innych szablonów, jakprzedstawiono w poprawionym szablonie widoknarz2 w przykładzie 14.13.

Przykład 14.13

Bardziej złożony widok narzędzi<% template widoknarz2(String stan) %><% tytul = "Lista narzędzi" tytul2 = "Lista narzędzi służących do tworzenia zawartości" 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ę."%>

Page 322: Java Servlet - Programowanie

<% call naglowek(tytul, tytul2, opis) %><% if (stan == null) { narzedzia = pobierzNarzedzia("zyje"); } else { narzedzia = pobierzNarzedzia(stan) }%><% foreach (narzedzie in narzedzia) { %> <HR SIZE=2 ALIGN=LEFT> <H3> <% narzedzie.nazwa %> <% if (narzedzie.czasUtworzeniaDni < 45) { '<FONT COLOR=#FF0000><B> (Nowość!) </B></FONT>' } else if (narzedzie.czasModyfkacjiDni < 45) { '<FONT COLOR=#FF0000><B> (Uaktualnienie!) </B></FONT>' } %> </H3> <A HREF="<% narzedzie.domURL %>"><% narzedzie.domURL %></A><BR> <% tool.komentarz %><% } %><% call stopka() %>

Powyższy nowy szablon definiuje zmienne dla tytułów i opisu strony. Następnie wywołuje szablon nagłówkaprzekazując mu te wartości. Szablon nagłówka tworzy zawartość nagłówka i paska bocznego, po czymumożliwia widoknarz2 dodanie właściwej zawartości. Na końcu strony szablon stopki dodaje stopkę strony.Przykładowe szablony nagłówka i stopki przedstawione są w przykładach 14.14 i 14.15. Strona wyświetlanaprzez poprawiony szablon jest przedstawiona na rysunku 14.4.

Przykład 14.14.

Plik nagłówka<% template naglowek(String tytul, String tytul2, String opis) %><HTML><HEAD><TITLE><% tytul %></TITLE></HEAD><BODY BGCOLOR="#FFFFFF" BACKGROUND="/obrazki/tlo.gif" LINK="#003333" ALINK="#669999" VLINK="#333333"><IMG SRC="/obrazki/banner.gif" WIDTH=600 HEIGHT=87 BORDER=0><BR><TABLE><TR><TD WIDTH=125 VALIGN=TOP> <BR><BR><BR> <FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#FF0000"> <A HREF="/indeks.html">Strona główna</A><BR> <A HREF="/hosting.html">Hosting</A><BR> <A HREF="/mechanizmy.html">Mechanizmy</A><BR> </FONT></TD><TD WIDTH=475> <TABLE CELLPADDING=5><TR><TD WIDTH=600 BGCOLOR="#006699" VALIGN=TOP> <B><FONT FACE="Arial,Helvetica" SIZE="+2"> <% tytul %> </FONT></B> </TD></TR></TABLE> <B><FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#003366"> <% tytul2 %> </FONT></B><P> <P> <FONT FACE="Arial,Helvetica">

Page 323: Java Servlet - Programowanie

<% opis %>Przykład 14.15.

Plik stopki<% template stopka() %> </FONT></TD></TR><TR><TD></TD><TD WIDTH=475 ALIGN=CENTER COLSPAN=3><HR><FONT FACE="Arial,Helvetica"><A HREF="/indeks.html">Strona główna</A>&nbsp;&nbsp;<A HREF="/hosting.html">Hosting</A>&nbsp;&nbsp;<A HREF="/mechanizmy.html">mechanizmy</A>&nbsp;&nbsp;<P></FONT><TABLE WIDTH=100%><TR><TD WIDTH=260 ALIGN=LEFT VALIGN=TOP><FONT FACE="Arial,Helvetica"><A HREF="/wlasnosc.html">Wlasność</A> &copy; 2000 Jason Hunter<BR>Wszystkie prawa zastrzeżone.</TD><TD WIDTH=5></FONT></TD><TD WIDTH=230 ALIGN=RIGHT VALIGN=TOP><FONT FACE="Arial,Helvetica">Kontakt: <A HREF="mailto:[email protected]">[email protected]</A></FONT></TD></TR></TABLE></TD></TR></TABLE></BODY></HTML>

Rysunek 14.4.

Lista narzędzi z otaczającymi dodatkami

Page 324: Java Servlet - Programowanie

Według powyższego projektu zmiany w nagłówku, paku bocznym i stopce są ograniczone do pojedynczychplików. Możliwe jest również wykorzystanie pojedynczego pliku do stworzenia wyglądu całej strony, wstawiającwłaściwą zawartość przy pomocy polecenia <% ... %>. Podejście to dostarcza większych możliwości ipozwala na oddzielenie właściwej zwartości od formatowania, chociaż trzeba się do tego podejściaprzyzwyczaić. Proszę przeczytać dokumentację TeaServlet w celu znalezienia dodatkowych informacji.

Ostatnie słowoJedną z najbardziej przydatnych własności Tea trudno opisać w książce — jest to graficzne środowiskoprogramistyczne o nazwie Kettle (imbryk), przedstawione na rysunku 14.5. Oprócz tradycyjnych własności IDE,Kettle dostarcza dokonywanej w czasie rzeczywistym oceny procesu tworzenia — podkreślając na bieżącowszystkie błędy składni na zielono, błędy semantyczne na czerwono, a błędy kompilacji na niebiesko. Podobnafunkcjonalność w narzędziach wspierających inne mechanizmy tworzenia zawartości byłaby wspaniała, alepraktycznie niemożliwa do osiągnięcia. Kettle oferuje również przywoływaną listę właściwości, która pozwalatwórcy szablonu na natychmiastowe sprawdzenie, jakie własności są dostępne dla danego obiektu. Kettle jestbezpłatne, ale działa jedynie pod Windows i nie jest to Open Source. Dodatkowo istnieją plany utworzeniareimplementacji Open Source opartej na Java Swing.

Rysunek 14.5.

Herbatę najlepiej robi się w imbryku

Podczas pracy z Tea i TeaServlet można dostrzec brak obsługi specjalnych rodzajów własności, gdyż ich twórcy(WDIG) ich nie potrzebowali. Ponieważ od niedawna jest to Open Source, inni użytkownicy będą dodawaćpotrzebne im własności i własne pomysły, a produkt będzie rósł. Chociaż język Tea dojrzał, to TeaServlet jestciągle narzędziem nowym, posiadającym duży potencjał.

Page 325: Java Servlet - Programowanie

Rozdział 15. WebMacro

Szkielet programistyczny WebMacro, dostępny pod adresem http://www.webmacro.org, pochodzi z tej samejrodziny, co Tea, ale charakteryzuje się podejściem opartym w większym stopniu na serwletach. WebMacrozostało utworzone przez Justina Wellsa z firmy Semiotek jako część kontraktu na stworzenie witryny firmy.Następnie udostępnione zostało jako Open Source w celu poprawienia go i podniesienia pozycji firmy. Obecnieistnieją plany włączenia WebMacro do Apache Jakarta Project dostępnym pod adresemhttp://jakarta.apache.org w celu uzyskania szerszego wsparcia programistycznego. Aktualnie w projekcieJakarta tworzony jest klon WebMacro o nazwie Velocity.

WebMacro często określa się jako mechanizm szablonów, co jest ogólną nazwą systemów wykonujących zadaniazamiany tekstu wewnątrz szablonów stron WWW. Dostępne są także inne mechanizmy szablonów Open Source,najbardziej znany z nich to FreeMarker (http://freemarker.sourceforge.net). Jednak w niniejszej książce opisanezostanie WebMacro, ponieważ posiada ono największą popularność, jest wykorzystywane w dużychkomercyjnych witrynach takich jak AltaVista, zostało zintegrowane z dużymi szkieletami Open Source takimi jakTurbine (http://java.apache.org/turbine) i Melati (http://www.melati.org) oraz zostało wykorzystane jakopodstawa znanych projektów Open Source takich jak JetSpeed (http://java.apache.org/jetspeed). Podobnie jakTea, WebMacro jest rozprowadzane według licencji typu Apache, tak więc może zostać bez problemuwykorzystane w połączeniu z aplikacjami komercyjnymi.

Niniejszy rozdział opisuje WebMacro 0.94, wersję próbną z października 2000. Na pewno dużo szczegółówzmieni się. Jeżeli przykładowy kod tu umieszczony nie będzie działał z ostateczną wersją, należy przeczytaćdokumentacje dołączoną do WebMacro aby określić, co się zmieniło. WebMacro wymaga Servlet API w wersji2.0 lub późniejszej i JDK 1.1.7 lub późniejszego.

Szkielet WebMacroSzkielet WebMacro działa w następujący sposób: serwlet odbiera żądanie od klienta, serwlet wykonuje logikękonieczną do obsługi żądania (taką jak wykorzystanie klas wspierających lub elementów takich, jak EJB), poczym tworzy obiekt kontekstowy wypełniony „obiektami odpowiedzi” zawierającymi wyniki logiki, którepowinny zostać wyświetlone klientowi. Następnie serwlet wybiera plik szablonu (zazwyczaj w oparciu o wynikilogiki) po czym przepycha obiekty odpowiedzi przez plik szablonu tworząc zawartość wyświetlaną klientowi.

Programista Javy tworzy serwlet i logikę w czystej Javie, a inżynier szablonów tworzy plik szablonuwyświetlający dane. Szablon składa się z zawartości HTML i XML wypełnionej prostą logiką do podstawiania iniewielkimi możliwościami skryptowymi. Składnia jest zgodna z edytorami HTML i XML, omija nawiasykątowe, aby zapewnić, że analizatory HTML i XML nie będą popełniać omyłek. Programista przekazujeinżynierowi szablonów listę zastosowanych zmiennych, a także listę właściwości i metod tych zmiennych.

Model ten posiada pewne podobieństwa z modelem TeaServlet, z tą różnicą, że WebMacro domyślniewykorzystuje model przepychania w większym stopniu oparty na serwletach, nie obsługuje zagnieżdżaniaszablonów i wykorzystuje bardziej skomplikowaną składnię skryptową. Zdaniem autorów, WebMacro posiadaprzewagę nad Tea przy tworzeniu wysoko funkcjonalnych stron WWW, które posiadają pewien punktoznaczający zakończenie pracy nad nimi, Tea natomiast przoduje w tworzeniu stron, w których stale dodawanajest nowa zawartość szablonów, konieczne jest zagnieżdżanie szablonów i wymagane jest mniejszezaangażowanie programistyczne.

Page 326: Java Servlet - Programowanie

Powitanie przy pomocy WebMacroPrzykład 15.1 przedstawia prosty serwlet wykorzystujący system szablonów WebMacro. Przykład 15.2przedstawia plik szablonu witaj.wm. Wspólnie wyświetlają one aktualna datę i godzinę W następnympodrozdziale opisane zostaną miejsca, w których powinny one zostać umieszczone.

Przykład 15.1.

Prosty serwlet prowadzący szablon Witajimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import org.webmacro.*;import org.webmacro.servlet.*;public class WMWitaj extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { FastWriter wyj = new FastWriter(odp.getOutputStream(), odp.getCharacterEncoding()); try { WebMacro wm = new WM(); // opcjonalnie WM("/ścieżka/do/pliku/konfiguracyjnego") Context k = wm.getWebContext(zad, odp); k.put("data", new Date()); Template scab = wm.getTemplate("witaj.wm"); szab.write(wyj, k); wyj.flush(); } catch (WebMacroException w) { throw new ServletException(w); } }}

Przykład 15.2.

Prosty szablon Witaj## witaj.wm#set $Response.ContentType = "text/html"<HTML><HEAD><TITLE>Test WebMacro</TITLE></HEAD><BODY>Witaj! <P>Obecny czas to $data. <BR>(Dla Was, kujony to $date.Time milisekund.) <P></BODY></HTML>

Na początku opisany zostanie serwlet. Jest to normalny serwlet, będący rozszerzeniem HttpServlet iwykorzystujący metodę doGet(). Na pierwszy rzut oka można jednak zauważyć, że jego logika jest inna.Pobiera on FastWriter zamiast normalnego PrintWriter. Pozwala to na specjalną optymalizacjępozwalającą na uniknięcie kosztu konwersji Unicode dla zawartości statycznej. Działa on jak zwykłe urządzeniezapisujące z jednym ulepszeniem takim, że WebMacro może przepchnąć wcześniej zakodowane bajty i wywołaćsetAsciiHack(true) w celu przyśpieszenia wyświetlania danych Latin-1 lub US-ASCII. Należy jedyniepamiętać, by (przynajmniej obecnie) wywołać metodę flush() urządzenia zapisującego w celu wysłaniazbuforowanej zawartości urządzenia do klienta.

Kod wewnątrz bloku try tworzy nowy obiekt WebMacro, który działa jako jego podstawowy zaczep dosystemu WebMacro. WebMacro to interfejs, tak więc serwlet właściwie konstruuje egzemplarz konkretnej klasyWM, która implementuje interfejs WebMacro. Następnie serwlet wywołuje wm.getWebContext(zad,odp) w celu odczytania kontekstu WebContext, w którym umieszczone zostaną obiekty odpowiedzi, którezostaną przekazane szablonowi. Kontekst działa podobnie jak tablica asocjacyjna Hashtable. Posiada onmetodę put(Object, Object), którą serwlet wykorzystuje do umieszczenia obiektu Date w kontekście,pod nazwą data. Obiekt ten zostanie później udostępniony szablonowi działającemu w obrębie tego kontekstu.Użytkownicy zaawansowani mogą zainteresować się faktem, że WebContext jest rozszerzeniem Context, aserwlet mógłby wywoływać kontekst nieświadomy sieci WWW — Context przy pomocy getContext().

Page 327: Java Servlet - Programowanie

Własność ta jest przydatna w niektórych sytuacjach, takich jak zastosowania offline lub niezwiązane zserwletami.

W celu pobrania szablonu, serwlet wywołuje wm.getTemplate("witaj.wm"). Metoda ta pobiera nazwęszablonu do odczytania, łącznie z rozszerzeniem pliku. Szablon może posiadać dowolne rozszerzenie, alewiększość użytkowników wykorzystuje standardową konwencję .wm. Posiadając szablon, serwlet może wywołaćjego metodę write() i przekazać jako jej parametry FastWriter do zapisu i WebContext wypełnionyobiektami odpowiedzi. Wyświetlane dane powinny również zostać ujęte jako łańcuch, przy pomocy metodyTemplate String evaluate(Context kontekst).

Jeżeli dowolna metoda spowoduje wyjątek WebMacroException, serwlet został przygotowany na wywołaniego wewnątrz ServletException. Prawie każda metoda WebMacro może wywołaćWebMacroException lub pewne jego podklasy. Konstruktor WM() może wywołać InitException jeżeli,na przykład, pożądany plik konfiguracyjny nie może zostać odnaleziony. Metoda getTemplate() możewywołać NotFoundException, jeżeli niemożliwe jest odnalezienie szablonu. Natomiast metoda write()może wywołać ContextException, jeżeli pożądane dane szablonu nie znajdowały się w kontekście, a takżeIOException, jeżeli wystąpił problem z zapisem do klienta. Wszystkie zdefiniowane przez WebMacrowyjątki są rozszerzeniami WebMacroException, który nie jest wyjątkiem czasu wykonania, i często stosujesię obsługę tego ogólnego wyjątku na końcu metody doGet().

Stosując inne podejście, można utworzyć serwlet obsługujący WebMacro będący rozszerzeniem superklasyorg.webmacro.servlet.WMServlet, jak przedstawiono w przykładzie 15.3.

Przykład 15.3.

Inne podejście do tworzenia serwletu WebMacroimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import org.webmacro.*;import org.webmacro.servlet.*;public class WMSerwletWitaj extends WMServlet { public Template handle(WebContext kontekst) throws HandlerException { try { kontekst.put("data", new Date()); return getTemplate("witaj.wm"); } catch (NotFoundException w) { throw new HandlerException(w.getMessage()); } }}

W powyższej metodzie, serwlet jest rozszerzeniem WMServlet i implementuje pojedynczą metodę handle(). Superklasa tworzy automatycznie obiekt WebMacro i przekazuje mu jako parametr WebContext. Servletmusi wykonać jedynie swoją logikę, wypełnić WebContext odpowiednimi obiektami odpowiedzi oraz zwrócićszablon wykorzystywany do utworzenia strony (lub null, jeżeli serwlet wewnętrznie obsługuje tworzeniestrony). Metody start() i stop() również mogą zostać wykorzystane przez serwlet, zamiast zwykłychmetod init() i destroy(). Jeżeli potrzebne będą obiekty HttpServletRequest iHttpServletResponse, mogą one zostać odczytane z WebContext przy pomocy metod getRequest() i getResponse().

Wykorzystanie superklasy WMServlet może okazać się wygodniejsze niż samodzielna obsługa obiektówWebMacro. Jego ceną jest utrata mocy. Konieczne jest wykorzystanie samodzielnego podejścia do umieszczaniana stronie dwóch niezależnych szablonów, przepuszczenia wyświetlanych danych przez filtr taki jakGZIPOutputStream, wykonania tworzenia offline, rozróżnienia między żądaniami GET i POST orazokreślenia, czy serwlet musi być rozszerzeniem innej niestandardowej superklasy.

Teraz opisany zostanie szablon. Wygląda on jak zwykła strona HTML poza niewielkimi fragmentami. Wgłównej części strony można dostrzec obiekt Date, dodany wcześniej do kontekstu pod nazwą data, dołączonydo strony przy pomocy składni $data. W następnej linii można zauważyć, że właściwość time obiektu Date(która przechowuje czas w formie licznika milisekund) została dołączona przy pomocy składni $data.Time.Jest to prosta składnia podstawienia wykorzystywana przez WebMacro — $nazwazmiennej wyświetla wartośćzmiennej (po konwersji do String, jeżeli jest to konieczne), a $nazwazmiennej.Wlasciwosc wyświetlawartość właściwości. Podwłaściwości właściwości są również dostępne przy pomocy składni

Page 328: Java Servlet - Programowanie

$nazwazmiennej.Wlasciwosc.Podwlasciwosc, a tak naprawdę można uzyskać dostęp do większejilości danych niż podstawowe właściwości, poprzez zaawansowane działanie refleksji WebMacro, co zostanieprzedstawione w dalszej części. W tym momencie należy jedynie pamiętać, że podczas odczytywaniapodstawowej właściwości jej nazwa musi zaczynać się od wielkiej litery.

Przyglądając się szablonowi dokładniej, można dostrzec, że na początku znajduje się komentarz zawierającynazwę pliku. Komentarze w WebMacro rozpoczynają się od ## i mają długość jednej linii. Zaraz podkomentarzem znajduje się polecenie #set, które w WebMacro nazywane jest instrukcją. Nadaje onawłaściwości ContentType obiektu Response wartość "text/html", co jest równoznaczne z wywołaniemresponse.setContentType("text/html"). Zmienna $Response reprezentuje odpowiedź serwletu ipodobnie jak $Request jest dostępna we wszystkich obiektach WebContext. WebMacro posiada kilkainstrukcji służących do manipulacji zmiennymi, dołączania plików i tworzenia pętli. Zostaną one opisaneszczegółowo w dalszej części niniejszego rozdziału.

Instalacja WebMacroW celu uruchomienia serwletu WebMacro i szablonu konieczne jest wykonanie niewielkiej pracy związanej zinstalacją. Po pierwsze należy pobrać dystrybucję z witryny http://www.webmacro.org i rozpakować ją.Następnie należy umieścić webmacro.jar i collections.jar w ścieżce klas serwera25. Następnie należy odnaleźćplik WebMacro.properties znajdujący się wewnątrz dystrybucji i skopiować go do katalogu znajdującego się wścieżce klas serwera. Proszę zauważyć, że ponieważ klasy w webmacro.jar wykorzystują swój własnymechanizm ładowania klas w celu odnalezienia pliku, plik ten musi znajdować się w ścieżce klas mechanizmuładującego webmacro.jar. W celu zapewnienia tej własności, należy umieścić oba te pliki w systemowej ścieżceklas, gdzie mogą zostać odnalezione przez podstawowy mechanizm ładowania klas, lub umieścić oba w kataloguWEB-INF, gdzie zostaną odnalezione przez mechanizm aplikacji WWW. (webmacro.jar należy umieścić wkatalogu WEB-INF/lib, a WebMacro.properties w katalogu WEB-INF/classes.)

Domyślny plik WebMacro.properties zawarty w dystrybucji zawiera długą listę opcji konfiguracyjnych, którychpierwsza część jest przedstawiona w przykładzie 15.4. Większość aspektów tej klasy jest dobrzeudokumentowana w samym pliku. Wszystko, co należy teraz skonfigurować, to parametr TemplatePath,określający katalog lub katalogi, w których przechowywane są szablony.

Przykład 15.4.

Standardowy plik WebMacro.properties# UWAGA DLA UŻYTKOWNIKÓW NT# # Proszę pamiętać, że znak \ to znak ucieczkowy w plikach właściwości Javy# Należy je zdublować (\\) lub wykorzystać w pliku znak Uniksowy (/)# Oba powinny działać. Również podczas określania TemplatePath, proszę pamiętać# o stosowaniu znaku oddzielającego NT (;), a nie Uniksa (:)############################################################# KONFIGURACJA PODSTAWOWA:# # Należy co najmniej określić TemplatePath! Jest to lista katalogów, która będzie # przeglądana w poszukiwaniu szablonów, jeżeli podana zostanie ścieżka względna# Jest to lista oddzielana przez : (UNIX) lub ; (NT).

TemplatePath = /tomcat/webapps/webmacro/WEB-INF/szablony;/local/webmacro/szablony

# WebMacro kompiluje i przechowuje szablony w celu osiągnięcia maksymalnejwydajności. # Podczas programowania poleca się wyłączenie tego przez ustawienie wartości na 0# tak, że zmiany w szablonach natychmiastowo odbijają się w witrynie.# W systemach produkcyjnych jest to ilość milisekund wolnego czasu, przez któryszablon# szablon umieszczony w pamięci podręcznej będzie przechowywany np. 600000 to 10minut.TemplateExpireTime = 0

25 Archiwum collections.jar zawiera klasy Java Collections (wprowadzone w JDK 1.2) utworzone do stosowania zwcześniejszym JDK 1.1. Ten plik JAR nie jest wymagany, jeżeli dysponuje się kompilacją WebMacro przeznaczonąkonkretnie dla JDK 1.2; jednak w trakcie tworzenie niniejszej książki domyślna kompilacja webmacro.jar byłaprzystosowana dla JDK 1.1.7 i w związku z tym ten plik jest wymagany nawet podczas korzystania z JDK 1.2 lubpóźniejszego.

Page 329: Java Servlet - Programowanie

# TemplateExpireTime == 600000

# LogLevel może wynosić: ALL, DEBUG, EXCEPTION, ERROR, WARNING, INFO, or NONE# w porządku od największej do najmniejszej ilości wyświetlanych informacji.LogLevel = EXCEPTION

# LogTraceExceptions powoduje umieszczanie stosów wyjątków w pliku dziennika,# co powoduje zwiększenie długości wyjątków i ich wskazywanie na konkretną linięlub # metodę, która powoduje błąd.LogTraceExceptions = TRUE

# Usunięcie komentarza powoduje zapisywanie w pliku dziennika, nie w stderr. # Jeżeli standardowy błąd już zapisuje w przydatnym dzienniku, nie trzeba tegorobić,# ale duża część mechanizmów serwletów po prostu połyka standardowy błąd.# LogFile = /usr/local/webmacro/wm.log

# Ustawienie szablonu (związanego z TemplateDirectory) wykorzystywanego do błędów.# Można dokonać edycji aby dostosować sposób wyświetlania niepowodzeń skryptuErrorTemplate = blad.wm

Zaawansowany użytkownik może utworzyć wiele wersji powyższego pliku i przełączać się pomiędzy nimi w celudokonania szybkiej i kompletnej zmiany wyglądu witryny (zmiana TemplatePath), lub po prostu byprzesunąć się z trybu programowania do produkcji (zmiana właściwości takich jak TemplateExpireTime).Ta zamiana może zostać wykonana również na poziomie serwlet-do-serwletu ponieważ konstruktor WM()pobiera opcjonalny argument typu String, który określa położenie pliku konfiguracyjnego, który powinienzostać odczytany. Jeżeli serwlet potrzebuje bezpośredniego dostępu do właściwości w tym pliku, są one dostępneprzy pomocy metody getConfig(String klucz) obecną zarówno w interfejsie WebMacro i klasieWMServlet. Pozwala to plikowi konfiguracyjnemu na przechowywanie ekwiwalentów parametrów inicjacjikontekstu, poza parametrami przywiązanymi do konkretnej konfiguracji — na przykład wskazującymi na testowąbazę danych w trakcie programowania, a na produkcyjną bazę danych po udostępnieniu.

Kiedy wszystkie pliki zostaną właściwie zainstalowane, wywoływanie serwletów obsługujących WebMacro jestwykonywane tak, jak wywoływanie wszystkich innych serwletów. Należy wywołać serwlet jakohttp://localhost:8080/servlet/WMWitaj lub, w przypadku serwletów umieszczonych w kontekście /webmacro, jakprzedstawiono w pliku WebMacro.properties, URL wynosi http://localhost:8080/webmacro/servlet/WMWitaj.Proszę spojrzeć na rysunek 15.1.

Rysunek15.1.

TestWebMacro

Język szablonów WebMacroJęzyk szablonów WebMacro przypomina składnię Perla i preprocesora C, która jest znajoma większościużytkowników. Zmienne rozpoczynają się znakiem dolara ($), a kończą pustym miejscem lub różnymi znakamiinterpunkcyjnymi takim jak <, / i ". Pozwala to na tworzenie łatwych do odczytania podstawień, na przykład:

$Request.ContextPath/servlet/WMWitaj<A HREF="$url">$url</A>

Page 330: Java Servlet - Programowanie

Kiedy jest to potrzebne, można wykorzystać nawiasy w celu wyraźnego oddzielenia nazwy zmiennej iwyjaśnienia analizy. Nawiasy są usuwane i niewyświetlane. Na przykład:

#set $przedrostek = "wyb"#set $przyrostek = "aczanie"Błądzenie to rzecz ludzka, $(przedrostek)$przyrostek — boska.

Mechanizm WebMacro nie wykorzystuje klasy java.beans.Introspector w celu uzyskania dostępu dowłaściwości obiektu; zamiast tego wykonuje bardziej wyczerpujące przeszukiwanie refleksyjne. Kiedy napiszesię $Request.ContextPath, WebMacro po pierwsze odnajduje obiekt określany jako $Request, poczym próbuje odnaleźć właściwość przy pomocy następującego wzoru poszukiwań:

request.ContextPathrequest.getContextPath()request.get("ContextPath");

Jeżeli utworzy się dłuższe podstawienie, takie jak $Request.Header.Accept, WebMacro dodajenastępujący wzór poszukiwań na końcu listy:

Request.getHeader("Accept")Przydzielenia zmiennych wykorzystują podobny wzór poszukiwań.

Przy pomocy WebMacro możliwe jest bezpośrednie wywoływanie metod na wszystkich obiektach w zakresie(czego nie można było w jasny sposób wykonać w Tea). Na przykład, poniższe podstawienie odnajduje listęproduktów w zapasie, wyszukuje produkt zawias i zwraca jego numer części:

$Zapas.Produkty.znajdzProdukt("zawias").NumerCzesciWłasność ta może okazać się przydatna, kiedy metody dostępu nie są zgodne z wzorem nazywania, któregospodziewa się WebMacro. Z drugiej strony, bezpośrednie wywoływanie metod oznacza, że w szablonieWebMacro nie może istnieć całkowite zagnieżdżanie.

Narzędzia kontekstu WebMacroJedną z potężnych cech WebMacro jest możliwość udostępnienia przez WebContext niewielkiej liczbyzmiennych nazywanych narzędziami kontekstu, wszystkim szablonom, a narzędzia te są zawsze dołączane bezkonieczności umieszczenia ich w kontekście przez wywołujący serwlet. Lista narzędzi, które powinny zostaćdołączone jest określana w pliku WebMacro.properties jako WebContextTools. Domyślnie WebMacro dostarczanastępujących narzędzi:$Request

Odwołanie do HttpServletRequest serwletu. Może zostać wykorzystane do uzyskania dostępu donagłówka Accept żądania przy pomocy $Request.Header.Accept lub równoważnie$Request.getHeader("Accept").

$ResponseOdwołanie do HttpServletResponse serwletu. Może zostać wykorzystane do ustawienia Content-Type odpowiedzi przy pomocy #set $Response.ContentType = "text/html".

$SessionOdwołanie do HttpSession użytkownika. Może zostać wykorzystane do odczytania wartości sesji przypomocy $Session.Attribute.Count lub równoważnie $Session.getAttribute("Count").

$FormNarzędzie służące do uzyskania dostępu do danych formularza żądania. Może zostać wykorzystane dopobrania parametru numerczesci przy pomocy $Form.numerczesci. Skrót dla$Request.getParameter("numerczesci") lub $Request.Parameter.numerczesci.

$FormListNarzędzie służące do uzyskania dostępu do danych formularza żądania, kiedy dane formularza posiadająwięcej niż jedną wartość. Stosowane w połączeniu z instrukcją #foreach.

$CookieNarzędzie służące do odczytywania i ustawiania cookies. Może zostać wykorzystane do ustawienia cookieprzy pomocy $Cookie.set("nazwa", "wartosc") lub #set $Cookie.nazwa ="wartosc". Cookies są odczytywane przy pomocy $Cookie.get("nazwa").Wartosc lub$Cookie.nazwa.Wartosc.

Page 331: Java Servlet - Programowanie

$CGINarżedzie służące do uzyskiwania dostępu do informacji żądania przy pomocy nazwa w stylu CGI. Możezostać wykorzystane przez inżynierów szablonów znających CGI do uzyskania dostępu do zmiennychtakich, jak katalog macierzysty dokumentów — $CGI.DOCUMENT_ROOT.

Dodatkowe narzędzia kontekstu mogą zostać udostępnione poprzez utworzenie klasy wykorzystującejorg.webmacro.ContextTool i dodanie klasy do listy WebContextTools. Można napisać lub pobraćnarzędzia odpowiedzialne za działania matematyczne, internacjonalizację, dostęp do bazy danych lub nawettworzenie obiektów HTML przy pomocy ECS opisanego w następnym rozdziale. W wielu aspektach narzędziakontekstu mają takie znaczenie dla WebMacro, jak biblioteki znaczników dla JSP.

Przykład 15.5 przedstawia proste narzędzie kontekstu wykonujące arytmetykę na liczbach typu integer. Tonarzędzie o nazwie MatTool często okazuje się przydatne, ponieważ język szablonów WebMacro został takuproszczony, że nie zawiera nawet podstawowych działań arytmetycznych26.

Przykład 15.5.

Narzędzie kontekstu służące do wykonywania arytmetyki na liczbach integer (skomplikowany kalkulator)import org.webmacro.*;

public class MatTool implements ContextTool { /** * Dla każdego żądania utworzony zostanie nowy obiekt-narzędzie poprzezwywołanie tej * metody. ContextTool jest właściwie fabryką wykorzystywaną do tworzeniaobiektów * wykorzystywanych w szablonach. Niektóre obiekty mogą po prostu zwracaćsiebie * przez tę metodę; inne mogą tworzyć nowe egzemplarze obiektów w celuutrzymania * stanu jeden-na żądanie. */ public Object init(Context k) { return this; } public static int dodaj(int x, int y) { return x + y; } public static int odejmij(int x, int y) { return x - y; } public static int pomnoz(int x, int y) { return x * y; } public static int podziel(int x, int y) { return x / y; } public static int modul(int x, int y) { return x % y; } public static boolean mniejszeNiz(int x, int y) { return (x < y); } public static boolean wiekszeNiz(int x, int y) { return (x > y); }}

Kiedy narzędzie to zostanie dodane do listy WebContextTools (proszę nie zapomnieć o tym kroku),wszystkie szablony mogą wykonywać podstawowe działania matematyczne. Na przykład, wyświetlenie roku zobiektu Date wymaga dodania 1900 do właściwości Year:

Obecny rok to $Mat.dodaj($data.Year, 1900).

26 Jednak z powodu dużych nacisków operacje arytmetyczne są dodawane i mogą być dostępne w momencie wydania książki

Page 332: Java Servlet - Programowanie

Instrukcje WebMacroInstrukcje to wyrażenia WebMacro wykonujące pewne operacje, warunkowo dołączające tekst lub powtarzająceblok wewnątrz szablonu. Lista dostępnych instrukcji jest przechowywana w pliku WebMacro.properties jakowłaściwość Directives. Wszystkie instrukcje rozpoczynają się znakiem #. Niektóre instrukcje działają nabloku, i bloki takie mogą zostać zaznaczone przy pomocy zakręconych nawiasów, { i }, lub słów kluczowych#begin i #end. Słowa kluczowe nie są tak proste w napisaniu jak nawiasy lecz powodują mniejszą ilośćproblemów z dołączonym JavaScriptem.

Istnieje siedem popularnych instrukcji. Podobnie jak w przypadku narzędzi kontekstu, można utworzyć swojewłasne instrukcje dodawane do domyślnych, chociaż nie jest to tak popularne. Poniżej przedstawiona jestdomyślna siódemka.

#if#if (warunek) { ... } #else { ... }

Instrukcja #if, samodzielna lub zastosowana w połączeniu z instrukcją #else, może zostać wykorzystana dowarunkowego dołączania tekstu. Tekst jest dołączany, jeżeli warunek jest prawdziwy, a nie dołączany, jeżelifałszywy. Warunek jest uważany za prawdziwy, jeżeli posiada niezerową wartość inną niż boolowskie false.Warunek #if może wykorzystywać znajome operatory boolowskie &&, || i !, a także nawiasy w celuokreślenia porządku. Obiekty mogą być porównywane jako część warunku przy pomocy operatorów == i !=,które właściwie wywołują metodę equals(),wykonujacą porównanie. Nie istnieje operator dla porównańwiększe-niż i mniejsze-niż, ponieważ obiekty nie są zobowiązane do bycia porównywalnym w ten sposób.MatTool zawiera także porównania do użytku z liczbami typu integer.

#if ($Klient.winienPieniadze() && $Klient.Nazwa != "Jason") { Zapłać koniecznie!}#else { Witamy!}

#set#set $wlasciwosc = wartosc

Instrukcja #set przypisuje wartość danej zmiennej lub właściwości zmiennej. Właściwość musi zostaćpomyślnie odnaleziona przez logikę refleksyjną WebMacro, oraz musi posiadać własność publicznegoustawiania w pewien sposób. Jeżeli takiej zmiennej nie ma w zakresie, tworzona jest nowa zmienna i przypisanajej zostaje podana wartość. Zmiennym w ukryty sposób przypisywany jest typ String lub Integer. TypString to typ domyślny, chyba że wartość może zostać zanalizowana jako Integer i nie jest otoczonapodwójnymi nawiasami, Dla ułatwienia, wartości Integer mogą zostać przekonwertowane do wartościString, kiedy jest to potrzebne. Ustawiane mogą być również tablice, kiedy jest to potrzebne. Należy użyćlewego ukośnika w celu wyłączenia znaku dolara:

#set $liczba1 = 4#set $liczba2 = 7#set $cena = "90"#set $faktura = "Jesteś winny \$$cena"#set $cytat = "$liczba1 mil i $liczba2 lat temu..."#set $wszystko = [ $liczba1, $liczba2, $cytat ] ## składnia tablicy

#foreach#foreach $rzecz in $lista { ... }

Instrukcja #foreach iteruje według listy, dołączając swój blok kodu raz dla każdego elementu na liście. Przykażdej pętli zmienna $rzecz pobiera wartość następnego egzemplarza a $lista. Lista może być tablicązadeklarowaną wewnątrz WebMacro lub obiektem Javy, który spełnia jeden z poniższych warunków(przeszukiwane przez refleksję w tej kolejności):

1. Obiekt sam w sobie jest tablicą.

2. Obiekt sam w sobie to Iterator.

3. Obiekt sam w sobie to Enumeration.

4. Obiekt posiada metodę Iterator iterator().

5. Obiekt posiada metodę Enumeration elements().

Poniższy kod wyświetla preferowane przez klienta lokalizację (jeżeli takie istnieją):

Page 333: Java Servlet - Programowanie

#set $tablica = $Request.Locales<UL>#foreach $element in #tablica { <LI>$element}</UL>

#parse#parse plik

Instrukcja #parse dołącza zawartość docelowego pliku w miejscu instrukcji tak, jak by był on częściąobecnego szablonu. Dostarcza to łatwego mechanizmu dołączania popularnego kodu szablonów do różnychplików szablonów. Plik może zostać określony przy pomocy ścieżki bezpośredniej lub pośredniej umieszczonejwewnątrz TemplatePath:

#set $tytul = "Tytuł strony"#parse "naglowek.wm" ##zmienna $tytul jest widoczna w naglowek.wm

#include#include url

Instrukcja #include dołącza zawartość docelowego URL-a w miejscu instrukcji, ale nie próbuje analizowaćjego zawartości. Dostarcza to łatwego mechanizmu dołączania JavaScriptu i innych rzeczy, których składniamoże wchodzić w konflikt z WebMacro, Jeżeli URL nie posiada protokołu, przyjmuje się file::

#include "http://webmacro.org/CREDITS ## Dołączenie surowego tekstu

#param#param $nazwa = wartosc

Instrukcja #param określa wartości wewnątrz szablonu, które mogą być przeglądane przez serwlet wywołującyszablon. Dostarczają one autorowi szablonu metody przekazywania informacji wspierającemu programiście Javy.Informacje te mogą zostać wykorzystane do określenia typu informacji, który ma zostać umieszczony wkontekście:

#param $autor = "Maria Kowalska"#param $wymagane = [ "uzyt", "dokument", "sesja" ]

Serwlet może pobrać wartość przez wywołanie Object getParam(String) na obiekcie Template.Metoda ta zwraca String lub Integer, jeżeli istnieje jedna wartość i Object[] zawierający obiektyString i/lub Integer, jeżeli wartość jest listą:

String autor = (String) szab.getParam("autor");Object[] wymagane = (Object[]) szab.getParam("wymagane");

#use#use 'analizator'#begin ...#end

Ostatnią z instrukcji jest #use, która pozwala blokowi tekstu szablonu (oznaczonemu przez znaczniki #begin i#end) na bycie zanalizowanym przez alternatywny analizator. Własność ta pozwala na rozszerzenie lub nawetcałkowitą wymianę składni WebMacro. Generalnie nie jest ona jednak stosowana do tak dużych zadań. Jej dwanajbardziej popularne zastosowania to dosłowne dołączenie bloku tekstu (przy pomocy analizatora text) lubusunięcia bloku tekstu (przy pomocy analizatora null):

#use 'text'#begin Cały tekst tu umieszczony jest dołączany bez zmian ## Komentarze również ponieważ format komentarza to kwestia analizatora#end#use 'null'#begin Tekst w tym miejscu można uznać za „wykomentowany”#end

Plik WebMacro.properties zawiera listę dostępnych domyślnych analizatorów we właściwości Parsers27.

27 Dołączany mechanizm analizatora może zostać zastąpiony przez docelowe instrukcje. Na przykład #use 'text' możezostać zastąpiony przez #text, a #use 'null' przez #comment.

Page 334: Java Servlet - Programowanie

Szablony WebMacroAby zademonstrować narzędzia kontekstu i instrukcje dostępne szablonom WebMacro, przykład 15.6przedstawia prosty szablon wyświetlający informacje z żądania.

Przykład 15.6.

Przeglądanie żądania przy pomocy WebMacro## przegl.wm

#set $Response.ContentType = "text/html"

<HTML><HEAD><TITLE>Przegladanie!</TITLE></HEAD><BODY>

## Szablon przeglądający pozwalający na przyzwyczajenie się do WebMacro

<H1>Rozne informacje</H1>##Lancuch zapytania: $Request.QueryString<BR>##Uzytkownik zdalny: $Request.RemoteUser<BR>

## WebMacro jeszcze nie obsługuje właściwości isXXX() trzeba użyć wywołania metody## Nie posiada też aktualnie elseif (jest dodawane)

##if ($Request.isRequestedSessionIdFromCookie()) {## Sesja dzieki cookies!##}##else { ##if ($Request.isRequestedSessionIdFromURL()) {## Sesja dzieki przepisywaniu URL!## }## #else {## Brak sesji, wspolczucia.## }##}

<H1>Informacja o parametrach</H1>#foreach $paramName in $Request.ParameterNames { $paramName: $Request.getParameter($paramName) <BR>}

<H1>Informacja o naglowku</H1>#foreach $headerName in $Request.HeaderNames { $headerName: $Request.getHeader($headerName) <BR>}

<H1>Informacja o cookie</H1>#foreach $cookie in $Request.Cookies { $cookie.Name: $cookie.Value <BR>}</BODY></HTML>

Page 335: Java Servlet - Programowanie

Rysunek 15.2 przedstawia przykładową stronę wyświetlaną przez powyższy przykład.

Rysunek 15.2.

Przeglądanie z WebMacro

Powyższy szablon demonstruje instrukcję #set ustawiającą typ zawartości odpowiedzi. Wykorzystujeinstrukcje #if i #else w celu określenia, czy klient utworzył sesję przy pomocy cookie, czy przepisywaniaURL-a, czy też nie jest częścią sesji. Wykorzystuje także instrukcję #foreach do wykonania pętli nawartościach parametrów, nagłówka i cookies obecnych w żądaniu.

Warto zauważyć pewne sztuczki — po pierwsze, podstawowa metoda dostępu o nazwie według wzoruisWlasciwosc() nie jest automatycznie odnajdywana przez WebMacro i musi zostać osobno wywołanapoprzez wywoływanie metod. Po drugie, obecnie nie istnieje instrukcja #elseif, tak więc konstrukcjaif/elseif/else musi być wykonywana poprzez zagnieżdżanie.

Gotowy do ponownego wykorzystania serwlet MacroPrzeglSzablon z przykładu 15.6 może być uważany za „samodzielny”, ponieważ nie wykorzystuje on żadnychzmiennych dostarczanych przez serwlet. Samodzielne szablony nie mogą być wywoływane bezpośrednio iwymagają serwletu, który je wywoła, ale ponieważ szablon nie ma żadnych specjalnych wymagań, serwlet tenmoże być ogólnym i możliwym do ponownego wykorzystania serwletem MacroPrzegl, jak przedstawiono wprzykładzie 15.7.

Przykład 15.7.

Ogólnie możliwy do ponownego wykorzystania serwlet WebMacroimport java.io.*;import javax.servlet.*;import javax.servlet.http.*;import org.webmacro.*;import org.webmacro.servlet.*;import org.webmacro.engine.*;import org.webmacro.broker.*;// Rozszerzanie com.oreilly.servlet.CacheHttpServlet może poprawić czas odpowiedzipublic class MacroPrzegl extends HttpServlet {

Page 336: Java Servlet - Programowanie

WebMacro wm; // główny zaczep WebMacro public void init() throws ServletException { try { wm = new WM(); } catch (InitException w) { throw new ServletException(w); } } public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { FastWriter wyj = new FastWriter(odp.getOutputStream(), odp.getCharacterEncoding()); // Nazwa szablonu jest pobierana jako dodatkowy element ścieżki // /servlet/MacroPrzegl/szab.wm // Lub jako ścieżka serwletu poprzez zasadę *.wm // /szab.wm String szablon = zad.getPathInfo(); if (szablon == null) { szablon = zad.getServletPath(); szablon = szablon.substring(1); // odcięcie początkowego "/" } // Jeżeli ciągle brak szablonu, problem if (szablon == null) { throw new ServletException( "Nie określono żadnego szablonu jako dodatkowej ścieżki lub serwletu"); } try { Template szab = wm.getTemplate(szablon); WebContext kontekst = wm.getWebContext(zad, odp); szab.write(wyj, kontekst); } catch (WebMacroException w) { throw new ServletException(w); } finally { wyj.flush(); } } public void destroy() { super.destroy(); if (wm != null) wm.destroy(); }}

Powyższy serwlet wywołuje dowolny szablon, którego nazwa zostanie przekazana jako dodatkowa informacja ościeżce, lub jeżeli jej nie ma, po prostu szablon, na który wskazuje ścieżka serwletu (poprzez dopasowanierozszerzeń plików). Ułatwieniem może okazać się zarejestrowanie powyższego serwletu tak, aby obsługiwałwszystkie żądania *.wm przy pomocy fragmentu pliku web.xml przedstawionego w przykładzie 15.8. Pozwala toplikom szablonów WebMacro na bycie wywoływanym bezpośrednio przy pomocy URL-i takich jak /przegl.wm lub webmacro/przegl.wm, podczas gdy w rzeczywistości serwlet MacroPrzegl wykonujecałą czarną robotę.

Przykład 15.8.

Rejestrowanie MacroPrzegl w celu obsługi *.wm<servlet> <servlet-name> mp </servlet-name> <servlet-class> MacroPrzegl </servlet-class></servlet><servlet-mapping> <servlet-name> mp </servlet-name> <url-pattern> *.wm </url-pattern></servlet-mapping>

Page 337: Java Servlet - Programowanie

Przydatnym może kazać się rozszerzenie serwletu MacroPrzegl tak, aby wykonywał pewną standardowąlogikę na każdym żądaniu i przypuszczalnie utworzył zbiór popularnych obiektów dostępnych w kontekściewszystkim szablonów systemowym. Serwlet podobny do powyższego, połączony z omówionymi wcześniejnarzędziami kontekstu, pozwala szablonom WebMacro na działanie z modelem przepychania podobnym do Tea(i to właściwie jest sposób, w jaki AltaVista wykorzystuje WebMacro).

Przetwarzanie szablonówWebMacro wykonuje w ukryciu dużą ilość analizy szablonów, o którą programiści i projektanci nie muszą sięmartwić, ale warto poznać mechanizmy tych działań w celu maksymalnego ich wykorzystania. WebMacroanalizuje szablony podczas ich pierwszego wykorzystania, a następnie umieszcza reprezentację odpowiedniegoszablonu w pamięci, w celu ich szybkiego wykonywania. WebMacro automatycznie przeładowuje i ponownieanalizuje zawartość szablonu po zmianie pliku szablonu, ale dla zachowania wydajności sprawdza znacznikiczasu jedynie co określony czas, wymieniony jako TemplateExpireTime w WebMacro.properties.Domyślnie czas przechowywania wynosi 0 milisekund tak, aby znaczniki czasy były sprawdzane na każdeżądanie. Jest to ułatwienie dla programowania, ale należy się upewnić, że dla wykorzystania produkcyjnegoznacznik czasu zostanie zwiększony. Dla użytkowników zaawansowanych obiekty szablonów posiadają metodęparse(), która wymusza pobranie i analizę. Metoda ta może zostać wykorzystana do analizy wszystkichszablonów podczas uruchomienia, lub wymuszenia wcześniejszej analizy, jeżeli szablon został zmieniony.

Kiedy w szablonach występują błędy, mogą wydarzyć się różne rzeczy. Jeżeli zmienna, do której następujeodwołanie, lub właściwość posiada wartość null lub nie istnieje, WebMacro traktuje tę niepomyślną zamianęjako błąd niekrytyczny. WebMacro generuje stronę w najlepszy możliwy sposób, ale zapisuje wiadomość WARNw dzienniku, a na wygenerowanej stronie umieszcza komentarz HTML/XML w miejscu błędu (podejście toposiada pewne wady, jeżeli generowana strona nie jest typu HTML ani XML):

<!-- warning: attempt to write out undefined variable Request.ContentType:java.lang.NullPointerException -->.

Jeżeli szablon zawiera błąd składni, WebMacro traktuje go jako błąd krytyczny i zapisuje wiadomość ERROR wdzienniku zdarzeń, a na wygenerowanej stronie wyświetla opis błędu28. W przypadku niektórych typów błędów,takich jak nieodnalezienie szablonu przez serwlet, WebMacro wyświetla domyślny szablon błędu,konfigurowany przy pomocy właściwości ErrorTemplate w WebMacro.properties.

Oczywiście większość błędów w witrynie WebMacro powinna zostać usunięta na etapie kodu serwletu —niewłaściwe parametry, uszkodzone bazy danych, brakujące pliki i wszystkie inne błędy, które powinny zostaćpoprawione przed przekazaniem kontroli szablonowi. WebMacro posiada duże możliwości w tym względzie.Serwlet może wykorzystać zbiór standardowych szablonów WebMacro do wyświetlenia informacji o tychbłędach, wybierając, do którego szablonu przekazać kontrolę i co zawrzeć w kontekście, zależnie od błędu. Naprzykład, jeżeli wystąpił błąd bazy danych , kontrola powinna zostać przekazana do pliku sqlException.wmrazem ze stosem ścieżek wyjątków do wyświetlenia.

Aplikacja „Narzędzia”Aby dokonać podsumowania opisu WebMacro, opisany zostanie sposób przetworzenia aplikacji „Narzędzia” zpoprzedniego rozdziału przy pomocy WebMacro zamiast Tea. Jest to najprostsze do zrozumienia, jeżeli napoczątku spojrzy się na szablon, przedstawiony w przykładzie 15.9.

Przykład 15.9.

Szablon WebMacro aplikacji „Narzędzia”## widoknarz.wm#set $Response.ContentType = "text/html"#set tytul = "Lista narzędzi"#set tytul2 = "Lista narzędzi służących do tworzenia zawartości"#set 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ę."#parse "naglowek.wm"

28 W WebMacro 0.94 (według tej wersji sprawdzany był kod w niniejszym rozdziale) występuje błąd, w którym błędy składniszablonów mogą spowodować wyświetlenie przez WebMacro pustej strony zamiast strony zawierającej opis błędu Bezwątpienia błąd ten zostanie naprawiony w następnych wersjach.

Page 338: Java Servlet - Programowanie

## Zdefiniowanie wartości, które powinny zostać odczytane przez serwlet#param $domyslnyStan = "ZYJE"#foreach $narzedzie in $narzedzia { <HR SIZE=2 ALIGN=LEFT> <H3> $narzedzia.Nazwa #if ($narzedzie.czyNowy(45)) { <FONT COLOR=#FF0000><B> (Nowość!) </B></FONT> } #else { #if ($narzedzie.czyUaktualniony(45)) { <FONT COLOR=#ff0000><B> (Uaktualnienie!) </B></FONT> } } </H3> <A HREF="$narzedzie.DomURL">$narzedzie.DomURL</A><BR> $narzedzie.Komentarz}#parse "stopka.wm"

Początek powyższego szablonu przypisuje wartości tytułom i opisom stron (przy pomocy znaków \ jako znakówkontynuacji opisu). Zmienne te są wykorzystywane wewnątrz zanalizowanego szablonu naglowek.wm w celuutworzenia struktury strony otaczającej podstawową zawartość. Zmienne są również widoczne wewnątrzszablonu stopka.wm przeanalizowanego na końcu szablonu, chociaż stopka nie wykorzystuje ich. Szablonynagłówka i stopki są przedstawione odpowiednio w przykładach 15.10 i 15.11.

Szablon widoknarz.wm wykorzystuje instrukcję #param w celu zdefiniowania ZYJE jako stałej wartościparametru szablonu domyslnyStan, pozwalając serwletowi wywołującemu ten szablon na poznaniedomyślnego stanu narzędzi, które powinna wyświetlać niniejsza strona.

Następnie szablon wykorzystuje instrukcję #foreach w celu dokonania działań na całej liście narzedzieprzekazanej przez serwlet. Zmienna narzędzi może być tablicą, Iterator, Enumeration lub obiektem takimjak Vector() lub List() posiadającym metodę iterator() lub elements() — nie jest to ważne dlaszablonu. Dla każdego egzemplarza narzędzi, szablon wyświetla jego nazwę, informację, czy jest nowy lubuaktualniony, jego URL i ostatecznie komentarz na temat narzędzia. Szablon wywołuje metody czyNowy(int) i czyUaktualniony(int) w celu określenia, czy zawartość powinna zostać uznana za nową lubuaktualnioną, Szablon powinien mieć dostęp do $narzedzie.CzasUtworzeniaDni i$narzedzie.CzasModyfikacjiDni, jednak wymagałoby to obsługi narzędzia kontekstu MatTool zprzykładu 15.5 w celu wykonania porównania mniejsze-niż.

Przykład 15.10.

Plik naglowek.wm## naglowek.wm<HTML><HEAD><TITLE>$tytul</TITLE></HEAD><BODY BGCOLOR="#FFFFFF" BACKGROUND="/obrazki/tlo.gif" LINK="#003333" ALINK="#669999" VLINK="#333333"><IMG SRC="/obrazki/banner.gif" WIDTH=600 HEIGHT=87 BORDER=0><BR><TABLE><TR><TD WIDTH=125 VALIGN=TOP> <BR><BR><BR> <FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#FF0000"> <A HREF="/indeks.html">Strona główna</A><BR> <A HREF="/hosting.html">Hosting</A><BR> <A HREF="/mechanizmy.html">Mechanizmy</A><BR> </FONT></TD><TD WIDTH=475> <TABLE CELLPADDING=5><TR><TD WIDTH=600 BGCOLOR="#006699" VALIGN=TOP> <B><FONT FACE="Arial,Helvetica" SIZE="+2"> <% tytul %> </FONT></B> </TD></TR></TABLE>

Page 339: Java Servlet - Programowanie

<B><FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#003366"> <% tytul2 %> </FONT></B><P> <P> <FONT FACE="Arial,Helvetica"> $opis

Przykład 15.11.

Plik stopka.wm## stopka.wm </FONT></TD></TR><TR><TD></TD><TD WIDTH=475 ALIGN=CENTER COLSPAN=3><HR><FONT FACE="Arial,Helvetica"><A HREF="/indeks.html">Strona główna</A>&nbsp;&nbsp;<A HREF="/hosting.html">Hosting</A>&nbsp;&nbsp;<A HREF="/mechanizmy.html">mechanizmy</A>&nbsp;&nbsp;<P></FONT><TABLE WIDTH=100%><TR><TD WIDTH=260 ALIGN=LEFT VALIGN=TOP><FONT FACE="Arial,Helvetica"><A HREF="/wlasnosc.html">Własność</A> &copy; 2000 Jason Hunter<BR>Wszystkie prawa zastrzeżone.</TD><TD WIDTH=5></FONT></TD><TD WIDTH=230 ALIGN=RIGHT VALIGN=TOP><FONT FACE="Arial,Helvetica">Kontakt: <A HREF="mailto:[email protected]">[email protected]</A></FONT></TD></TR></TABLE></TD></TR></TABLE></BODY></HTML>

Kod klasy Narzedzie pozostaje taki sam jak w przypadku aplikacji Tea, chociaż przy stosowaniu WebMacronie są konieczne podstawowe metody dostępu, a funkcje czyNowy(int) i czyUaktualniony(int) sąwidoczne w szablonie. Kod klasy SerwletNarz jest przedstawiony w przykładzie 15.12.

Przykład 15.12.

Serwlet prowadzący aplikację „Narzędzia”import org.webmacro.*;import org.webmacro.servlet.*;import org.webmacro.util.*;import java.io.*;import java.sql.*;import java.util.*;import javax.servlet.*;public class SerwletNarz extends WMServlet { private Log dziennik; private Narzedzie[] narzedzia; public void start() throws ServletException { // Załadowanie danych narzędzi do init w celu zachowania prostoty String plikNarz = getInitParameter("plikNarz"); // z web.xml if (plikNarz == null) { throw new ServletException( "Plik danych narzędzi musi być określony w parametrze inicjacjiplikNarz"); } dziennik = new Log(getServletName(), "Dziennik błędów przykładu Narzędzia");

Page 340: Java Servlet - Programowanie

dziennik.debug("Ładowanie narzędzi z " + plikNarz); try { narzedzia = Narzedzie.ladujNarzedzia(plikNarz); if (narzedzia.length == 0) { dziennik.warning("Nie znaleziono żadnych narzędzi w " + plikNarz); } else { dziennik.info(narzedzia.length + " narzędzi znalezionych w " + plikNarz); } } catch (Exception w) { dziennik.error(w); throw new ServletException(w); } } // Tworzenie kontekstu dostarcza funkcji dostępnych z szablonów. public Template handle(WebContext kontekst) throws HandlerException { // Często przekazuje się żądanie odpowiedź i aplikację, nawet jeżeli nie są // wykorzystane wszystkie obiekty, ponieważ mogą zostać wykorzystane później try { Template widok = getTemplate("widoknarz.wm"); String stan = kontekst.getRequest().getParameter("stan"); if (stan == null) { stan = (String)widok.getParam("domyslnyStan"); } if (stan == null) { kontekst.put("narzedzia", pobierzNarzedzia()); } else { kontekst.put("narzedzia", pobierzNarzedzia(stan)); } return view; } catch (WebMacroException w) { dziennik.exception(w); throw new HandlerException(w.getMessage()); } catch (IOException w) { dziennik.exception(w); throw new HandlerException(w.getMessage()); } } public Narzedzie[] pobierzNarzedzia() { return narzedzia; } public Narzedzie[] pobierzNarzedzie(String stan) { List lista = new LinkedList(); for (int i = 0; i < narzedzia.length; i++) { if (narzedzia[i].getStateFlag().equalsIgnoreCase(state)) { lista.add(tools[i]); } } return (Narzedzie[]) lista.toArray(new Narzedzie[0]); }}

Serwlet jest rozszerzeniem WMServlet i w związku z tym definiuje metodę start() zamiast init() orazmetodę handle() zamiast doGet() i doPost(). Metoda start() odczytuje parametr inicjacjiplikNarz i ładuje dane narzędzi z pliku przy pomocy Narzedzie.ladujNarzedzia(plikNarz).Metoda ta tworzy również egzemplarz Log i wykorzystuje go do zapisywania raportów na temat pracy, ostrzeżeńi komunikatów o błędach.

Metoda handle() ładuje szablon widoknarz.wm, po czym odczytuje parametr żądania stan w celu określeniastanu narzędzi, które powinny być wyświetlane. Jeżeli taki parametr nie istnieje, serwlet wykorzystuje domyślnąwartość określoną wewnątrz samego szablonu. Następnie serwlet umieszcza w WebContext listę odpowiednichnarzędzi jako zmienną o nazwie narzedzie i zwraca szablon. Każdy wywołany wyjątek jest zapisywany wdzienniku zdarzeń i przekazywany jako HandlerException.

Metody pobierzNarzedzia() umieszczone na końcu serwletu są jedynie metodami wspierającymi. Nie sąone dostępne szablonowi, chociaż mogłyby zostać uwidocznione poprzez zawarcie w kontekście obiektulookup zawierającego te metody. Następnie szablon mógłby wykonywać własną obsługę parametru stan:

#set $stan = $Form.stan;#if (!stan) { #set $narzedzia = $lookup.pobierzNarzedzia("zyje")

Page 341: Java Servlet - Programowanie

}#else { #set $narzedzia = $lookup.pobierzNarzedzia($stan)}

Wybór pomiędzy podejściem opartym na przeciąganiu i modelem przepychania przedstawionym powyżej jest wwiększość rzeczą gustu. WebMacro popiera raczej model przepychania, a Tea — przeciągania; jednak obanarzędzia obsługują oba modele.

Można wykorzystać klasę com.oreilly.servlet.CacheHttpServlet, opisaną w rozdziale 3, „Okrestrwałości serwletu” w celu zapisania wyświetlanych danych w pamięci podręcznej i poprawienia wydajnościszablonów. Technika ta jest najlepsza w przypadku szablonów, które wyświetlają dane wymagające dłuższegoczasu przygotowania, a zmieniają się stosunkowo rzadko, na przykład szablony odczytujące wyniki z bazydanych. Aby skorzystać z pamięci podręcznej należy utworzyć swój serwlet jako rozszerzenieCacheHttpServlet (wiąże się to z niemożnością skorzystania z WMServlet i koniecznością samodzielnejobsługi szablonów) oraz wykorzystać metodę getLastModified(). Jeżeli dane pochodzą z bazy danych,metoda getLastModified() może zwrócić czas ostatniego uaktualnienia bazy (poprzez znajdującą się wkontekście zmienną serwletu wykonującego uaktualnienie) lub, jeżeli czas uaktualnienia nie może zostaćokreślony, metoda może po prostu zwrócić aktualny czas minus pewną wartość, tak więc uaktualnienia mogą byćwykonywane w regularnych odstępach czasu. Jeżeli dane pochodzą z pliku, metoda może zwrócić ostatni czasmodyfikacji pliku (przypuszczalnie sprawdzając ją jedynie co pewien czas w celu poprawienia wydajności).

FiltryW trakcie pisania niniejszego tekstu, proces tworzenia WebMacro dopiero od niedługiego czasu byłudostępniony większej ilości osób i został przeniesiony ze stosunkowo restrykcyjnej licencji GPL do mniejograniczającej licencji w stylu Apache'a. Wynikiem udostępnienia narzędzia większej ilości programistów jestaktualna ich praca nad wieloma interesującymi i przydatnymi rozszerzeniami.

Najbardziej istotne rozszerzenie zawiera dołączany mechanizm filtrów. Jego ideą jest wyświetlanie zmiennejpoprzez filtr kontrolujący sposób jej wyświetlania. Standardowe filtry wykonują następujące działania:

• Kody ucieczkowe HTML — filtrowanie wprowadzonego przez użytkownika pola komentarza w celuusunię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 celuwyświetlenia komunikatu o błędzie lub ukrytego zignorowania zmiennej.

Filtry niestandardoweFiltry niestandardowe wykonują konkretne działania. Na przykład, zmienna klient może zostać przefiltrowanaprzez szablon klienta zaprojektowany w celu wyświetlania zmiennej i jej właściwości w oparciu o zewnętrznyplik 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śniejwartoś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.

Page 342: Java Servlet - Programowanie

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ówJavy. Strona WWW w ECS jest zarządzana jak obiekt, który może zawierać inne obiekty HTML (takie jak listy itabele), które mogą zawierać jeszcze więcej obiektów HTML (takich jak elementy listy i komórki tabeli). Tenmodel „obiektowego tworzenia HTML” okazuje się być bardzo potężny, ale również skoncentrowany naprogramiś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 rozdzialeopisywany jest ECS w wersji 1.3.3, dostępny pod adresem http://jakarta.apche.org/ecs.

Elementy strony jako obiektyECS zawiera klasy dla wszystkich konstrukcji HTML 4.0. Przykład 16.1 przedstawia sposób tworzenia prostejstrony HTML przy pomocy ECS.

Przykład 16.1.

Strona jako zbiór obiektówimport 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 nowyobiekt 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.

Page 343: Java Servlet - Programowanie

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 klasserwera (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. Pozainstalowaniu ECS można wywołać serwlet w zwykły sposób, a wygeneruje on wynik podobny do poniższego:

<html><head><title>Test ECS</title></head><body><big>Witaj!</big><p>Aktualny czas to FRI OCT 25 23:17:37 GMT 2001</body></html>

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ą doodczytania 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.propertieszostanie 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 jakoorg/apache/ecs/ecs.properties, tak więc plik nie może zostać umieszczony bezpośrednio w ścieżce klas, ale wpodkatalogu org/apache/ecs katalogu wewnątrz ścieżki klas (na przykład WEB-INF/classes/org/apache/ecs/ecs.properties).

Serwlet importuje dwa pakiety ECS — org.apache.ecs zawierający podstawowe klasy ECS iorg.apache.ecs.html zawierający klasy specyficzne dla HTML. (Istnieją inne pakiety odpowiedzialne zaXML, WML i RTF.)29 Pakiet org.apache.ecs.html zawiera prawie sto klas reprezentujących wszystkieelementy 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 posiadametody służące do konfiguracji elementu. Na przykład klasa TD posiada metodę setBackground(Stringurl), która ustawia tło tej komórki tabeli. Klasa Body również posiada podobną metodę służącą do ustawianiatła dla całej strony. Żaden inny element ECS nie posiada metody setBackground(), ponieważ żaden innyelement nie posiada możliwości ustawienia swojego tła, co pozwala ECS na upewnienie się, że programowoutworzone elementy zawsze zawierają prawidłowo utworzony HTML.

Aby dodać elementy głównej części, serwlet wykorzystuje łączenie metod w łańcuchy, w których kilka metodjest wywoływanych na tym samym obiekcie. W ECS można dotrzeć wiele takich konstrukcji. Na przykład, wcelu 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 doobiektu, 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ówWykorzystanie ECS do pełnego tworzenia strony wypadło z łask po ulepszeniu opartych na serwletachtechnologii 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 tychczęści strony, które są wyjątkowo dynamiczne, w których do określenia zawartości do utworzenia konieczna jestpeł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ń wbazie danych. Na przykład mógł zostać zaimplementowany system śledzenia błędów i potrzebny jest serwletdający zaawansowanym użytkownikom możliwość wykonywania swoich własnych zapytań w bazie danych (przypomocy połączenia z uprawnieniami tylko-do-odczytu). ECS sprawdza się w tworzeniu strony wyświetlającejwyniki z baz danych, programowo tworząc tabelę dostosowaną do danych. Przykład 16.2 przedstawia prostyelement przeglądający ResultSet. Przypomina on klasę HtmlSQLWynik przedstawioną w rozdziale 9,„Łączność z bazą danych”, która wykorzystywała wyj.println(). Zastosowanie ECS zamiastwyj.println() pozwala na uproszczenie i większe możliwości dostosowania kodu.

29 Osoby zainteresowane programowym tworzeniem XML przy pomocy Javy powinny się raczej skupić na wykorzystaniuJDOM (http://jdom.org), ponieważ JDOM jest lepiej zintegrowany z technologiami XML.

Page 344: Java Servlet - Programowanie

Przykład 16.1.

Ulepszona tabela ZbiorWynikimport 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 <= colCount; i++) { addElement(new TH().addElement(rsmd.getColumnName(i))); } addElement(rzad); while (rs.next()) { rzad = new TR(); for (int i = 1; i <= colCount; i++) { addElement(new TD().addElement(rs.getString(i))); } addElement(rzad); } }}

Powyższa kasa jest rozszerzeniem org.apache.ecs.html.Table, w związku z tym reprezentuje onaelement HTML <TABLE>. Wykonuje ona całą swoją pracę w konstruktorze, odczytując ResultSet i jegoResultSetMetaData w celu wygenerowania prostej tablicy wyników. Konstruktor po pierwsze wywołujesetBorder(1) w celu ustawienia atrybutu obramowania tabeli. Następnie tworzy wiersz (TR) wypełnionyelementami nagłówka tabeli (TH), z których każdy wyświetla nazwę kolumny odczytaną z metadanych.Ostatecznie konstruktor wykonuje pętlę nad zbiorem wyników i tworzy wiersz dla każdego wpisu, wypełniającwiersz elementami danych tabeli (TD) zawierającymi prostą reprezentację String zbioru wyników.

Klasa ProstaTabelaResultSet może zostać bezpośrednio wykorzystana przez serwlet przy pomocyfragmentu kodu przedstawionego w przykładzie 16.3.

Przykład 16.3.

Wykorzystanie ProstaTabelaResultSetStatement wyraz = lacz.createStatement();boolean gotResultSet = wyraz.execute(sql); // SQL od użytkownikaif (!gotResultSet) { wyj.println(wyraz.getUpdateCount() + "wierszy uaktualnionych.");}else { wyj.println(new ProstaTabelaResultSet(wyraz.getResultSet()));}

Powyższy kod tworzy tabelę podobną do przedstawionej na rysunku 16.1.

Page 345: Java Servlet - Programowanie

Rysunek16.1.

Surowywidokwyników

Powyższa klasa może zostać wykorzystana w połączeniu z technologiami szablonów. Nie istnieje powód, dlaktórego szablon musiałby tworzyć całą zawartość HTML samodzielnie; jeżeli pewna część strony potrzebujemocy ECS szablon może dołączyć dane wyświetlane przez element ECS do strony w odpowiednim miejscu.

Co się stanie, jeżeli tekst zwracany przez ResultSet będzie zawierał znaki, które HTML traktuje jakospecjalne, jak <, > i &? Domyślnie są one dołączane bezpośrednio i potencjalnie mogą zniszczyć strukturęHTML. Aby rozwiązać ten problem, ECS zawiera klasę org.apache.ecs.filter.CharacterFilter,która dokonuje konwersji znaków specjalnych HTML na odpowiadające im encje znakowe. Każdy elementużywa tego filtru jako filtru domyślnego, ale z drugiej strony domyślnie całe filtrowanie jest wyłączone w celuprzyśpieszenia działania. Filtrowanie można włączyć dla danego elementu poprzez wywołaniesetFilterState(true) lub dla całego systemu poprzez edycję ecs.properties i ustawienie wartościfilter_state i filter_attribute_state na true. W takim przypadku znaki specjalne w całymwyświetlanym tekście zostaną automatycznie przekonwertowane na encje znakowe.

Dostosowywanie wyświetlaniaECS wykorzystuje obiektową naturę Javy w celu stworzenia modelu wyświetlania danych o wysokim stopniudostosowalności. Przy pomocy niewielkiej ilości kodu Javy możliwe jest utworzenie na klasieProstaTabelaResultSet nie uproszczonej w takim stopniu tabeli TabelaResultSet. Ta nowa klasatabeli będzie przyjmować tablicę obiektów TabelaDostosuj w celu kontrolowania zawartości dodanej dotablicy. Przykład 16.4 przedstawia interfejs TabelaDostosuj.

Przykład 16.4.

Klasa TabelaDostosujimport java.sql.*;import org.apache.ecs.*;import org.apache.ecs.html.*;public interface TabelaDostosuj { public boolean przyjmij(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException; public Element wyswietl(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException;}

Dla mechanizmów dostosowujących, implementujących powyższy interfejs metoda accept() powinnazwracać true, jeżeli mechanizm posiada cel w obsłudze aktualnej komórki tabeli, a false, jeżeli nie. Metodaprzyjmij() posiada dostęp do typu kolumny, nazwy typu kolumny, nazwy kolumny, obiektu ResultSet iindeksu kolumny w celu wspomożenia jej decyzji. Metoda wyswietl() zostaje wywołana, jeżeli przyjmij() zwróci true. Tworzy ona i zwraca Element zawierający dane komórki tabeli, które mają zostać dodane dotabeli. Wszystkie elementy HTML wykorzystują interfejs Element, tak więc poprzez zwracanie Elementmetoda wyswietl() ma możliwość zwracania dowolnego typu elementu.

Możliwe jest utworzenie mechanizmów dostosowujących, służących do wyświetlania wartości null, dat i liczb,jak przedstawiono w przykładach 16.5, 16.6 i 16.7. Tworzenie obiektów elementów HTML zamiast łańcuchówpozwala na uproszczenie i łatwiejsze rozszerzanie kodu. Możliwe jest także programowe ograniczenie rodzajuwyświetlanych elementów.

Przykład 16.5.

Page 346: Java Servlet - Programowanie

Mechanizm dostosowujący wartości nullimport java.sql.*;import org.apache.ecs.*;import org.apache.ecs.html.*;public class NullDostosuj implements TabelaDostosuj { public boolean przyjmij(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { rs.getObject(indeks); return rs.wasNull(); } public Element wyswietl(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { // Wyświetlenie „niedostepne” dla wpisów null return new StringElement("niedostepne"); }}

Przykład 16.6.

Mechanizm dostosowujący wartości datimport java.sql.*;import java.text.*;import java.util.*;import org.apache.ecs.*;import org.apache.ecs.html.*;public class DataDostosuj implements TabelaDostosuj { DateFormat fmt; public DataDostosuj(Locale lok) { fmt = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, lok); } public boolean przyjmij(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { return (kolumnaTyp == Types.DATE || kolumnaTyp == Types.TIMESTAMP); } public Element wyswietl(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { // Wyświetlenie skróconej daty i godziny przy pomocy podanej lokalizacji return new StringElement(fmt.format(rs.getDate(indeks))); }}

Przykład 16.7.

Mechanizm dostosowujący wartości liczboweimport java.sql.*;import java.text.*;import java.util.*;import org.apache.ecs.*;import org.apache.ecs.html.*;public class LiczbaDostosuj implements TabelaDostosuj { NumberFormat fmt; public LiczbaDostosuj(Locale lok) { fmt = NumberFormat.getNumberInstance(lok); } public boolean przyjmij(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { return (kolumnaTyp == Types.TINYINT || kolumnaTyp == Types.SMALLINT || kolumnaTyp == Types.INTEGER || kolumnaTyp == Types.BIGINT || kolumnaTyp == Types.REAL || kolumnaTyp == Types.FLOAT || kolumnaTyp == Types.DOUBLE); }

Page 347: Java Servlet - Programowanie

public Element wyswietl(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { // Wyświetlenie liczby przy pomocy podanej lokalizacji if (kolumnaTyp == Types.TINYINT || kolumnaTyp == Types.SMALLINT || kolumnaTyp == Types.INTEGER || kolumnaTyp == Types.BIGINT) { return new StringElement(fmt.format(rs.getLong(indeks))); } else { return new StringElement(fmt.format(rs.getDouble(indeks))); } }}

Możliwe jest również utworzenie bardziej zaawansowanego mechanizmu dostosowującego, zmieniającegowszystkie identyfikatory błędów w tabeli na hiperłącza do serwletu „widok błędów”. Następnie niezależnie odzapytania przekazanego przez użytkownika, identyfikatory błędów będą tworzone jako hiperłącza. Przykład 16.8przedstawia ten mechanizm.

Przykład 16.8.

Mechanizm dostosowujący identyfikatory błędówimport java.sql.*;import java.text.*;import org.apache.ecs.*;import org.apache.ecs.html.*;

public class BladIdDostosuj implements TabelaDostosuj { String bladWidokSerwlet; public BladIdDostosuj(String bladWidokSerwlet) { this. bladWidokSerwlet = bladWidokSerwlet; } public boolean przyjmij(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { return ((kolumnaTyp == Types.CHAR || kolumnaTyp == Types.VARCHAR || kolumnaTyp == Types.LONGVARCHAR) && "idbledu".equalsIgnoreCase(kolumnaNazwa)); } public Element wyswietl(int kolumnaTyp, String kolumnaTypNazwa, String kolumnaNazwa, ResultSet rs, int indeks) throws SQLException { // Utworzenie łącza do serwletu wyświetlającego dany błąd String idbledu = rs.getString(indeks); return new A(bladWidokSerwlet + "?idbledu=" + idbledu, idbledu); }}

Klasa TabelaResultSet przyjmuje w swoim konstruktorze tablicę elementów TabelaDostosuj. Dlakażdej komórki mechanizmy dostosowujące umieszczone w tablicy mają możliwość kontroli tworzenia każdejkomórki tabeli. Mechanizmy będą wywoływane w porządku, w jakim umieszczone są w tablicy, a zwyciężapierwszy mechanizm przyjmujący komórkę tabeli. Kod klasy TabelaResultSet jest przedstawiony wprzykładzie 16.9.

Przykład 16.9.

Dostosowywana tabela ResultSetimport 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 TabelaResultSet extends Table { public TabelaResultSet (ResultSet rs) throws SQLException { this(rs, null); }

Page 348: Java Servlet - Programowanie

public TabelaResultSet(ResultSet rs, TabelaDostosuj[] dostosuj) throws SQLException { setBorder(1); if (dostosuj == null) { dostosuj = new TabelaDostosuj[0]; } ResultSetMetaData rsmd = rs.getMetaData(); int iloscKol = rsmd.getColumnCount(); TR rzad = new TR(); for (int i = 1; i <= iloscKol; i++) { addElement(new TH().addElement(rsmd.getColumnName(i))); } addElement(rzad); while (rs.next()) { rzad = new TR(); for (int i = 1; i <= iloscKol; i++) { TD td = new TD(); int kolumnaTyp = rsmd.getColumnType(i); String kolumnaTypNazwa = rsmd.getColumnTypeName(i); String kolumnaNazwa = rsmd.getColumnName(i); // Danie każdemu mechanizmowi szansy kontrolowania sposobu wyświetlania boolean dostosowany = false; for (int c = 0; c < dostosuj.length; c++) { TabelaDostosuj dost = dostosuj[c]; if (dost.przyjmij(kolumnaTyp, kolumnaTypNazwa, kolumnaNazwa, rs, i)) { td.addElement(dost.wyswietl(kolumnaTyp, kolumnaTypNazwa, kolumnaNazwa, rs, i)); dostosowany = true; break; } } // Jeżeli brak właściwych mechanizmów, wyświetlenie wartości jako String if (!dostosowany) { td.addElement(rs.getString(i)); } addElement(td); } addElement(rzad); } }}

Zewnętrzna pętla while dokonuje iteracji na wierszach tabeli, podczas gdy zewnętrzna pętla for dokonujeiteracji na kolumnach tabeli, a wewnętrzna pętla for zarządza logiką nowego mechanizmu dostosowującego.Pierwszy mechanizm, który „przyjmie” komórkę obsługuje sposób jej wyświetlania. Jeżeli komórki nie przyjmieżaden mechanizm, tabela wyświetla prostą wartość String. Serwlet wywołujący TabelaResultSet jestprzedstawiony w przykładzie 16.10.

Przykład 16.10.

Serwlet pracujący z TabelaResultSetimport java.io.*;import java.sql.*;import java.text.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import org.apache.ecs.*;import org.apache.ecs.html.*;import com.oreilly.servlet.*;public class SerwletResultSet extends HttpServlet { public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/html"); PrintWriter wyj = odp.getWriter();

Page 349: Java Servlet - Programowanie

String url = zad.getParameter("url"); String driver = zad.getParameter("driver"); String sql = zad.getParameter("sql"); // Szybkie sprawdzenie, czy url/driver/sql istnieje ParameterParser analiza = new ParameterParser(zad); String[] wymagany = { "url", "driver", "sql" }; String[] brakujacy = analiza.getMissingParameters(wymagany); if (brakujacy != null && brakujacy.length > 0) { odp.sendError(odp.SC_BAD_REQUEST, "Podane muszą być łańcuchyURL, Driver i SQL "); return; } String param1 = zad.getParameter("param1"); String param2 = zad.getParameter("param2"); String param3 = zad.getParameter("param3"); String param4 = zad.getParameter("param4"); String param5 = zad.getParameter("param5"); String param6 = zad.getParameter("param6"); String wart1 = zad.getParameter("wart1"); String wart2 = zad.getParameter("wart2"); String wart3 = zad.getParameter("wart3"); String wart4 = zad.getParameter("wart4"); String wart5 = zad.getParameter("wart5"); String wart6 = zad.getParameter("wart6"); Properties wlasc = new Properties(); if (param1 != null && wart1 != null) { wlasc.put(param1, wart1); } if (param2 != null && wart2 != null) { wlasc.put(param2, wart2); } if (param3 != null && wart3 != null) { wlasc.put(param3, wart3); } if (param4 != null && wart4 != null) { wlasc.put(param4, wart4); } if (param5 != null && wart5 != null) { wlasc.put(param5, wart5); } if (param6 != null && wart6 != null) { wlasc.put(param6, wart6); } Connection lacz = null; try { Class.forName(driver); lacz = DriverManager.getConnection(url, wlasc); Statement wyraz = lacz.createStatement(); boolean gotResultSet = wyraz.execute(sql); if (!gotResultSet) { wyj.println(wyraz.getUpdateCount() + " rzędów uaktualniono."); } else { TabelaDostosuj[] dostosuj = { new NullDostosuj(), new DataDostosuj(zad.getLocale()), new BladIdDostosuj(zad.getContextPath() + "/servlet/BladWidok"), new LIczbaDostosuj(zad.getLocale()), }; wyj.println(new ResultSetTable(wyraz.getResultSet(), dostosuj)); } } catch (Exception w) { throw new ServletException(w); } }}

Rysunek 16.2 przedstawia przykładowy wynik uruchomienia powyższego kodu. Proszę zauważyć, że terazidentyfikator błędu jest hiperłączem, data jest ładnie sformatowana, a opis null wyświetla „niedostepne”.

Page 350: Java Servlet - Programowanie

Rysunek16.2.

Dostosowanywidok zbioruwynikówResultSet(dataeuropejska)

Przykład 16.11 przedstawia prosty fronton HTML dla serwletu.

Przykład 16.11.

Prosty fronton HTML dla SerwletResultSet<HTML><HEAD><TITLE>Zapytanie SQL</TITLE></HEAD><BODY><P> Niniejsza aplikacja wykonuje zapytanie SQL lub uaktualnia dowolną bazę danych znajdującą się w sieci.</P><FORM METHOD="POST" ACTION="servlet/SerlwetResultSet"> <TABLE WIDTH="75%" BORDER="1"> <TR> <TD WIDTH="35%">URL bazy danych:</TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="url" SIZE="60"></TD> </TR> <TR> <TD WIDTH="35%">Sterownik bazy danych:</TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="driver" SIZE="60"></TD> </TR> <TR> <TD COLSPAN="2"><P ALIGN=CENTER> -- Właściwości bazy danych -- </p></TD> </TR> <TR> <TD WIDTH="35%"><INPUT TYPE=TEXT NAME="wlasc1" VALUE="Nazwaużytkownika"></TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="wart1"></TD> </TR> <TR> <TD WIDTH="35%"><INPUT TYPE=TEXT NAME="wlassc2" VALUE="password"></TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="wart2"></TD> </TR> <TR> <TD WIDTH="35%"><INPUT TYPE=TEXT NAME="walasc3" VALUE="cacherows"></TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="wart3"></TD> </TR> <TR> <TD WIDTH="35%"><INPUT TYPE=TEXT NAME="wlasc4"></TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="wart4"></TD> </TR> <TR> <TD WIDTH="35%"><INPUT TYPE=TEXT NAME="wlasc5"></TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="wart5"></TD> </TR> <TR> <TD WIDTH="35%"><INPUT TYPE=TEXT NAME="wlasc6"></TD> <TD WIDTH="65%"><INPUT TYPE=TEXT NAME="wart6"></TD> </TR> </TABLE> <P> <TEXTAREA NAME="sql" COLS="80" ROWS="5"></TEXTAREA> <BR> <INPUT TYPE=SUBMIT VALUE="Wyślij SQL"> </P></FORM>

Powyższa demonstracja ECS jedynie dotknęła możliwości ECS. Przy dostępności całej mocy Javy możnatworzyć do własnej dyspozycji strony lub części stron, które zostały w idealny sposób dostosowane do danychoraz, przy pomocy obiektowej natury ECS można ułatwić sobie pracę przy pomocy dziedziczenia, poliformizmui silnego sprawdzania typów.

Page 351: Java Servlet - Programowanie

Rozdział 17. XMLC

XMLC wykorzystuje technologię XML w celu zmiany plików HTML w zasoby Javy. Został on utworzony przezfirmę Lutris jako część serwera Open Source Enhydra Application Server i może zostać pobrany niezależnie, wcelu zastosowania go w połączeniu z dowolnym innym kontenerem serwletów30. W niniejszym rozdzialeomówiony zostanie XMLC 1.2b1 dostępny pod adresem http://xmlc.enhydra.org. XMLC wymaga JDK1.1 lubpóźniejszego i dowolnej wersji Servlet API. Jest ona udostępniania w licencji Enhydra Public License (EPL),licencji Open Source podobnej do Mozilla Public License (MPL). Mówiąc krótko, według tej licencji zmiany wistniejącym kodzie powinny być przesłane z powrotem do producenta, ale pozwala ona na prywatne, a nawetkomercyjne rozszerzenia.

XMLC oznacza XML Compiler (Kompilator XML). Narzędzie to pobiera standardowy dokument HTML lubXML i „kompiluje” go do klasy Javy. Klasa ta zawiera instrukcje Javy potrzebne do utworzenia reprezentacjidokumentu XML DOM (Document Object Model — Model Obiektu Dokumentu) w formie drzewa w pamięci.Programista może manipulować drzewem znajdującym się w pamięci w celu dodania zawartości dynamicznej, apo przetworzeniu może wyświetlić zmodyfikowane drzewo jako HTML, XHTML lub dowolny inny XML.

W tradycyjnym podejściu, projektant strony tworzy jedną lub więcej „makiet” tworzonej strony WWW. Makietyto czyste pliki HTML. Są one przeglądane przez zainteresowane osoby, odbywa się nad nimi dyskusja, po czym,jeżeli to konieczne, zostają one poprawione. Projektant nie musi dodawać do pliku żadnych instrukcji XMLC,jedynie zgodne z HTML 4.0 atrybuty ID do obszarów strony, które mają zostać zastąpione zawartościądynamiczną.

Narzędzie XMLC kompiluje offline makietę strony do klasy Javy zawierającej reprezentację dokumentu wformie drzewa DOM. Programista Javy wykorzystuje publiczny interfejs tej klasy w celu utworzenia klasymanipulacyjnej, która tworzy egzemplarz drzewa, odnajduje części dokumentu, które powinny zostać zmienionei odpowiednio jej modyfikuje. Części te są odnajdywane przy pomocy metod dostępu dodanych do drzewa przezXMLC dla każdego atrybutu ID w dokumencie. W ten sposób pliki HTML stają się właściwie zasobami dlaJavy.

Jeżeli na stronie powinna znaleźć się lista elementów, makieta może zawierać pięć lub dziesięć z nich, abywyglądać realistycznie. Programista Javy może usunąć wszystkie elementy oprócz pierwszego, a następnieskonstruować właściwą listę poprzez powtarzane klonowanie pierwszego elementu — w ten sposóbdostosowując się do stylu makiety, ale wykorzystując dane tworzone dynamicznie. Możliwe jest nawet nakazaniekompilatorowi XMLC automatycznego usunięcia wszystkich elementów oprócz pierwszego, co poprawiawydajność.

XMLC osiąga wysoki poziom oddzielenia zawartości i prezentacji. Plik HTML to czysty HTML, pliki Javy toczysta Java, a pliki łączą się jedynie poprzez uzgodnione znaczniki ID. Kiedy utworzone zostają nowe makiety,mogą one zostać wykorzystane bezpośrednio bez konieczności wstecznego dopasowania, a jeżeli ważnegoznacznika nie ma w nowej makiecie, wywołany zostaje błąd kompilacji. Dzieje się tak, kiedy klasamanipulacyjna próbuje wywołać metodę odczytującą nieistniejący ID.

Prosta kompilacja XMLAby nauczyć się stosowania XML należy rozpocząć od prostej aplikacji podstawiającej. Przykład 17.1przedstawia prostą makietę strony WWW witaj.html, która wita użytkownika po imieniu i podaje informację, ile

30 Enhydra Lutris to naukowa nazwa morskiej wydry, co jest odpowiednim identyfikatorem dla firmy z siedzibą w SantaCruz. W pobliżu tego miasta znajduje się zatoka Monterey, w której żyje mnóstwo wydr.

Page 352: Java Servlet - Programowanie

wiadomości czeka na niego. Tekst, który zostanie podmieniony jest otoczony zgodnymi z HTML 4.0znacznikami <SPAN>.

Przykład 17.1.

Proste podstawienie XMLC<!-- witaj.html --><HTML><HEAD><TITLE>Witaj</TITLE></HEAD><BODY><H2><SPAN ID="Powitanie">Witaj Agnieszka</SPAN></H2>Masz <SPAN ID="Wiadomosci">103</SPAN> nowe wiadomości.</BODY></HTML>

W celu uruchomienia XMLC z powyższym plikiem, po pierwsze należy zainstalować XMLC. Należy pobrać plikz wiryny http://xmlc.enhydra.org, rozpakować archiwum i postępować według instrukcji w pliku READMEmówiących o uruchomieniu xmlc-config. Potem można już uruchomić kompilator xmlc:xmlc –class Witaj –keep witaj.htmlPowyższa linia kodu nakazuje XMLC skompilowanie pliku witaj.html do klasy o nazwie Witaj, zatrzymującwygenerowany plik Witaj.java do sprawdzenia. Ponieważ standardowe pliki HTML nie muszą posiadaćwłaściwej formy XML, XMLC wykorzystuje analizator składniowy Tidy do obsługi konwersji z HTML doDOM. Proszę jednak pamiętać, że Tidy może popełniać błędy podczas obsługi plików HTML zbytniooddalonych od prawidłowego HTML, a kiedy to nastąpi, podczas kompilacji XMLC wyświetlona zostanie długalista błędów. Aby wyeliminować ten problem, można wykorzystać narzędzia służące do projektowania HTML(takie jak Dreamweaver) w celu utworzenia HTML nadającego się do przetwarzania XMLC. Większa ilośćinformacji na temat Tidy jest dostępna pod adresem www.w3.org/People/Raggett/tidy, a na temat portu Javy onazwie Jtidy pod adresem http://www3.sympatico.ca/ac.quick/jtidy.html.

XMLC obsługuje wiele opcji kompilacyjnych. Najpopularniejsze z nich zostały wymienione poniżej31. Składnialinii poleceń wygląda następująco:xmlc [opcje] plikdok-class Nazwaklasy

Określa pełną nazwę generowanej klasy. Na przykład Witaj lub com.servlets.xml.Witaj.-keep

Nakazuje zapamiętanie utworzonego źródła Javy. Przydatne podczas rozpoczynania nauki XMLC.-methods

Wyświetla podpis każdej tworzonej metody dostępu. Również przydatne przy rozpoczynaniu nauki.-verbose

Wyświetla dodatkowe informacje na temat procesu kompilacji

-urlmapping staryURL nowyURL

Określa zasadę podmiany URL-i. Wszystkie egzemplarze staryURLzostaną zastąpione przez nowyURL. Pozwala na zmiany wURL-u pomiędzy makietą i produktem. Opcja może zostaćpodana wielokrotnie.

-urlregexpmapping wyrreg podst

Określa zaawansowaną zasadę podmiany URL-i przy pomocy wyrażeń regularnych. Opcja może zostaćpodana wielokrotnie.

-urlsetting id nowyURL

31 XMLC jest dostępny również jako zadanie Ant, co może zainteresować osoby wykorzystujące doskonałe narzędziekompilacji ANT. Proszę zobaczyć http://jakarta.apache.org.

Page 353: Java Servlet - Programowanie

Określa URL, który powinien zostać zmodyfikowany w DOM wedługjego znacznika ID. Url o identyfikatorze id zostaniezmieniony na nowyURL.

-Java prog.

Określa kompilator Javy, który powinien zostać wykorzystany.-d katalog

Określa docelowy katalog. Przekazywany bezpośredniokompilatorowi Javy podczas fazy kompilacji.

-delete-class nazwaklasy

Usuwa wszystkie elementy dokumentu posiadające atrybut CLASSnazwaklasy. Przydatne także przy usuwaniu danychmakiety, jak zostanie to przedstawione w dalszejczęści. Opcja może zostać podana wielokrotnie.

-version

Wyświetla wersję XMLC.Po uruchomieniu XMLC ze znacznikiem – keep można przejrzeć wygenerowane źródło. Źródło Witaj.java jestprzedstawione w przykładzie 17.2.

Przykład 17.2.

Generowane automatycznie źródło Witaj.java (komentarze przetłumaczone)/* ******************************************* * KOD WYGENEROWANY PRZEZ XMLC NIE EDYTOWAĆ* ******************************************* */import org.w3c.dom.*;import org.enhydra.xml.xmlc.XMLCError;import org.enhydra.xml.xmlc.XMLCUtil;import org.enhydra.xml.xmlc.dom.XMLCDomFactory;/** * klasa dokument XMLC utworzona z pliku * witaj.html */public class Witaj extends org.enhydra.xml.xmlc.html.HTMLObjectImpl implementsorg.enhydra.xml.xmlc.XMLObject, org.enhydra.xml.xmlc.html.HTMLObject { private int $elementId_Powitanie = 8; private int $elementId_Wiadomosci = 13; private org.enhydra.xml.lazydom.html.LazyHTMLElement $element_Powitanie; private org.enhydra.xml.lazydom.html.LazyHTMLElement $element_Wiadomosci; /** * Pole wykorzystywane do stwierdzenia, że to jest klasa stworzona przez XMLC * w łańcuchu dziedziczenia. Zawiera odwołanie do obiektu klasy. */ public static final Class XMLC_GENERATED_CLASS = Witaj.class; /** * Pole zawierające zależną od CLASSPATH nazwę pliku źródłowego, z którego * ta klasa została wygenerowana. */ public static final String XMLC_SOURCE_FILE = "/witaj.html"; /** * fabryka XMLC DOM skojarzona z tą klasą.

Page 354: Java Servlet - Programowanie

*/ private static final org.enhydra.xml.xmlc.dom.XMLCDomFactory fDOMFactory =org.enhydra.xml.xmlc.dom.XMLCDomFactoryCache.getFactory(org.enhydra.xml.xmlc.dom.lazydom.LazyHTMLDomFactory.class);; /** * Opcje wykorzystywane do preformatowania dokumentu po kompilacji */ private static final org.enhydra.xml.io.OutputOptions fPreFormatOutputOptions; /** * Szablon wykorzystywany przez wszystkie egzemplarze. */ private static final org.enhydra.xml.lazydom.TemplateDOM fTemplateDocument; /** * dokument Lazy DOM */ private org.enhydra.xml.lazydom.LazyDocument lazyDocument; /* * Inicjator klas. */ static { org.enhydra.xml.lazydom.html.LazyHTMLDocument doc =(org.enhydra.xml.lazydom.html.LazyHTMLDocument)fDOMFactory.createDocument(null,"HTML", null); buildTemplateSubDocument(doc, doc); fTemplateDocument = new org.enhydra.xml.lazydom.TemplateDOM(doc); fPreFormatOutputOptions = new org.enhydra.xml.io.OutputOptions(); fPreFormatOutputOptions.setFormat(org.enhydra.xml.io.OutputOptions.FORMAT_AUTO); fPreFormatOutputOptions.setEncoding("ISO-8859-1"); fPreFormatOutputOptions.setPrettyPrinting(false); fPreFormatOutputOptions.setIndentSize(4); fPreFormatOutputOptions.setPreserveSpace(true); fPreFormatOutputOptions.setOmitXMLHeader(false); fPreFormatOutputOptions.setOmitDocType(false); fPreFormatOutputOptions.setOmitEncoding(false); fPreFormatOutputOptions.setDropHtmlSpanIds(true); fPreFormatOutputOptions.setOmitAttributeCharEntityRefs(true); fPreFormatOutputOptions.setPublicId(null); fPreFormatOutputOptions.setSystemId(null); fPreFormatOutputOptions.setMIMEType(null); fPreFormatOutputOptions.markReadOnly(); } /** * Domyślny konstruktor. */ public Witaj() { buildDocument(); } /** * Konstruktor z opcją tworzenia DOM. */ public Witaj(boolean buildDOM) { if (buildDOM) { buildDocument(); }

Page 355: Java Servlet - Programowanie

} /** * Kopiowanie konstruktora. */ public Witaj(Witaj src) { setDocument((Document)src.getDocument().cloneNode(true), src.getMIMEType(), src.getEncoding()); syncAccessMethods(); } /** * Stworzenie dokumentu jako DOM i inicjacja pól metod dostępu. */ public void buildDocument() { lazyDocument = (org.enhydra.xml.lazydom.html.LazyHTMLDocument)((org.enhydra.xml.xmlc.dom.lazydom.LazyDomFactory)fDOMFactory).createDocument(fTemplateDocument); lazyDocument.setPreFormatOutputOptions(fPreFormatOutputOptions); setDocument(lazyDocument, "text/html", "ISO-8859-1"); } /** * Utworzenie poddrzewa dokumentu. */ private static void buildTemplateSubDocument(org.enhydra.xml.lazydom.LazyDocument document, org.w3c.dom.Node parentNode) { Node $node0, $node1, $node2, $node3, $node4, $node5; Element $elem0, $elem1, $elem2, $elem3, $elem4; Attr $attr0, $attr1, $attr2, $attr3, $attr4; Element $docElement = document.getDocumentElement(); $node1 = document.createTemplateComment(" witaj.html ", 1); parentNode.insertBefore($node1, $docElement); $elem1 = document.getDocumentElement(); ((org.enhydra.xml.lazydom.LazyElement)$elem1).makeTemplateNode(2); ((org.enhydra.xml.lazydom.LazyElement)$elem1).setPreFormattedText("<HTML>"); $elem2 = document.createTemplateElement("HEAD", 3, "<HEAD>"); $elem1.appendChild($elem2); $elem3 = document.createTemplateElement("TITLE", 4, "<TITLE>"); $elem2.appendChild($elem3); $node4 = document.createTemplateTextNode("Witaj", 5, "Witaj"); $elem3.appendChild($node4); $elem2 = document.createTemplateElement("BODY", 6, "<BODY>"); $elem1.appendChild($elem2);

Page 356: Java Servlet - Programowanie

$elem3 = document.createTemplateElement("H2", 7, "<H2>"); $elem2.appendChild($elem3); $elem4 = document.createTemplateElement("SPAN", 8, "<SPAN>"); $elem3.appendChild($elem4); $attr4 = document.createTemplateAttribute("id", 9); $elem4.setAttributeNode($attr4); $node5 = document.createTemplateTextNode("Powitanie", 10, "Powitanie"); $attr4.appendChild($node5); $node5 = document.createTemplateTextNode("Witaj Agnieszka", 11, "WitajAgnieszka"); $elem4.appendChild($node5); $node3 = document.createTemplateTextNode("Masz ", 12, "Masz "); $elem2.appendChild($node3); $elem3 = document.createTemplateElement("SPAN", 13, "<SPAN>"); $elem2.appendChild($elem3); $attr3 = document.createTemplateAttribute("id", 14); $elem3.setAttributeNode($attr3); $node4 = document.createTemplateTextNode("Wiadomosci", 15, "Wiadomosci"); $attr3.appendChild($node4); $node4 = document.createTemplateTextNode("103", 16, "103"); $elem3.appendChild($node4); $node3 = document.createTemplateTextNode(" nowe wiadomo\u0153ci.", 17, "nowe wiadomo&#339;ci."); $elem2.appendChild($node3); } /** * Kolonowanie dokumentu. */ public Node cloneNode(boolean deep) { cloneDeepCheck(deep); return new Witaj(this); } /** * Pobranie fabryki XMLC DOM skojarzonej z klasą. */ protected final org.enhydra.xml.xmlc.dom.XMLCDomFactory getDomFactory() { return fDOMFactory; }

Page 357: Java Servlet - Programowanie

/** * Pobranie dokumentu o identyfikatorze <CODE>Powitanie</CODE>. * proszę zobaczyć org.w3c.dom.html.HTMLElement */ public org.w3c.dom.html.HTMLElement getElementPowitanie() { if (($element_Powitanie == null) && ($elementId_Powitanie >= 0)) { $element_Powitanie = (org.enhydra.xml.lazydom.html.LazyHTMLElement)lazyDocument.getNodeById($elementId_Powitanie); } return $element_Powitanie; } /** * Pobranie dokumentu o identyfikatorze <CODE>Wiadomosci</CODE>. * proszę zobaczyć org.w3c.dom.html.HTMLElement */ public org.w3c.dom.html.HTMLElement getElementWiadomosci() { if (($element_Wiadomosci == null) && ($elementId_Wiadomosci >= 0)) { $element_Wiadomosci = (org.enhydra.xml.lazydom.html.LazyHTMLElement)lazyDocument.getNodeById($elementId_Wiadomosci); } return $element_Wiadomosci; } /** * Pobranie wartości potomka tekstowego elementu <CODE>Powitanie</CODE>. * Proszę zobaczyć org.w3c.dom.Text */ public void setTextPowitanie(String text) { if (($element_Powitanie == null) && ($elementId_Powitanie >= 0)) { $element_Powitanie = (org.enhydra.xml.lazydom.html.LazyHTMLElement)lazyDocument.getNodeById($elementId_Powitanie); } doSetText($element_Powitanie, text); } /** * Pobranie wartości potomka tekstowego elementu <CODE>Wiadomosci</CODE>. * Proszę zobaczyć org.w3c.dom.Text */ public void setTextWiadomosci(String text) { if (($element_Wiadomosci == null) && ($elementId_Wiadomosci >= 0)) { $element_Wiadomosci = (org.enhydra.xml.lazydom.html.LazyHTMLElement)lazyDocument.getNodeById($elementId_Wiadomosci); } doSetText($element_Wiadomosci, text); } /** * Rekursja funkcji ustawiającej pola funkcji dostępu DOM. * Pola brakujących identyfikatorów ustawione na null. */ protected void syncWithDocument(Node node) { if (node instanceof Element) { String id = ((Element)node).getAttribute("id"); if (id.length() == 0) { } else if (id.equals("Powitanie")) { $elementId_Powitanie = 8; $element_Powitanie =(org.enhydra.xml.lazydom.html.LazyHTMLElement)node;

Page 358: Java Servlet - Programowanie

} else if (id.equals("Wiadomosci")) { $elementId_Wiadomosci = 13; $element_Wiadomosci =(org.enhydra.xml.lazydom.html.LazyHTMLElement)node; } } Node child = node.getFirstChild(); while (child != null) { syncWithDocument(child); child = child.getNextSibling(); } }}

Powyższy przykład zawiera dużą ilość kodu, nie jest jednak skomplikowany. Klasa nosi nazwę Witaj, jakpodano w opcji –class. Jest ona rozszerzeniem org.ehydra.xml.xmlc.html.HTMLObjectImpl,superklasy XMLC dla wszystkich obiektów HTML i implementuje org.w3c.dom.html.HTMLObject,standardowy interfejs DOM przedstawiający dokument HTML. Konstruktor Witaj wywołuje metodębuildDocument() wypełniającą drzewo DOM zawartością z pliku HTML. DOM jest po prostu zbioreminterfejsów i do wyboru jest wiele jego implementacji. W metodzie createDocument() można dostrzec, żeniniejsza klasa wykorzystuje domyślną implementację XMLC, oznaczoną przez DefaultHTMLDomFactory,którą w wersji 1.2b1 jest Apache Xerces (proszę zobaczyć http://xml.apache.org).

Po uruchomieniu xmlc z opcją –methods można ujrzeć podsumowania wygenerowanych metod dostępu wźródle:

% xmlc –class Witaj –keep –methods witaj.htmlpublic org.w3c.dom.html.HTMLElement getElementPowitanie();public void setTextPowitanie(String text);public org.w3c.dom.html.HTMLElement getElementWiadomosci();public void setTextWiadomosci (String text);

Metody get zwracają obiekt DOM HTMLElement reprezentujący element HTML oznaczony atrybutem ID.Metody set ustawiają zawartość tekstową tych elementów. Proszę zauważyć, że wartość atrybutu ID jestzawarta w nazwach metod. Dla wartości, które nie mogą zostać przekonwertowane na prawidłowe identyfikatoryJavy nie zostaną wygenerowane metody dostępu. Tak więc należy być ostrożnym i stosować jedynie litery, cyfryi kreski dolne. Proszę również pamiętać, że wartości identyfikatorów nie powinny powtarzać się na jednejstronie.

Klasa manipulacyjnaPo skompilowaniu pliku HTML do jego opartej na Javie reprezenatcji XML, następnym krokiem jest utworzenietak zwanej klasy manipulacyjnej, Klasa ta tworzy egzemplarz dokumentu, modyfikuje jego zawartość i wyświetlago we właściwym miejscu. Przykład 17-3 przedstawia pojedynczą klasę manipulacyjną dla strony Witaj.Proszę zauważyć, że jest to samodzielny program. Klasa manipulacyjna powinna być serwletem, lub klasąwywoływaną przez serwlet, ale nie jest to absolutnie konieczne. XMLC nie jest zależny od Servlet API.

Przykład 17.3

Klasa manipulacyjna dla strony Witajimport java.io.*;import org.w3c.dom.*;import org.w3c.dom.html.*;import org.enhydra.xml.io.DOMFormatter;public class WitajManipulacja { public static void main(String[] args) { // Pewna pseudo-dynamiczna zawartość String nazwaUzyt = "Jan Kowalski"; int iloscWiadomosci = 43;

Page 359: Java Servlet - Programowanie

// Stworzenie drzewa DOM Witaj witaj = new Witaj(); // Ustawienie tytułu przy pomocy standardowej metody DOM witaj.setTitle("Witaj XMLC!"); // Ustawienie wartości dla „powitanie” witaj.setTextPowitanie("Witaj, " + nazwaUzyt); // Ustawienie wartości dla „wiadomosci” witaj.setTextWiadomosci("" + iloscWiadomosci); try { DOMFormatter formatuj = new DOMFormatter(); // może być dostosowana formatuj.write(witaj, System.out); } catch (IOException w) { w.printStackTrace(); } }}

Powyższa klasa na początku ustawia pseudodynamiczne wartości, które zostaną dodane do strony — nazwęużytkownika i ilość wiadomości. Następnie tworzy egzemplarz klasy Witaj, która przechowuje zawartośćdokumentu HTML. Jeżeli egzemplarz witaj zostałby w tym momencie wyświetlony, ukazałaby się dokładnareprezentacja oryginalnego pliku HTML. Klasa manipulacyjna następnie ustawia tytuł strony, wartość powitaniai wartość ilości wiadomości. Powitanie i ilość wiadomości są ustawiane przy pomocy metod dostępu dodanychprzez XMLC. Tytuł ustawiany jest przy pomocy standardowej metody DOM. Istnieje wiele ciekawychstandardowych metod dostępu dla klas HTML DOM, jak na przykład getApplets(), getImages(),getLinks(), getForms(), getAnchors() i tak dalej. Większa ilość informacji na temat możliwości klasDOM jest zawarta w dokumentacji dołączonej do XMLC. Na końcu klasa wykorzystujeorg.enhydra.xml.io.DOMFormatter w celu wyświetlenia egzemplarza Hello w System.out.DOMFormatter może próbować zastosowania w swoim konstruktorze klasy OutputOption w celusprawdzenia, jak wyświetlany jest XML, ale w przypadku standardowych przeglądarek WWW domyślnezachowanie działa prawidłowo i tworzy:

<HTML><HEAD><TITLE>Witaj XMLC!</TITLE></HEAD><BODY><H2><SPAN id='Powitanie'>Witaj,Jan Kowalski</SPAN></H2>Masz <SPAN id='Wiadomosci'>43</SPAN> nowewiadomości.</BODY><!-- witaj.html --></HTML>

Proszę zauważyć, że wartości z makiety zostały zastąpione nowymi i usunięta została niepotrzebna wolnaprzestrzeń w celu poprawy wydajności. Można także dostrzec, że komentarz został umieszczony na końcu pliku.Jest to nieszkodliwy błąd Xerces.

W celu samodzielnego uruchomienia klasy manipulacyjnej należy upewnić się, że plik xmlc.jar zawierający klasyXMLC może zostać odnaleziony w ścieżce klas (lub ścieżce klas serwera, jeżeli XMLC jest wywoływany przezserwlet). Proszę pamiętać, że xmlc.jar zawiera kilka pakietów wspierających, które mogą powodować konflikty,jeżeli zainstalowane zostały także inne wersje tych pakietów. W przypadku XMLC 1.2b1 należy uważać nazewnętrzne kolizje z Apache Xerces 1.0.2, SAX 1.0, DOM Level 2, GNU RegExp 1.0.8 i Jtidy 26jul1999.Wynikiem kolizji pakietów mogą być dziwne komunikaty o błędach. Na przykład, podczas uruchomienia xmlckompilator może zgłosić, że klasa utworzona przez XMLC jest abstrakcyjna i w związku z tym nie może zostaćwykonany jej egzemplarz, ponieważ klasa nie definiuje konkretnej metody z konkretnej klasy. Prawdziwymproblemem jest niedopasowanie dwóch wersji DOM. Rozwiązaniem jest upewnienie się że najnowsza wersjawystępuje wcześniej w ścieżce klas. Rozwiązanie to działa, dopóki wersje pakietów są wstecznie kompatybilne.

Modyfikacja listyTeraz zostanie opisana nieco bardziej zaawansowana operacja — modyfikacja listy. Po pierwsze utworzonazostanie strona prototypowa zawierająca makietę listy, jak przedstawiono w przykładzie 17.4.

Przykład 17.4.

Makieta listy języków<!-- przegl.html --><HTML><HEAD><TITLE>Przeglądanie XMLC </TITLE></HEAD><H1>Lokalizacje klienta</H1><UL><LI ID="lokal">en<LI CLASS="makieta">es<LI CLASS="makieta">jp</UL>

Page 360: Java Servlet - Programowanie

</BODY></HTML>W rzeczywistości powyższa lista byłaby częścią dużo większej strony, ale dla XMLC nie jest to w ogóleinteresujące. Strona zostaje skompilowana przy pomocy kompilatora XMLC, z opcją –delete-classmakieta w celu automatycznego usunięcia w fazie kompilacji elementów oznaczonych jako makieta:

% xmlc –class Przegl –keep –delete-class makieta –methods przegl.htmlpublic 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 <ID> oznaczony jako lokal w obiekcie HTMLLIElement. Klasa manipulacyjnadynamicznie zmieniająca powyższy dokument jest przedstawiona w przykładzie 17.5.

Przykład 17.5.

Klasa manipulacyjna dla listy językówimport 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 elementprototypowy, zmienia tekst przechowywany przez klon i wstawia klon na końcu listy. Po pętli while serwletusuwa element prototypowy, pozostawiając listę pełną klonów zawierającą prawdziwe dane. Wynikprzedstawiono na rysunku 17.132.

32 Proszę pamiętać o interakcjach w ścieżce klas podczas uruchamiania powyższego przykładu. Wykorzystywane są bibliotekipotrzebne ZMLC, JDOM i przypuszczalnie samemu serwerowi. Usatysfakcjonowanie wymagań biblioteki wersji XML dlawszystkich trzech składników bez konfliktów może wymagać sporych umiejętności w czarnej magii i nie zawsze jestmożliwe.

Page 361: Java Servlet - Programowanie

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 modeluobiektó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 nadokumencie, do którego ma zostać dodany tekst, a kiedy zawartość jest już gotowa, model dodawania tekstu dodokumentu 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 wersjiXMLC. Poza tym, pakiety narzędziowe dla XMLC przejmują DOM i odchodzą od niektórych z bardziejnieprzyjemnych 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 zostanieszablon HTML przedstawiający wspólny projekt graficzny witryny, ale nie posiadający żadnej zawartościodwołującej się do listy narzędzi. Jest on przedstawiony w przykładzie 17.6.

Przykład 17.6

Plik szablonu aplikacji „Narzędzia”<!-- szablon.html --><HTML><HEAD><TITLE>Przykładowy Tytuł</TITLE></HEAD><BODY BGCOLOR="#FFFFFF" BACKGROUND="/obrazki/tlo.gif" LINK="#003333" ALINK="#669999" VLINK="#333333"><IMG SRC="/obrazki/banner.gif" WIDTH=600 HEIGHT=87 BORDER=0><BR><TABLE><TR><TD WIDTH=125 VALIGN=TOP> <BR><BR><BR> <FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#FF0000"> <A HREF="/indeks.html">Strona główna</A><BR> <A HREF="/hosting.html">Hosting</A><BR> <A HREF="/mechanizmy.html">Mechanizmy</A><BR> </FONT></TD><TD WIDTH=475> <TABLE CELLPADDING=5><TR><TD WIDTH=600 BGCOLOR="#006699" VALIGN=TOP> <B><FONT FACE="Arial,Helvetica" SIZE="+2"> <SPAN ID="tytul">Przykładowy tytuł</SPAN> </FONT></B> </TD></TR></TABLE> <B><FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#003366"> <SPAN ID="tytul2">Przykładowy drugi tytuł</SPAN> </FONT></B><P> <P> <FONT FACE="Arial,Helvetica">

Page 362: Java Servlet - Programowanie

<SPAN ID="opis">Przykładowy opis</SPAN> <DIV id="zawartosc">Tu właściwa zawartość</DIV> </FONT></TD></TR><TR><TD></TD><TD WIDTH=475 ALIGN=CENTER COLSPAN=3><HR><FONT FACE="Arial,Helvetica"><A HREF="/indeks.html">Strona Główna</A>&nbsp;&nbsp;<A HREF="/hosting.html">Hosting</A>&nbsp;&nbsp;<A HREF="/mechanizmy.html">Mechanizmy</A>&nbsp;&nbsp;<P></FONT><TABLE WIDTH=100%><TR><TD WIDTH=260 ALIGN=LEFT VALIGN=TOP><FONT FACE="Arial,Helvetica"><A HREF="/wlasnosc.html">Własność</A> &copy; 2000 Jason Hunter<BR>Wszystkie prawa zastrzeżone.</TD><TD WIDTH=5></FONT></TD><TD WIDTH=230 ALIGN=RIGHT VALIGN=TOP><FONT FACE="Arial,Helvetica">Kontakt: <A HREF="mailto:[email protected]">[email protected]</a></FONT></TD></TR></TABLE></TD></TR></TABLE></BODY></HTML>

Proszę zauważyć, że tytuł strony to obszar zablokowany oraz że występują trzy elementy <SPAN> o nazwachtytul, tytul2 i opis zawierające makietowy tekst, który zostanie wymieniony. Występuje także element<DIV> o nazwie zawartosc, oznaczający miejsce, w którym powinna zostać wyświetlona właściwa zawartośćstrony. Plik HTML, który zostanie wykorzystany do wygenerowania zawartości aplikacji „Narzędzia” jestprzedstawiony w przykładzie 17.7.

Przykład 17.7.

Plik zawartości aplikacji „Narzędzia”<!-- widoknarz.html --><HTML><HEAD><TITLE>Lista narzędzi</TITLE></HEAD><BODY BGCOLOR="#FFFFFF" BACKGROUND="/obrazki/tlo.gif" LINK="#003333" ALINK="#669999" VLINK="#333333"><IMG SRC="/obrazki/banner.gif" WIDTH=600 HEIGHT=87 BORDER=0><BR><TABLE><TR><TD WIDTH=125 VALIGN=TOP> <BR><BR><BR> <FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#FF0000"> <A HREF="/indeks.html">Strona główna</A><BR> <A HREF="/hosting.html">Hosting</A><BR> <A HREF="/mechanizmy.html">Mechanizmy</A><BR> </FONT></TD><TD WIDTH=475> <TABLE CELLPADDING=5><TR><TD WIDTH=600 BGCOLOR="#006699" VALIGN=TOP> <B><FONT FACE="Arial,Helvetica" SIZE="+2"> <SPAN ID="tytul">Lista narzędzi</SPAN> </FONT></B> </TD></TR></TABLE> <B><FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#003366"> <SPAN ID="tytul2">Lista narzędzi do tworzenia zawartości</SPAN> </FONT></B><P>

Page 363: Java Servlet - Programowanie

<P> <FONT FACE="Arial,Helvetica"> <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> <DIV ID="rekord"> <HR SIZE=2 ALIGN=LEFT> <FONT FACE="Arial,Helvetica"> <H3> <SPAN ID="nazwaNarz">Jakaś nazwa narzędzia</SPAN> <FONT COLOR=#FF0000><B> <SPAN ID="stanNarz">(Nowość!)</SPAN> </B></FONT> </H3> <A ID="laczeNarz" HREF="http://narzedzia.com">http://narzedzia.com</A><BR> <SPAN ID="komentarzNarz"> Tutaj komentarz na temat narzędzia. </SPAN> </FONT> </DIV> <DIV> <HR SIZE=2 ALIGN=LEFT> <FONT FACE="Arial,Helvetica"> <H3> Inna nazwa narzędzia <FONT COLOR=#FF0000><B> (Uaktualnienie!) </B></FONT> </H3> <A HREF="http://narzedzia.com">http://narzedzia.com</A><BR> Tu komentarz na temat tego narzędzia. </FONT> </DIV> </FONT></TD></TR><TR><TD></TD><TD WIDTH=475 ALIGN=CENTER COLSPAN=3><HR><FONT FACE="Arial,Helvetica"><A HREF="/indeks.html">Strona Główna</A>&nbsp;&nbsp;<A HREF="/hosting.html">Hosting</A>&nbsp;&nbsp;<A HREF="/mechanizmy.html">Mechanizmy</A>&nbsp;&nbsp;<P></FONT><TABLE WIDTH=100%><TR><TD WIDTH=260 ALIGN=LEFT VALIGN=TOP><FONT FACE="Arial,Helvetica"><A HREF="/wlasnosc.html">Własność</A> &copy; 2000 Jason Hunter<BR>Wszystkie prawa zastrzeżone.</TD><TD WIDTH=5></FONT></TD><TD WIDTH=230 ALIGN=RIGHT VALIGN=TOP><FONT FACE="Arial,Helvetica">Kontakt: <A HREF="mailto:[email protected]">[email protected]</a></FONT></TD></TR></TABLE></TD></TR></TABLE></BODY></HTML>

Powyższy plik wygląda bardzo podobnie do szablonu, ale wartości obszarów zablokowanych zostaływymienione na realistyczne i wypełniono obszar zawartości pewnymi prototypowymi rekordami. Oto sposób

Page 364: Java Servlet - Programowanie

wykorzystania tego pliku: programowo pobrane zostaną jego kluczowe elementy (tytuły, opis i prototypowyrekord), po czym zostaną one skopiowane i umieszczone w pliku szablonu w celu utworzenia ostatecznej wersjistrony.

Po co wykorzystywać dwa pliki? Czy nie jest możliwe po prostu bezpośrednie zmodyfikowanie plikuwidoknarz.html? Jest to możliwe, ale szablon wykorzystywany jest po to, by w przypadku przyszłegouaktualnienia nagłówka, paska bocznego lub stopki konieczne było uaktualnienie jedynie pliku szablon.html izmiana 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ładzie17.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

Page 365: Java Servlet - Programowanie

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); // element oznaczony „tytul2” template.setTextOpis(opis); // 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, przypomocy 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 prototypowyrekord narzędzia w WidokNarz oraz punkt wstawiania dla rekordów narzędzie w Szablon. Następnieodczytywane 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 doprototypowego 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łyprzypisane są do dokumentu, który je utworzył, tak więc zastosowana zostaje metoda importNode() w celuudostępnienia rekordu przesuwanego z dokumentu WidokNarz do Szablon. Metoda importNode() wykonujegłębokie kopiowanie (ponieważ przekazano jej jako drugi argument true), tak więc każda iteracja pętli for dodajeinna kopię zawartości rekordu. Po zakończeniu pętli for punkt wstawiania zostaje usunięty z szablonu i dokumentjest wyświetlany klientowi.

Podczas uruchamiania powyższego serwletu należy pamiętać, że metoda importNode() jest nowością wDOM Level 2, tak więc, aby mógł on zostać uruchomiony, serwer musi posiadać ścieżkę klas zawierającą DOMLevel 2 przed wszystkimi klasami DOM Level 1. Archiwum xmlc.jar zawiera DOM Level 2, tak więc możnapomyśleć, że nie ma żadnego problemu, ale niektóre serwery (włączając w to Tomcat 3.2) posiadają w swojejdomyślnej ścieżce implementację DOM Level 1 pomagającą w odczytywaniu plików web.xml. Aby powyższyserwlet mógł pracować na takich serwerach konieczna jest edycja ścieżki klas serwera w celu upewnienia się, żeplik XMLC xmlc.jar znajduje się przed własnymi bibliotekami XML serwera. (Tomcat 3.2 automatycznie ładujepliki 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 warunkowodołą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żliwyblok tekstu do dołączenia. W powyższym przykładzie oczywiste jest, że kod Javy wie więcej niż powinien natemat tworzenia uwag.

Miłą własnością XMLC jest automatyczne umieszczanie kodów ucieczkowych przed znakami specjalnymi takimijak <, > i & podczas wyświetlania ich do klienta, ponieważ wtedy narzędzie dokonujące wyświetlenia może bez

Page 366: Java Servlet - Programowanie

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 łączyw 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.

Page 367: Java Servlet - Programowanie

Rozdział 18. JavaServer Pages

JavaServer Pages, znana powszechnie jako JSP, to technologia utworzona przez Sun Microsystems, bliskozwiązana z serwletami. JSP była jedną z pierwszych prób utworzenia ogólno dostępnego systemu tworzeniazawartości opartego na serwletach. Osoby od dawna zajmujące się serwletami mogą pamiętać, że JSP zostałaprzedstawiona po raz pierwszy na wiosnę 1998 — na tyle wcześnie, by pierwsze wydanie niniejszej książkimogł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ówskładającą się z niezależnych producentów i osób), po czym inni producenci konkurują w swoichimplementacjach tego standardu. Różnica pomiędzy JSP i innymi technologiami opartymi bezpośrednio naserwletach 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órymmechanizm 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 istatycznej”. 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 zkwestią „maksymalnej mocy” przy tworzeniu JSP, autor strony JSP zawsze posiada całkowitą kontrolę nadsystemem, i w związku z tym można powiedzieć, że JSP umożliwia oddzielenie zawartości od grafiki, ale niewymusza jej ani nie narzuca, jak to się dzieje w przypadku jej alternatyw. Jest to zjawisko podobne do tego, żeC++ umożliwia projektowanie obiektowe, ale nie promuje tego doskonałego sposobu tak mocno, jak to się dziejew 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ć kodJavy bezpośrednio na stronie JSP, zamiast pisać kompletny serwlet — co daje ułatwienia takie jakwyeliminowanie wywołań wyj.println(), umożliwienie bezpośredniego wskazywania na pliki JSP iwykorzystanie własności autokompilacji JSP, Jednak poprzez tworzenie strony JSP zamiast serwletu programistatraci 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 wielesposobów. Jednym z nich jest użycie komponentów JavaBeans osadzonych na stronie. Innym tworzeniewłasnych znaczników wyglądających jak HTML, ale będących tak naprawdę punktami zaczepienia dowspomagają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 mechanizmuRequestDispatcher. Technika ta jest często nazywana architekturą Model 2, która to nazwa pochodzi odspecyfikacji 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” askończywszy na własnych znacznikach. Opis będzie krótki, ale powinien dostarczyć podstaw do porównania JSPz jej alternatywami. Nie zostaną opisane wszystkie opcje architektury, a zamiast tego nacisk zostanie położony naszczegóły techniczne. Opcje architektury i większa ilość informacji na temat JavaServer Pages jest dostępna nastronie głównej JSP pod adresem http://java.sun.com/products/jsp („ściąga” składni jest dostępna pod adresem

Page 368: Java Servlet - Programowanie

http://java.sun.com/products/jsp/syntax.pdf) oraz w książce „JavaServer Pages” autorstwa Hansa Bergstena(O'Reilly).

Wykorzystywanie JavaServer PagesNajbardziej podstawową funkcją JSP jest umożliwienie bezpośredniego umieszczenia kodu serwletu wstatycznym pliku HTML33. Każdy blok kodu serwletu (nazywany skryptletem) jest ograniczany otwierającymznacznikiem <% i zamykającym %>. 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 rozdzielaniaimplementacji serwera, ale często wykorzystywanybezpośrednio do współdzielenia zmiennych pomiędzystronami JSP i wspomagającymi elementami i znacznikami.

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 pomocypredefiniowanych zmiennych request i out. Jeżeli posiada się serwer obsługujący JavaServer Pages i pragniesię przetestować tę stronę, należy umieścić plik w podkatalogu katalogu macierzystego dokumentów serwera izapamiętać ją z rozszerzeniem .jsp. Zakładając, że strona została zapamiętana jako witaj1.jsp, dostęp do niejmożna uzyskać pod URL-em http://serwer:port/witaj1.jsp lub, jeżeli plik zostanie umieszczony w ścieżcekontekstowej jsp, URL będzie wynosił http://serwer:port/jsp/witaj1.jsp.

Przykład 18.1.

Powitanie przy pomocy JSP<HTML><HEAD><TITLE>Witaj</TITLE></HEAD><BODY><H1><%if (request.getParameter("nazwa") == null) { out.println("Witaj świecie");}else { out.println("Witaj, " + request.getParameter("nazwa"));}%></H1>

33 Przed rozpoczęciem opisu warto zauważyć, że umieszczanie kodu Javy na stronie JSP jest uważane za działanie w złymstylu. Uważane za lepsze zaawansowane zastosowania JSP zostaną opisane w dalszej części rozdziału.

Page 369: Java Servlet - Programowanie

</BODY></HTML>Przykładowy wynik uruchomienia powyższego programu przedstawiony jest na rysunku 18.1.

Rysunek 18.1.

Powitanie przy pomocyJavaServer Pages

Zasady działaniaJak działa JSP? Poza obszarem widoczności serwer automatycznie tworzy, kompiluje, ładuje i uruchamiaspecjalny serwlet tworzący zawartość strony, jak przedstawiono na rysunku 18.2. Można myśleć o tymspecjalnym serwlecie jak o serwlecie roboczym, działającym w tle. Statyczne części strony HTML są tworzoneprzez serwlet roboczy przy pomocy ekwiwalentów wywołań wyj.println(), podczas gdy części dynamicznesą dołączane bezpośrednio. Na przykład, serwlet przedstawiony w przykładzie 18.2 mógłby być serwletemroboczym dla witaj1.jsp działającym na serwerze Tomcat34.

Rysunek 18.2.

Generowanie stron JavaServer Pages

Przykład 18.2.

Generowany automatycznie serwlet roboczy dla witaj1.jspimport javax.servlet.*;

34 Osoby zainteresowane zobaczeniem prawdziwego kodu źródłowego serwletu dla strony JSP, w większości przypadkówmogą 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ć, żejest ono o wiele bardziej skomplikowane niż to przedstawione w tym miejscu.

Page 370: Java Servlet - Programowanie

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( ) { } private static boolean _jspx_inited = false; public final void _jspx_init() throws JasperException { } public void _jspService(HttpServletRequest request, HttpServletResponseresponse) 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("<HTML>\r\n<HEAD><TITLE>Witaj</TITLE></HEAD>\r\n<BODY>\r\n<H1>\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</H1>\r\n</BODY></HTML>\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ą

Page 371: Java Servlet - Programowanie

pierwszej osoby. Następne żądania powinny następować ze zwykłą szybkością, ponieważ serwlet może ponowniewykorzystać serwlet w pamięci. Jedynym wyjątkiem jest zmiana pliku .jsp, co serwer rozpoznaje podczaswykonywania następnego żądania tej strony, po czym rekompiluje serwlet działający w tle. Jeżeli kiedykolwiekwystąpi błąd kompilacji, można spodziewać się, że serwer zakomunikuje go w pewien sposób, zazwyczajpoprzez stronę zwracaną klientowi i/lub dziennik zdarzeń serwera.

Wyrażenia i deklaracjeOprócz skryptletów, JavaServer Pages pozwala na umieszczanie kodu na stronie przy pomocy wyrażeń ideklaracji. Wyrażenie JSP rozpoczyna się znacznikiem <%=, a kończy %>. Każde wyrażenie Javy pomiędzy tymidwoma znacznikami jest oceniane, jego wynik konwertowany do łańcucha String, a tekst dołączanybezpośrednio do strony. Technika ta eliminuje nieporządek wywołania wyj.println(). Na przykład, <%=buu => dołącza wartość zmiennej buu.

Deklaracja rozpoczyna się od <%!, a kończy na %>. Pomiędzy tymi znacznikami można umieścić dowolny kodserwletu, który powinien pozostać poza metodą usługową serwletu. Zadeklarować można zmienne statyczne lubegzemplarzowe, albo też zdefiniować nowe metody. Przykład 18-3 przedstawia stronę JSP, która wykorzystujedeklarację do zdefiniowania metody pobierzNazwa() oraz wyrażenie ją wyświetlające. Komentarz napoczątku pliku pokazuje, że komentarze JSP oznaczane są znacznikami <%-- i --%>.

Przykład 18.3.

Powitanie przy pomocy deklaracji JSP<%-- witaj2.jsp --%><HTML><HEAD><TITLE>Witaj</TITLE></HEAD><BODY><H1>Witaj, <%= pobierzNazwa(request) %></H1></BODY></HTML><%!private static final String DOMYSLNA_NAZWA = "świecie";private String pobierzNazwa(HttpServletRequest zad) { String nazwa = zad.getParameter("nazwa"); if (nazwa == null) return DOMYSLNA_NAZWA; else return nazwa;}%>

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 JSPmożliwość zadeklarowania kodu, który powinien zostać wykonany podczas inicjacji i niszczenia. Strona JSP niemoże omijać standardowych metod init() i destroy(), ponieważ metody te są ostatecznie deklarowaneprzez serwlet roboczy.

InstrukcjeInstrukcja 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, importowaniapakietu, kontroli buforowania wyświetlania i zarządzania sesją, rozszerzania różnych superklas oraz specjalnejobsł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ć zwartapomiędzy znacznikami <%@ i %>. Cudzysłowy zamykające wartość atrybutu są obowiązkowe:

<%@ nazwaInstrukcji nazwaAtrybutu="wartoscAtrybutu" %>Instrukcja page pozwala JSP na kontrolowanie generowanego serwletu poprzez ustawienie specjalnychatrybutów wymienionych poniżej:contentType

Określa typ zawartości generowanej strony. Na przykład:

Page 372: Java Servlet - Programowanie

<%@ page contentType="text/plain" %>

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:

<%@ page import="java.io.*,java.util.Hashtable" %>Domyślna, ukryta i zawsze dostępna lista klas tojava.lang.*,javax.servlet.*,javax.servlet.http.*,javax.servlet.jsp.*.

bufferOkreśla minimalną wymaganą wielkość bufora odpowiedzi w kilobajtach, podobnie do metody serwletusetBufferSize(). Wartość powinna zostać zapisana w formie ##kb. Specjalna wartość nonewskazuje, że zawartość powinna zostać przekazana bezpośrednio do odpowiedniego PrintWriter wServletResponse (który może, ale nie musi przekazać zawartość klientowi). Na przykład:

<%@ page buffer="12kb" %>Domyślna wartość wynosi 8kb.

autoFlushOkreśla, czy bufor powinien być opróżniany, kiedy jest pełny, czy zamiast tego powinien zostać wywołanywyjątek IOException. Wartość true wskazuje na opróżnianie, false na wyjątek. Na przykład:

<%@ page autoFlush="true" %>Wartość domyślna wynosi true.

sessionWskazuje, że strona pragnie posiadać dostęp do sesji użytkownika. Wartość true umieszcza w zakresiezmienną session i może ustawić cookie klienta zarządzające sesją. Na przykład:

<%@ page session="false" %>Wartość domyślna wynosi true.

errorPageOkreśla stronę, którą należy wyświetlić, kiedy na stronie wystąpi Throwable, które nie zostanieprzechwycone 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:

<%@ page errorPage="blad.jsp" %>Domyślne zachowanie zależy od implementacji. Ścieżka jest względna wobec kontekstu, tak więc nie trzebasię 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 kontekstujavax.servlet.jsp.jspException.

isErrorPageWskazuje, że strona została zaprojektowana jako cel errorPage. Jeżeli wartość wynosi true, stronamoże uzyskać dostęp do ukrytej zmiennej o nazwie exception w celu odczytania Throwable.

languageOkreśla język skryptowy wykorzystany w częściach strony zawierających kod. Zastosowany język musi natyle dobrze współpracować z Javą, aby uwidocznić potrzebne obiekty Javy środowisku skryptu. Naprzykład:

<%@ page language="javascript" %>Domyślna wartość wynosi java, jest to jedyny język rekomendowany przez specyfikację.

Wykorzystanie instrukcjiPrzykład 18.4 przedstawia stronę JSP noszącą nazwę bladRob.jsp, która wykorzystuje kilka instrukcji. Popierwsze ustawia atrybut session dyrektywy page na false, ponieważ strona nie wykorzystuje obiektu sesjii 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

Page 373: Java Servlet - Programowanie

obsługiwać wyświetlanie komunikatu o błędzie. Główna część strony jest prosta. Wyzwala ona wyjątek w celuwywołania zachowania errorPage. (Powód wykorzystania sprawdzenia if zostanie opisany później.)

Przykład 18.4.

Zły chłopiec<%-- bladRob.jsp --%><%@ page session="false" %> <%-- Wyłączenie niepotrzebnych cookies --%><%@ page errorPage="/bladBierz.jsp" %> <%-- Ogólna strona obsługująca błędy --%><%-- A teraz wywołanie błędu --%><% if (System.currentTimeMillis() > 0) { throw new Exception("uups"); }%>

Strona bladBierz.jsp jest przedstawiona w przykładzie 18.5. Ustawia ona instrukcję strony isErrorPage natrue w celu wskazania, że ta strona obsługuje błędy, oraz wykorzystuje atrybut import w celu zaimportowaniaklas com.oreilly.servlet. JSP wykorzystuje skryptlet do ustawienia kodu stanu odpowiedzi na 500, poczym 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...<%-- bladBierz.jsp --%><%@ page isErrorPage="true" %><%@ page import="com.oreilly.servlet.*" %><% response.setStatus(500); %><HTML><HEAD><TITLE>Błąd: <%= exception.getClass().getName() %></TITLE></HEAD><BODY><H1><%= exception.getClass().getName() %></H1>Wystąpił błąd podczas tworzenia oryginalnego WWW<PRE><%= ServletUtils.getStackTraceAsString(exception) %></PRE><% String name = request.getServerName(); %>Proszę skontaktować się z <A HREF="mailto:webmaster@<%= name %>">webmaster@<%=name %></A> w celu zgłoszenia błędu.</BODY></HTML>

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.

Page 374: Java Servlet - Programowanie

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 zfaktu, że strony JSP muszą przechowywać wszystkie puste miejsca z tekstu dokumentu. Pozwala to nawykorzystanie JSP do tworzenia nie tylko HTML, ale także XML, w którym puste miejsca mogą być niezwykleważne. Niestety zachowanie wszystkich pustych miejsc oznacza, że prosty skryptlet:

<% throw new Exception("uups"); %>musi wygenerować kod w celu wyświetlenia nowej linii po wywołaniu błędu — kod, który nigdy nie zostanieosią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, kompilatorprzyjmuje, że istnieje możliwość, że wyjątek nie zostanie wywołany i w związku z tym nie wystąpi błądkompilacji. Podobne problemy mogą pojawiać się w przypadku wyrażeń return i throw wewnątrzskryptletów.

Tak naprawdę istnieje wiele sztuczek powiązanych z wykorzystaniem skryptletów w JSP. Jeżeli przypadkowonapisze 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, przypuszczalnieotrzyma się dziwny komunikat o błędzie, ponieważ kompilator działa na wygenerowanym kodzie Javy, nie napliku JSP. Aby zademonstrować problem, proszę wyobrazić sobie, że w pliku bladBierz.jsp <%= nazwa %>zostało zastąpione przez <% nazwa %>. 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 JSPSkryptlet, wyrażenia i deklaracje umożliwiają umieszczanie kodu Javy na stronie JSP. Są to narzędzia o dużejmocy, jednak ich wykorzystywanie nie jest polecane. Nawet w rękach doświadczonego programistyumieszczanie 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.

Page 375: Java Servlet - Programowanie

Sytuacja staje się jeszcze gorsza, kiedy mechanizm ten wykorzystuje nowicjusz. Osoby nie będące programistamimuszą nauczyć się Javy chociaż w pewnym stopniu, a Java nie jest zaprojektowana jako język skryptowy, nie jestteż przeznaczona dla takich osób. Język taki jak JavaScipt — prostszy mniej ścisły i lepiej znany przezprojektantó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ąskiegardło i dodatkowa możliwość wystąpienia błędu. Zawartość i grafika powinny być od siebie oddzielone. Aby toumożliwić i usunąć kod Javy ze strony, Java pozwala na zastosowanie JavaBeans i bibliotek własnychznaczników. Poniżej opisane zostaną JavaBeans.

JSP i JavaBeansJedną z najbardziej interesujących i potężnych metod wykorzystania JavaServer Pages jest współpraca zkomponentami JavaBeans. JavaBeans to klasy Javy zaprojektowane do wielokrotnego wykorzystania, a nazwyich metod i zmiennych tworzone są według pewnej konwencji, która daje im dodatkowe możliwości. Mogą onebyć osadzane bezpośrednio na stronie Javy przy pomocy znacznika <jsp:useBean>, czyli działanianazywanego przez JSP akcją. Komponent JavaBean może wykonywać dobrze zdefiniowane zadanie (zapytaniebazy danych, łączenie się z serwerem poczty, utrzymywanie informacji na temat klienta itp.), którego wynik jestdostę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 osadzonena stronie w specjalny sposób. Na przykład, serwer może automatycznie skonfigurować komponent (zwłaszczazmienne egzemplarza) przy pomocy wartości parametrów w żądaniu klienta. Innymi słowy, jeżeli żądaniezawiera parametr nazwa, a serwer odkryje poprzez introspekcję (technikę, w której metody i zmienne klasy Javymogą 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 dokonkretnej strony (gdzie jest wykorzystany raz i zniszczony), konkretnego żądania (podobne do zakresu stronypoza tym, że komponent może przetrwać wewnętrzne wywołania dołączenia i przekazania), sesji klienta (wktórym komponent jest automatycznie udostępniany, kiedy tan sam klient połączy się ponownie) lub aplikacji (wktórym to zakresie ten sam komponent jest udostępniany całej aplikacji WWW).

Osadzanie komponentu BeanKomponenty 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" class="nazwaKlasy" type "nazwaTypu"></jsp:useBean>

Ustawione mogą być następujące atrybuty akcji <jsp:useBean>:

id

Określa nazwę komponentu. Jest to klucz pod którym komponentjest przechowywany, jeżeli jego zakres rozciąga siępoza stronę. Jeżeli egzemplarz komponentu zapamiętanypod podaną nazwą już istnieje w zakresie strony,egzemplarz ten zostaje wykorzystany na stronie. Wpozostałych przypadkach tworzony jest nowy komponent.Na przykład:id="preferencjeUzyt"

scope

Page 376: Java Servlet - Programowanie

Określa zakres widoczności komponentu. Wartość musi wynosi page, request, session lubapplication. Jeżeli page, zmienna jest tworzona jako zmienna egzemplarza. Jeżeli request,zmienna jest przechowywana jako atrybut żądania; jeżeli session, komponent przechowywany jest wsesji 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 przypierwszym tworzeniu komponentu. Nazwa klasy musi byćpodana w pełnej formie. Na przykład:class="com.firma.sledzenie.PreferencjeUzytImpl"

Atrybut class nie jest potrzebny, jeżeli komponent istnieje już w aktualnym zakresie, ale ciągle musiwystąpić atrybut type pozwalający na stworzeni obiektu odpowiedniego typu. Jeżeli wystąpi problem zestworzeniem danej klasy, strona JSP zgłasza wyjątek InstatiationException.

type

Określa typ komponentu, który powinien zostać pobrany zsystemu, wykorzystywany do utworzenia obiektu popobraniu z żądania, sesji lub kontekstu. Wartość typupowinna zostać podana w pełnej formie superklasy lubinterfejsu 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 doprawdziwego typu obiektu, strona JSP zgłasza wyjątek ClassCastException.

beanNameAtrybut class może zostać zastąpiony atrybutem beanName. Różnica między nimi jest taka, żebeanName wykorzystuje Beans.instatiate() do utworzenia egzemplarza komponentu, któryposzukuje zserializowanej wersji komponentu (pliku .ser) przed utworzeniem egzemplarza od zera.Pozwala to na wykorzystanie komponentów prekonfigurowanych. Proszę przejrzeć dokumentację Javadocdla 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 informacjepomiędzy znacznikiem otwierającym <jsp:useBean> azamykającym </jsp:useBean> — jest interpretowane poutworzeniu komponentu. Jeżeli komponent nie zostanieutworzony (ponieważ w danym zakresie odnalezionoistnieją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 pustegoznacznika />:

<jsp:useBean id="preferencje" class=com.firma.sledzenie.PreferencjeUzytImpl" />

Page 377: Java Servlet - Programowanie

Kontrola parametrów komponentuAkcja <jsp:setProperty> umożliwia parametrom żądania automatyczne (poprzez introspekcję) ustawianiewł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 kilkasposobó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 posiadametodę ustawWielkosc(int wielkosc) oraz żądanie posiada parametr wielkosc o wartości 12, serwerautomatycznie 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ć, żepusta wartość parametru łańcuchowego jest traktowana tak, jakby parametr nie istniał, i pusta wartość łańcuchanie zostanie przypisana własności String).

Proszę zauważyć, że akcja wykorzystuje skrót XML dla pustego znacznika. Jest to bardzo ważne. SpecyfikacjaJSP wymaga od wszystkich akcji JSP by posiadały one właściwą formę XML, nawet kiedy są umieszczone wplikach 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 tymsamym 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 dowłaściwego typu, jeżeli okaże się to konieczne. Na przykład, aby ustawić domyślną wielkość na 18 zpominię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ślonypoprzez wyrażenie, przy pomocy mechanizmu nazywanego wyrażeniem atrybutu czasu uruchomienia. Naprzykł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="<%= Mat.maks(6, preferencje.pobierzWielkosc()) %>" />

Natomiast akcja <jsp:getProperty> dostarcza mechanizmu służącego do odczytywania wartości własnościbez 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 onadł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 traktowaneprzez HTML jako specjalne, może to powodować problemy. Projekt Apache Struts pozwala na rozwiązanie tegoproblemu przy pomocy własnego znacznika <property>, co zostanie opisane w dalszej części tego rozdziału.

Powitania przy pomocy komponentuPrzykład 18.6 przedstawia zastosowanie komponentu JavaBeans na stronie JSP. Strona ta wyświetla „Witaj”przy pomocy WitajKomp.

Przykład 18.6.

Page 378: Java Servlet - Programowanie

Powitanie przy pomocy JavaBean<%-- witaj3.jsp --%><%@ page import="WitajKomp" %><jsp:useBean id="witaj" class="WitajKomp"> <jsp:setProperty name="witaj" property="*" /></jsp:useBean><HTML><HEAD><TITLE>Witaj</TITLE></HEAD><BODY><H1>Witaj, <jsp:getProperty name="witaj" property="nazwa" /></H1></BODY></HTML>

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 JSPdziała jedynie jako szablon.

Kod WitajKomp jest przedstawiony w przykładzie 18.7. Plik jego klasy powinien zostać umieszczony wstandardowym katalogu klas wspierających (WEB-INF/classes).

Przykład 18.7.

Klasa WitajKomppublic 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 pomocyustawNazwe(), a odczytywaną przy pomocy pobierzNazwe(). Domyślną wartością nazwa jestświecie, ale kiedy nadchodzi żądanie zawierające parametr nazwa, własność jest automatycznie ustawianaprzez 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.

Page 379: Java Servlet - Programowanie

Rysunek 18.4.

Powitanie przy pomocyJavaServer Pages wewspółpracy zkomponentem 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 zostaniezmieniona, 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 lubaplikacji, a typ starego komponentu przechowywany w sesji lub aplikacji nie pasuje do spodziewanego typunowego komponentu. Najprostszym rozwiązaniem jest ponowne uruchomienie serwera.

Dołączenia i przekazaniaPoprzez połączenie instrukcji i akcji, JSP umożliwia obsługę dołączeń i przekazań. Istnieją dwa typy dołączeń ijedno przekazań. Pierwszym dołączeniem jest instrukcja include:

<%@ include file="SciezkaDoPliku" %>Powyższe dołączenie następuje w czasie tłumaczenia, co oznacza, że następuje podczas tworzenia serwletudziałającego w tle. Cała zawartość zewnętrznego pliku jest dołączana tak, jak gdyby była bezpośrednioumieszczona na stronie JSP. Programiści C mogą dostrzec jej bliskie podobieństwo do #include. Ścieżka dopliku jest zakotwiczona w katalogu macierzystym kontekstu (tak więc /indeks.jsp odnosi się do index.jspaktualnej 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ęcmożna pomyśleć o zastosowaniu rozszerzenia pliku .inc lub .jin w celu wskazania tego faktu. Poniższa stronaJSP wykorzystuje kilka instrukcji dołączenia w celu utworzenia strony ze zbioru elementów (elementy nie sąpokazane):

<%@ include file="/naglowek.html" %><%@ include file="obliczNazwe.inc" %>Witaj, <%= nazwa %><%@ include file="/stopka.html" %>

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 doklienta. 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ć żadnychnagłówków, a ścieżka strony jest zakotwiczona w katalogu macierzystym kontekstu35. Wymagany jest atrybutflush, który w JSP 1.1 musi być ustawiony na true. Wskazuje to na konieczność opróżnienia buforaodpowiedzi 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 stronypowitanie.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" />

35 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.

Page 380: Java Servlet - Programowanie

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 akcjaprzekazuje powitaniu domyślną nazwę:

<jsp:include page="/powitanie.jsp"> <jsp:param name="domyslnaNazwa" value="Nowy użytkownik" /></jsp:include>

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łyzbuforowany wynik zostaje wyczyszczony, a jeżeli stało się to wcześniej, system zgłasza wyjątek. Poniższa akcjaprzekazuje żądanie specjalnej stronie, jeżeli użytkownik nie jest zalogowany:

<% if (session.getAttribute("uzyt") == null { %> <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ć tewłasności do stworzenia aplikacji wyświetlającej narzędzia. Na stronie JSP zostanie osadzony komponent dającystronie dostęp do informacji na temat narzędzi i pozwalający na traktowanie strony jako przeglądarki tychdanych. Przykład 18.8 przedstawia stronę JSP o nazwie widoknarz.jsp.

Przykład 18.8.

Aplikacja przeglądająca narzędzia przy pomocy JSP<%-- widoknarz.jsp --%><% String tytul = "Lista narzędzi"; String tytul2 = "Lista narzędzi do tworzenia zawartości"; String 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ę.";%><%@ include file="/naglowek.jsp" %><%@ page session="false" %><%@ page errorPage="/bladBierz.jsp" %><jsp:useBean id="narzkomp" class="NarzKomp" scope="application"> <jsp:setProperty name="narzkomp" property="plikNarz" value='<%= application.getInitParameter("plikNarz") %>' /></jsp:useBean><% Narzedzie[] narzedzia = plikNarz.pobierzNarz(request.getParameter("stan")); for (int i = 0; i < narzedzia.length; i++) { Narzedzie narzedzie = narzedzia[i];%> <HR SIZE=2 ALIGN=LEFT> <H3> <%= narzedzie.nazwa %> <% if (narzedzie.czyNowy(45)) { %> <FONT COLOR=#FF0000><B> (Nowość!) </B></FONT> <% } else if (narzedzie.czyUaktualniony(45)) { %> <FONT COLOR=#FF0000><B> (Uaktualniony!) </B></FONT> <% } %> </H3> <A HREF="<%= narzedzie.domURL %>"><%= narzedzie.domURL %></A><BR> <%= narzedzie.komentarz %><% } %>

Page 381: Java Servlet - Programowanie

<%@ include file="/stopka.jsp" %>Na początku wewnątrz skryptletu, jako łańcuchy String, definiowane są tytuły i opis strony. Następniewykorzystana jest instrukcja include w celu dołączenia standardowej zawartości nagłówka z pliku naglowek.jsporaz standardowej zawartości stopki (na końcu pliku) z pliku stopka.jsp. Instrukcja include umieszczazawartość plików nagłówka i stopki do pliku podczas fazy tłumaczenia, tak więc zmienne tytul, tytul2 iopis są widoczne wewnątrz dołączanych plików, i plik naglowek.jsp wykorzystuje te zmienne. Plikinaglowek.jsp i stopka.jsp są przedstawione w przykładach 18.9 i 18.10.

Przykład 18.9.

Plik naglowek.jsp<%-- naglowek.jsp --%><%-- Zależy od obecności zmiennych tytul, tytul2 i opis --%><HTML><HEAD><TITLE><%= tytul %></TITLE></HEAD><BODY BGCOLOR="#FFFFFF" BACKGROUND="/obrazki/tlo.gif" LINK="#003333" ALINK="#669999" VLINK="#333333"><IMG SRC="/obrazki/banner.gif" WIDTH=600 HEIGHT=87 BORDER=0><BR><TABLE><TR><TD WIDTH=125 ROWS=10 VALIGN=TOP> <BR><BR><BR> <FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#FF0000"> <A HREF="/indeks.html">Strona główna</A><BR> <A HREF="/hosting.html">Hosting</A><BR> <A HREF="/mechanizmy.html">Mechanizmy</A><BR> </FONT></TD><TD WIDTH=475> <TABLE CELLPADDING=5><TR><TD WIDTH=600 BGCOLOR="#006699" VALIGN=TOP> <B><FONT FACE="Arial,Helvetica" SIZE="+2"> <%= tytul %> </FONT></B> </TD></TR></TABLE> <B><FONT FACE="Arial,Helvetica" SIZE="+1" COLOR="#003366"> <%= tytul2 %> </FONT></B><P> <P> <FONT FACE="Arial,Helvetica"> <%= opis %>

Przykład 18.10.

Plik stopka.jsp<%-- stopka.jsp --%><%-- Nie zależy od obecności żadnych zmiennych --%> </FONT></TD></TR><TR><TD></TD><TD WIDTH=475 ALIGN=CENTER COLSPAN=3><HR><FONT FACE="Arial,Helvetica"><A HREF="/indeks.html">Strona główna</A>&nbsp;&nbsp;<A HREF="/hosting.html">Hosting</A>&nbsp;&nbsp;<A HREF="/mechanizmy.html">Mechanizmy</A>&nbsp;&nbsp;<P></FONT><TABLE WIDTH=100%><TR><TD WIDTH=260 ALIGN=LEFT VALIGN=TOP><FONT FACE="Arial,Helvetica"><A HREF="/wlasnosc.html">Własność</A> &copy; 2000 Jason Hunter<BR>Wszystkie prawa zastrzeżone.</TD><TD WIDTH=5></FONT></TD>

Page 382: Java Servlet - Programowanie

<TD WIDTH=230 ALIGN=RIGHT VALIGN=TOP><FONT FACE="Arial,Helvetica">Kontakt: <A HREF="mailto:[email protected]">[email protected]</a></FONT></TD></TR></TABLE></TD></TR></TABLE></BODY></HTML>

Następnie na stronie widoknarz.jsp wykorzystana zostaje akcja <jsp:useBean> w celu osadzeniakomponentu JavaBean o nazwie narzkomp, będącego egzemplarzem klasy NarzKomp. Komponent posiadazakres application, tak więc pojedynczy egzemplarz danych zostanie automatycznie zachowany wkontekście i udostępniony wszystkim stronom.

Wewnątrz głównej części znacznika <jsp:useBean> umieszczona została akcja <jsp:setProperty>,która ustawia własność komponentu plikNarz na wartość parametru inicjacji kontekstu plikNarz,wykorzystując wyrażenie atrybutu czasu uruchomienia w celu odczytania wartości. Informuje to komponent,którego pliku powinien użyć do pobrania informacji na temat narzędzi. Idealnym rozwiązaniem byłoby, jeżelikomponent mógłby uzyskać bezpośredni dostęp do zmiennej aplikacji w celu odczytania tych wartości, alekomponenty mają dostęp jedynie do informacji udostępnionych przez stronę JSP. Eleganckim rozwiązaniembyłaby możliwość akceptowania przez komponent nazwy pliku jako argumentu jego konstruktora, aleegzemplarze komponentów są tworzone przy pomocy domyślnego, nie posiadającego argumentów konstruktora,tak więc to również nie jest możliwe. Metoda zastosowana powyżej to dołączenie akcji <jsp:setProperty>do głównej części znacznika <jsp:UseBean> tak więc podczas pierwszego konstruowania komponentubezpośrednio otrzymuje on nazwę pliku, z którego powinien pobierać informacje.

Należy zauważyć dwie sprawy dotyczące wyrażenia atrybutu czasu uruchomienia. Po pierwsze, konieczne byłowykorzystanie pojedynczych nawiasów dookoła wartości, ponieważ podwójne cudzysłowy zostały zastosowanewewnątrz wyrażenia. Pozwala to na uniknięcie błędów analizatora JSP. Możliwe jest również wyłączeniepodwójnych cudzysłowów z wyrażenia przy pomocy lewych ukośników. Po drugie, jeżeli parametr inicjacji nieistnieje i getInitParameter() zwraca null, wywołany zostaje wyjątek JspException, ponieważwartość null nie jest dozwolona w wyrażeniu atrybutu czasu uruchomienia36.

Po znaczniku <jsp:useBean> skryptlet wykorzystany zostaje w celu pobrania tablicy narzędzi z komponentu,co wykonywane jest poprzez wywołanie metody pobierzNarzedzia() z wartością parametru stan, nawypadek gdyby użytkownik chciał przeglądać jedynie narzędzia o określonym stanie. Kuszące może byćpragnienie napisania akcji <jsp:setProperty>, która umożliwiłaby komponentowi bezpośredni dostęp dowartości parametru stan ale, ponieważ komponent posiada zakres application, nie jest to możliwe —wykonanie tego spowodowałoby problemy z współbieżnością, gdyby klika żądań próbowało ustawić tę samąwłasność pojedynczego współdzielonego egzemplarza komponentu.

Po odczytaniu tablicy narzędzi, wykorzystany zostaje kolejny fragment kodu skryptletu w celu rozpoczęcia pętlifor dokonującej iteracji na tablicy. Wewnątrz pętli for umieszczona zostaje logika wyświetlająca każde osobnenarzędzie. Wyrażenia zostają wykorzystane do wyświetlenia prostych wartości takich, jak nazwa narzędzia, URLi komentarz. Skryptlety służą do wyświetlania uwag na temat stanu — Nowość lub Uaktualnienie.37 Kod klasyNarzKomp przedstawiony jest w przykładzie 18.11. Kod klasy Narzedzie jest identyczny jak tenprzedstawiony w rozdziale 14, „Szkielet Tea”.

Przykład 18.11.

Klasa wspierająca NarzKomp

36 Z technicznego punktu widzenia, specyfikacja JSP nie określa sposobu postępowania z zerowymi wartościami wyrażeńatrybutów czasu uruchomienia. W związku z tym modelem staje się wzorcowa implementacja Tomcata, a Tomcat 3.2 zgłaszawyjątek.37 Z uwagi na zachowujące puste miejsca zasady JSP należy być ostrożnym podczas tworzenia wyrażeń if/else wewnątrzskryptletów. Poniższy kod nie działałby: <% if (narzedzie.czyNowy(45)) { %>

<FONT COLOR=#FF0000><B> (Nowość!) </B></FONT> <% } %> <% else if (narzedzie.czyUaktualniony(45)) { %> <FONT COLOR=#FF0000><B> (Uaktualniony!) </B></FONT> <% } %>

W przypadku powyższego kodu, serwlet działający w tle próbowałby wstawić nową linię pomiędzy klauzulami if i else,powodując paskudny błąd kompilacji — 'else' without 'if'.

Page 383: Java Servlet - Programowanie

import java.util.*;public class NarzKomp { private Narzedzie[] narzedzia; private String stan; public NarzKomp() { } public void ustawPlikNarz(String plikNarz) throws Exception { // Nie ma sposobu dostępu do kontekstu aplikacji bezpośrednio z komponentu narzedzia = Narzedzie.ladujNarz(plikNarz); } public Narzedzie[] pobierNarz(String stan) throws Exception { if (narzedzia == null) { throw new IllegalStateException( "Zawsze należy ustawić własność plikNarz NarzKomp"); } if (stan == null) { return narzedzia; } else { // Zwrócenie jedynie narzędzi posiadających dany „stan” List lista = new LinkedList(); for (int i = 0; i < narzedzia.length; i++) { if (narzedzia[i].getStateFlag().equalsIgnoreCase(stan)) { lista.add(narzedzia[i]); } } return (Narzedzie[]) lista.toArray(new Narzedzie[0]); } }}

Metoda ustawPlikNarz() jest automatycznie wywoływana przez akcję <jsp:setProperty> zaraz poskonstruowaniu elementu. Pobiera ona informacje na temat narzędzi z określonego pliku.

Metoda pobierzNarz() jest wywoływana przez stronę JSP bezpośrednio wewnątrz skryptletu. Metodapobiera jako parametr stan narzędzi, według którego będą one sprawdzane. Jeżeli stan posiada wartość null,zwracana jest pełna lista. Jeżeli zmienna narzędzi posiada wartość null oznacza to, że ustawPlikNarz()nie zakończyła się sukcesem, a komponent zgłasza wyjątek IllegalStateException, który ostrzegaużytkownika przed problemem.

Biblioteki własnych znacznikówNa koniec opisany zostanie najbardziej interesujący aspekt JavaServer Pages — obsługa bibliotek własnychznaczników. Własne znaczniki (znane także jako własne akcje) pozwalają stronie JSP na zawieranie znacznikówXML które, chociaż wyglądają jak HTML, tak naprawdę pozwalają na wykonywanie konkretnego kodu Javy,kiedy wystąpi znacznik. Możliwe zastosowania bibliotek własnych znaczników są naprawdę interesujące. Naprzykład, firma Live Software (twórca Jrun) wykorzystała własne znaczniki JSP do zaimplementowaniacałkowicie przenośnej wersji znaczników Cold Fusion Allaire o nazwie CF_Anywhere. (Krótko później LiveSoftware została wykupiona przez Allaire.)

Szczegółowe zasady tworzenia biblioteki własnych znaczników są dość skomplikowane i wykraczają poza zakresniniejszego rozdziału. Na szczęście tworzonych i już przydatnych jest kilka bibliotek znaczników Open Source.Dwie najbardziej znane to Apache Taglibs i Apache Struts, obie pochodzące z projektu Apache Jakartaznajdującego się pod adresem http://jakarta.apache.org. Apache Taglibs zawiera biblioteki znaczników oogólnym przeznaczeniu; Apache Struts zawiera bibliotekę znaczników przeznaczoną do obsługi architektury wstylu Model 2, ale duża ilość znaczników jest ogólnie przydatna. Lista bibliotek znaczników JSP jest dostępnapod adresem http://jsptags.com. Podejmowane są również próby stworzenia standardowej biblioteki znacznikówwystępujące jako formalny proces żądania specyfikacji Javy o kodzie JSR-052. Większa ilość informacji natemat JSR-052 jest dostępna pod adresemhttp://java.sun.com/aboutJava/communityprocess/jsr/jsr_052_jsptaglib.html.

Stosowanie bibliotek własnych znacznikówWłasne akcje umożliwiają osadzanie logiki aplikacji na stronach JSP przy pomocy znaczników podobnych doHTML. Przy pomocy odpowiedniego zestawu znaczników logika, która wcześniej musiała być utworzona przypomocy skryptletów może być napisana przy pomocy znaczników. Na przykład, projekt Apache Struts posiada

Page 384: Java Servlet - Programowanie

znacznik <iterate>, który może zastąpić pętlę for. Znacznik <iterate> powtarza główną częśćznacznika raz dla każdego elementu ze zbioru (List, Set, Vector, Map, tablica itp.). Podczas każdej iteracjiumieszcza element, który może zostać wykorzystany w głównej części, w zakresie page38. Struts posiadarównież znacznik <property>, który działa jak <jsp:getProperty> z dodatkowym usprawnieniemtakim, że filtruje zawartość wyświetlaną przez HTML poprzez konwersję wszystkich znaków specjalnych HTMLna odpowiadające im encje znakowe. Na przykład < staje się %lt;.

Poprzez wspólne wykorzystanie <iterate> i <property> można utworzyć poniższy fragment stronywyświetlający wszystkie cookies wysłane w żądaniu — z właściwie przefiltrowanymi wszystkimi znakamispecjalnymi:

<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %><struts:iterate id="cookie" collection="<%= request.getCookies() %>"> <struts:property name="cookie" property="name" /> = <struts:property name="cookie" property="value" /> <BR></struts:iterate>

Aby zainstalować i korzystać z biblioteki takiej jak Struts należy wykonać kilka czynności:

1. Pobrać i rozpakować dystrybucje biblioteki znaczników. Struts, na przykład, jest dostępny pod adresemhttp://jakarta.apache.org.

2. Umieścić klasy biblioteki znaczników Javy tak, aby mogły zostać odnalezione przez serwer. Naprzykład umieścić plik struts.jar w katalogu WEB-INF/lib lub umieścić skompilowane klasy w WEB-INF/classes.

3. Umieścić plik Tag Library Descriptor (Deskryptor Biblioteki Znaczników — TLD) wewnątrz kataloguWEB-INF. Zazwyczaj umieszcza się go w WEB-INF/tlds lub bezpośrednio WEB-INF, dokładneumiejscowienie nie jest ważne. Na przykład proszę umieścić struts.tld w WEB-INF. Plik TLD to plikdanych XML dostarczający serwerowi informacji na temat każdego znacznika w bibliotece — jegonazwy, klasy, sposoby wykorzystania jego atrybutów i części głównej itd. Posiadanie plików TLD madodatkową zaletę — na podstawie TLD można utworzyć stronę dokumentującą bibliotekę znaczników(na przykład przy pomocy arkusza stylów XSL).

4. Dodać pozycję <taglib> do pliku web.xml. Następnie wewnątrz niej umieścić pozycję <taglib-uri> podającą nazwę do poszukiwań biblioteki znaczników, a także <taglib-location>podającą umiejscowienie pliku TLD. Krok ten nie jest konieczny, jeżeli pozycja <taglib-uri> jestrównoważna pozycji <taglib-location>. Na przykład:

<taglib> <!—jeżeli URI pasuje do umiejscowienia to jest opcjonalne --> <taglib-uri>/WEB-INF/struts.tld</tablib-uri> <taglib-location>/WEB-INF/struts.tld</taglib-location></taglib>

Pozycja <taglib-uri> musi być ścieżką URI, tak więc jej prawidłowe wartości to struts,

http://jakarta.apache.org/struts, i /WEB-INF/struts.ltd. Ścieżka URI tradycyjnie

odnosi się do prawdziwego umiejscowienia zasobów, ale w powyższym przykładzie została zastosowana

jedynie jako unikatowy identyfikator biblioteki znaczników.

5. Na każdej stronie JSP wykorzystującej bibliotekę własnych znaczników należy dodać poniższąinstrukcję taglib. Nakazuje ona serwerowi pobranie biblioteki znaczników o podanym „URIposzukiwań” i umieszczenie tych znaczników pod przedrostkiem nazwy XML struts:

<%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %>6. Należy wykorzystać znaczniki na stronie JSP, upewniając się, że przedrostek nazw XML jest taki, jak

zadeklarowany w instrukcji taglib:<struts:property name="nazwakomponentu" property="nazwawlasciwosci" />

Kreator biblioteki znaczników może połączyć TLD z plikiem JAR biblioteki znaczników. W tymprzypadku, zamiast wskazywać na .tld należy wskazać bezpośrednio na .jar.

38 Znacznik <iterate> wymaga JDK 1.2 lub późniejszego. W JDK 1.1 występuje znacznik <enumerate>.

Page 385: Java Servlet - Programowanie

Aplikacja „Narzędzia” wykorzystująca bibliotekę własnych znacznikówWykorzystanie znaczników Struts <iterate> i <property> może uprościć stronę widoknarz.jsp. Przyokazji zademonstrowana zostanie oparta na serwletach architektura Model 2, w której serwlet otrzymuje żądanie,dodaje atrybuty do obiektu żądania i przekazuje żądanie do JSP, która działa jak szablon. Przykład 18.12przedstawia serwlet kontrolujący.

Przykład 18.12.

Serwlet kontrolujący Model 2import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class SerwletNarz extends HttpServlet { Narzedzie[] narzedzia = null; public void init() throws ServletException { // Pobranie danych narzędzi w init w celu zachowania prostoty String plikNarz = getServletContext().getInitParameter("plikNarz"); // z web.xml if (plikNarz == null) { throw new ServletException("Plik danych narzędzi musi być określony jako " + "parametr inicjacji kontekstu plikNarz"); } log("Pobieranie narzędzi z " + plikNarz); try { narzedzia = Narzedzie.ladujNarz(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 { Narzedzie[] narzedzia = null; // Umieszczenie właściwego atrybutu „narzedzia” w kontekście String stan = zad.getParameter("stan"); if (stan == null) { zad.setAttribute("narzedzia", pobierzNarz()); } else { zad.setAttribute("narzedzia", pobierzNarz(stan)); } // Przesłanie żądania do JSP w celu dalszego przetworzenia RequestDispatcher disp = zad.getRequestDispatcher("/widoknarz-znacznik.jsp"); disp.forward(zad, odp); } public Narzedzie[] pobierzNarz() { return narzedzia; } public Narzedzie[] pobierzNarz(String stan) { List lista = new LinkedList(); for (int i = 0; i < narzedzia.length; i++) { if (narzedzia[i].getStateFlag().equalsIgnoreCase(stan)) { lista.add(narzedzia[i]); } } return (Narzedzia[]) lista.toArray(new Narzedzia[0]); }}

Serwlet zachowuje się podobnie do NarzedziaAp w Tea i SerwletNarz w WebMacro. Pobiera informacjeo narzędziach w swojej metodzie init(), a dla każdego żądania dokłada do obiektu żądania odpowiednipodzbiór narzędzi. Serwlet przekazuje każde żądanie plikowi JSP, który właściwie tworzy stronę. Posiada on

Page 386: Java Servlet - Programowanie

dostęp do narzędzi w każdym obiekcie żądania przy pomocy znacznika <jsp:useBean>. Przykład 18.13przedstawia plik JSP noszący nazwę widoknarz-znacznik.jsp.

Przykład 18.13.

Aplikacja „Narzędzia” przystosowana do bibliotek znaczników<%-- widoknarz-znacznik.jsp --%><%@ taglib uri="/WEB-INF/struts.tld" prefix="struts" %><% String tytul = "Lista narzędzi"; String tytul2 = "Lista narzędzi do tworzenia zawartości"; String 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ę.";%><%@ include file="/naglowek.jsp" %><%@ page session="false" %><%@ page errorPage="/bladBierz.jsp" %><%-- Ustawienie tablicy narzędzi jako atrybutów żądania--%><jsp:useBean id="narzedzia" class="Narzedzie[]" scope="request"/><struts:iterate id="narzedzie" collection="<%= narzedzia %>"> <HR SIZE=2 ALIGN=LEFT> <H3> <%-- Automatycznie wartości opuszczane przez HTML --%> <struts:property name="narzedzie" property="nazwa" /> <% if (((Narzedzie)narzedzie).czyNowy(45)) { %> <FONT COLOR=#FF0000><B> (Nowość!) </B></FONT> <% } else if (((Narzedzie)narzedzie).czyUaktualniony(45)) { %> <FONT COLOR=#FF0000><B> (Uaktualnienie!) </B></FONT> <% } %> </H3> <A HREF="<struts:property name="narzedzie" property="domURL"/>"> <struts:property name="narzedzie" property="domURL"/></A><BR> <%-- Przyjęcie, że w komentarzu ma nie występować HTML --%> <struts:property name="narzedzie" property="komentarz" /></struts:iterate><%@ include file="/stopka.jsp" %>

Powyższa strona po pierwsze wykorzystuje instrukcję taglib do pobrania biblioteki własnych znacznikówStruts. Nie wolno zapomnieć o tym kroku! Jeżeli krok ten zostanie pominięty, nie będzie wyświetlonybezpośredni komunikat o błędzie, ale strona nie będzie działać. Powodem tego jest fakt, że bez instrukcjitaglib znaczniki <struts:iterate> i <struts:property> serwer traktuje jak wszystkie inneznaczniki 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ędzioraz 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, pozostajejednak kilka skryptletów. Usunięcie wszystkich skryptletów wiązałoby się z koniecznością utworzenia własnegoznacznika wykonującego porównanie znaczników czasu. Niestety, z powodu skomplikowania procesu tworzeniawłasnych znaczników, samouczek nie został umieszczony w tej książce. Osoby zainteresowane tworzeniemwłasnych znaczników powinny przejrzeć książkę „JavaServer Pages” autorstwa Hansa Bergstena (O'Reilly).

Page 387: Java Servlet - Programowanie

Rozdział 19. Informacje

dodatkowe

W każdym domu znajduje się szuflada na szpargały — szuflada załadowana do pełna rzeczami, które nie dokoń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 zestawprzydatnych 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ą mechanizmywyraż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ówOsoby, 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 parametryte muszą zostać przekonwertowane do formatu innego niż String. Na przykład, konieczne jest odczytanieparametru 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 pomocystandardowego 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 przekazanieodpowiedzialności za pobieranie i analizę parametrów klasie narzędziowej. Klasacom.oreilly.servlet.ParameterParser jest właśnie taką klasą. Przy pomocy ParameterParsermoż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) {

Page 388: Java Servlet - Programowanie

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 intoraz 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 wprzypadku wysyłanych formularzy, gdy nie wpisano żadnych wiadomości do pól tekstowych, co we wszystkichprzypadkach 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 jestczę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 zamiastzgł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 APIgetParameter() i getParameterValues().

Kod ParameterParserKlasa ParameterParser zawiera ponad tuzin metod, które zwracają parametry żądania — dwie dla każdegordzennego typu Javy. Posiada również dwie metody getStringParameter() w przypadku, gdybykonieczne było pobranie parametru w jego surowym formacie String. Kod ParameterParser jestprzedstawiony w przykładzie 19.1. Wyjątek ParameterNotFoundExceptoion znajduje się w przykładzie19.2.

Przykład 19.1.

Klasa ParameterParserpackage 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 {

Page 389: Java Servlet - Programowanie

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) {

Page 390: Java Servlet - Programowanie

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 ParameterNotFoundExceptionpackage com.oreilly.servlet;public class ParameterNotFoundException extends Exception { public ParameterNotFoundException() { super(); } public ParameterNotFoundException(String s) { super(s); }}

Page 391: Java Servlet - Programowanie

Wysyłanie poczty elektronicznejCzasami 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 swojekomentarze. 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ć administratorowipocztą 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 iwysyłając wiadomość poprzez protokół pocztowy niskiego poziomu, zazwyczaj tak zwany Simple MailTransfer Protocol (Prosty Protokół Transferu Poczty — SMTP).

• Może uruchomić z wiersza poleceń zewnętrzny program pocztowy, jeżeli system serwera posiada takiprogram.

• 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óływysył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ę. Dlabardziej skomplikowanych zastosowań rekomendowany jest JavaMail — zwłaszcza w przypadku serwletówdziałających pod kontrolą serwera J2EE, w którym na pewno dostępne są dwa pliki JAR wymagane przezJavaMail.

Stosowanie klasy MailMessageDla 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 podadresem http://www.servlets.com. Została ona utworzona na podstawie klasy sun.net.smtp.SmtpClientzawartej w JDK firmy Sun, ale unika politycznego problemu wykorzystywania „niewspieranej i będącej celemzmian” klasy sun.*. Posiada ona również kilka przyjemnych usprawnień funkcjonalności. Jej stosowanie jestproste:

1. Wywołanie MailMessage wiad = new MailMessage(). Opcjonalnie przekazaniekonstruktorowi nazwy komputera, który należy wykorzystać jako serwer poczty i który zastępujedomyślny localhost. Większość komputerów pracujących pod Uniksem może działać jako serwerpoczty 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łanakilka 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 towymagane, ale umieszczenie tematu zawsze jest dobrym pomysłem.

5. Wywołanie wiad.setHeader(nazwa, wartosc) jeżeli powinny zostać dołączone dodatkowenagłó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ć donadpisania wartości domyślnych. Nazwy i wartości nagłówków powinny być podporządkowanezasadom 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 potokuwynikowego 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 zserwerem.

Page 392: Java Servlet - Programowanie

Wysyłanie pocztą danych formularzaPrzykł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; //Opuszczeniegó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 zmiennychFROM i TO, chociaż wysyłany formularz może zawierać (najprawdopodobniej ukryte) pola określającealternatywy 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 dozawartoś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óbdodana. W przypadku zastosowań bardziej zaawansowanych, dobrą alternatywą jest JavaMail.

Stosowanie wyrażeń regularnychTen podrozdział przeznaczony jest szczególnie dla osób znających oparte na CGI skrypty Perla iprzyzwyczajonych do możliwości wyrażeń regularnych w Perlu. W tym rozdziale zostaną przedstawione zasady

Page 393: Java Servlet - Programowanie

korzystania z wyrażeń regularnych w Javie. Osobom nie znającym tematu należy się wyjaśnienie, że wyrażeniaregularne są mechanizmem umożliwiającym bardzo zaawansowaną manipulację łańcuchami przy minimalnejilości kodu. Cała moc wyrażeń regularnych jest doskonale przedstawiona w książce „Mastering ReularExpressions” 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 wnim wyrażeń regularnych. Nie należy się tym jednak martwić. Podobnie jak w przypadku większości funkcjiJavy, jeżeli nie dostarcza ich Sun, można je uzyskać za rozsądną cenę od innego niezależnego producenta, ajeżeli jest to coś tak przydatnego, jak wyrażenia regularne, istnieje duża szansa, że dostępna jest odpowiedniabiblioteka Open Source. Tak jest również w tym przypadku — istnieje mechanizm wyrażeń regularnych OpenSource dostępny jako część Apache Jakarta Project, stworzony początkowo przez Jonathana Locke'a i dostępnyw 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 nowychproduktów bez konieczności udostępniania tych produktów jako Open Source.

Odnajdywanie łącz przy pomocy wyrażeń regularnychW 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 <A HREF>HTML odnalezionych na stronie WWW. Jego kod jest przedstawiony w przykładzie 19.4.

Przykład 19.4.

Wyszukiwanie wszystkich łączimport 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); // odcięcie wiodącego '/' String strona = null; 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 + ":<BR>" + ServletUtils.getStackTraceAsString(w)); return; }

Page 394: Java Servlet - Programowanie

wyj.println("<HTML><HEAD><TITLE>Pobieranie łączy</TITLE>"); try { // Konieczne określenie <BASE> ż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("<base[^>]*>", RE.MATCH_CASEINDEPENDENT); boolean maBase = wr.match(strona); if (maBase) { // Wykorzystanie istniejącego <BASE> 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("<BASE HREF=\"" + wr.getParen(0) + "\">"); } else { // Brak wiodącego ukośnika, dodanie go wyj.println("<BASE HREF=\"" + url + "/" + "\">"); } } wyj.println("</HEAD><BODY>"); wyj.println("Łącza na <A HREF=\"" + url + "\">" + url + "</A>" + " są następujące: <BR>"); wyj.println("<UL>"); String szukaj = "<a\\s+[^<]*</a\\s*>"; wr = new RE(szukaj, RE.MATCH_CASEINDEPENDENT); int indeks = 0; while (wr.match(strona, indeks)) { String pasuje = wr.getParen(0); indeks = wr.getParenEnd(0); wyj.println("<LI>" + pasuje + "<BR>"); } wyj.println("</UL>"); wyj.println("</BODY></HTML>"); } catch (RESyntaxException w) { // Nie powinien wystąpić, bo łańcuchy poszukiwań są sztywne w.printStackTrace(wyj); } }}

Przykładowy wynik uruchomienia powyższego przykładu jest przedstawiony na rysunku 19.1.

Page 395: Java Servlet - Programowanie

Rysunek 19.1.

Przeglądanie przy minimalnej szybkości transferu

Teraz powyższy kod zostanie opisany dokładniej. Po pierwsze, serwlet określa URL, którego łącza powinnyzostać pobrane poprzez odszukanie dodatkowych informacji ścieżki. Oznacza to, że powyższy serwlet powinienzostać wywoływany podobnie do http://localhost:8080/servlet/Lacza/http://www.servlets.com. Następnie serwletodczytuje zawartość tego URL-a przy pomocy klasy HttpMessage i przechowuje stronę jako String. Dlabardzo dużych stron podejście to nie jest wydajne, ale może służyć jako dobry przykład.

Następnym krokiem jest upewnienie się, że wyświetlana strona posiada właściwy znacznik <BASE> tak, abyłącza względne na stronie były właściwie interpretowane przez przeglądarkę. Jeżeli na pobieranej stronie istniejejuż znacznik <BASE>, to można go wykorzystać, tak więc następuje jego poszukiwanie przy pomocy wyrażeniaregularnego <base[^>]*>. Łańcuch ten zostaje przekazany do konstruktora org.apache.regexp.RErazem ze znacznikiem wskazującym na niezależność od wielkości liter. Ta składnia wyrażenia regularnego jeststandardowa i identyczna jak w Perlu. Nakazuje ona wyszukanie tekstu <base>, po którym następuje dowolnailość znaków nie będących >, po których następuje tekst >. Jeżeli właściwy tekst zostanie odnaleziony, jest onpobierany przy pomocy wr.getParen(0). Metoda ta pobiera najbardziej zewnętrzne łańcuch (pasującełańcuchy mogą być zagnieżdżane przy pomocy nawiasów).

Jeżeli na stronie nie występuje znacznik <BASE>, należy go skonstruować. <BASE> powinien zawierać całyźródłowy URL razem z ostatnim ukośnikiem39. Informacja ta może zostać pobrana przy pomocy prostegowyrażenia regularnego http://.*/. Nakazuje ono dopasowanie do tekstu http://, po którym następujedowolna ilość znaków kończąca się /. Wzór .* odczytuje maksymalną możliwą ilość znaków, która nie kolidujez innymi warunkami wyrażenia regularnego (terminologia wyrażeń regularnych nazywa to działaniezachłannym), tak więc wyrażenie to zwraca wszystko łącznie z wiodącym ukośnikiem. Jeżeli ukośnik taki niewystępuje, zostaje on po prostu dodany.http://www.jdom.org/news/. Aby się zabezpieczyć, należy uwidocznić każdy wiodący ukośnik w UTL-u przekazanymserwletowi.

Page 396: Java Servlet - Programowanie

Na końcu ze strony pobrane zostają znaczniki <A HREF>, przy pomocy stosunkowo skomplikowanegowyrażenia regularnego <a\s+[^<]*</a\s*>. (Można dostrzec, że w kodzie znaki \ zostały wyłączone przypomocy dodatkowego znaku \.) Nakazuje to poszukiwanie tekstu <a, po którym następuje dowolna liczbaznaków, które nie są <, następnie tekst </a, dalej dowolna ilość pustego miejsca, potem >. Jeżeli złoży się teinformacje, okaże się, że następuje pobranie znaczników <A HREF> od początkowego <A do końcowego</A>, niezależnie od wielkości liter i ilości pustego miejsca, zabezpieczone przed omyłkowym dopasowaniemznaczników takich jak <APPLET>. Po odnalezieniu każdego pasującego tekstu jest on wyświetlany na liście, aposzukiwanie jest kontynuowane przy pomocy indeksu zapisującego następny punkt początkowy.

Większa ilość informacji na temat działań możliwych do wykonania przy pomocy wyrażeń regularnych jestdostępna w dokumentacji dołączonej do biblioteki.

Uruchamianie programówCzasami serwlet musi wykonać program zewnętrzny. Jest to generalnie działanie ważne w sytuacjach, w którychzewnętrzny program oferuje funkcjonalność niedostępną przy pomocy samej Javy. Na przykład, serwlet możewywołać zewnętrzny program w celu manipulowania obrazkiem lub sprawdzenia stanu serwera. Uruchamianieprogramów zewnętrznych utrudnia przenośność i zachowanie bezpieczeństwa. Jest to działanie, które powinnobyć podejmowane tylko wtedy, gdy jest absolutnie konieczne i jedynie przez serwlety, pracujące pod kontroląstosunkowo liberalnego mechanizmu bezpieczeństwa — konkretnie mechanizmu zezwalającego serwletom nawywoływanie metody exec() java.lang.Runtime.

FingerProgram finger zapytuje (przypuszczalnie zdalny) komputer o listę aktualnie zalogowanych użytkowników. Jeston dostępny w praktycznie wszystkich systemach Uniksowych i niektórych komputerach z Windows NTposiadających zdolność obsługi sieci. Program finger działa przez połączenie z demonem finger (zazwyczajnoszącym nazwę fingerd), który nasłuchuje na porcie 79. finger wykonuje swoje żądanie do fingerd przy pomocywłasnego protokołu „finger”, a fingerd odpowiada właściwą informacją. Większość systemów Uniksowychposiada fingerd, ale duża część skoncentrowanych na bezpieczeństwie administratorów wyłącza go w celuograniczenia ilości informacji, które mogłyby zostać wykorzystane podczas prób włamania. Ciągle ciężko jestodnaleźć fingerd w systemach Windows. Uruchomiony bez żadnych argumentów, finger zgłasza wszystkichużytkowników na lokalnym komputerze. Na przykład:

% fingerLogin Name TTY Idle When Officejkowalski Jan Kowalski q0 3:13 Thu 12:13anowak Anna Nowak q1 Thu 12:18

Uruchomiony z nazwą użytkownika jako argumentem, finger zwraca informacje na temat tego użytkownika:% finger jkowalskiLogin name: jkowalski In real life: Jan KowalskiDirectory: /usr/people/jkowalski Shell: /bin/tcshOn since Jan 1 12:13:28 on ttyq0 from :0.03 hours 13 minutes Idle TimeOn since Jan 1 12:13:28 on ttyq2 from :0.0

Uruchomiony z nazwą komputera jako argumentem, finger zwraca informacje o wszystkich użytkownikachokreślonego komputera. Zdalny komputer musi posiadać działający fingerd:

% finger @fikcjaLogin Name TTY Idle When Officeppawlak Piotr Pawlak q0 17d Mon 10:45

I, oczywiście, jeżeli uruchomiony z nazwą użytkownika i nazwą komputera, finger zwraca informacje na tematokreślonego użytkownika na określonym komputerze:

% finger ppawlak@fikcja[fikcja.calkowita.com]Login name: ppawlak In real life: Piotr PawlakDirectory: /usr/people/ppawlak Shell: /bin/tcshOn since Dec 15 10:45:22 on ttyq0 from :0.017 days Idle Time

Uruchamianie polecenia fingerZakładając, że serwlet pragnie dostępu do informacji odczytanych przez finger, posiada on dwie możliwości —może ustanowić połączenie przez port z fingerd i wykonać żądanie informacji tak, jak dowolny inny klientfinger, lub może uruchomić z wiersza poleceń program finger, który wykonuje połączenie na własną rękę iodczytać informacje zwracane przez finger. Tu zostanie przedstawiona druga technika.

Page 397: Java Servlet - Programowanie

Przykład 19.5 przedstawia sposób wykonania przez serwlet polecenia finger w celu sprawdzenia, którzyużytkownicy są zalogowani na lokalnym komputerze. Odczytuje on wynik polecenia i wyświetla go wstandardowym urządzeniu.

Przykład 19.5.

Wykonywanie polecenia finger przy pomocy serwletuimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils;public class Finger extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); String polecenie = "finger"; Runtime czas = Runtime.getRuntime(); Process proces = null; try { proces = czas.exec(polecenie); DataInputStream in = new DataInputStream(proces.getInputStream());

// Odczytanie i wyświetlenie wyników String linia = null; while ((linia = in.readLine()) != null) { wyj.println(linia); } } catch (Exception w) { wyj.println("Problem z finger: " + ServletUtils.getStackTraceAsString(w)); } }}

Serwlet wykorzystuje polecenie exec() tak, jak każda inna klasa Javy. Uruchamia polecenie finger, po czymodczytuje i wyświetla wynik. Jeżeli wystąpi problem, serwlet wyłapuje wyjątek i wyświetla użytkownikowiścieżkę stosu. Powyższy serwlet zakłada, że polecenie finger znajduje się w domyślnej ścieżce programów. Jeżelinie jest to prawda, należy zmienić łańcuch polecenia tak, by określał on ścieżkę do finger.

Należy wskazać, że chociaż Java wykonuje rdzenny kod podczas uruchamiania programu finger, sama nie narażasię na ryzyko normalnie związane z wykonywaniem rdzennego kodu. Dzieje się tak, ponieważ program fingerjest wykonywany jako oddzielny proces. Może on załamać się lub zostać zabity bez wpływu na serwer, naktórym działa serwlet.

Uruchamianie finger z argumentamiZakładając, że polecenie finger powinno zostać wywołane z argumentem, jego wywołanie powinno wyglądaćnieco inaczej. Metoda exec() pobiera pojedynczy łańcuch określający polecenie, lub tablicę łańcuchów,określającą polecenie i argumenty, które należy mu przekazać. Kod uruchamiający finger jkowalskiprzedstawiony jest w przykładzie 19.6.

Przykład 19.6.

Dodawanie parametru do uruchamianego poleceniaimport java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;import com.oreilly.servlet.ServletUtils;public class Finger extends HttpServlet { public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter();

Page 398: Java Servlet - Programowanie

String[] polecenie = { "finger", "jkowalski" }; // jedyna zmiana Runtime czas = Runtime.getRuntime(); Process proces = null; try { proces = czas.exec(polecenie); DataInputStream in = new DataInputStream(proces.getInputStream()); // Odczytanie i wyświetlenie wyników String linia = null; while ((linia = in.readLine()) != null) { wyj.println(linia); } } catch (Exception w) { wyj.println("Problem z finger: " + ServletUtils.getStackTraceAsString(w)); } }}

Zmienna polecenie jest teraz tablicą łańcuchów {"finger", "jkowalski"}. Polecenie nie działałobyjako pojedynczy łańcuch "finger jkowalski".

Uruchamianie finger z przekierowywanym wynikiemOstatnim przykładem będzie opis przekierowywanego wyniku polecenia finger. Wynik może zostaćprzekierowany do pliku w celu późniejszego wykorzystania przy pomocy wywołania finger jkowalski >/tmp/jkowalski. Można również przekierować wynik do programu grep w celu usunięcia odwołań dopewnego użytkownika, na przykład finger|grep –v jkowalski.

Zadanie to jest trudniejsze, niż może się wydawać. Jeżeli zmienna polecenia jest ustawiona jako łańcuchfinger|grep –v jkowalski, Java traktuje ten łańcuch jako nazwę pojedynczego programu — któregonajprawdopodobniej nie odnajdzie. Jeżeli zmienna polecenia jest ustawiona jako tablica łańcuchów{"finger", "|", "grep", "–v", "jkowalski"}, Java wykonuje polecenie finger i przekazujemu następne cztery łańcuchy jako parametry, co bez wątpienia zmyli program finger.

Rozwiązanie tego problemu wymaga zrozumienia, że przekazanie jest funkcją powłoki. Powłoka to program, wktórym zazwyczaj wpisuje się polecenia. W Uniksie najpopularniejszymi powłokami są csh, tsh, bash i sh. WWindows 95/98 powłoka to zazwyczaj command.com. W Windows NT i Windows 2000, powłoką jestcommand.com lub cmd.exe.

Zamiast bezpośrednio uruchamiać finger, uruchomiona zostaje powłoka i przekazuje się jej łańcuch polecenia,które powinno zostać wykonane. Taki łańcuch może zawierać polecenie finger i dowolny rodzaj przekierowania.Powłoka może zanalizować polecenie, prawidłowo rozpoznać i wykonać przekierowanie. Dokładne poleceniemusi uruchomić powłokę, tak więc program zależy od powłoki, czyli od systemu operacyjnego. Technika ta wzwiązku z tym ogranicza niezależność serwletów od platformy. W systemie Uniksowym poniższa zmiennapolecenia nakazuje csh wykonanie polecenia finger|grep –v jkowalski:

String[] polecenie = { "/bin/csh", "-c", "finger | grep –v jkowalski" };Program, który wykonuje Java to /bin/csh. csh zostają przekazane dwa argumenty: -c, które nakazuje powłocewykonanie następnego parametru, oraz finger|grep –v jkowalski, który jest parametremwykonywanym przez powłokę.

W systemie Windows zmienna polecenia powinna wyglądać następująco:String[] polecenie = { "command.com", "/c", "finger | grep –v jkowalski" };

Argument /c command.com działa w ten sam sposób, co –c csh. Przyrostek .com jest konieczny. UżytkownicyWindows NT powinni zapamiętać, że wykorzystanie cmd.exe może sprawiać problemy, ponieważ przekierowujeono wynik do nowego okna zamiast do konsoli Javy, która je uruchomiła. Właściwie nawet uruchomienieserwera z powłoki cmd.exe może spowodować błąd w poleceniu command.com.

Stosowanie rdzennych metodPomimo nacisków firmy Sun na stosowanie czystego kodu Javy, rdzenny kod wciąż posiada swoje zastosowania.Rdzenny kod jest konieczny przy wykonywaniu zadań, których Java (i zewnętrzne problemy przez niąuruchamiane) nie może wykonać — są to blokowanie plików, uzyskiwanie dostępu do identyfikatorówużytkowników i współdzielonej pamięci, wysyłanie faksów i tak dalej. Rdzenny kod jest przydatny równieżpodczas uzyskiwania dostępu do starych danych poprzez bramki nie przystosowane do Javy. Także w sytuacjach,

Page 399: Java Servlet - Programowanie

w których ważny jest każdy element wydajności, biblioteki rdzennego kodu mogą spowodować poważneprzyśpieszenie serwletu.

Jednak rdzenny kod nie powinien być wykorzystywany poza sytuacjami, w których jest absolutnie konieczny,ponieważ jeśli rdzenny kod uruchomiony przez serwlet spowoduje błąd, cały serwer może się załamać!Zabezpieczenia Javy nie mogą chronić serwera przed załamaniami rdzennego kodu. Z tego powodu nie jestrozsądnym wykorzystywanie rdzennego mostu JDBC-ODBC przez serwlet, ponieważ wiele sterowników ODBCmoże mieć problemy z dostępem wielowątkowym. Rdzenny kod ogranicza również niezależność serwletu odplatformy. Chociaż nie jest to ważne w przypadku własnych serwletów przywiązanych do konkretnego serwera,należy o tym pamiętać.

Sposób uzyskiwania przez serwlet dostępu do rdzennych metod zależy od serwera WWW i JVM, na których jesturuchomiony. Można podjąć ryzyko i powiedzieć ogólnie, że można z dużym prawdopodobieństwemspodziewać się, że serwer WWW i wirtualna maszyna Javy obsługują standardowy Java Native Interface(Rdzenny Interfejs Javy — JNI). Wykorzystywanie JNI jest stosunkowo skomplikowane i nawet podstawowewprowadzenie do niego wykracza poza zakres tego rozdziału.

Podczas stosowania JNI z serwletami należy pamiętać o następujących sprawach:

• Jedynie najbardziej liberalne mechanizmy bezpieczeństwa serwerów pozwalają serwletem nawykonywanie rdzennego kodu.

• W JDK 1.1.x występuje błąd, który nie pozwala na wykorzystywanie rdzennego kodu przez klasępobraną przy pomocy własnego mechanizmu ładowania klas (takiego jak mechanizm pobierającyserwlety z domyślnego katalogu serwletów). Serwlety wykorzystujące rdzenny kod muszą w związku ztym być umieszczone w ścieżce klas serwera (takim jak katalog_macierzysty/classes).

• Katalog, w którym umieszczona jest współdzielona biblioteka (lub biblioteki dołączane dynamicznie,czyli DLL) zawierająca rdzenny kod zależy od serwera WWW i JVM. Niektóre serwery posiadająkonkretne miejsca przeznaczone dla bibliotek współdzielonych. Jeżeli serwer nie określa konkretnegokatalogu bibliotek współdzielonych, proszę spróbować umieścić bibliotekę w miejscu zależnym odJVM, takim jak katalog_JDK/bin lub katalog_JDK/lib (gdzie katalog_JDK jest katalogiemmacierzystym instalacji JDK), lub miejscu zależnym od systemu, takim jak katalog_windows/system32lub /usr/lib.

Występowanie jako klient RMIW rozdziale 10, „Komunikacja aplet-serwlet” opisany został sposób, w jaki serwlet występować może jakoserwer RMI. Tutaj role zostaną odwrócone i przedstawiony zostanie serwlet działający jako klient RMI. Poprzezpodjęcie się roli klienta RMI, serwlet może wykorzystać usługi innych serwerów w celu wypełnienia swojegozadania, skoordynować swoje wysiłki z innymi serwerami lub serwletami na tych serwerach i/lub działać jakoproxy dla apletów nie mogących samodzielnie komunikować się z serwerami RMI.

Przykład 19.7 przedstawia SerwletKlientGodziny, serwlet, który pobiera aktualną godzinę dnia z serweraRMI SerwletGodziny przedstawionego w rozdziale 10.

Przykład 19.7.

Serwlet jako klient RMIimport java.io.*;import java.rmi.*;import java.rmi.registry.*;import javax.servlet.*;import javax.servlet.http.*;public class SerwletKlientGodziny extends HttpServlet { SerwerGodziny godzina; // Zwraca odwołanie do SerwerGodziny, lub null jeżeli wystąpi problem. protected SerwerGodziny pobierzSerwerGodziny() { // Jeżeli wykorzysta się funkcję dynamicznego ładowania kodu RMI. // trzeba ustawić mechanizm bezpieczeństwa taki jak RMISecurityManager // if (System.getSecurityManager() == null) { // System.setSecurityManager(new RMISecurityManager()); // } try { Registry rejestr = LocateRegistry.getRegistry(pobierzRejestrKomp(), pobierzRejestrPort());

Page 400: Java Servlet - Programowanie

return (SerwerGodziny)registry.lookup(pobierzRejestrNazwa()); } catch (Exception w) { getServletContext().log(w, "Problem z pobraniem odwołania doSerwerGodziny"); return null; } } private String pobierzRejestrNazwa() { String nazwa = getInitParameter("registryName"); return (nazwa == null ? "SerwletGodziny" : nazwa); } private String pobierzRejestrKomp() { // Zwrócenie nazwy komputera podanej przez "registryHost" // lub w przeciwnym wypadku null nakazujący wybranie localhost return getInitParameter("registryHost"); } private int pobierzRejestrPort() { try { return Integer.parseInt(getInitParameter("registryPort")); } catch (NumberFormatException w) { return Registry.REGISTRY_PORT; } } public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException { odp.setContentType("text/plain"); PrintWriter wyj = odp.getWriter(); // pobranie obiektu godzina jeżeli nie zostało to zrobione wcześniej if (godzina == null) { godzina = pobierzSerwerGodziny(); if (godzina == null) { // Pobranie niemożliwe, więc zgłoszenie niedostępności. throw new UnavailableException(this, "Zlokalizowanie godzina niemożliwe"); } } // Pobranie i wyświetlenie aktualnego czasu na (zazwyczaj zdalnym) komputerze // godzina wyj.println(godzina.getDate().toString()); }}

Powyższy serwlet powinien wydawać się podobny do apletu opisanego w rozdziale 10. Zarówno serwlety jak iaplety muszą wykonać te same podstawowe działania w celu uzyskania dostępu do serwera RMI. Oba lokalizująrejestr przy pomocy nazwy komputera i numeru portu, następnie wykorzystują ten rejestr do odszukaniaodwołania do obiektu zdalnego. Jedyna możliwa różnica jest taka, że serwlet, jeżeli korzysta z funkcjidynamicznego ładowania kodu RMI w celu automatycznego pobrania klasy-końcówki z innego komputera, musinajpierw upewnić się, że działa pod strażą mechanizmu bezpieczeństwa w celu uchronienia siebie odpotencjalnie niebezpiecznej pobranej zdalnie klasy-końcówki. Aplet zawsze działa pod kontrolą mechanizmubezpieczeństwa apletów, tak więc krok ten nie jest konieczny. Serwlet może jednak działać bez domyślnegomechanizmu bezpieczeństwa, tak więc kiedy działa jako klient RMI, należy mu go przypisać.

Usuwanie błędówFaza testowania/usuwania błędów może być jednym z najtrudniejszych aspektów programowania serwletów.Serwlety mogą wykorzystywać w dużym stopiniu interakcję klient/serwer, co ułatwia powstawanie błędów, aleutrudnia ich reprodukcję. Ciężkie może również okazać się wyśledzenie nieoczywistego błędu, ponieważserwlety nie współpracują zbyt dobrze ze standardowymi programami uruchomieniowymi, ponieważ działają namocno wielowątkowych i ogólnie złożonych serwerach WWW. Poniżej umieszczono kilka rad i sztuczekpomocnych przy usuwaniu błędów.

Sprawdzenie dzienników zdarzeńKiedy podejrzewa się istnienie błędu, należy po pierwsze sprawdzić dzienniki zdarzeń. Większość serwerówtworzy dziennik błędów, w którym można odnaleźć listę wszystkich błędów zaobserwowanych przez serwer orazdziennik zdarzeń, który zawiera listę interesujących zdarzeń serwletów. Dziennik zdarzeń może równieżprzechowywać wiadomości zapisane przez serwlety przy pomocy metody log(), ale nie zawsze jest tomożliwe.

Page 401: Java Servlet - Programowanie

Proszę zauważyć, że wiele serwerów buforuje wyświetlane informacje w dziennikach w celu poprawieniawydajności. Podczas odkrywania problemu można spróbować zatrzymania buforowania (zazwyczaj przezzmniejszenie wielkości buforu serwera do zera bajtów), co pozwala na dostrzeżenie problemu w momencie jegowystąpienia. Należy pamiętać o przywróceniu później rozsądnej wielkości bufora.

Wyświetlenie dodatkowych informacjiJeżeli w dzienniku zdarzeń serwera nie można odnaleźć w dziennikach serwera, można spróbować zapisania wdzienniku dodatkowych informacji przy pomocy metody log(). Jak przedstawiono w przykładach w innychmiejscach niniejszej książki, zazwyczaj w dziennikach zapisywane są ścieżki stosów i inne sytuacje błędów.Podczas usuwania błędów można dodać kilka tymczasowych poleceń log() działających jako prosty programuruchomieniowy w celu poznania ogólnego sposobu wykonywania kodu i wartości zmiennych serwletu. Czasamidobrze jest umieścić polecenia log() w serwlecie otoczone klauzulami if tak, aby uruchamiały się jedyniewtedy, gdy konkretny parametr inicjacji uruchamiania błędów posiada wartość true.

Pobieranie dodatkowych informacji z dzienników serwera może czasami okazać się nieporęczne. W celuułatwienia odnajdywania tymczasowych informacji błędów można nakazać serwletowi wyświetlanie klientowiinformacji o błędach (poprzez PrintWriter) lub do konsoli serwera (poprzez System.out). Nie wszystkiekonsole serwerów są połączone z System.out serwletu; niektóre przekierowują wyświetlane informacje dopliku.

Stosowanie standardowego programu uruchomieniowegoMożliwe jest zastosowanie standardowego programu uruchomieniowego w celu wyśledzenia problemówserwletów, chociaż nie musi to być intuicyjnie oczywiste. Nie można wykonać tego działania bezpośrednio naserwlecie, ponieważ serwlety nie są samodzielnymi programami. Serwlety są rozszerzeniami serwera i jako takiemuszą działać wewnątrz serwera.

Na szczęście Tomcat jest serwerem napisanym wyłącznie w Javie i w związku z tym jest idealny do usuwaniabłędów z serwletów. Jedyną sztuczką, o której należy pamiętać jest konieczność uruchomienia Tomcatawewnątrz programu uruchomieniowego. Dokładne procedury mogą różnić się w zależności od wersji Tomcata(lub innego opartego na Javie serwera WWW), idea jednak pozostaje ta sama:

1. Ustawienie ścieżki klas programu uruchomieniowego tak, aby mogła odnaleźć klasy i pliki JARkonieczne do uruchomienia Tomcata. Można sprawdzić informacje wyświetlane podczas startu orazskrypty startowe serwera (tomcat.sh i tomcat.bat), aby uzyskać pomoc w określeniu ścieżki klas.

2. Ustawienie ścieżki klas programu uruchomieniowego tak, aby mógł on również odnaleźć serwlety iklasy wspierające, co oznacza zazwyczaj katalog WEB-INF/classes i pliki w katalogu WEB-INF/lib.Zazwyczaj te katalogi i pliki JAR nie powinny znajdować się w ścieżce klas, ponieważ uniemożliwia toprzeładowywanie serwletów. To dołączenie jednak jest przydatne przy usuwaniu błędów. Umożliwiaono programowi uruchomieniowemu ustawienie punktów kontrolnych w serwlecie zanim mechanizmładujący serwlety odpowiedzialny za pobieranie klas z WEB-INF załaduje serwlet.

3. Po właściwym ustawieniu ścieżki klas należy rozpocząć usuwanie błędów poprzez uruchomienie klasyserwera zawierającej podstawową metodę main(). W przypadku Tomcata 3.2 klasa ta toorg.apache.tomcat.startup.Tomcat. Inne serwery oparte na Javie i przyszłe wersjeTomcata mogą wykorzystywać inne klasy. Informacje na temat nazwy podstawowej klasy powinnyznajdować się w skrypcie startowym.

4. Tomcat może nakazywać ustawienie konkretnych właściwości systemu lub zmiennych środowiskowych.Na przykład, Tomcat 3.2 poszukuje własności systemu tomcat.home lub zmiennej środowiskowejTOMCAT_HOME w celu określenia swojego podstawowego katalogu. Proszę ustawić je, jeżeli okaże sięto konieczne.

5. Ustawienie punktów kontrolnych w dowolnym serwlecie, w którym powinny zostać usunięte błędy, anastępnie wykorzystanie przeglądarki WWW w celu wykonania żądania do HttpServer o danyserwlet (http://localhost:8080/servlet/SerwletDoSprawdzenia). Wykonywanie powinno być przerywanew punktach kontrolnych.

Duża część graficznych programów uruchomieniowych ukrywa te szczegóły i umożliwia zintegrowanesprawdzanie serwletów wykorzystując wbudowany serwer do uruchamiania serwletów. Często dobrze jest jednakznać ręczną procedurę, ponieważ serwery dołączane do graficznych programów uruchomieniowych są zazwyczajnieco przestarzałe.

Page 402: Java Servlet - Programowanie

Niektóre programy dołączane do kontenerów serwletów (które normalnie działają jedynie w połączeniu zserwerem WWW nie utworzonym w Javie) posiadają samodzielne wersje utworzone w czystej Javiezaprojektowane specjalnie do wykorzystania podczas usuwania błędów, jak opisano powyżej. Zaletąwykorzystania tych serwerów, kiedy są one dostępne, jest możliwość przesuwania dowolnych własnych plikówkonfiguracyjnych z serwera zewnętrznego do środowiska testowego i z powrotem.

Analiza żądania klientaCzasami kiedy serwlet nie zachowuje się w spodziewany sposób, warto jest przyjrzeć się surowemu żądaniuHTTP, na które serwlet ten odpowiada. Osoby znające strukturę HTTP mogą odczytać żądanie i zobaczyćdokładnie, w którym miejscu serwlet mógł się pomylić40. Jednym ze sposobów dojrzenia surowego żądania jestzamiana procesu serwera WWW na własną aplikację, która wyświetla wszystkie otrzymane informacje. Przykład19.8 przedstawia taki serwer.

Przykład 19.8.

Przechwytywanie żądania klientaimport java.io.*;import java.net.*;import java.util.*;public class PatrzPort { private static void printUsage() { System.out.println("zastosowanie: java PatrzPort port"); } public static void main(String[] arg) { if (arg.length < 1) { printUsage(); return; } // Pierwszym argumentem jest port na którym należy nasłuchiwać int port; try { port = Integer.parseInt(arg[0]); } catch (NumberFormatException w) { printUsage(); return; } try { // Ustanowienie portu serwera przyjmującego połączenia klienta // Każde połączenie przekazywane do wątku obsługującego ServerSocket ss = new ServerSocket(port); while (true) { Socket zadanie = ss.accept(); new WatekObslugi(zadanie).start(); } } catch (Exception w) { w.printStackTrace(); } }}class WatekObslugi extends Thread { Socket s; public WatekObslugi(Socket s) { this.s = s; } public void run() { try { // Wyświetlenie każdego bajtu wychodzącego z portu InputStream in = s.getInputStream(); byte[] bajty = new byte[1]; while ((in.read(bajty)) != -1) {

40 Oczywiście jeżeli sprawdzenia dokonuje osoba nie znająca struktury HTTP, to ona może się pomylić, W tym przypadkuwarto jest przeczytać wprowadzenie do HTTP w rozdziale 2, „Podstawy serwletów HTTP” oraz książkę „HTTP PocketReference” autorstwa Clintona Wonga (O'Reilly).

Page 403: Java Servlet - Programowanie

System.out.print((char)bajty[0]); } } catch (Exception w) { w.printStackTrace(); } }}

Nasłuchiwanie na porcie 8080 można rozpocząć przy pomocy następującego polecenia powłoki:java PatrzPort 8080

Proszę zauważyć, że na jednym porcie nie mogą nasłuchiwać równocześnie dwie aplikacje, tak więc należyupewnić się, że na wybranym porcie nie nasłuchuje żaden inny serwer. Po uruchomieniu serwera można do niegoprzekierować żądania HTTP tak, jakby był on zwykłym serwerem WWW. Na przykład, można wykorzystaćprzeglądarkę WWW do obejrzenia strony http://localhost:8080. Kiedy PatrzPort otrzymuje żądanie HTTPprzeglądarki, wysyła żądanie do standardowego urządzenia wyświetlającego, aby można je było obejrzeć,Przeglądarka może być zajęta czekaniem na odpowiedź, która nigdy nie nadejdzie, Można to zakończyć przeznaciśnięcie przycisku Stop.

Poniżej przedstawiono przykładowy wynik uruchomienia PatrzPort, który pokazuje szczegóły żądania GETna http://localhost:8080:

GET / HTTP 1.0Connection: Keep-AliveUser-Agent: Mozilla/4.7 [pl] (X11; U; IRIX 6.2 IP22)Pragma: no-cacheHost: localhost:8080Accept: image.gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*Cookie: JSESSIONIN=To1010mC10934500694587412At

Utworzenie własnego żądania klientaOprócz przechwytywania i przeglądania żądań HTTP klienta, przydatne może okazać się utworzenie własnegożądania HTTP. Można to wykonać poprzez połączenie się z portem serwera, na którym serwer nasłuchuje, anastępnie wprowadzenie właściwie uformowanego żądania HTTP. W celu ustanowienia połączenia możnawykorzystać program telnet, dostępny we wszystkich komputerach Uniksowych i większości komputerówWindows posiadających funkcje sieciowe. Program telnet przyjmuje jako argumenty nazwę komputera i numerportu, z którym powinien się połączyć. Po połączeniu można wykonać żądanie, które będzie wyglądać podobniedo przedstawionego w poprzednim podrozdziale. Na szczęście własne żądanie może być o wiele prostsze —należy określić jedynie pierwszą linię, określającą cel żądania oraz ostatnią, która musi być pustą liniąwskazującą na koniec żądania. Na przykład:

% telnet localhost 8080Trying 127.0.0.1...Connected to localhostEscape character is '^]'.GET /servlet/PrzeglParametr?nazwa=wartosc HTTP/1.0http/1.1 200 OK.Server: Tomcat Web Server/3,2Content-Type: text/plainConnection: closeDate: Sun, 25 Jun 2000 20:29:06 GMTWuery String:Nazwa=wartoscRequest ParametersNazwa (0): wartoscConnection closed by foreign host.

Jak często się to zdarza, Windows zachowuje się nieco inaczej, niż jest to pożądane. Domyślny programtelnet.exe Windows 95/98/NT nadaje złą formę wielu odpowiedziom serwerów WWW, ponieważ nie rozumie,że w sieci WWW znak końca linii powinien być traktowany jako koniec linii i powrót karetki. Zamiast telnet.exe,

Page 404: Java Servlet - Programowanie

programiści Windows mogą wykorzystać zachowujący się bardziej odpowiednio program Javy przedstawiony wprzykładzie 19.9.

Przykład 19.9.

Inny sposób łączenia się z serwerem WWWimport java.io.*;import java.net.*;import java.util.*;public class KlientHttp { private static void printUsage() { System.out.println("usage: java KlientHttp komputer port"); } public static void main(String[] arg) { if (arg.length < 2) { printUsage(); return; } // Komputer to pierwszy parametr, port to drugi String komp = arg[0]; int port; try { port = Integer.parseInt(arg[1]); } catch (NumberFormatException w) { printUsage(); return; } try { // Otwarcie portu do serwera Socket s = new Socket(host, port); // Rozpoczęcie wątku wysyłającego wpisy z klawiatury do serwera new ZarzadWpisKlaw(System.in, s).start(); // Teraz wyświetlenie wszystkich informacji otrzymanych z serwera BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); String linia; while ((linia = in.readLine()) != null) { System.out.println(linia); } } catch (Exception w) { w.printStackTrace(); } }}class ZarzadWpisKlaw extends Thread { InputStream in; Socket s; public ZarzadWpisKlaw(InputStream in, Socket s) { this.in = in; this.s = s; setPriority(MIN_PRIORITY); // Odczyty z portu powinny posiadać wyższypriorytet // Niestety nie można użyć select() ! setDaemon(true); // Pozwolenie na śmierć aplikacji nawet jeżeli ten wątekpracuje } public void run() { try { BufferedReader klaw = new BufferedReader(new InputStreamReader(in)); PrintWriter serwer = new PrintWriter(s.getOutputStream()); String linia; System.out.println("Połączono… Proszę wprowadzić żądanie HTTP"); System.out.println("------------------------------------------"); while ((linia = klaw.readLine()) != null) { server.print(linia); server.print("\r\n"); // linia HTTP kończy się \r\n

Page 405: Java Servlet - Programowanie

server.flush(); } } catch (Exception w) { w.printStackTrace(); } }}

Powyższy program KlientHttp działa podobnie do telnet:% java KlientHttp localhost 8080Połączono… Proszę wprowadzić żądanie HTTP------------------------------------------GET /index.html HTTP/1.0HTTP /1.0 200 OK.Content-Type: text/htmlContent-Length: 2582Last-Modified: Fri, 15 Sep 2000 22:20:15 GMTServlet-Engine: Tomcat Web Server/3.2 (JSP 1.1; Servlet 2.2; Java 1.2.2;Windows NT 4.0 x86; java.vendor=Sun Microsystems Inc.)<!doctype html public "-//w3c//dtd html 4.0 transitional//en"><html><head> <meta http-equiv="Content-Type" content="text/html"; charset=iso-8859-1"> <title>Tomcat v3.2</title></head>...

Wykorzystanie niezależnego narzędziaNiezależne narzędzia dają nowe możliwości i łatwość wykorzystania zadaniu usuwania błędów. IBMAlphaWorks produkuje program o nazwie Distributed Application Tester (DAT), który przegląda żądania HTTPi 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ń iprzeglądanie odpowiedzi. Program jest utworzony całkowicie w Javie, ale jest udostępniany z programeminstalacyjnym 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. DATjest 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 znanenarzędzie służące do usuwania błędów z serwletów o nazwie ServletDebugger. Narzędzie to jest zaprojektowaneprogramowego wspomagania testów i usuwania błędów z serwletów. ServletDebugger nie wymagawykorzystania serwera WWW lub przeglądarki do wykonania żądania. Zamiast tego, wykorzystuje się zbiór klasdo stworzenia niewielkiej klasy-końcówki, która przygotowuje i wykonuje żądanie serwletu. Końcówka określawszystko — parametry inicjacji serwletu, nagłówki HTTP żądania oraz parametry żądania. ServletDebugger jeststosunkowo prosty i dobrze przystosowany do zautomatyzowanego testowania. Jego największą wadą jestkonieczność 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.com41.

Ostatnie wskazówkiJeżeli wszystkie powyższe porady nie pomogły w odnalezieniu i usunięciu błędu, proszę obejrzeć poniższeostateczne wskazówki dotyczące suwania błędów z serwletów:

• Proszę wykorzystać System.getProperty("java.class.path") przy pomocy serwletu wcelu 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ściserwerów niezwiązanych z serwletami, klasy wspierające w katalogu klas aplikacji WWW (WEB-INF/classes). Zazwyczaj przeładowywane są jedynie klasy serwletów w katalogu klas aplikacji WWW.

41 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.

Page 406: Java Servlet - Programowanie

• Należy zażądać od przeglądarki pokazania surowej zawartości wyświetlanej strony. Może to pomóc wzidentyfikowaniu 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ącametoda od razu wywołuje super.init(config).

Poprawa wydajnościSerwlety poprawiające wydajność wymagają nieco innego podejścia niż aplikacje lub aplety Javy wykonujące tosamo 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ściedziałają, ale posiadają mniejszy wpływ, kiedy zostają wykorzystane w systemie wielowątkowym. Poniżejprzedstawiono niektóre sztuczki najczęściej wykorzystywane przez programistów serwletów.

Tworzyć, ale nie przesadzaćNależy unikać niepotrzebnego tworzenia obiektów. To zawsze była dobra rada — tworzenie niepotrzebnychobiektów marnuje pamięć i dużą ilość czasu. W przypadku serwletów to jest rada jeszcze lepsza. Tradycyjniewiele JVM wykorzystywało globalną stertę obiektów, która musi zostać przypisana do każdej nowej alokacjipamię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 koduprzygotowującego łańcuch do późniejszego wyświetlania w następujący sposób:

String wyswietl;wyswietl += "<TITLE>";wyswietl += "Witaj, " + uzyt;wyswietl += "</TITLE>";

Chociaż powyższy kod wygląda miło i sprawnie, w trakcie uruchomienia działa, jak gdyby wyglądał mnie więcejtak 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("</TITLE>").toString();

Kiedy wydajność jest ważną kwestią, należy przepisać oryginalny kod tak, aby wyglądał jak poniższy, tak abyutworzone zostały tylko pojedyncze StringBuffer i String:

StringBuffer buf = new StrngBuffer();buf.append("<TITLE>");buf.append("Witaj, "").append(uzyt);buf.append("</TITLE>");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 wserwlecie wydłuża czas odpowiedzi serwletu. Ponieważ ten sam egzemplarz serwletu może obsługiwać wielewspółbieżnych żądań, musi, oczywiście, zająć się ochroną swojej klasy i zmiennych egzemplarza przy pomocyzsynchronizowanych bloków. Jednak przez cały czas jeden wątek żądania znajduje się w zsynchronizowanymbloku i żaden inny wątek nie może zostać do bloku wprowadzony. W związku z tym najlepiej jest uczynić tebloki jak najmniejszymi.

Należy także przyjrzeć się najgorszemu z możliwych wyników sporu między wątkami. Jeżeli najgorszyprzypadek jest znośny (jak w przykładzie licznika w rozdziale 3, „Okres trwałości serwletów”), można rozważyc

Page 407: Java Servlet - Programowanie

całkowite usuniecie bloków synchronizacji. Można również rozważyć zastosowanie interfejsu znacznikówSingleThreadModel, w którym serwer zarządza pulą egzemplarzy serwletów, aby zagwarantować, że każdyegzemplarz jest wykorzystywany przez co najwyżej jeden wątek w jednym czasie. Serwlety będąceimplementacją 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ą zawszewewnętrznie zsynchronizowane, podczas gdy równoważne im java.util.ArrayList ijava.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świetlaneNależy buforować dane wprowadzane i wyświetlane, wszystkie pliki magazynowe, wszystkie potoki pobrane zbazy danych itd. To prawie zawsze podnosi wydajność, ale poprawa ta może być szczególnie widoczna wprzypadku 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ólniebuforowanie podczas zapisu do PrintWriter lub ServletOutputStream lub podczas odczytywania zBufferedReader lub ServletInputStream nie jest konieczne. Większość implementacji serwerówsama buforuje te potoki.

Spróbować wykorzystania OutputStreamW przypadku stron WWW wykorzystujących kodowanie znaków Latin-1, technicznie możliwe jestwykorzystanie PrintWriter lub ServletOutputStream. Rekomendowanym podejściem jestwykorzystanie PrintWriter, ponieważ obsługuje on internacjonalizację, ale na niektórych serwerachwykorzystanie ServletOutputStream powoduje zauważalny wzrost wydajności, aServletOutputStream posiada także ułatwiające pracę metody print() i println() będącedziedzictwem Servlet API 1.0, w którym nie występowała opcja PrintWriter. Proszę jednak uważać. Wprzypadku 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 czasowychpowinny 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ęzykczy 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żeopis 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).

Page 408: Java Servlet - Programowanie

Rozdział 20. Zmiany w Servlet

API 2.3

Krótko przed przekazaniem niniejszej książki do druku firma Sun Microsystems opublikowała Proposed FinalDraft (Proponowaną Ostateczną Próbę) specyfikacji Servlet API 2.342. Nie jest to ostateczna wersja specyfikacji;Proposed Final Draft to krok do formalnej Final Release (Wersji Ostatecznej), tak więc szczegóły technicznecią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ą sposobytworzenia serwletów przy pomocy nowych funkcji43.

Zmiany w Servlet API 2.3Servlet API 2.3 właściwie pozostawia nietknięte jądro serwletów, co oznacza, że serwlety osiągnęły wysokipoziom dojrzałości. Większość działań związana była z dodaniem nowych funkcji poza jądrem. Zmiany te tomię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.

• 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ólniena programistach serwletów (poza faktem, że programiści ujrzą poprawioną przenośność), tak więc te szczegółyzostaną tu ominięte.

42 Chociaż specyfikacja ta została opublikowana przez firmę Sun, Servlet API 2.3. został tak naprawdę utworzony przezwiele 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.43 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 zapozwoleniem. (Proszę zobaczyć także http://www.javaworld.com/j2-01-2001/jw-0126-servletapi.html).

Page 409: Java Servlet - Programowanie

Przed rozpoczęciem przyglądania się zmianom należy zaznaczyć, że wersja 2.3 została udostępniona jedyniejako specyfikacja próbna. Większość opisanych tu funkcji nie będzie działać na wszystkich serwerach. Abyprzetestować te funkcje, poleca się pobranie oficjalnego wzorcowego serwera, Apache Tomcat 4.0. Jest to OpenSource i może być pobrany za darmo. Tomcat 4.0 jest aktualnie dostępny w wersji beta; obsługa Servlet API 2.3staje się coraz lepsza, ale ciągle jest niekompletna. Proszę przeczytać plik NEW_SPECS.txt dołączony doTomcata 4.0 w celu poznania jego poziomu wsparcia dla wszystkich funkcji nowej specyfikacji.

Serwlety w J2SE i J2EEJedną 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żnazmiana 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 przezserwery 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 stosunkowoobskurnych znaczników deskryptora związanych z J2EE w deskryptorze web.xml — <resource-env-ref>obsługującego „obiekty administrowane”, takie jak te wymagane przez Java Messaging System (JMS), <res-ref-sharing-scope> pozwalający na współdzielony lub wyłączny dostęp do odwołania do zasobów oraz<run-as> określający identyfikator bezpieczeństwa dla wywołującego EJB. Większość autorów serwletów niepowinna przejmować się tymi znacznikami J2EE; pełny ich opis dostępny jest w specyfikacji J2EE 1.3.

FiltryNajbardziej 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ą oneodpowiedzi. 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óryzostaje 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żezostać przefiltrowana przez dowolną ilość filtrów. Niektóre praktyczne idee filtrów to między innymi filtryuwierzytelniania, filtry logowania i nadzoru, filtry konwersji obrazków, filtry kompresji danych, filtrykodowania, filtry dzielenia na elementy, filtry potrafiące wywołać zdarzenia dostępu do zasobów, filtry XSLTprzetwarzają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ępniewywołuje doFilter() dowolną ilość razy dla różnych obiektów. Interfejs FilterConfig posiada metodypobierają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.

Page 410: Java Servlet - Programowanie

UWAGA!!!!

Przewiduje się, że w ostatecznej wersji 2.3 metoda getFilterConfig() zostanie usunięta, a metodasetFilterConfig(FilterConfig konfig)zostanie zastąpiona przez init(FilterConfig) idestroy().

Każdy filtr pobiera aktualne żądanie i odpowiedź w swojej metodzie doFilter(), a także FilterChainzawierający filtry, które muszą stale być przetwarzane. W metodzie doFilter() filtr może wykonać dowolnedział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 celuprzekazania kontroli następnemu filtrowi. Kiedy wywołanie to wraca, filtr może, na końcu swojej własnejmetody doFilter(), wykonać dodatkowe działania na odpowiedzi; na przykład może zapisać informacje natemat 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 konkretnejmetody wywołują implementację wpływającą na późniejsze działania obsługujące żądania. Serwlet API 2.3dostarcza nowych klas HttpServletRequestWrapper i HttpServletResponseWrapper w tympomagających. Posiadają one domyślną implementację wszystkich metod żądań i odpowiedzi oraz domyślnieprzekazują wywołania do oryginalnych żądań i odpowiedzi. Poniżej przedstawiony jest kod prostego filtralogowania, 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 dokonfiguracji filtra, które później zostanie wykorzystane w metodzie doFilter() w celu pobraniaServletContext. Logika doFilter() jest prosta — obliczenie czasu obsługi żądania i zapamiętanie czasupo zakończeniu przetwarzania. Aby wykorzystać powyższy filtr, trzeba wcześniej zadeklarować go wdeskryptorze DTD przy pomocy znacznika <filter>, jak przedstawiono poniżej:

<filter> <filter-name> dziennik </filter-name> <filter-class> FiltrDziennik </filter-class></filter>

Powyższy fragment kodu przekazuje serwerowi informację, że filtr o nazwie dziennik jest zaimplementowanyw klasie FiltrDziennik. Można przypisać zarejestrowany filtr do konkretnych wzorów URL-i lub nazwserwletów przy pomocy znacznika <filter-mapping>:

<filter-mapping> <filter-name>log</filter-name> <url-pattern>/*</url-pattern></filter-mapping>

Page 411: Java Servlet - Programowanie

Powyższy fragment konfiguruje filtr tak, aby był stosowany do wszystkich żądań serwera (statycznych lubdynamicznych), co jest działaniem odpowiednim dla filtra zapisującego informacje w dzienniku. Jeżeli nastąpipołączenie, dziennik mógłby wyglądać następująco:

Żądanie /indeks.jsp: 10

Zdarzenia okresu trwałościDrugą bardzo poważną zmianą w Servlet API 2.3 jest dodanie zdarzeń okresu trwałości aplikacji, któreumożliwiają powiadomienie obiektów „nasłuchujących", kiedy inicjowane bądź niszczone są kontekstyserwletó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 pierwszygotowa o przetwarzania żądań (tzn. podczas uruchomieniaserwera lub dodania albo przeładowania kontekstu).Żądania nie są obsługiwane, dopóki metoda nie zwróciswoich wartości.

void contextDestroyed(ServletContextEvent z)

Wywoływana z aplikacji WWW, która powinna zostać zakończona(tzn. podczas wyłączania serwera lub usuwania alboprzeładowania kontekstu). Obsługa żądań zostajezatrzymana 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żezaimplementować 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 kontekstuserwletu.

void attributeReplaced(ServletContextAttributeEvent z)

Wywoływana, kiedy atrybut w kontekście serwletu zostajewymieniony na inny.

Klasa ServletContextAttributeEvent jest rozszerzeniem ServletContextEvent i dodaje metodygetName() i getValue(), tak więc obiekt nasłuchujący może uzyskać informacje na temat zmieniającychsię atrybutów. Jest to przydatna własność, ponieważ aplikacje WWW które muszą zsynchronizować stanaplikacji (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 modelusesji występuje interfejs HttpSessionListener posiadający dwie metody:

void SessionCreated(HttpSessionEvent z)

Page 412: Java Servlet - Programowanie

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 celuzwrócenia sesji, która jest tworzona bądź niszczona. Metody te mogą być zastosowane podczas implementacjiinterfejsu 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ładprzez 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 rozszerzeniemHttpSessionEvent i dodaje metody getName() i getValue(). Jedyna dość niezwykła własność jesttaka, że klasa zdarzeń nosi nazwę HttpSessionBindingEvent, a nieHttpSessionAttributeEvent. Dzieje się tak z powodu tradycji — API posiadał już klasęHttpSessionBindingEvent, tak więc została ona wykorzystana ponownie. Ten nieco mylący aspekt APImoż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, wnastępujący sposób:

<listener> <listener-class> com.firma.MojZarzadcaPolaczen </listener-class></listener>

Serwer tworzy egzemplarz klasy nasłuchującej służącej do otrzymywania zdarzeń i wykorzystuje introspekcje wcelu określenia, jaki interfejs (lub interfejsy) nasłuchu powinien zostać wykorzystany przez klasę. Należypamiętać, że ponieważ mechanizm nasłuchujący jest konfigurowany w deskryptorze, można dodawać nowemechanizmy 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ścieserwletu oraz zamknięcie wszystkich połączeń po zamknięciu kontekstu.

Interfejs HttpSessionActivationListener, kolejny nowy interfejs nasłuchu w API 2.3 jestzaprojektowany do obsługi sesji wędrujących z jednego serwera do drugiego. Obiekt nasłuchujący będącyimplementacją HttpSessionActivationListener jest powiadamiany o gotowości każdej sesji dowę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

Page 413: Java Servlet - Programowanie

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ępnakiedy 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łaniamigracji i aktywacji najprawdopodobniej będą występować na dwóch różnych serwerach!

Wybranie kodowania znakówServlet API 2.3 dostarcza tak bardzo potrzebnej obsługi formularzy wysyłanych w różnych językach. Nowametoda, zadanie.setCharacterEncoding(String kodowanie) pozwala na poinformowanieserwera o kodowaniu znaków żądania. Kodowanie znaków (albo po prostu kodowanie) to sposób odwzorowaniabajtów na znaki. Serwer może wykorzystać określone kodowanie, aby prawidłowo zanalizować parametry i danePOST.

Domyślnie serwer analizuje parametry przy pomocy popularnego kodowania Latin-1 (ISO 8859-1) Niestety jestono 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ądarkanie stosuje się do tej zasady. Metoda setCharacterEncoding() pozwala serwletowi na poinformowanieserwera, 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 zakodowanegoprzy pomocy Shift_JIS powinien odczytywać parametry w następujący sposób:

// ustawienie kodowania na Shift_JISDzad.setCharacterEncoding("Shift_JIS");// Odczytanie parametru przy pomocy tego kodowaniaString nazwa = zad.getParameter("nazwa");

Proszę pamiętać o ustawieniu kodowania przed wywołaniem getParameter() lub getReader().Wywołanie setCharacterEncoding() może zgłosić wyjątekjava.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ęść klasycom.oreilly.servlet.ParameterParser.

Zależności plików JARPlik WAR (plik Web Application Archive — Archiwum Aplikacji WWW, dodane w Servlet API 2.2) częstowymaga istnienia i prawidłowego działania na serwerze innych bibliotek JAR. Na przykład, aplikacja WWWwykorzystująca klasę ParameterParser wymaga w ścieżce klas serwera pliku cos.jar. Aplikacja WWWwykorzystująca WebMacro wymaga webmacro.jar.

Przed powstaniem API 2.3, zależności te musiały być udokumentowane (ale czy ktoś tak naprawdę czytadokumentację!), lub każda aplikacja WWW musiała zawierać wszystkie wymagane pliki JAR w swoim własnymkatalogu WEB-INF/lib. Servlet API 2.3 pozwala na zadeklarowanie zależności JAR w WAR przy pomocypozycji META-INF/MANIFEST.MF archiwum WAR. Jest to standardowy sposób deklarowania zależnościplików JAR, ale od API 2.3 pliki WAR muszą oficjalnie obsługiwać ten mechanizm. Jeżeli zależność nie możezostać 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źć przypomocy algorytmu wyszukiwania.

Page 414: Java Servlet - Programowanie

Mechanizmy ładowania klasNastąpiła tu mała zmiana, posiadająca jednak ogromne znaczenie — w API 2.3, kontener serwletów (czyliserwer) musi blokować możliwość dostrzeżenia klas implementacji serwera przez klasy aplikacji WWW. Innymisł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 klasamiserwera. Stały się one poważnym problemem z powodu konfliktów analizatora XML. Każdy serwer potrzebujeanalizatora XML w celu zinterpretowania plików web.xml, a obecnie wiele aplikacji WWW wykorzystujeanalizator XML do obsługi odczytu, manipulacji i zapisu danych XML. Jeżeli analizatory obsługiwały różnewersje DOM lub SAX, powodowało to nienaprawialny konflikt. Oddzielenie zakresów klas elegancko tenproblem rozwiązuje.

Nowe atrybuty błędówPoprzednia 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 wniniejszej książce opisano, że zasady <error-page> pozwalają na skonfigurowanie aplikacji WWW tak, abykonkretne kody stanu lub typy wyjątków powodowały wyświetlenie konkretnych stron:

<web-app> <!-- ..... --> <error-page> <error-code> 404 </error-code> <location> /404.html </location> </error-page> <error-page> <exception-type> javax.servlet.ServletException </exception-type> <location> /servlet/WyswietlBlad </location> </error-page> <!-- ..... --></web-app>

Serwlet umieszczony w <location> 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 takiistnieje.

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, przekazywanydo konstruktora wyjątku.

Przy pomocy powyższych atrybutów serwlet mógł wygenerować stronę błędu dostosowaną do błędu, jakprzedstawiono 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();

Page 415: Java Servlet - Programowanie

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("<HTML>"); wyj.println("<HEAD><TITLE>" + powod + ": " + wiadomosc + "</TITLE></HEAD>"); wyj.println("<BODY>"); wyj.println("<H1>" + powod + "</H1>"); wyj.println("<H2>" + wiadomosc + "</H2>"); wyj.println("<PRE>"); } wyj.println("</PRE>"); wyj.println("<HR>"); wyj.println("<I>Błąd przy dostępie do " + zad.getRequestURI() + "</I>"); wyj.println("</BODY></HTML>"); }}

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. Serwletprzedstawiony poniżej został ponownie napisany tak, abywykorzystywał nowe atrybuty. Proszę zauważyć, że eleganckoprzerywa działanie, jeżeli one nie istnieją. Dzieje się tak wcelu 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");

Page 416: Java Servlet - Programowanie

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("<HTML>"); wyj.println("<HEAD><TITLE>" + powod + ": " + wiadomosc + "</TITLE></HEAD>"); wyj.println("<BODY>"); wyj.println("<H1>" + powod + "</H1>"); wyj.println("<H2>" + wiadomosc + "</H2>"); wyj.println("<PRE>"); if (throwable != null) { throwable.printStackTrace(wyj); } wyj.println("</PRE>"); wyj.println("<HR>"); wyj.println("<I>Błąd przy dostępie do " + uri + "</I>"); wyj.println("</BODY></HTML>"); }}

Nowe atrybuty bezpieczeństwaServlet API 2.3 dodaje również dwa nowe atrybuty żądania, które mogą pomóc serwletowi w podejmowaniudobrze umotywowanych decyzji dotyczących obsługi bezpiecznych połączeń HTTPS. Do żądań wykonanychprzy pomocy HTTPS serwer dołączy następujące nowe atrybuty żądania:javax.servlet.request.cipher_suite

Łańcuch String reprezentujący typ szyfrowania stosowany przezHTTPS, 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, żemożna z niego skorzystać. Aplikacja może odrzucić połączenia o niewielkiej liczbie bitów, lub algorytmyniegodne zaufania. Na przykład, serwlet mógłby wykorzystać poniższą metodę w celu upewnienia się, że jegopołą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ą onezmienione w wersji ostatecznej, jak przedstawiono powyżej, w celu uzyskania większej spójności z nazwamiistniejących atrybutów.

Page 417: Java Servlet - Programowanie

Niewielkie poprawkiW 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, abyzwracać 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() wcelu 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órróż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 zobiektu żą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. Nowemetody 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ługiparametró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 nazwykontekstu 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ówString. Każdy String posiada wiodący ukośnik (/) i powinien być uważany za względny do katalogumacierzystego kontekstu.

Pojawiła się także nowa metoda obiektu odpowiedzi zwiększająca kontrolę programisty nad buforemodpowiedzi. API 2.2 wprowadził metodę odp.reset() pozwalającą na usunięcie odpowiedzi i wyczyszczeniejej głównej części, nagłówków i kodu stanu. API 2.3 dodaje odp.resetBuffer(), który czyści jedyniegłó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 dziejesię podczas wywołania odp.sendRedirect("indeks.html") w przypadku serwletu nie działającego wkontekś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ę wkontekście o ścieżce "/sciezkakontekstu", czy URI przekierowania powinien być określony względemkatalogu macierzystego kontenera (http://serwer:port/indeks.html), czy katalogu macierzystegokontekstu (http://serwer:port/sciezkakontekstu/indeks.html)? W celu zapewnieniamaksymalnej przenośności zdefiniowanie zachowania jest konieczne. Po długiej debacie eksperci wybralitłumaczenie ścieżek względem katalogu macierzystego serwera. Osoby pragnące tłumaczenia względemkontekstu powinny dodać na początku URI wynik wywołania getContextPath().

Page 418: Java Servlet - Programowanie

Wyjaśnienia deskryptora DTDServlet API 2.3 rozwiewa kilka niejasności związanych z zachowaniem deskryptora web.xml. Obowiązkowestało się przycięcie wartości tekstowych w pliku web.xml przed jego zastosowaniem. (W standardowym niesprawdzanym 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</servlet-name>i

<servlet-name> witaj</servlet-name>

Servlet API 2.3 zezwala również na wykorzystanie zasady <auth-constraint>, tak więc specjalna wartość„*” może zostać wykorzystana jako wieloznacznik <role-name> zezwalający na wszystkie role. Pozwala tona utworzenie zasady podobnej do poniższej, która pozwala na wejście wszystkim użytkownikom, jeżeli zostalioni poprawnie rozpoznani jako posiadający dowolną rolę w aplikacji WWW:

<auth-constraint> <role-name>*</role-name> <!—dopuść wszystkie rozpoznane role --></auth-constraint>

Ostatnią zmianą jest dopuszczenie nazwy roli zadeklarowanej przez zasadę <security-role> jakoparametru metody isUserInRole(). Na przykład, proszę spojrzeć na następujący fragment pozycji web.xml:

<servlet> <servlet-name> sekret </servlet-name> <servlet-class> PrzegladPensji </servlet-class> <security-role-ref> <role-name> men <!-- nazwa wykorzystywana przez serwlet --> </role-name> <role-link> menedzer <!-- nazwa wykorzystywana w deskryptorze--> </role-link> </security-role-ref></servlet><!-- ... --><security-role> <role-name> menedzer </role-name></security-role>

Przy pomocy powyższego kodu możliwe jest wywołanie zarówno isUserInRole("men") jak iisUserInRole("menedzer"); oba wywołania spowodują to samo zachowanie. Najprościej rzecz ujmując,security-role-ref tworzy alias, chociaż nie jest to konieczne. Jest to zachowanie, którego można sięintuicyjnie spodziewać, ale specyfikacja API 2.2 mogła być interpretowana jako narzucająca wykorzystaniejedynie tych ról, które zostały zdefiniowane w zasadzie aliasów <security-role-ref>. (Osoby, które niezrozumiały powyższego wywodu, nie powinny się tym przejmować; należy po prostu pamiętać, że teraz funkcjepowinny działać tak, jak się tego po nich spodziewa.)

KonkluzjaServlet API 2.3 zawiera ekscytujący nowy mechanizm filtrujący, rozwinięty model okresu trwałości oraz nowąfunkcjonalność wspierającą internacjonalizację, obsługę błędów, połączenia bezpieczne i role użytkowników.Również dokument specyfikacji został zacieśniony w celu usunięcia niejasności, które mogłyby przeszkadzać wtworzeniu programów niezależnych od platformy.

Page 419: Java Servlet - Programowanie

Dodatek A. Krótki opis Servlet API

Pakiet javax.servlet jest jądrem Servlet API. Zawiera on podstawowy interfejs Servlet, który musi byćimplementowany w tej czy innej formie przez wszystkie serwlety oraz abstrakcyjną klasę GenericServletsłużącą do tworzenia prostych serwletów. Zawiera on również klasy służące do komunikowania się z serwerem iklientem komputera (ServletRequest i ServletResponse) oraz komunikowania z klientem(ServletInputStream i ServletOutputStream). Hierarchia klas pakietu javax.servlet jestprzedstawiona na rysunku A.1. Serwlety powinny graniczyć z klasami tego pakietu w sytuacjach, w którychnieznany jest leżący poniżej protokół.

Rysunek A.1.

Pakiet javax.servlet

GenericServlet

ZestawienieNazwa Klasy javax.servlet.GenericServletSuperklasa java.lang.ObjectBezpośrednie podklasy javax.servlet.http.HttpServet

Page 420: Java Servlet - Programowanie

Implementowane interfejsy javax.servlet.Servletjavax.servlet.ServletConfigjava.io.Serializable

Dostępność Servlet API 1.0 i późniejsze

OpisGenericServlet dostarcza podstawowej implementacji interfejsu Servlet dla serwletów niezależnych odprotokołu. Jako ułatwienie implementuje ona również interfejs ServletConfig. Większość programistówserwletów tworzy swoje klasy jako podklasy tej klasy lub HttpServlet, zamiast bezpośrednioimplementować interfejs Servlet.

GenericServlet zawiera podstawowe wersje metod init() i destroy(), które wykonują podstawowezadania tworzenia i czyszczenia, takie jak zarządzanie obiektem serwera ServletConfig. Serwlet omijajedną z tych metod powinien wywoływać wersje tych metod pochodzące z superklasy. GenericServletzawiera również metody log() umożliwiające dostęp do funkcji zapisujących dane w dzienniku pochodzącychz ServletContext.

Metoda service() jest deklarowana abstrakcyjnie musi zostać ominięta. Dobrze napisane serwlety omijająrównież getServletInfo().

Podsumowanie klasypublic abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { // Konstruktory public GenericServlet() // Metody egzemplarzy public void destroy(); public String getInitParameter(String name); public Enumeration getInitParameterNames(); public ServletConfig getServletConfig(); public ServletContext getServletContext(); public String getServletInfo(); public String getServletName(); // Nowość w2.2 public void init() throws ServletException; // Nowość w2.1 public void init(ServletConfig konfig) throws ServletException; public void log(String wiad); public void log(String wiad, Throwable t); // Nowość w2.1 public abstract void service(ServletRequest zad, ServletResponse odp) throws ServletException, IOException;}

Konstruktory

GenericServlet()public GenericServlet()

Domyślny konstruktor GenericServlet() nie wykonuje żadnej pracy. Wszystkie zadania związane zinicjalizacją serwletu powinny być wykonywane w init(), a nie w konstruktorze.

Metody egzemplarzy

destroy()public void destroy()

Wywoływane przez kontener serwletów, aby wskazać serwletowi, że został on wyłączony. Metoda ta jestwywoływana jedynie po wyłączeniu wszystkich wątków wewnątrz metody usługowej serwletu lub po określonymczasie. Po wywołaniu tej metody przez kontener serwletów, nie będzie wywoływał on już metody usługowejserwletu. Domyślna implementacja zapisuje zniszczenie serwletu w dzienniku przy pomocy metody log().Serwlet może ominąć tę metodę w celu zachowania jej stanu, uwolnienia jej zasobów (połączenia z bazamidanych, wątki, uchwyty plików itd.) itp.

Page 421: Java Servlet - Programowanie

getInitParameter()public String getInitParameter(String nazwa)

Zwraca wartość danego parametru inicjacji serwletu lub null, jeżeli nie odnajdzie pasujących parametrów.Pochodzi z interfejsu ServletConfig.

getInitParameterNames()public Enumeration getInitParameterNames()

Zwraca nazwy wszystkich parametrów inicjacji serwletu jako Enumeration obiektów String lub pustąEnumeration, jeżeli nie występują żadne parametry. Pochodzi z interfejsu ServletConfig.

getServletConfig()public ServletConfig getServletConfig()

Zwraca obiekt ServletConfig serwletu. W praktyce, metoda ta jest rzadko wywoływana przezGenericServlet ponieważ wszystkie metody ServletConfig są wewnętrznie duplikowane.

getServletContext()public ServletContext getServletContext ()

Zwraca obiekt ServletContext serwletu. Pochodzi z interfejsu ServletConfig.

getServletInfo()public String getServletInfo()

Zwraca zdefiniowany przez programistę łańcuch String opisujący serwlet. Serwlet powinien omijać tę metodę idostarczać własnego łańcucha identyfikacyjnego (na przykład „Serwlet Wiadomości Janka v.1.21”), ale nie jestto wymagane.

getServletName()public String getServletName()

Zwraca nawę aktualnego egzemplarza serwletu. Nazwa może być dostarczona prze administrację serwera lubprzypisana w deskryptorze aplikacji WWW, lub, w przypadku niezarejestrowanego (i w związku z tymnienazwanego) egzemplarza serwletu, będzie równa nazwie klasy serwletu. Pochodzi z interfejsuServletConfig. Metoda ta została wprowadzona w Servlet API 2.2.

init()public void init() throws ServletExceptionpublic void init(ServletConfig konfig) throws ServletException

Wywoływana przez kontener serwletów po pierwszym pobraniu serwletu i przed wywołaniem jego metodyservice(). Serwlet może omijać tę metodę w celu przeprowadzenia jednorazowej konfiguracji, stworzeniazasobów i tak dalej. Serwlety utworzone według Servlet API 2.1 lub późniejszego mogą implementować wersjębez argumentów. Serwlety, które muszą być kompatybilne wstecz z Servlet API 2.0 powinny implementowaćwersję pobierającą parametr ServletConfig. Nie należy implementować obu wersji. Domyślnaimplementacja init() zapisuje w dzienniku inicjalizację serwletu oraz przechowuje obiekt ServletConfigw celu wykorzystania go przez metody interfejsu ServletConfig. Serwlet implementujący wersjępobierającą ServletConfig musi wywołać super.init(konfig) przed wykonaniem własnego koduinicjacji. Serwlety korzystające z nowej bezargumentowej wersji nie muszą się tym przejmować.

log()public void log(String wiad)public void log(String wiad, Throwable t)

Zapisuje daną wiadomość w dzienniku serwletu po nazwie serwletu wywołującego. Miejsce zapisu jest zależneod serwera, zazwyczaj jest to dziennik zdarzeń.

service()public abstract void service(ServletRequest zad, ServletResponse odp) throws ServletException, IOException;

Wywoływana w celu obsłużenia pojedynczego żądania klienta. Serwlet otrzymuje informacje żądania poprzezobiekt ServletRequest i odsyła dane poprzez obiekt ServletResponse. Jest to jedyna metoda, któramusi zostać ominięta podczas rozszerzania GenericServlet.

Page 422: Java Servlet - Programowanie

RequestDipatcher

ZestawienieNazwa interfejsu javax.servlet.RequestDispatcherSuperinterfejs BrakBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 2.1 i późniejsze

OpisInterfejs obiektu, który może wewnętrznie rozsyłać żądania do dowolnych zasobów (takich jak serwlety, plikiHTML lub pliki JSP) na serwerze, Kontener serwletów tworzy obiekt RequestDispatcher, który następniewykorzystywany jest jako obwódka wokół zasobu serwera umieszczonego w konkretnej ścieżce lubposiadającego konkretną nazwę. Interfejs ten został zaprojektowany w celu wykorzystania z serwletami i plikamiJSP, ale kontener serwletów może utworzyć obiekty RequestDispatcher służące jako obwódki dowolnegotypu zasobu. Rozsyłanie może zostać wykorzystane do przekazania żądania do zasobu lub do dołączeniazawartości tego zasobu do aktualnej odpowiedzi. Klasa ta została wprowadzona w Servlet API 2.1.

Deklaracja interfejsupublic interface RequestDispatcher { //Metody public abstract void forward(ServletRequest zad, ServletResponse odp) throws ServletException, java.io.IOException // Nowość w2.1. public abstract void include(ServletRequest zad, ServletResponse odp) throws ServletException, java.io.IOException // Nowość w2.1.}

Metody

forward()public abstract void forward(ServletRequest zad, ServletResponse odp) throws ServletException, java.io.IOException

Przekazuje żądanie z serwletu do innego zasobu na serwerze. Metoda ta pozwala jednemu serwletowi nawykonanie wstępnego przetwarzania żądania, po czym innemu na wygenerowanie odpowiedzi. W przypadkuRequestDispatcher otrzymanego przez getRequestDispatcher(), obiekt ServletRequestposiada elementy ego ścieżki i parametry dołączone w celu dopasowania ścieżki zasobu docelowego. Metoda tapowinna zostać wywołana przed wysłaniem odpowiedzi do klienta. Jeżeli odpowiedź została już wysłana,metoda wywołuje wyjątek IllegalStateException. Niewysłana zawartość w buforze odpowiedzi zostajeautomatycznie wyczyszczona przed przekazaniem. Parametry żądania i odpowiedzi muszą być tymi samymiobiektami, które zostały przekazane metodzie usługowej serwletu wywołującego. Metoda ta zostaławprowadzona w Servlet API 2.1.

include()public abstract void include(ServletRequest zad, ServletResponse odp) throws ServletException, java.io.IOException

Dołącza zawartość zasobu do aktualnej odpowiedzi. Elementy ścieżki i parametry ServletRequestpozostają niezmienione w porównaniu z zasobem wywołującym; jeżeli dołączane zasoby wymagają dostępu doswoich własnych elementów i parametrów, może odczytać je przy pomocy przypisanych do serwera atrybutówżądania javax.servlet.include.request_uri, javax.servlet.include.context_path,javax.servlet.include.path_info i javax.servlet.include.query_string. Dołączonyserwlet nie może zmieniać kodu stanu odpowiedzi ani ustawiać nagłówków; każda próba zmiany jestignorowana. Parametry żądania i odpowiedzi muszą być tymi samymi obiektami, które zostały przekazanemetodzie usługowej serwletu wywołującego. Metoda został wprowadzona w Servlet API 2.1.

Page 423: Java Servlet - Programowanie

Servlet

ZestawienieNazwa interfejsu javax.servlet.ServletSuperinterfejs BrakBezpośrednie podinterfejsy BrakImplementowany przez javax.servlet.GenericServletDostępność Servlet API 1.0 i późniejsze

OpisWszystkie serwlety implementują interfejs Servlet, czy to bezpośrednio, czy poprzez podklasę GenericServletlub HttpServlet. Większość programistów serwletów uważa za łatwiejsze uczynienie serwletu podklasą jednej zdwóch istniejących klas serwletów niż bezpośrednią implementację tego interfejsu. Interfejs deklarujepodstawową funkcjonalność serwletów — inicjowanie serwletu, obsługę żądania klienta i niszczenie serwletu.

Deklaracja interfejsupublic interface Servlet { // Metody public abstract void destroy(); public abstract ServletConfig getServletConfig(); public abstract String getServletInfo(); public abstract void init(ServletConfig konfig) throws ServletException; public abstract void service(ServletRequest zad, ServletResponse odp) throws ServletException, IOException;}

Metody

destroy()public abstract void destroy()

Wywoływana przez kontener serwletów w celu wskazania serwletowi, że został wyłączony. Proszę zobaczyćpełny opis w GenericServlet.

getServletConfig()public abstract ServletConfig getServletConfig()

Zwraca obiekt ServletConfig zapamiętany przez metodę init().

getServletInfo()public abstract String getServletInfo()

Zwraca zdefiniowany przez programistę łańcuch String opisujący serwlet.

init()public abstract void init(ServletConfig konfig) throws ServletException

Wywoływana przez kontener serwletów w po pierwszym załadowaniu serwletu i przed wywołaniem metodyservice() serwletu. Proszę zobaczyć pełny opis w GenericServlet.

service()public abstract void service(ServletRequest zad, ServletResponse odp) throws ServletException, IOException

Wywoływana w celu obsłużenia pojedynczego żądania klienta. Serwlet otrzymuje informację o żądaniu przezobiekt ServletRequest i wysyła dane z powrotem do klienta przez obiekt ServletResponse.

ServletConfig

ZestawienieNazwa interfejsu javax.servlet.ServletConfigSuperinterfejs Brak

Page 424: Java Servlet - Programowanie

Bezpośrednie podinterfejsy BrakImplementowany przez javax.servlet.GenericServletDostępność Servlet API 1.0 i późniejsze

OpisKontenery serwletów wykorzystują obiekty ServletConfig do przekazywania serwletom informacji oinicjacji i kontekście. Informacje inicjacji ogólnie składają się z serii parametrów inicjacji i obiektuServletContext dostarczającego informacji na temat środowiska serwletu. Serwlet może zaimplementowaćServletConfig w celu ułatwienia dostępu do parametrów inicjacji i informacji o kontekście, jak to robiGenericServlet.

Deklaracja interfejsupublic interface ServletConfig { // Metody public abstract String getInitParameter(String nazwa); public abstract Enumeration getInitParameterNames(); public abstract ServletContext getServletContext(); public abstract String getServletName(); // Nowość w2.2

Metody

getInitParameter()public abstract String getInitParameter(String nazwa)

Zwraca wartość danego parametru inicjacji serwletu lub null, jeżeli nie odnajdzie pasującego parametru.

getInitParameterNames()public abstract Enumeration getInitParameterNames()

Zwraca nazwy wszystkich parametrów inicjacji serwletu jako Enumeration obiektów String, lub pustąEnumeration, jeżeli nie występują żądne parametry.

getServletContext()public abstract ServletContext getServletContext()

Zwraca obiekt ServletContext wywołującego ją serwletu, umożliwiając interakcję z kontenerem serwletów.

getServletName()public abstract String getServletName()

Zwraca nawę aktualnego egzemplarza serwletu. Nazwa może być dostarczona prze administrację serwera lubprzypisana w deskryptorze aplikacji WWW, lub, w przypadku niezarejestrowanego (i w związku z tymnienazwanego) egzemplarza serwletu, będzie równa nazwie klasy serwletu. Pochodzi z interfejsuServletConfig. Metoda ta została wprowadzona w Servlet API 2.2.

ServletContext

ZestawienieNazwa interfejsu javax.servlet.ServletContextSuperinterfejs BrakBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 1.0 i późniejsze

OpisInterfejs ServletContext definiuje zbiór metod wykorzystywanych do komunikacji z kontenerem serwletóww sposób niezależny od żądania. Oznacza to odnajdywanie informacji o ścieżce, uzyskiwanie dostępu do innychserwletów działających na serwerze i zapis w pliku dziennika serwera. Każda aplikacja WWW posiada innykontekst serwletów.

Page 425: Java Servlet - Programowanie

Deklaracja interfejsupublic interface ServletContext { // Metody public abstract Object getAttribute(String nazwa); public abstract Enumeration getAttributeNames(); // Nowość w2.1 public abstract ServletContext getContext(String sciezkauri); // Nowość w2.1 public abstract String getInitParameter(String nazwa); // Nowość w2.2 public abstract Enumeration getInitParameterNames(); // Nowość w2.2 public abstract int getMajorVersion(); // Nowość w2.1 public abstract String getMimeType(string plik); public abstract int getMinorVersion(); // Nowość w2.1 public abstract RequestDispatcher getNamedDispatcher(String nazwa); // Nowość w2.2 public abstract String getRealPath(String sciezka); public abstract URL getResource(String sciezka) // Nowość w 2.1 throws MalformedURLException; public abstract InputStream getResourceAsStream(String sciezka); // Nowość w2.1 public abstract String getServerInfo(); public abstract Servlet getServlet(String nazwa) // Opuszczona throws ServletException; public abstract Enumeration getServletNames(); // Opuszczona public abstract Enumeration getServlets(); // Opuszczona public abstract void log(Exception wyjatek, String wiad); // Opuszczona public abstract void log(String wiad); public abstract void log(string wiad, Throwable t); // Nowość w2.1 public abstract void removeAttribute(String nazwa); // Nowość w2.1 public abstract void setAttribute(String name, Object o); // Nowość w2.1}

Metody

getAttribute()public abstract Object getAttribute(String nazwa)

Zwraca wartość nazwanego atrybutu kontekstu jako Object lub null, jeżeli atrybut nie istnieje. Atrybutyzależne od serwera mogą zostać automatycznie ustawione przez kontener serwletów w celu dostarczeniaserwletom informacji o zakresie dużo szerszym niż dostarczany przez podstawowy Servlet API. Atrybuty mogąteż być ustawiane programowo przez serwlety jako sposób dzielenia informacji wewnątrz aplikacji WWWreprezentowanej przez dany kontekst. Nazwy atrybutów powinny być zgodne z tą samą konwencją, co nazwypakietów. Nazwy pakietów java.* i sun.* są zarezerwowane przez dział Java Software firmy SunMicrosystems (dawniej znany jako JavaSoft), a com.sun.* jest zarezerwowany dla użytku firmy SunMicrosystems. Proszę przejrzeć dokumentację serwera w celu obejrzenia listy wbudowanych atrybutów. Proszępamiętać, że serwlety oparte na atrybutach zależnych od serwera nie są przenośne.

getAttributeNames()public abstract Enumeration getAttributeNames()

Zwraca nazwy wszystkich atrybutów obecnego kontekstu jako Enumeration obiektów String. Zwraca pustąEnumeration, jeżeli kontekst nie posiada atrybutów. Metoda została wprowadzona w Servlet API 2.1.

getContext()public abstract ServletContext getContext(String sciezkauri)

Zwraca egzemplarz ServletContext przypisany do danej ścieżki URI. Dana ścieżka musi być bezwzględna(musi rozpoczynać się od /) i jest interpretowana w oparciu o katalog macierzysty serwera. Metoda ta zapewniaserwletowi dostęp do kontekstu innego niż własny, umożliwiając przeglądanie danych wewnątrz tego kontekstulub otrzymanie RequestDispatcher do zasobów wewnątrz tego kontekstu. W środowisku zabezpieczonymlub rozproszonym kontener serwletów może zwrócić null dla dowolnej ścieżki. Metoda została wprowadzonaw Servlet API 2.1.

getInitParameter()public abstract String getInitParameter(String nazwa)

Page 426: Java Servlet - Programowanie

Zwraca wartość danego parametru inicjacji lub null, jeżeli nie odnajdzie pasującego parametru. Metoda ta możeudostępnić przydatne informacje o konfiguracji całej aplikacji WWW. Na przykład, może udostępnić adrespoczty elektronicznej administratora systemu lub nazwę systemu przechowującego dane krytyczne. Parametryinicjacji kontekstu są przydzielane w deskryptorze aplikacji WWW. Metoda została wprowadzona w Servlet API2.2.

getInitParameterNames()public abstract Enumeration getInitParameterNames()

Zwraca nazwy wszystkich parametrów inicjacji danego kontekstu jako Enumeration obiektu String. Zwraca onapustą Enumeration, jeżeli kontekst nie posiada żadnych atrybutów. Parametry inicjacji kontekstu są przydzielanew deskryptorze aplikacji WWW. Metoda została wprowadzona w Servlet API 2.2.

getMajorVersion()public abstract int getMajorVersion()

Zwraca numer głównej wersji Servlet API obsługiwanego przez kontener serwletów. Na przykład, kontenerimplementujący wersję 2.1 zwróci 2. Metoda została wprowadzona w Servlet API 2.1.

getMimeType()public abstract String getMimeType(string plik)

Zwraca typ MIME danego pliku lub null, jeżeli jest on nieznany. Niektóre implementacje zwracajątext/plain jeżeli plik nie istnieje. Popularne typy MIME to text/html, text/plain, image/gif iimage/jpeg.

getMinorVersion()public abstract int getMinorVersion()

Zwraca numer pobocznej wersji Servlet API obsługiwanego przez kontener serwletów. Na przykład, kontenerimplementujący wersję 2.1 zwróci 1. Metoda została wprowadzona w Servlet API 2.1.

getNamedDispatcher()public abstract RequestDispatcher getNamedDispatcher(String nazwa)

Zwraca RequestDispatcher, który odsyła działanie do zasobu według nazwy, zamiast według ścieżki.Pozwala to na rozsyłanie do zasobów, które niekoniecznie są dostępne publicznie. Serwlety (oraz strony JSP)mogą otrzymać nazwy poprzez administrację serwera lub przez deskryptor aplikacji WWW. Metoda zwraca null,jeżeli, niezależnie od powodu, kontekst nie może zwrócić rozesłania. Metoda została wprowadzona w ServletAPI 2.2.

getRealPath()public abstract String get RealPath(String sciezka)

Zwraca prawdziwą ścieżkę systemu plików danej „ścieżki wirtualnej” lub null, jeżeli tłumaczenie nie mogłozostać przeprowadzone (na przykład kiedy plik znajduje się w zdalnym systemie plików lub jest dostępny jedyniewewnątrz archiwum .war). Jeżeli dana ścieżka wynosi /, metoda zwraca katalog macierzysty dokumentówkontekstu. Jeżeli dana ścieżka jest taka sama, jak zwrócona przez getPathInfo(), metoda zwraca tę samąrealną ścieżkę, jaką zwróciłaby przy wywołaniu getPathTranslated(). Nie istnieje odpowiednik CGI.

getResource()public abstract URL getResource(String sciezka)

Zwraca URL zasobu odwzorowanego w podanej ścieżce. Ścieżka musi rozpoczynać się od /, i jestinterpretowana jako względna do katalogu macierzystego kontekstu. Metoda może zwrócić null, jeżeliskojarzenie jakiegokolwiek URL-a ze ścieżką nie będzie możliwe. Metoda ta umożliwia kontenerowiudostępnienie zasobów serwletom z dowolnego źródła. Zasoby mogą zostać odnalezione lokalnie, w zdalnymsystemie plików, bazie danych lub pliku .war. Niektóre kontenery mogą również umożliwiać zapis w obiekcieURL przy pomocy metod klasy URL. Metoda ta powinna zostać zastosowana kiedy nie wszystkie zasoby to plikilokalne — tak jest w przypadku środowiska rozproszonego (w którym kontener serwletów może znajdować sięna innym komputerze niż zasoby) lub kiedy zawartość pochodzi z pliku .war (którego pliki nie są dostępnebezpośrednio). Zawartość zasobów zwracana jest w formie surowej, tak więc należy pamiętać, że żądanie strony.jsp zwróci kod źródłowy JSP w celu dołączenia zasobów uruchomieniowych JSP należy zamiennie zastosowaćRequestDispatcher. Metoda ta posiada inny cel niż Class.getResource(), która wyszukuje woparciu o mechanizm ładowania klas. Metoda ta nie wykorzystuje mechanizmów ładowania klas. Została onawprowadzona w Servlet API 2.1.

Page 427: Java Servlet - Programowanie

getResourceAsStream()public abstract InputStream getResourceAsStream(String sciezka)

Zwraca InputStream w celu odczytania zawartości zasobu odwzorowanego według konkretnej ścieżki.Ścieżka musi rozpoczynać się / i jest interpretowana według katalogu macierzystego kontekstu. Metoda możezwracać null, jeżeli ze ścieżką nie można było połączyć żadnego zasobu. Wykorzystanie tej metody jest częstołatwiejsze niż getResource(); jednak podczas wykorzystywania tej metody tracone są metainformacje natemat zasobów, takie jak wielkość i typ jej zawartości, dostępne przy pomocy getResource(). Metodazostała wprowadzona w Servlet API 2.1.

getServerInfo()public abstract String getServerInfo()

Zwraca nawę i numer wersji oprogramowania serwera, rozdzielone ukośnikiem (/). Wartość jest równa wartościzmiennej CGI SERVER_SOFTWARE.

getServlet()public abstract Servlet getServlet(String nazwa) throws ServletException;

Zwraca null w Servlet API 2.1 i późniejszych. Poprzednio zwracała pobrany serwlet pasujący do podanejnazwy i URL-u lub null, jeżeli serwlet nie mógł zostać odnaleziony. W API 2.1 metoda została opuszczona izdefiniowana tak, by zwracała null, ponieważ bezpośredni dostęp do innego egzemplarza serwletu otwiera zbytwiele możliwości dla błędu. Dzieje się tak dlatego, że serwlety mogą zostać zniszczone przez kontener wdowolnym czasie, tak więc żaden obiekt oprócz kontenera nie powinien posiadać bezpośredniego odwołania doserwletu. Poza tym, w przypadku serwera obsługującego dystrybucję ładunku, w którym serwlety rozkładane sąna kilku różnych serwerach, niemożliwe może okazać się zwrócenie lokalnego odwołania do serwletu. Zamiasttego serwlety powinny współpracować poprzez wykorzystanie współdzielonych atrybutów ServletContext.Technicznie rzecz biorąc, zdefiniowanie metody tak, by zawsze zwracała null, nie usuwa wstecznejkompatybilności, ponieważ serwer zawsze miał możliwość zwracania null w tej metodzie.

getServletNames()public abstract Enumeration getServletNames();

Zwraca pustą Enumeration w Servlet API 2.1 i późniejszych. Poprzednio zwracała Enumerationzawierającą nazwy obiektów serwletów załadowanych do danego kontekstu. Metoda ta została opuszczona izdefiniowana do zwracania pustej Enumeration w Servlet API 2.1 z tych samych powodów, dla którychgetServlet() została opuszczona i zdefiniowana tak, by zwracała. Metoda ta została wprowadzona wServlet API 2.0.

getServlets()public abstract Enumeration getServlets();

Zwraca pustą Enumeration w Servlet API 2.1 i późniejszych. Poprzednio zwracała Enumerationzawierającą obiekty Servlet załadowane do danego kontekstu. Metoda ta została opuszczona w Servlet API2.0 na rzecz getServletNames(). Została zdefiniowana do zwracania pustej Enumeration w Servlet API2.1 według zachowania metody getServletNames().

log()public abstract void log(String wiad);

Zapisuje daną wiadomość w dzienniku serwletu. Miejsce zapisu jest zależne od serwera, zazwyczaj jest to plikdziennika zdarzeń.

public abstract void log(string wiad, Throwable t);Zapisuje daną wiadomość i ścieżkę stosu obiektu Throwable w dzienniku serwletu. Miejsce zapisu jest zależneod serwera, zazwyczaj jest to plik dziennika zdarzeń. Metoda została wprowadzona w Servlet API 2.1.

public abstract void log(Exception wyjatek, String wiad);Zapisuje daną wiadomość i ścieżkę wyjątku Exception w dzienniku serwletu. Miejsce zapisu jest zależne odserwera. Proszę zauważyć niestandardowe umieszczenie parametru Exception na pozycji pierwszej, a nieostatniej. Metoda ta została opuszczona w Servlet API 2.1 na rzecz metody log(String wiad,Throwable t), w której zastosowano standardowy porządek elementów i która pozwala na zapisanie każdegoobiektu Trowable, nie tylko Exception. Metoda została wprowadzona w Servlet API 2.0.

removeAttribute()public abstract void removeAttribute(String nazwa);

Page 428: Java Servlet - Programowanie

Usuwa z kontekstu atrybut o danej nazwie. Atrybuty powinny zostać usunięte, kiedy nie są już dłużej potrzebne,w celu odblokowania miejsca zajmowanego przez nie w pamięci. Metoda została wprowadzona w Servlet API2.1.

setAttribute()public abstract void setAttribute(String nazwa, Object o);

Dowiązuje obiekt o danej nazwie do kontekstu serwletu. Każde istniejące dowiązanie o tej samej nazwie zostajezastąpione. Metoda została wprowadzona w Servlet API 2.1.

ServletException

ZestawienieNazwa klasy javax.servlet.ServletExceptionSuperklasa java.lang.exceptionBezpośrednie podklasy javax.servlet.UnavailableExceptionImplementowane interfejsy BrakDostępność Servlet API 1.0 i późniejsze

OpisOgólny wyjątek zgłaszany przez serwlety napotykające trudności.

Podsumowanie klasypublic class ServletException extends java.lang.Exception { // Konstruktory public ServletException(); // Nowość w2.0 public ServletException(String wiad); public ServletException(String wiad, Throwable podstPrzycz); // Nowość w2.1 public ServletException(Throwable podstPrzycz); // Nowość w2.1 public Throwable getRootCause(); // Nowość w2.1}

Konstruktory

public ServletException() public ServletException(); public ServletException(String wiad); public ServletException(String wiad, Throwable podstPrzycz); public ServletException(Throwable podstPrzycz);

Konstruuje nowy wyjątek ServletException, z opcjonalną opisową wiadomością i opcjonalną„podstawową przyczyną” wyjątku. Jeżeli wiadomość została określona, może być ona odczytana przy pomocywywołania getMessage(); jeżeli określona została podstawowa przyczyna, może ona być odczytana przypomocy wywołania getRootCause(). Wiadomości i podstawowe przyczyny są zazwyczaj dołączane dodzienników serwera i komunikatów o błędach dla użytkowników. Wersje konstruktora pobierające podstawowąprzyczynę zostały wprowadzone w Servlet API 2.1.

Metody egzemplarzy

getRootCause()public Throwable getRootCause();

Zwraca obiekt Throwable, który spowodował wyjątek serwletu lub null, jeżeli nie istnieje podstawowaprzyczyna. Metoda została wprowadzona w Servlet API 2.1.

Page 429: Java Servlet - Programowanie

ServletInputStream

ZestawienieNazwa klasy javax.servlet.ServletInputStreamSuperklasa java.io.InputStreamBezpośrednie podklasy BrakImplementowane interfejsy BrakDostępność Servlet API 1.0 i późniejsze

OpisDostarcza potoku wyjściowego służącego do odczytywania danych binarnych z żądania klienta oraz metodyreadLine() służącej do odczytywania danych linia po linii. ServletInputStream jest zwracany przezmetodę getInputStream() ServletRequest. W przypadku serwletów HTTPServletInputStream umożliwia dostęp do wysłanych danych POST.

Podsumowanie klasypublic abstract class ServletInputStream extends java.io.InputStream { // Konstruktory protected ServletInputStream(); // Metody egzemplarzy public int readLine(byte b[], int off, int dlug) throws IOException;}

Konstruktory

ServletInputStream()protected ServletInputStream();

Domyślny konstruktor nie wykonuje żadnych działań. Serwlet nie powinien nigdy konstruować swojegowłasnego ServletInputStream.

Metody egzemplarzy

readLine()public int readLine(byte b[], int off, int dlug) throws IOException;

Odczytuje bajty z potoku wejściowego do tablicy bajtów b, rozpoczynając od offsetu w tablicy podanego w off.Zatrzymuje odczytywanie po napotkaniu znaku \n lub po odczytaniu ilości dlug bajtów. Kończący znak \njest również odczytywany do bufora. Zwraca ilość odczytanych bajtów lub –1, jeżeli osiągnięto koniec potoku.Wersja tej klasy dołączona do Servlet API 2.0 zawierała błąd, który powodował ignorowanie parametru dlug,co dawało wynik w zgłaszaniu wyjątku ArrayIndexOutOfBoundsException podczas odczytywania liniidłuższej niż bufor. Błąd ten został naprawiony w Servlet API 2.1.

ServletOutputStream

ZestawienieNazwa klasy javax.servlet.ServletOutputStreamSuperklasa java.io.OutputStreamBezpośrednie podklasy BrakImplementowane interfejsy BrakDostępność Servlet API 1.0 i późniejsze

OpisDostarcza strumienia wyjściowego służącego do wysyłania danych binarnych z powrotem do klienta. Serwletuzyskuje obiekt ServletOutputStream poprzez metodę getOutpuStream() ServletResponse.Chociaż zawiera różnorodne metody println() służące do wysyłania tekstu lub HTML,ServletOutputStream został przewyższony przez PrintWriter. Powinien być wykorzystywany jedynie

Page 430: Java Servlet - Programowanie

do wysyłania danych binarnych. Jeżeli tworzy się podklasę ServletOutputStream, należy dostarczyćimplementacji metody write(int).

Podsumowanie klasypublic abstract class ServletOutputStream extends java.io.OutputStream { // Konstruktory protected ServletOutputStream(); // Metody egzemplarzy public void print(boolean b) throws IOException; public void print(char c) throws IOException; public void print(double d) throws IOException; public void print(float f) throws IOException; public void print(int i) throws IOException; public void print(long l) throws IOException; public void print(String s) throws IOException; public void println() throws IOException; public void println(boolean b) throws IOException; public void println(char c) throws IOException; public void println(double d) throws IOException; public void println(float f) throws IOException; public void println(int i) throws IOException; public void println(long l) throws IOException; public void println(String s) throws IOException;}

Konstruktory

ServletOutputStream()protected ServletOutputStream();

Domyślny konstruktor nie wykonuje żadnych działań.

Metody egzemplarzy

print()public void print(boolean b) throws IOException;public void print(char c) throws IOException;public void print(double d) throws IOException;public void print(float f) throws IOException;public void print(int i) throws IOException;public void print(long l) throws IOException;public void print(String s) throws IOException;

Wyświetla klientowi dane informacje, bez kończącego znaku powrotu karetki/końca linii (carriage return/linefeed —CRLF).

println()public void println() throws IOException;public void println(boolean b) throws IOException;public void println(char c) throws IOException;public void println(double d) throws IOException;public void println(float f) throws IOException;public void println(int i) throws IOException;public void println(long l) throws IOException;public void println(String s) throws IOException;

Wyświetla klientowi dane informacje, z kończącym CRLF. Metoda bez żadnych parametrów wyświetla po prostuCRLF.

ServletRequest

ZestawienieNazwa interfejsu javax.servlet.ServletRequestSuperinterfejs BrakBezpośrednie podinterfejsy javax.servlet.http.HttpServletRequestImplementowany przez BrakDostępność Servlet API 1.0 i późniejsze

Page 431: Java Servlet - Programowanie

OpisObiekt ServletRequest zawiera w sobie wszystkie informacje na temat żądania klienta, włączając w to parametryżądania, atrybut żądania, lokalizacje klienta oraz potok wejściowy służący do odczytywania danych binarnych zgłównej części żądania. Możliwe jest utworzenie podklasy ServletRequest w celu dostarczenia dodatkowychinformacji zależnych od protokołu. Na przykład HttpServletRequest zawiera metody służące do manipulowanianagłówkami HTTP.

Deklaracja interfejsupublic interface ServletRequest { // Metody public abstract Object getAttribute(String nazwa); public abstract Enumeration getAttributeNames(String nazwa); // Nowość w2.1 public abstract String getCharacterEncoding(); // Nowość w2.0 public abstract int getContentLength() public abstract String getContentType(); public abstract ServletInputStream getInputStream() throws IOException; public abstract Locale getLocale(); // Nowość w2.2 public abstract Enumeration getLocales(); // Nowość w2.2 public abstract String getParameter(String nazwa); public abstract Enumeration getParameterNames(); public abstract String[] getParameterValues(String nazwa); public abstract String getProtocol(); public abstract BufferedReader getReader() throws IOException // Nowość w2.0 public abstract String getRealPath(String sciezka); // Opuszczona public abstract String getRemoteAddr(); public abstract String getRemoteHost(); public abstract RequestDispatcher getRequestDispatcher(String sciezka) // Nowośćw 2.1 public abstract String getScheme(); public abstract String getServerName(); public abstract int getServerPort(); public abstract boolean isSecure(); // Nowość w2.2 public abstract void removeAttribute(String nazwa); // Nowość w2.2 public abstract void setAttribute(String nazwa, Object o); // Nowość w2.1}

Metody

getAttribute()public abstract Object getAttribute(String nazwa)

Zwraca wartość danego atrybutu żądania jako Object lub null, jeżeli atrybut nie istnieje. Atrybuty żądaniazależne od serwera mogą być ustawiane automatycznie przez kontener serwletów w celu dostarczenia serwletominformacji wykraczających poza zasięg tych dostarczanych przez podstawowy Servlet API. Atrybuty mogąrównież zostać automatycznie ustawione przez serwlety, co jest sposobem przekazywania informacji pomiędzyserwletami podczas wykorzystywania RequestDispatcher. Nazwy atrybutów powinny być zgodne z tąsamą konwencją, co nazwy pakietów. Nazwy pakietów java.* i sun.* są zarezerwowane przez dział JavaSoftware firmy Sun Microsystems (dawniej znany jako JavaSoft), a com.sun.* jest zarezerwowany dla użytkufirmy Sun Microsystems. Proszę przejrzeć dokumentację serwera w celu obejrzenia listy wbudowanychatrybutów. Proszę pamiętać, że serwlety oparte na atrybutach zależnych od serwera nie są przenośne.

getAttributeNames()public abstract Enumeration getAttributeNames()

Zwraca nazwy wszystkich atrybutów obecnego kontekstu jako Enumeration obiektów String. Zwraca pustąEnumeration, jeżeli kontekst nie posiada atrybutów. Metoda została wprowadzona w Servlet API 2.1.

getCharacterEncoding()public abstract String getCharacterEncoding()

Zwraca kodowanie potoku wejściowego serwletu lub null, jeżeli nie jest on znany. Metoda zostałwprowadzona w Servlet API 2.0.

Page 432: Java Servlet - Programowanie

getContentLength()public abstract int getContentLength()

Zwraca długość w bajtach zawartości przesyłanej przez potok wejściowy lub –1, jeżeli długość nie jest znana (naprzykład podczas braku danych). Równoważna ze zmienną CGI CONTENT_LENGTH.

getContentType()public abstract String getContentType()

Zwraca typ zawartości przesyłanej poprzez potok wejściowy lub null, jeżeli typ nie jest znany lub niewystępują żadne dane. Równoważna ze zmienną CGI CONTENT_TYPE.

getInputStream()public abstract ServletInputStream getInputStream() throws IOException, IllegalStateException

Pobiera potok wejściowy jako obiekt ServletInputStream. ServletInputStream jest bezpośredniąpodklasą InputStream, i obiekt może być traktowany w identyczny sposób jak zwykły obiektInputStream, z dodatkową możliwością efektywnego odczytywania wpisanych danych linia po linii dotablicy bajtów. Metoda ta powinna być wykorzystywana do odczytywania wpisów binarnych. Zgłasza onawyjątek IllegalStateException, jeżeli wcześniej w żądaniu wywołana została metoda getReader().IllegalStateException nie musi być otwarcie przechwytywany.

getLocale()public abstract Locale getLocale()

Zwraca lokalizację Locale preferowaną przez klienta, odczytaną z nagłówka żądania klientaAccept_Language. Jeżeli żądanie klienta nie posiada nagłówka Accept_Language, metoda zwracadomyślną lokalizację serwera. W celu wykonania dokładniejszego poszukiwania lokalizacji należy wykorzystaćklasę com.oreilly.servlet.LocaleNegotiator. Metoda została wprowadzona w Servlet API 2.2.

getLocales()public abstract Enumeration getLocales()

Zwraca Enumeration obiektów Locale wskazujących na lokalizacje akceptowane przez klienta, napodstawie nagłówka Accept-Language, od najbardziej do najmniej preferowanej. Jeżeli żądania klienta niezawiera nagłówka Accept_Language, metoda zwraca Enumeration zawierającą jeden Locale,domyślną lokalizację serwera. W celu wykonania dokładniejszego poszukiwania lokalizacji należy wykorzystaćklasę com.oreilly.servlet.LocaleNegotiator. Metoda została wprowadzona w Servlet API 2.2.

getParameter()public abstract String getParameter(String nazwa)

Zwraca wartość danego parametru jako łańcuch String. Zwraca null, jeżeli parametr nie istnieje, lub pustyłańcuch, jeżeli parametr istnieje, ale nie posiada żadnej wartości. Wartość jest zawsze zwracana w swojejzwykłej, zdekodowanej formie. Jeżeli parametr posiada kilka wartości, należy wykorzystać metodęgetParameterValues(), która zwraca tablicę wartości. Jeżeli metoda jest wywoływana na parametrzeposiadającym kilka wartości, zwracana wartość jest równa pierwszemu elementowi tablicy zwracanej przezgetParameterValues(). Jeżeli informacje na temat parametrów nadeszły w formie zakodowanych danychPOST, mogą być one niedostępne, jeżeli dane POST zostały wcześniej ręcznie odczytane przy pomocy metodygetReader() lub getInputStream(). Metoda ta była przez pewien czas opuszczona na rzeczgetParameterValues(), ale dzięki powszechnym protestom środowiska programistów, zostałaprzywrócona w Servlet API 2.0.

getParameterNames()public abstract Enumeration getParameterNames()

Zwraca nazwy wszystkich parametrów jako Enumeration obiektów String. Zwraca pustą Enumeration,jeżeli serwlet nie posiada żadnych parametrów.

getParameterValues()public abstract String[] getParameterValues(String nazwa)

Zwraca wszystkie wartości danego parametru jako tablicę obiektów String lub null, jeżeli dany parametr nieistnieje. Pojedyncza wartość jest zwracana jako tablica o długości 1.

Page 433: Java Servlet - Programowanie

getProtocol()public abstract String getProtocol()

Zwraca nazwę i wersję protokołu wykorzystywanego przez żądanie jako String o wzorzeprotokol/wersja-glowna.wersja-poboczna. Równoważna ze zmienną CGI SERVER_PROTOCOL.

getReader()public abstract BufferedReader getReader() throws IOException, IllegalStateExeption

Metoda ta odczytuje potok wejściowy jako obiekt BufferedReader, który powinien zostać wykorzystany doodczytania wprowadzonych danych opartych na znakach, ponieważ mechanizm odczytujący odpowiedniotłumaczy kodowania. Metoda zgłasza IllegalStateException, jeżeli na tym samym żądaniu poprzedniowywołano getInputStream(). Zgłasza UnsupportedEncodingException, jeżeli kodowaniewykorzystywane we wprowadzonych danych nie jest obsługiwane lub znane. Metoda została wprowadzona wServlet API 2.0.

getRealPath()public abstract String getRealPath(String sciezka)

Zwraca bezwzględną ścieżkę systemu plików każdej danej „ścieżki wirtualnej” lub null, jeżeli tłumaczenie niemogło zostać przeprowadzone. Jeżeli dana ścieżka wynosi /, zwraca macierzysty katalog dokumentów serwera.Jeżeli dana ścieżka jest taka sama, jak zwracana przez getPathInfo(), zwraca tę samą ścieżkę, jak byłabyzwrócona przez getPathTraslated(). Dla tej metody nie istnieje równoważnik CGI. Metoda zostałaopuszczona w Servlet API 2.1 na rzecz metody getRealPath() w ServletContext.

getRemoteAddr()public abstract String getRemoteAddr()

Zwraca adres IP komputera klienta jako String. Informacja ta jest odczytywana z portu łączącego serwer zklientem, tak więc adres zdalny może być adresem serwera proxy. Równoważna zmiennej CGI REMOTE_ADDR.

getRemoteHost()public abstract String getRemoteHost()

Zwraca nazwę komputera klienta. Informacja ta jest odczytywana z portu łączącego serwer z klientem, tak więcnazwa może być nazwą serwera proxy. Równoważna zmiennej CGI REMOTE_HOST.

getRequestDispatcher() public abstract RequestDispatcher getRequestDispatcher(String sciezka)

Zwraca RequestDispatcher posiadający możliwość przekierowywania żądania do danej ścieżki.RequestDispatcher może być wykorzystany do przekierowania żądania do zasobu lub do dołączeniazawartość tego zasobu do aktualnej odpowiedzi. Zasób może być dynamiczny lub statyczny. Określona ścieżkamoże być względna, chociaż nie może wykraczać poza aktualny kontekst serwletów. Jeżeli ścieżka rozpoczynasię od /, jest interpretowana jako względna do aktualnego macierzystego katalogu dokumentów. Metoda zwracanull, jeżeli kontener serwletów nie może zwrócić RequestDispatcher, niezależnie od powodu. Różnicapomiędzy tą metodą i getRequestDispatcher() w ServletContext jest taka, że ta metoda możepobierać ścieżkę względną. Metoda została wprowadzona w Servlet API 2.2.

getScheme()public abstract String getScheme()

Metoda zwraca schemat wykorzystany do wykonania tego żądania. Może być to na przykład http, https iftp, jak również jeden z nowszych właściwych Javie schematów takich jak jdbc i rmi.

GetServerName()public abstract String getServerName()

Zwraca nazwę serwera, który otrzymał żądanie. Jest to atrybut ServletRequest, ponieważ może sięzmieniać dla różnych żądań, co jest związane z różnymi typami zależności klienta od serwera. Podobna dozmiennej CGI SERVER_NAME.

GetServerPort()public abstract int getServerPort()

Zwraca numer portu, który otrzymał żądanie, Równoważna zmiennej CGI SERVER_PORT.

Page 434: Java Servlet - Programowanie

isSecure()public abstract boolean isSecure()

Zwraca odpowiedź, czy żądanie zostało wykonane przy pomocy bezpiecznego kanału, takiego jak HTTPS.Metoda została wprowadzona w Servlet API 2.2.

removeAtrribute()public abstract void removeAttribute(String nazwa)

Usuwa z żądania atrybut o danej nazwie. Atrybuty zależne od serwera generalnie nie mogą zostać usunięte.Metoda została wprowadzona w Servlet API 2.2.

setAtrribute()public abstract void setAttribute(String nazwa, Object o)

Dowiązuje do aktualnego żądania obiekt o danej nazwie. Wszystkie istniejące dowiązania o tej samej nazwiezostają usunięte. Atrybuty żądania są najczęściej ustawiane jako sposób przekazywania informacji pomiędzyserwletami wykorzystującymi RequestDispatcher. Nazwy atrybutów powinny być zgodne z tą samąkonwencją, co nazwy pakietów. Nazwy pakietów java.* i sun.* są zarezerwowane przez dział Java Softwarefirmy Sun Microsystems (dawniej znany jako JavaSoft), a com.sun.* jest zarezerwowany dla użytku firmySun Microsystems. Metoda została wprowadzona w Servlet API 2.2.

ServletResponse

ZestawienieNazwa interfejsu javax.servlet.ServletResponseSuperinterfejs BrakBezpośrednie podinterfejsy javax.servlet.http.HttpServletResponseImplementowany przez BrakDostępność Servlet API 1.0 i późniejsze

OpisSerwlety wykorzystują obiekty ServletResponse do wysyłania zakodowanych w MIME danych z powrotemdo klienta. Aby wysłać dane binarne, należy wykorzystać ServletOutputStream zwrócony przezgetOutputStream(). W celu wysłania danych zawierających znaki należy wykorzystać PrintWriterzwrócony przez getWriter(). Można otwarcie ustawić typ MIME wyświetlanych danych przy pomocymetody setContentType(), a lokalizację odpowiedzi przy pomocy setLocale(). Należy wykonać tewywołania przed wywołaniem getWriter(), ponieważ metoda getWriter() sprawdza typ zawartości ilokalizację w celu określenia kodowania, które należy wykorzystać. Więcej informacji na temat MIME jestdostępnych w dokumencie RFC 2045 pod adresem http://www.ietf.org/rfc/rfc2045.txt.

Deklaracja interfejsupublic interface ServletResponse { // Metody public abstract void flushBuffer() throws IOException; // Nowość w2.2 public abstract int getBufferSize(); // Nowość w2.2 public abstract String getCharacterEncoding(); // Nowość w2.0 public abstract Locale getLocale(); // Nowość w2.2 public abstract ServletOutputStream getOutputStream() throws IOException, IllegalStateException; public abstract PrintWriter getWriter() // Nowość w2.0 throws IOException, IllegalStateException; public abstract boolean isCommitted(); // Nowość w2.2 public abstract void reset() throws IllegalStateException; // Nowość w2.2 public abstract void setBufferSize(int wielkosc) // Nowość w2.2 throws IllegalStateException; public abstract void setContentLength(int dlug); public abstract void setContentType(String typ);

Page 435: Java Servlet - Programowanie

public abstract void setLocale(Locale lok); // Nowość w2.2}

Metody

FlushBuffer()public abstract void flushBuffer() throws IOException;

Wymusza wyświetlenie klientowi wszystkich danych znajdujących się w buforze. Wywołanie tej metodyautomatycznie zatwierdza odpowiedź, co oznacza, że kod stanu oraz nagłówki zostaną zapisane, a metodareset() nie będzie już dostępna. Metoda została wprowadzona w Servlet API 2.2.

getBufferSize()public abstract int getBufferSize();

Zwraca liczbę int informującą o wielkości aktualnego bufora lub 0 w rzadkim przypadku, kiedy buforowanienie jest wykorzystywane. Metoda została wprowadzona w Servlet API 2.2.

GetCharacterEncoding()public abstract String getCharacterEncoding()

Zwraca kodowanie wykorzystywane w aktualnej części głównej MIME. Jest to kodowanie określone przezprzydzielony typ zawartości, lub ISO-8859-1, jeżeli nie określono żadnego kodowania. Metoda zostaławprowadzona w Servlet API 2.0.

getLocale()public abstract Locale getLocale()

Zwraca lokalizację Locale aktualnie przypisaną do odpowiedzi. Metoda została wprowadzona w Servlet API2.2.

GetOutputStream()public abstract ServletOutputStream getOutputStream() throws IOException, IllegalStateException;

Zwraca ServletOutputStream służący do zapisywania binarnych (bit-po-bicie) danych odpowiedzi. Niejest wykonywane żadne kodowanie. Zgłasza IllegalStateException, jeżeli na aktualnej odpowiedziwywołano wcześniej getWriter().

getWriter()public abstract PrintWriter getWriter() throws IOException, IllegalStateException;

Zwraca PrintWriter służący do zapisywania opartych na znakach danych odpowiedzi. Mechanizmwyświetlający koduje znaki zależnie od jakiegokolwiek kodowania podanego w typie zawartości. Jeżeli w typiezawartości nie określono żadnego kodowania, jak to się zdarza najczęściej, mechanizm wykorzystuje kodowanieISO-8859-1 (Latin-1), odpowiednie dla języków zachodnioeuropejskich. ZgłaszaIllegalStateException, jeżeli na aktualnej odpowiedzi wywołano wcześniej getOutputStream(), aUnsupportedEncodingException, jeżeli kodowanie potoku wyjściowego nie jest obsługiwane lub znane.Metoda została wprowadzona w Servlet API 2.2.

isCommitted()public abstract boolean isCommitted()

Zwraca wartość boolean wskazującą, czy jakakolwiek część odpowiedzi została już wysłana. Jeżeli metodazwróci true, jest już za późno, by zmienić kody stanu i nagłówki. Metoda została wprowadzona w Servlet API2.2.

reset()public abstract void reset() throws IllegalStateException;

Czyści bufor odpowiedzi, a także aktualnie przypisany kod stanu i nagłówki odpowiedzi. Metoda ta musi zostaćwywołana przed zatwierdzeniem odpowiedzi, inaczej zgłasza IllegalStateException. reset() jestautomatycznie wywoływana przez metody sendError() i sendRedirect(). Metoda została wprowadzonaw Servlet API 2.2.

Page 436: Java Servlet - Programowanie

setBufferSize()public abstract void setBufferSize(int wielkosc) throws IllegalStateException;

Przekazuje serwerowi minimalną wielkość (w bajtach) bufora odpowiedzi, którą zaakceptuje serwlet. Serwermoże dostarczyć większego bufora niż ten, którego zażądano — na przykład w celu utrzymania buforów jakobloków o wielkości 8Kb w celu ułatwienia ich ponownego wykorzystania. Większy bufor pozwala na zapisaniedłuższej zawartości przez jej właściwym wysłaniem, a w związku z tym daje serwletowi więcej czasu naustawienie odpowiednich kodów stanu i nagłówków. Mniejszy bufor zmniejsza obciążenie pamięci serwera ipozwala na szybsze otrzymywanie danych przez klienta. Metoda ta musi zostać wywołana przed zapisaniemdowolnej zawartości w głównej części odpowiedzi; jeżeli zawartość została zapisana, metoda zgłasza wyjątekIllegalStateException. Metoda została wprowadzona w Servlet API 2.2.

setContentLength()public abstract void setContentLength(int dlug)

Ustawia długość zawartości zwracanej przez serwer. W przypadku serwletów HTTP, ustawia nagłówek HTTPContent-Length. Serwlety HTTP wykorzystuję tę metodę w celu umożliwienia trwałych połączeń iwspomożenia monitorów postępu klienta. Jej wykorzystanie jest opcjonalne. Jeżeli zawartość odpowiedzi mieścisię całkowicie w wielkości przydzielonego bufora odpowiedzi, serwer może automatycznie ustawić długośćzawartości.

setContentType()public abstract void setContentType(String typ)

Metoda ustawia typ zawartości odpowiedzi na typ określony. W przypadku serwletów HTTP, ustawia nagłówekHTTP Content-Type.

setLocale()public abstract void setLocale(Locale lok)

Ustawia lokalizację odpowiedzi. Serwer modyfikuje nagłówki Content-Language i Content-Typeodpowiednio do danej lokalizacji. Metoda powinna zostać wywołana po ustawieniu Content-Type, a przedwywołaniem getWriter(). Domyślnie lokalizacja odpowiedzi jest równa domyślnej lokalizacji serwera.Metoda została wprowadzona w Servlet API 2.2.

SingleThreadModel

ZestawienieNazwa interfejsu javax.servlet.SingleThreadModelSuperinterfejs BrakBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 2.0 i późniejsze

OpisSingleThreadModel jest interfejsem znaczników nie posiadającym metod. Jeżeli serwlet implementuje teninterfejs, kontener serwletów upewnia się, że każdy egzemplarz serwletu obsługuje jedynie jedno żądanie usługiw jednym czasie. Na przykład, kontener serwletów może zaimplementować tę funkcjonalność poprzezutrzymywanie puli egzemplarzy serwletów i przekierowywanie przychodzących żądań do wolnych serwletów wpuli. Wykorzystanie SingleThreadModel zabezpiecza sam serwlet przed wątków; jednak wykorzystanietego interfejsu nie przeciwdziała problemom z synchronizacją, które są powodowane przez serwlety uzyskującedostęp do współdzielonych zasobów takich jak statyczne zmienne klas lub zmienne zdalne dla serwletu. Interfejsten jest przydatny w bardzo niewielkiej ilości sytuacji.

Deklaracja interfejsupublic interface SingleThreadModel {}

Page 437: Java Servlet - Programowanie

UnavailableException

ZestawienieNazwa klasy javax.servlet.UnavailableExceptionSuperklasa java.io.ServletExceptionBezpośrednie podklasy BrakImplementowane interfejsy BrakDostępność Servlet API 1.0 i późniejsze

OpisSerwlet może zgłosić wyjątek UnavailableException w dowolnym czasie w celu wskazania, że nie jestdostępny w celu zaspokajania żądań klientów. Istnieją dwa typy niedostępności — stała (w której problem niemoże sam się rozwiązać i musi zostać podjęta akcja administracyjna) i tymczasowa (w której problemnajprawdopodobniej sam się rozwiąże po pewnym czasie). Aby oznaczyć serwlet jako czasowo niedostępnynależy podczas konstruowania wyjątku określić czas (w sekundach). Dobrze napisane kontenery serwletówwykorzystają określony czas w celu utworzenia lepszych komunikatów o błędach dla klienta. Implementacjeserwletów mogą traktować niedostępność tymczasową jako niedostępność stałą.

Podsumowanie klasypublic class UnavailableException extends ServletException { // Konstruktory public UnavailableException(int sekundy, Servlet serwlet, String wiad); //Opuszczona public UnavailableException(Servlet serwlet, String wiad); // Opuszczona public UnavailableException(String wiad); // Nowość w2.2 public UnavailableException(String wiad, int sekundy); // Nowość w2.2 // Metody egzemplarzy public Servlet getServlet(); // Opuszczona public int getUnavailableSeconds(); public boolean isPermanent();}

Konstruktory

UnavailableException()public UnavailableException(String wiad)public UnavailableException(String wiad, int sekundy)public UnavailableException(Servlet serwlet, String wiad)public UnavailableException(int sekundy, Servlet serwlet, String wiad)

Konstruuje wyjątek UnavailbleException z daną wyjaśniającą wiadomością. Okres niedostępności możezostać opcjonalnie określony (w sekundach). Dwa konstruktory z podpisami, które przyjmują parametr Servletzostały opuszczone w Servlet API 2.2 na rzecz prostszych i bezpieczniejszych wersji konstruktora nieprzyjmujących egzemplarza serwletu.

Metody egzemplarzy

getServlet()public Servlet getServlet()

Zwraca serwlet, który zgłosił aktualny wyjątek lub null, jeżeli egzemplarz serwletu nie został dostarczonykonstruktorowi. Metoda została opuszczona w Servlet API 2.2 ze względów bezpieczeństwa.

getUnavailableSeconds()public int getUnavailableSeconds()

Zwraca ilość sekund, przez które serwlet będzie niedostępny. Niedodatnia liczba sekund wskazuje naniedostępność stałą. Nie jest wykonywana żadna próba obliczenia czasu określonego od zgłoszenia wyjątku.

isPermanent()public boolean isPermanent()

Page 438: Java Servlet - Programowanie

Zwraca true, jeżeli serwlet jest niedostępny stale, a w przeciwnym wypadku false.

Page 439: Java Servlet - Programowanie

Dodatek B. Krótki opis HTTP

Servlet API

Pakiet javax.servlet.http umożliwia obsługę serwletów porozumiewających się przy pomocy protokołuHTTP. Klasy w tym pakiecie są utworzone na podstawie podstawowej funkcjonalności pakietujavax.servlet i umożliwiają serwletom dostęp do specyficznych dla HTTP własności takich jak kody stanu,nagłówki żądania i odpowiedzi, sesje i cookies. Rysunek B.1 przedstawia hierarchię klas pakietujavax.servlet.http.

Page 440: Java Servlet - Programowanie

Rysunek B.1.

Pakiet javax.servlet.http

Cookie

ZestawienieNazwa klasy javax.servlet.http.CookieSuperklasa java.lang.ObjectBezpośrednie podklasy BrakImplementowane interfejsy java.lang.CloneableDostępność Servlet API 2.0 i późniejsze

OpisKlasa Cookie dostarcza serwletom łatwego sposobu odczytywania, tworzenia i manipulowania cookies w styluHTTP, które umożliwiają serwletom przechowywanie niewielkich ilości danych na komputerze klienta. Cookiessą ogólnie wykorzystywane do śledzenia sesji lub przechowywania niewielkich ilości informacjikonfiguracyjnych specyficznych dla danego klienta. Większa ilość informacji znajduje się w rozdziale 7,„Śledzenie sesji”.

Serwlet wykorzystuje metodę getCookies() w celu odczytania cookies wysłanych jako część żądania klienta.Metoda addCookie() HttpServletResponse wysyła do przeglądarki nowe cookie. Ponieważ cookie są

Page 441: Java Servlet - Programowanie

ustawiane przy pomocy nagłówków HTTP, addCookie() musi zostać wywołana przed zatwierdzeniemodpowiedzi.

Metody getXXX() są rzadko wykorzystywane, ponieważ kiedy cookie zostaje wysłane do serwera, zawierajedynie jego nazwę, wartość i wersję, Jeżeli ustawi się atrybut w cookie otrzymanym od klienta, należy dodać godo odpowiedzi, aby zmiana wywarła efekt, a także należy zadbać o to, by wszystkie atrybuty poza nazwą,wartością i wersją zostały ponownie ustawione również w cookie.

Niniejsza klasa jest zgodna zarówno ze specyfikacją cookie Netscape, jak i dokumentem RFC 2109.

Podsumowanie klasypublic class Cookie implements java.lang.Cloneable { // Konstruktory public Cookie(String nazwa, String wartosc); // Metody egzemplarzy public Object clone(); public String getComment; public String getDomain(); public int getMaxAge(); public String getName(); public String getPath(); public boolean getSecure(); public String getValue(); public int getVersion(); public void setComment(String cel); public void setDomain(String wzor); public void setMaxAge(int wygasa); public void setPath(String uri); public void setSecure(boolean znacznik); public void setValue(String nowaWartosc); public void setVersion(int w);}

Konstruktory

Cookie()public Cookie(String nazwa, String wartosc)

Konstruuje nowe cookie o początkowej nazwie i wartości. Zasady nadawania prawidłowych nazw i wartości sąokreślone w specyfikacji cookie Netscape i dokumencie RFC 2109.

Metody egzemplarzy

clone()public Object clone()

Omija standardową metodę clone() w celu zwrócenia kopii aktualnego obiektu (duplikatu cookie).

getComment()public String getComment

Zwraca komentarz związany z cookie. Informacja ta jest dostępna jedynie zaraz po ustawieniu komentarza; kiedycookie jest zwracane przez klienta, informacja ta nie zostaje dołączona.

GetDomain()public String getDomain()

Zwraca ograniczenia domeny związane z aktualnym cookie. Informacja ta jest dostępna jedynie zaraz poustawieniu domeny; kiedy cookie jest zwracane przez klienta, informacja ta nie zostaje dołączona.

getMaxAge()public int getMaxAge()

Zwraca maksymalny wiek dozwolony dla aktualnego cookie. Informacja ta jest dostępna jedynie zaraz poustawieniu maksymalnego wieku; kiedy cookie jest zwracane przez klienta, informacja ta nie zostaje dołączona.

getName()public String getName()

Page 442: Java Servlet - Programowanie

Zwraca nazwę aktualnego cookie.

getPath()public String getPath()

Zwraca ograniczenia ścieżki związane z aktualnym cookie. Informacja ta jest dostępna jedynie zaraz poustawieniu ścieżki; kiedy cookie jest zwracane przez klienta, informacja ta nie zostaje dołączona.

getSecure()public boolean getSecure()

Zwraca true, jeżeli aktualne cookie wymaga bezpiecznego połączenia, w przeciwnym wypadku false.Informacja ta jest dostępna jedynie zaraz po ustawieniu znacznika; kiedy cookie jest zwracane przez klienta,informacja ta nie zostaje dołączona.

getValue()public String getValue()

Zwraca wartość aktualnego cookie w formie łańcucha.

getVersion()public int getVersion()

Zwraca wersję aktualnego cookie.

setComment()public void setComment(String cel)

Ustawia pole komentarza cookie. Komentarz opisuje zamierzone przeznaczenie cookie. Przeglądarka WWWmoże wyświetlić ten tekst klientowi. Komentarze nie są obsługiwane przez cookies w wersji 0.

setDomain()public void setDomain(String wzor)

Określa wzór ograniczenia domeny. Wzór domeny określa serwery, które powinny widzieć cookie. Domyślniecookies są zwracane jedynie do komputera, który je zapisał. Określenie wzoru nazwy domeny omija tę zasadę.Wzór musi rozpoczynać się kropką i zawierać co najmniej dwie kropki. Wzór pasuje jedynie do jednej pozycjipoza początkową kropką. Na przykład wzór .buu.com jest prawidłowy i pasuje do www.buu.com iwyslij.buu.com, ale nie do www.wyslij.buu.com. Szczegółowe informacje na temat wzorów domen sądostępne w specyfikacji cookie Netscape i dokumencie RFC 2109.

SetMaxAge()public void setMaxAge(int wygasa)

Określa maksymalny wiek cookie w sekundach do jego wygaśnięcia. Ujemna wartość wskazuje na działaniedomyślne, czyli na wygaśnięcie cookie z chwilą wyłączenia przeglądarki. Wartość zerowa nakazuje przeglądarcenatychmiastowe usunięcie cookie.

setPath()public void setPath(String uri)

Określa ścieżkę cookie, która jest podzbiorem URI, do których powinno zostać wysłane cookie. Domyślniecookie są wysyłane do strony, która je ustawiła oraz do wszystkich stron w jej katalogu lub podkatalogach. Naprzykład, jeżeli /servlet/CiasteczkowyPotwor ustawiła cookie, domyślną ścieżką jest /servlet. Ścieżka tawskazuje, że cookie powinno zostać wysłane do /servlet/Elmo i /servlet/podkat/WielkiPtak — ale nie do aliasuserwletu /Oskar.html ani do żadnego programu CGI w /cgi-bin. Ścieżka ustawiona na „/” powoduje wysłaniecookie do wszystkich stron na serwerze. Ścieżka cookie musi być tak skonstruowana, aby uwzględniała serwlet,który je ustawił.

SetSecure()public void setSecure(boolean znacznik)

Znacznik bezpieczeństwa wskazuje, czy cookie powinno zostać wysłane jedynie przez kanał bezpieczny, taki jakSSL. Domyślna wartość wynosi false.

SetValue()public void setValue(String nowaWartosc)

Page 443: Java Servlet - Programowanie

Przypisuje cookie nową wartość. W przypadku cookie w wersji 0, wartości nie mogą zawierać: pustych miejsc,klamr, cudzysłowów pojedynczych i podwójnych, nawiasów, znaków równości, przecinków, ukośników, znakówzapytania, dwukropków i średników. Puste wartości mogą zachowywać się w różny sposób w różnychprzeglądarkach.

SetVersion()public void setVersion(int w)

Serwlety mogą wysyłać i przyjmować cookies sformatowane tak, aby zgadzały się zarówno trwałymi cookiesNetscape (wersja 0), jak i nowszymi, w pewnym sensie eksperymentalnymi cookies zgodnymi z dokumentemRFC 2109 (wersja 1). Nowo skonstruowane cookies są domyślnie sformatowane zgodnie z wersją 0 w celuzmaksymalizowania zasięgu. Podczas wykorzystywania cookies w wersji 1 kontener serwletów może równieżwysyłać cookies w stylu wersji 0 o tej samej nazwie i wartości w celu zachowania wstecznej kompatybilności.

HttpServlet

ZestawienieNazwa klasy javax.servlet.http.HttpServletSuperklasa Javax.servlet.GenericServletBezpośrednie podklasy BrakImplementowane interfejsy java.servlet.Servlet

java.io.SerializableDostępność Servlet API 1.0 i późniejsze

OpisHttpServlet jest abstrakcyjną klasą służącą jako podstawowa klasa dla serwletów HTTP (WWW). Publicznametoda service() przekierowuje żądania do specyficznej dla HTTP, chronionej metody service(), któranastępnie przekazuje żądania do konkretnych funkcji obsługujących każdy typ wysyłania HTTP — doGet(),doPost() i tak dalej. Ponieważ domyślna implementacja serwletów HTTP obsługuje przekierowania do tychmetod, to jeżeli ominie się chronioną metodę service(), należy samodzielnie obsłużyć przekierowania, lubnie wykorzystywać funkcji obsługujących dla metod żądań HTTP.

Podsumowanie klasypublic abstract class HttpServlet extends javax.servlet.GenericServlet implements javax.servlet.Servlet, java.io.Serializable { // Konstruktory public HttpServlet(); // Publiczna metoda egzemplarzy public void service(ServletRequest zad, ServletResponse odp) throws ServletException, IOException; // Chronione metody egzemplarzy public void doDelete(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; // Nowość w2.0 public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; public void doOptions(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; // Nowość w2.0 public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; public void doPut(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; // Nowość w2.0 public void doTrace(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; // Nowość w2.0 public void getLastModified(HttpServletRequest zad); public void service(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException; // Nowość w2.0}

Page 444: Java Servlet - Programowanie

Konstruktory

HttpServlet()public HttpServlet()

Domyślny konstruktor nie wykonuje żadnych działań. Ponieważ nie można mieć pewności co do sposobu i czasuładowania klas, nie poleca się omijania tego konstruktora w celu wykonania czynności startowych. Zamiast tegonależy wykorzystać metodę init().

Publiczne metody egzemplarzy

service()public void service(ServletRequest zad, ServletResponse odp) throws ServletException, IOException

Ta metoda service() obsługuje przekierowywanie żądań do chronionej, specyficznej dla HTTP metodyservice(). Ogólnie rzecz biorąc nie powinna być omijana.

Chronione metody egzemplarzy

doDelete()public void doDelete(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP DELETE dotej metody. Serwlety implementują tę metodę w celu obsługi żądań DELETE. Domyślna implementacja zwracabłąd HTTP SC_BAD_REQUEST. Metoda została wprowadzona w Servlet API 2.0.

doGet()public void doGet(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP GET do tejmetody. Serwlety implementują tę metodę w celu obsługi żądań DELETE. Domyślna implementacja zwraca błądHTTP SC_BAD_REQUEST.

doOptions()public void doOptions(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP OPTIONSdo tej metody. Domyślna implementacja określa, które opcje są obsługiwane i zwraca odpowiedni nagłówek. Naprzykład, jeżeli serwlet omija doGet() i doPost(), przeglądarka otrzymuje informację, że obsługiwane sąGET, POST, HEAD, TRACE i OPTIONS. Niemal nigdy nie ma powodu, by omijać tę metodę. Metoda zostaławprowadzona w Servlet API 2.0.

doPost()public void doPost(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP POST do tejmetody. Serwlety implementują tę metodę w celu obsługi żądań POST. Domyślna implementacja zwraca błądHTTP SC_BAD_REQUEST.

doPut()public void doPut(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP PUT do tejmetody. Serwlety implementują tę metodę w celu obsługi żądań PUT. Domyślna implementacja zwraca błądHTTP SC_BAD_REQUEST. Większa ilość informacji na temat żądań HTTP PUT jest dostępna w dokumencieRFC 2068 pod adresem http://www.ietf.org/rfc/rfc2068.txt. Metoda została wprowadzona w Servlet API 2.0.

doTrace()public void doTrace(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Page 445: Java Servlet - Programowanie

Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP TRACE dotej metody. Serwlety implementują tę metodę w celu obsługi żądań TRACE. Niemal nigdy nie ma powodu, byomijać tę metodę. Metoda została wprowadzona w Servlet API 2.0.

getLastModified()public void getLastModified(HttpServletRequest zad)

Zwraca datę i godzinę (wyrażoną w milisekundach od północy 1 stycznia 1970 GMT) ostatniej modyfikacjizawartości tworzonej przez serwlet. Wartości ujemne oznaczają, że czas ten nie jest znany. Domyślnaimplementacja zwraca –1. Metoda jest wywoływana przez serwery w celu wspomożenia obsługi warunkowychżądań HTTP GET i zarządzania pamięcią podręczną odpowiedzi. Większa ilość informacji znajduje się wrozdziale 4, „Pobieranie informacji”.

service()public void service(HttpServletRequest zad, HttpServletResponse odp) throws ServletException, IOException

Publiczna metoda service() przekierowuje żądania do tej metody service(). Metod obsługujeprzekierowywanie żądań do doGet(), doPost() i innych funkcji obsługujących w oparciu o typ żądania.Jeżeli metoda zostanie ominięta, nie są wywoływane żadne funkcje obsługujące.

HttpServletRequest

ZestawienieNazwa interfejsu javax.servlet.http.HttpServletRequestSuperinterfejs javax.servlet.ServletRequestBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 1.0 i późniejsze

OpisHttpServletRequest rozszerza podstawową klasę ServerRequest dostarczając dodatkowejfunkcjonalności serwletom HTTP (WWW). Zawiera obsługę cookies i śledzenia sesji oraz dostęp do informacji znagłówków HTTP. HttpServletRequest analizuje również przychodzące dane formularzy HTTP iprzechowuje je jako parametry serwletów. Serwer przekazuje obiekt HttpServletRequest metodzieusługowej HttpServlet.

Deklaracja interfejsupublic interface HttpServletRequest extends javax.servlet.ServletRequest { // Metody public abstract String getAuthType(); public abstract String getContextPath(); // Nowość w2.2 public abstract Cookie[] getCookies(); // Nowość w2.0 public abstract long getDateHeader(String nazwa); public abstract String getHeader(String nazwa); public abstract Enumeration getHeaderNames(); public abstract Enumeration getHeaders(String nazwa); // Nowość w2.2 public abstract int getIntHeader(String nazwa); public abstract String getMethod(); public abstract String getPathInfo(); public abstract String getPathTranslated(); public abstract String getQueryString(); public abstract String getRemoteUser(); public abstract String getRequestedSessionId(); // Nowość w2.0 public abstract String getRequestURI(); public abstract String getServletPath(); public abstract HttpSession getSession(); // Nowość w2.1 public abstract HttpSession getSession(boolean tworz); // Nowość w2.0 public abstract java.security.Principal getUserPrincipal(); // Nowość w2.2

Page 446: Java Servlet - Programowanie

public abstract boolean isRequestedSessionIdFromCookie(); // Nowość w2.0 public abstract boolean isRequestedSessionIdFromUrl(); // Opuszczona public abstract boolean isRequestedSessionIdFromURL(); // Nowość w2.1 public abstract boolean isRequestedSessionIdValid(); // Nowość w2.0 public abstract boolean isUserInRole(String rola); // Nowość w2.2}

Metody

getAuthType()public abstract String getAuthType()

Zwraca schemat uwierzytelniania serwletu lub null, jeżeli serwlet nie jest chroniony przez mechanizm kontrolidostępu. Możliwe schematy to BASIC, DIGEST, FORM i CLIENT-CERT. Równoważna zmiennej CGIAUTH_TYPE.

getContextPath()public abstract String getContextPath()

Zwraca część żądanego URI wskazującą na kontekst (aplikację WWW) żądania. Ścieżka kontekstu zawszeznajduje się na początku URI żądania. Ścieżka rozpoczyna się znakiem /, ale nie kończy się / i jest zwracanabezpośrednio bez zdekodowania URL. W przypadku serwletów działających w głównym (ROOT) kontekście,metoda zwraca pusty łańcuch. Metoda została wprowadzona w Servlet API 2.2.

getCookies()public abstract Cookie[] getCookies()

Zwraca tablicę obiektów Cookie zawierającą wszystkie cookies wysłane przez przeglądarkę jako część żądanialub null, jeżeli nie wysłano żadnych cookies. Metoda została wprowadzona w Servlet API 2.0.

getDateHeader()public abstract String getHeader(String nazwa)

Zwraca wartość danego nagłówka jako długą wartość reprezentującą Date (milisekundy od północy 1 stycznia1970 GMT) lub –1, jeżeli nagłówek nie został wysłany jako część żądania. Nazwa nie zwraca uwagi na wielkośćliter. Zgłasza IllegalArgumentException, jeżeli zostanie wywołana na nagłówku, którego wartość niemoże zostać przekonwertowana na Date. Metoda ta jest przydatna podczas obsługi nagłówków takich jakLast-Modified i If-Modified-Since.

getHeader()public abstract String getHeader(String nazwa)

Zwraca wartość danego nagłówka jako łańcuch String lub null, jeżeli nagłówek nie został wysłany jakoczęść żądania. Nazwa nie zwraca uwagi na wielkość liter. Metoda ta może odczytywać wszystkie typynagłówków.

getHeaderNames()public abstract Enumeration getHeaderNames()

Zwraca nazwy wszystkich nagłówków, do których serwlet może uzyskać dostęp, jako Enumeration obiektówString lub pustą Enumeration, jeżeli nie występują żądne nagłówki. Niektóre implementacje serwletówmogą nie pozwalać na dostęp do nagłówków w ten sposób, w którym to przypadku metoda zwróci null.

getHeaders()public abstract Enumeration getHeaders(String nazwa)

Zwraca wszystkie wartości danego nagłówka jako Enumeration obiektów String. Niektóre nagłówki, takiejak Accept-Language, mogą być wysyłane klientom jako kilka nagłówków, każdy z inną wartością. Tametoda pozwala na odczytanie wszystkich wartości. Jeżeli żądania nie zawiera żadnych nagłówków o danejnazwie, metoda zwraca pustą Enumeration. Nazwa nie zwraca uwagi na wielkość liter. Metoda zostaławprowadzona w Servlet API 2.2.

getIntHeader()public abstract int getIntHeader(String nazwa)

Page 447: Java Servlet - Programowanie

Zwraca wartość danego nagłówka jako liczbę int lub –1, jeżeli nagłówek nie został wysłany jako częśćżądania. Nazwa nie zwraca uwagi na wielkość liter. Zgłasza wyjątek NumberFormatException, jeżelizostanie wywołana na nagłówku o wartości, która nie może zostać przekonwertowana na int.

getMethod() public abstract String getMethod();

Zwraca metodę HTTP wykorzystaną do wysłania żądania. Może to być na przykład GET, POST lub HEAD.Równoważna zmiennej CGI REQUEST_METHOD. Implementacja service() HttpServlet wykorzystuje tęmetodę podczas przekierowywania żądań.

GetPathInfo()public abstract String getPathInfo()

Zwraca dodatkowe informacje na temat ścieżki związane z żądaniem lub null, jeżeli informacje te niewystępują. Ścieżka jest zdekodowana przez URL przed zwróceniem. Równoważna zmiennej CGI PATH_INFO.

getPathTranslated()public abstract String getPathTranslated()

Zwraca dodatkowe informacje na temat ścieżki przetłumaczone na ścieżkę systemu plików lub null, jeżelidodatkowe informacje nie występują lub kontener serwletów nie mógł stworzyć prawidłowej ścieżki do pliku (naprzykład kiedy plik znajduje się w zdalnym systemie plików lub może zostać odnaleziony jedynie wewnątrzarchiwum .war). Zwracana ścieżka nie musi koniecznie wskazywać na istniejący plik lub katalog. Wywołanie jestpodobne do zmiennej CGI PATH_TRANSLATED. Jest także równoważne getServletContext().getRealPath(zad.getPathInfo()).

getQueryString()public abstract String getQueryString()

Zwraca łańcuch zapytania z URL-a żądania. Wartość jest równoważna wartości zmiennej CGI QUERY_STRING.Ponieważ HttpServletRequest przetwarza ten łańcuch w zbiór parametrów serwletów dostępnych poprzezgetParameter(), większość serwletów może ignorować tę metodę.

getRemoteUser()public abstract String getRemoteUser()

Zwraca nazwę użytkownika wykonującego żądanie jako String lub null, jeżeli dostęp do serwletu nie zostałograniczony. Równoważna zmiennej CGI REMOTE_USER. Ogólnie wymaga zalogowania użytkownika przypomocy uwierzytelniania HTTP. Nie istnieje porównywalna metoda służąca do bezpośredniego odczytywaniahasła użytkownika zdalnego.

getRequestedSessionId()public abstract String getRequestedSessionId()

Metoda zwraca identyfikator sesji określony przez klienta. Nie musi być to prawdziwy identyfikator sesji będącyaktualnie w użyciu — na przykład, jeżeli sesja wygasła przez pojawieniem się żądania, serwer tworzy nowyidentyfikator sesji i używa go zamiast poprzedniego. Metoda została wprowadzona w Servlet API 2.0.

getRequestURI()public abstract String getRequestURI()

Zwraca Universal Resource Identifier (Uniwersalny Identyfikator Zasobów — URI) żądania. Jest to zasóbżądany przez klienta w pierwszej linii jego żądania HTTP, wszystko pomiędzy protokołem i łańcuchemzapytania. W przypadku zwykłych serwletów HTTP, URI żądania to URI żądania bez schematu, komputera,portu i łańcucha zapytania, ale z dodatkowymi informacjami na temat ścieżki. Ścieżka jest zwracanabezpośrednio, bez dekodowania URL. Wczesne wersje Servlet API definiowały i implementowały tę metodę naróżne sposoby. Podczas tworzenia kodu zależnego od tej metody należy upewnić się co do jej wyników wzależności od różnych wersji Servlet API.

getServletPath()public abstract String getServletPath()

Zwraca część URI odnoszącą się do serwletu. Ścieżka przechodzi dekodowanie URL przed zwróceniem i niezawiera żadnych dodatkowych informacji, ani też łańcucha zapytania. W przypadku dopasowań do rozszerzeńplików ścieżka nie zawiera tych rozszerzeń. Równoważna zmiennej CGI SCRIPT_NAME.

Page 448: Java Servlet - Programowanie

getSession()public abstract HttpSession getSession()public abstract HttpSession getSession(boolean tworz)

Zwraca aktualną sesję związaną z użytkownikiem wykonującym żądanie. Jeżeli użytkownik nie posiada aktualnejprawidłowej sesji, metoda tworzy nową, jeżeli tworz posiada wartość true lub zwraca null, jeżeli tworzwynosi false. Wersja bez argumentów posiada tworz ustawioną wewnętrznie na true. Aby zapewnićprawidłowe utrzymanie sesji, metoda powinna zostać wywołana przynajmniej raz przed przesłaniem wodpowiedzi dowolnej zawartości. Serwlety nie wykorzystujące śledzenia sesji mogą zignorować tę metodę.Metoda została wprowadzona w Servlet API 2.0. Wersja bez argumentów została wprowadzona w Servlet API2.1.

getUserPrincipal()public abstract java.security.Principal getUserPrincipal()

Zwraca obiekt java.security.Principal zawierający nazwę obecnie uwierzytelnianego użytkownika.Jeżeli użytkownik nie został uwierzytelniony, metoda zwraca null. Metoda została wprowadzona w Servlet API2.2.

isRequestedSessionIdFromCookie()public abstract boolean isRequestedSessionIdFromCookie()

Zwraca true, jeżeli klient przesłał identyfikator sesji przez cookie, w przeciwnym przypadku false. Metodazostała wprowadzona w Servlet API 2.0.

isRequestedSessionIdFromURL()public abstract boolean isRequestedSessionIdFromUrl()public abstract boolean isRequestedSessionIdFromURL()

Zwraca true, jeżeli żądany identyfikator sesji został przesłany przez napisany ponownie URL, w przeciwnymwypadku false. Metoda isRequestedSessionIdFromUrl() została wprowadzona w Servlet API 2.0,następnie opuszczona w Servlet API 2.1 z wprowadzeniem bardziej standardowo nazwanej metodyisRequestedSessionIdFromURL().

isRequestedSessionIdValid()public abstract boolean isRequestedSessionIdValid()

Zwraca true, jeżeli sesja żadna przez klienta jest prawidłową sesją i w związku z tym jest sesją obecniewykorzystywaną. W przypadku sesji nowych i tych, które wygasły zwraca false. Metoda została wprowadzonaw Servlet API 2.0.

isUserInRole()public abstract boolean isUserInRole(String rola)

Zwraca wartość boolean wskazującą, czy uwierzytelniony użytkownik posiada określona logiczną „rolę”. Rolei członkostwo w rolach mogą być definiowane przy pomocy deskryptorów, a ich odwzorowanie względemużytkowników jest wykonywane przy pomocy narzędzi administracyjnych serwera. Jeżeli użytkownik nie zostałuwierzytelniony, metoda zawsze zwraca false. Metoda została wprowadzona w Servlet API 2.2.

HttpServletResponse

ZestawienieNazwa interfejsu javax.servlet.http.HttpServletResponseSuperinterfejs javax.servlet.ServletResponseBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 1.0 i późniejsze

OpisHttpServletResponse rozszerza klasę ServletResponse w celu umożliwienia manipulacji danymispecyficznymi dla protokołu HTTP, włączając w to nagłówki odpowiedzi i kody stanu. Definiuje również pewnąilość stałych reprezentujących różne kody stanu HTTP i zawiera funkcje wspomagające operacje śledzenia sesji.

Page 449: Java Servlet - Programowanie

Deklaracja interfejsupublic interface HttpServletResponse extends javax.servlet.ServletResponse { // Stałe public static final int SC_ACCEPTED; public static final int SC_BAD_GATEWAY; public static final int SC_BAD_REQUEST; public static final int SC_CONFLICT; public static final int SC_CONTINUE; // Nowość w2.0 public static final int SC_CREATED; public static final int SC_EXPECTATION_FAILED; // Nowość w2.2 public static final int SC_FORBIDDEN; public static final int SC_GATEWAY_TIMEOUT; // Nowość w2.0 public static final int SC_GONE; // Nowość w2.0 public static final int SC_HTTP_VERSION_NOT_SUPPORTED; // Nowość w2.0 public static final int SC_INTERNAL_SERVER_ERROR; public static final int SC_LENGTH_REQUIRED; // Nowość w2.0 public static final int SC_METHOD_NOT_ALLOWED; // Nowość w2.0 public static final int SC_MOVED_PERMAMENTLY; public static final int SC_MOVED_TEMPORARILY; public static final int SC_MULTIPLE_CHOICES; // Nowość w2.0 public static final int SC_NO_CONTENT; public static final int SC_NON_AUTHORITATIVE_INFORMATION; // Nowość w2.0 public static final int SC_NOT_ACCEPTABLE; // Nowość w2.0 public static final int SC_NOT_FOUND; public static final int SC_NOT_IMPLEMENTED; public static final int SC_NOT_MODIFIED; public static final int SC_OK; public static final int SC_PARTIAL_CONTENT; // Nowość w2.0 public static final int SC_PAYMENT_REQUIRED; // Nowość w2.0 public static final int SC_PRECONDITION_FAILED; // Nowość w2.0 public static final int SC_PROXY_AUTHENTICATION_REQUIRED; // Nowość w2.0 public static final int SC_REQUEST_ENTITY_TOO_LARGE; // Nowość w2.0 public static final int SC_REQUEST_TIMEOUT; // Nowość w2.0 public static final int SC_REQUEST_URI_TOO_LONG; // Nowość w2.0 public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE; // Nowość w2.2 public static final int SC_RESET_CONTENT; // Nowość w2.0 public static final int SC_SEE_OTHER; // Nowość w2.0 public static final int SC_SERVICE_UNAVAILABLE; public static final int SC_SWITCHING_PROTOCOLS; // Nowość w2.0 public static final int SC_UNAUTHORIZED; public static final int SC_UNSUPPORTED_MEDIA_TYPE; // Nowość w2.0 public static final int SC_USE_PROXY; // Nowość w2.0 // Metody public abstract void addCookie(Cookie cookie); // Nowość w2.0 public abstract void addDateHeader(String nazwa, long data); // Nowość w2.2 public abstract void addHeader(String nazwa, String wartosc); // Nowość w2.2 public abstract void addIntHeader(String nazwa, int wartosc); // Nowość w2.2 public abstract boolean containsHeader(String nazwa); public abstract String encodeRedirectUrl(String url); // Opuszczona public abstract String encodeRedirectURL(String url); // Nowość w2.1 public abstract String encodeUrl(String url); // Opuszczona

Page 450: Java Servlet - Programowanie

public abstract String encodeURL(String url); // Nowość w2.1 public abstract void sendError(int sc) throws IOException, IllegalStateException; public abstract void sendError(int sc, String wiad) throws IOException, IllegalStateException; public abstract void sendRedirect(String miejsce) throws IOException, IllegalStateException; public abstract void setDateHeader(String nazwa, long data); public abstract void setHeader(String nazwa, String wartosc); public abstract void setIntHeader(String nazwa, int wartosc); public abstract void setStatus(int sc); public abstract void setStatus(int sc, String wiad); //Opuszczona}

StałeDodatek D, „Kody stanu HTTP”, zawiera kompletny opis wszystkich kodów stanu SC_XXX.

Metody

addCookie()public abstract void addCookie(Cookie cookie)

Dodaje do odpowiedzi określone cookie. Dodatkowe cookies mogą być dodawane przy pomocy powtarzanychwywołań addCookie(). Ponieważ cookies są wysyłane przy pomocy nagłówków HTTP, powinny one zostaćdodane do odpowiedzi przed jej zatwierdzeniem. Od przeglądarek wymaga się przyjmowanie jednocześniejedynie 20 cookies na witrynę, 300 na użytkownika, a wielkość każdego cookie może zostać ograniczona do4096 bajtów.

addDateHeader()public abstract void addDateHeader(String nazwa, long data)

Dodaje nagłówek o danej nazwie i wartości daty. Metoda przyjmuje datę jako liczbę long reprezentującą ilośćmilisekund od północy 1 stycznia 1970 GMT. Metoda pozwala nagłówkom odpowiedzi na posiadanie więcej niżjednej wartości. Metoda została wprowadzona w Servlet API 2.2.

addHeader()public abstract void addHeader(String nazwa, String wartosc)

Dodaje nagłówek o danej nazwie i wartości. Metoda pozwala nagłówkom odpowiedzi na posiadanie więcej niżjednej wartości. Metoda została wprowadzona w Servlet API 2.2.

addIntHeader()public abstract void addIntHeader(String nazwa, int wartosc)

Dodaje nagłówek o danej nazwie i wartości int. Metoda przyjmuje datę w formie long, reprezentującą ilośćmilisekund od północy 1 stycznia 1970 GMT. Metoda pozwala nagłówkom odpowiedzi na posiadanie więcej niżjednej wartości. Metoda została wprowadzona w Servlet API 2.2.

containsHeader();public abstract boolean containsHeader(String nazwa)

Zwraca true, jeżeli dany nagłówek został wcześniej ustawiony, w przeciwnym wypadku false.

encodeRedirectURL()public abstract String encodeRedirectUrl(String url)public abstract String encodeRedirectURL(String url)

Zwraca określony URL zakodowany (ponownie napisany) tak, aby zawierał identyfikator sesji. Jeżeli kodowanienie jest potrzebne lub obsługiwane, metoda pozostawia URL niezmieniony. Zasady wykorzystywane dopodejmowania decyzji czy i kiedy kodować URL są zależne od serwera. Metoda ta może wykorzystywać innezasady niż encodeURL(). W celu umożliwienia śledzenia sesji, wszystkie URL-e przekazane metodziesendRedirect() powinny przejść prze tę metodę. Metoda encodeRedirectUrl() zostaławprowadzona w Servlet API 2.0, po czym opuszczona w Servlet API 2.1 z wprowadzeniem bardziejstandardowo nazwanej metody encodeRedirectURL().

Page 451: Java Servlet - Programowanie

encodeURL()public abstract String encodeUrl(String url)public abstract String encodeURL(String url)

Zwraca określony URL zakodowany (ponownie napisany) tak, aby zawierał identyfikator sesji. Jeżeli kodowanienie jest potrzebne lub obsługiwane, metoda pozostawia URL niezmieniony. Zasady wykorzystywane dopodejmowania decyzji czy i kiedy kodować URL są zależne od serwera. W celu umożliwienia śledzenia sesji,wszystkie URL-e wysyłane przez serwlet powinny przejść prze tę metodę. Metoda encodeUrl() zostaławprowadzona w Servlet API 2.0, po czym opuszczona w Servlet API 2.1 z wprowadzeniem bardziejstandardowo nazwanej metody encodeURL().

sendError()public abstract void sendError(int sc) throws IOException, IllegalStateExceptionpublic abstract void sendError(int sc, String wiad) throws IOException, IllegalStateException

Metody te są podobne do setStatus() poza tym, że są wykorzystywane, kiedy kod stanu wskazuje błądpodczas obsługi żądania i serwlet chciałby, aby serwer wygenerował odpowiednią stronę błędu. Metoda tapowinna zostać wywołana przed zatwierdzeniem odpowiedzi, ponieważ w innym przypadku zgłosi wyjątekIllegalStateException. Metoda ta wykonuje ukryte wyczyszczenie bufora odpowiedzi przedwygenerowaniem strony błędu. Nagłówki ustawione przed sendError() powinny pozostać niezmienione.

sendRedirect()public abstract void sendRedirect(String miejsce) throws IOException, IllegalStateException

Przekierowuje odpowiedź do określonej lokalizacji, automatycznie ustawiając kod stanu i nagłówek Location.Domyślna implementacja zapisuje również krótka główną część odpowiedzi, która zawiera hiperłącze do nowejlokacji w celu wspomożenia przeglądarek nie posiadających zdolności przekierowywania. W związku z tym nienależy tworzyć własnej głównej części odpowiedzi podczas wykorzystywania tej metody. Specyfikacja HTTPnakazuje, aby wszystkie URL-e przekierowań były bezwzględne; jednak począwszy od Servlet API 2.2 metoda taprzyjmuje również URL-e względna — serwer przekształca je do formy bezwzględnej (automatycznie dodającaktualny protokół, serwer i port — ale nie ścieżkę kontekstu; należy wykonać to działanie samodzielne, jeżeliokaże się konieczne) przed wysłaniem go do klienta. Metoda ta powinna zostać wywołana przed zatwierdzeniemodpowiedzi, ponieważ w innym przypadku zgłosi wyjątek IllegalStateException. Metoda ta wykonujeukryte wyczyszczenie bufora odpowiedzi przed wygenerowaniem strony błędu. Nagłówki ustawione przedsendRedirect() powinny pozostać niezmienione.

setDateHeader()public abstract void setDateHeader(String nazwa, long data)

Ustawia wartość danego nagłówka jako String określający konkretną datę i czas. Metoda przyjmuje datę wformie long, reprezentującą ilość milisekund od północy 1 stycznia 1970 GMT. Jeżeli nagłówek został jużwcześniej ustawiony, nowa wartość nadpisuje wszystkie poprzednie wartości.

setHeader()public abstract void setHeader(String nazwa, String wartosc)

Ustawia wartość danego nagłówka jako String. Nazwa nie zwraca uwagi na wielkość liter (tak jak wewszystkich metodach związanych z nagłówkami). Jeżeli nagłówek został już wcześniej ustawiony, nowa wartośćnadpisuje wszystkie poprzednie wartości. Nagłówki zawsze powinny zostać ustawione przed zatwierdzeniemodpowiedzi.

setIntHeader()public abstract void setIntHeader(String nazwa, int wartosc)

Ustawia wartość danego nagłówka jako int. Jeżeli nagłówek został już wcześniej ustawiony, nowa wartośćnadpisuje wszystkie poprzednie wartości.

setStatus()public abstract void setStatus(int sc);public abstract void setStatus(int sc, String wiad)

Ustawia kod stanu HTTP. Kod może zostać określony przy pomocy wartości liczbowej lub kodów SC_XXXzdefiniowanych w HttpServletResponse. Jako drugi parametr może zostać określona własna wiadomość obłędzie protokołu HTTP; jednak nie powinno się wykonywać tego działania ponieważ wersja metodyprzyjmująca String została opuszczona w ServletAPI 2.1. Kod stanu powinien zostać ustawiony przedzatwierdzeniem odpowiedzi, w innym przypadku wywołanie jest ignorowane.

Page 452: Java Servlet - Programowanie

HttpSession

ZestawienieNazwa interfejsu javax.servlet.http.HttpSessionSuperinterfejs BrakBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 2.0 i późniejsze

OpisInterfejs HttpSession dostarcza mechanizmu służącego do przechowywania tymczasowych informacji natemat odwiedzających witrynę WWW. Dokładne omówienie śledzenia sesji znajduje się w rozdziale 7. Saminterfejs HttpSession umożliwia serwletom przeglądanie i manipulację informacjami specyficznymi dladanej sesji, takimi jak czas utworzenia i unikatowy identyfikator sesji. Zawiera również metody służące dodowiązywania obiektów do sesji w celu późniejszego ich odczytania, umożliwiając „koszykom na zakupy” iinnym aplikacjom przechowywanie danych pomiędzy żądaniami.

Serwlet pobiera obiekt HttpSession przy pomocy metody getSession() HttpServletRequest.Zachowanie sesji, takie jak ilość czasu, która musi upłynąć przed zniszczeniem sesji, może zostać ustawioneprogramowo lub przed deskryptor aplikacji WWW.

W przypadku sesji związanych z nierozporoszoną aplikacją WWW, każdy obiekt może zostać dowiązany dosesji. Obiekty implementujące java.io.Serializable mogą zostać zapisane na dysku w celu uwolnieniapamięci i przetrwania pomiędzy ponownymi uruchomieniami serwera.

W przypadku sesji związanych z rozproszoną aplikacją WWW, obiekt umieszczony w sesji musi implementowaćjava.io.Serializable. Jeżeli warunek ten nie zostanie spełniony, serwer może zgłosićIllegalArgumentException. Serwery wykorzystują przyklejanie sesji w celu efektywnego zarządzaniasesjami w środowisku rozproszonym, w którym często występuje kilka serwerów wspierających. Oznacza to, żewszystkie żądania będące częścią pojedynczej sesji konkretnego użytkownika są obsługiwane przez tylko jednąJVM w jednym czasie. Eliminuje to konieczność ciągłego replikowania informacji o sesji pomiędzy serweramiwspierającymi. Odpowiedzialność za sesję może zostać przekazana innemu serwerowi pomiędzy żądaniamiużytkownika, a w celu umożliwienia przenoszenia sesji wszystkie obiekty w niej umieszczone muszą byćSerializable.

Deklaracja interfejsupublic interface HttpSession { // Metody // Większość metod może zgłaszać IllegalStateException public abstract Object getAttribute(String nazwa); // Nowość w2.2 public abstract Enumeration getAttributeNames(); // Nowość w2.2 public abstract long getCreationTime(); public abstract String getID(); public abstract long getLastAccessedTime(); public abstract int getMaxInactiveInterval(); // Nowość w2.1 public abstract HttpSessionContext getSessionContext(); // Opuszczona public abstract Object getValue(String nazwa); // Opuszczona public abstract String[] getValueNames(); // Opuszczona public abstract void invalidate(); public abstract boolean isNew(); public abstract void putValue(String nazwa, Object wartosc); // Opuszczona public abstract void removeAttribute(String nazwa); // Nowość w2.2 public abstract void removeValue(String nazwa); // Opuszczona public abstract void setAttribute(String nazwa, Object value); // Nowość w2.2 public abstract void setMaxInactiveInterval(int sekundy); // Nowość w2.1}

Page 453: Java Servlet - Programowanie

Metody

getAttribute()public abstract Object getAttribute(String nazwa) throws IllegalStateException

Zwraca obiekt dowiązany do sesji pod określoną nazwą lub null, jeżeli nie istnieje pasujące dowiązanie. ZgłaszaIllegalStateException, jeżeli sesja jest nieważna.

getAttributeNames()public abstract Enumeration getAttributeNames() throws IllegalStateException

Zwraca Enumeration zawierającą nazwy wszystkich obiektów dowiązanych do aktualnej sesji jako obiektyString lub pustą Enumeration, jeżeli nie występują żadne dowiązania. ZgłaszaIllegalStateException, jeżeli sesja jest nieważna.

getCreationTime()public abstract long getCreationTime() throws IllegalStateException

Zwraca czas utworzenia sesji, jako liczbę long reprezentującą ilość milisekund od północy 1 stycznia 1970GMT. Zgłasza IllegalStateException, jeżeli sesja jest nieważna.

getId()public abstract String getID() throws IllegalStateException

Zwraca niepowtarzalny identyfikator String przypisany do danej sesji. Struktura identyfikatora jest zależna odimplementacji; powinna być po prostu trudna do odgadnięcia dla kogoś innego. Na przykład identyfikatorTomcata mógłby wyglądać podobnie do awj4qyhsn2. Zgłasza IllegalStateException, jeżeli sesja jestnieważna.

getLastAccessedTime()public abstract long getLastAccessedTime() throws IllegalStateException

Zwraca czas, w którym klient po raz ostatni wysłał żądanie związane z daną sesją (nie włączając w to aktualnegożądania), jako liczbę long reprezentującą ilość milisekund od północy 1 stycznia 1970 GMT. ZgłaszaIllegalStateException, jeżeli sesja jest nieważna.

getMaxInactiveIntervalpublic abstract int getMaxInactiveInterval() throws IllegalStateException

Zwraca czas (w sekundach) jaki musi upłynąć pomiędzy żądaniami, zanim kontener serwletów unieważni sesję.Ujemny czas oznacza, że sesja nigdy nie ulegnie przedawnieniu. Domyślny czas przedawnienia jest ustawionywewnątrz deskryptora aplikacji WWW. Zgłasza IllegalStateException, jeżeli sesja jest nieważna.Metoda została wprowadzona w Servlet API 2.1.

getSessionContext()public abstract HttpSessionContext getSessionContext()

Zwraca pusty kontekst ze względów bezpieczeństwa, począwszy od Servlet API 2.1. Opuszczona również odServlet API 2.1. Proszę zobaczyć HttpSessionContext w celu uzyskania większej ilości informacji.

getValue()public abstract Object getValue(String nazwa) throws IllegalStateException

Zwraca obiekt dowiązany do sesji pod określoną nazwą lub null, jeżeli nie występuje pasujące dowiązanie.Zgłasza IllegalStateException, jeżeli sesja jest nieważna. Opuszczona w Servlet API 2.2 na rzeczgetAttribute().

getValueNames()public abstract String[] getValueNames() throws IllegalStateException

Page 454: Java Servlet - Programowanie

Zwraca tablicę zawierającą nazwy wszystkich obiektów dowiązanych do aktualnej sesji lub pustą (zerowejdługości) tablicę, jeżeli nie występują żadne dowiązania. Zgłasza IllegalStateException, jeżeli sesja jestnieważna. Opuszczona w Servlet API 2.2 na rzecz getAttributeNames().

invalidate()public abstract void invalidate() throws IllegalStateException

Powoduje natychmiastowe unieważnienie sesji. Wszystkie dowiązania obiektów przechowywane w sesji zostająusunięte. Zgłasza IllegalStateException, jeżeli sesja została już wcześniej unieważniona.

isNew()public abstract boolean isNew() throws IllegalStateException

Zwraca odpowiedź, czy sesja jest nowa. Sesja jest uważana za nową, jeżeli została już utworzona przez serwer,ale klient nie potwierdził jeszcze dołączenia do niej. Na przykład, jeżeli serwer obsługuje jedynie sesje oparte nacookies, a klient całkowicie zablokował ich użycie, wywołania getSession() zawsze zwracają nowe sesje.Zgłasza IllegalStateException, jeżeli sesja została już wcześniej unieważniona.

putValue()public abstract void putValue(String nazwa, Object wartosc) throws IllegalStateException

Dowiązuje do sesji dany obiekt pod określoną nazwą. Wszystkie istniejące dowiązania o tej samej nazwie zostająusunięte. Zgłasza IllegalStateException, jeżeli sesja jest nieważna. Opuszczona w Servlet API 2.2 narzecz setAttribute().

removeAttribute()public abstract void removeAttribute(String nazwa) throws IllegalStateException

Usuwa obiekt dowiązany pod określoną nazwą lub nie wykonuje żadnego działania, jeżeli dowiązanie nieistnieje. Zgłasza IllegalStateException, jeżeli sesja jest nieważna.

removeValue()public abstract void removeValue(String nazwa) throws IllegalStateException

Usuwa obiekt dowiązany pod określoną nazwą lub nie wykonuje żadnego działania, jeżeli dowiązanie nieistnieje. Zgłasza IllegalStateException, jeżeli sesja jest nieważna. Opuszczona w Servlet API 2.2 narzecz removeAttribute().

setAttribute()public abstract void setAttribute(String nazwa, Object value) throws IllegalStateException

Dowiązuje do sesji dany obiekt pod określoną nazwą. Wszystkie istniejące dowiązania o tej samej nazwie zostająusunięte. Zgłasza IllegalStateException, jeżeli sesja jest nieważna. Metoda została wprowadzona wServlet API 2.2.

setMaxInactiveInterval()public abstract void setMaxInactiveInterval(int sekundy)

Określa czas (w sekundach) jaki musi upłynąć pomiędzy żądaniami, zanim kontener serwletów unieważni sesję.Ujemny czas oznacza, że sesja nie powinna nigdy ulec przedawnieniu. Domyślny czas przedawnienia możezostać ustawiony wewnątrz deskryptora aplikacji WWW. Zgłasza IllegalStateException, jeżeli sesjajest nieważna. Metoda została wprowadzona w Servlet API 2.1.

HttpSessionBindingEvent

ZestawienieNazwa klasy javax.servlet.http.HttpSessionBindingEventSuperklasa java.util.EventObjectBezpośrednie podklasy BrakImplementowane interfejsy Brak

Page 455: Java Servlet - Programowanie

Dostępność Servlet API 2.0 i późniejsze

OpisHttpSessionBindingEvent jest przekazywany HttpSessionBindingListener, kiedy obiektnasłuchujący jest dowiązywany do sesji, lub kiedy jego dowiązanie zostaje usunięte.

Podsumowanie klaspublic class HttpSessionBindingEvent extends java.util.EventObject { // Konstruktory public HttpSessionBindingEvent(HttpSession sesja, String nazwa); // Metody egzemplarzy public String getName(); public HttpSession getSession();}

Konstruktory

HttpSessionBindingEvent()public HttpSessionBindingEvent(HttpSession sesja, String nazwa)

Konstruuje nowy HttpSessionBindingEvent wykorzystując sesję, do której następuje dowiązanie inazwę, pod którą dany obiekt zostaje przypisany (jest to ta sama nazwa, która zostaje przekazana metodziesetAttribute() HttpSession). Programiści serwletów nie powinni nigdy zostać zmuszeni dowykorzystania tego konstruktora.

Metody egzemplarzy

getName()public String getName()

Zwraca nazwę, pod którą dany obiekt został przydzielony do sesji.

GetSession()public HttpSession getSession()

Zwraca sesję, do której dany obiekt jest dowiązywany, lub którego dowiązanie jest usuwane.

HttpSessionBindingListener

ZestawienieNazwa interfejsu javax.servlet.http.HttpSessionBindingListenerSuperinterfejs java.util.EventListenerBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 2.0 i późniejsze

OpisObiekt implementujący HttpSessionBindingListener jest informowany poprzez wywołaniavalueBound() i valueUnbound(), kiedy zostaje dowiązany (lub jego dowiązanie zostaje usunięte) doHttpSession. Interfejs ten umożliwia między innymi uporządkowane wyczyszczenie zasobów związanych zsesją, takich jak połączenia z bazą danych. Proszę zauważyć, że w środowisku rozproszonym wywołanievalueBound() może nastąpić w innej JVM niż wywołanie valueUnbound().

Deklaracja interfejsupublic interface HttpSessionBindingListener extends java.util.EventListener { // Metody public abstract void valueBound(HttpSessionBindingEvent zdarzenie); public abstract void valueUnbound(HttpSessionBindingEvent zdarzenie);}

Page 456: Java Servlet - Programowanie

Metody

valueBound()public abstract void valueBound(HttpSessionBindingEvent zdarzenie)

Wywoływana podczas dowiązywania mechanizmu nasłuchującego do sesji.

valueUnbound()public abstract void valueUnbound(HttpSessionBindingEvent zdarzenie)

Wywoływana podczas usuwania dowiązania mechanizmu nasłuchującego do sesji (włączając w to zniszczeniesesji).

HttpSessionContext

ZestawienieNazwa interfejsu javax.servlet.http.HttpSessionContextSuperinterfejs BrakBezpośrednie podinterfejsy BrakImplementowany przez BrakDostępność Servlet API 2.0 i późniejsze; opuszczony w Servlet API 2.1

OpisHttpSessionContext jest opuszczona od czasów Servlet API 2.1. Poprzednio klasa ta umożliwiała dostępdo wszystkich aktywnych w danym momencie sesji w kontenerze serwletów. Tworzyło to potencjalną dziurę wsystemie zabezpieczeń, ponieważ serwlet mógł wykorzystać te klasę w celu wyświetlenia wszystkichidentyfikatorów sesji odnalezionych wewnątrz kontekstu, a informacja ta mogła zostać wykorzystana przeznieuczciwych klientów do utorowania sobie drogi do innej sesji. Ponieważ możliwość jednoczesnego dostępu dowszystkich sesji jest nie jest prawie nigdy potrzebna, klasa została opuszczona ze względów bezpieczeństwa.

Deklaracja interfejsuPublic interface HttpSessionContext { // Metody public abstract Enumeration getIds(); // Opuszczona public abstract HttpSession(String idSesji); // Opuszczona}

Metody

getIds()public abstract Enumeration getIds()

Opuszczona w Servlet API 2.1. W Servlet API 2.0 zwracała Enumeration zawierającą identyfikatorywszystkich ważnych w danym momencie sesji, lub pustą Enumeration, jeżeli takie nie występowały.Identyfikatory sesji zwracane przez getIds() muszą być traktowane jak tajemnica serwera, ponieważ każdyklient znający identyfikator sesji innego klienta mógłby, przy pomocy utworzonego samodzielnie cookie lubURL-a dołączyć do sesji drugiego klienta.

getSession()public abstract HttpSession(String idSesji)

Opuszczona w Servlet API 2.1. W Servlet API 2.0 zwracała sesję związaną z danym identyfikatorem sesji. Listaważnych identyfikatorów sesji może zostać odczytana przez metodę getIds().

HttpUtils

ZestawienieNazwa klasy javax.servlet.http.HttpUtilsSuperklasa java.lang.ObjectBezpośrednie podklasy Brak

Page 457: Java Servlet - Programowanie

Implementowane interfejsy BrakDostępność Servlet API 1.0 i późniejsze

OpisObiekt służący jako kontener dla kilku potencjalnie przydatnych, zorientowanych na HTTP metod.

Podsumowanie klasypublic class HttpUtils // Konstruktory public HttpUtils(); // Metody klasy public static StringBuffer getRequestURL(HttpServletRequest zad); public static Hashtable parsePostData(int dlug, ServletInputStream in); public static Hashtable parseQueryString(String s);}

Konstruktory

HttpUtils()public HttpUtils()

Domyślny konstruktor nie wykonuje żadnych działań.

Metody klasy

getRequestURL()public static StringBuffer getRequestURL(HttpServletRequest zad)

Odtwarza URL żądania w oparciu o informacje dostępne w obiekcie HttpServletRequest. ZwracaStringBuffer, który zawiera schemat, nazwę serwera, port serwera oraz dodatkowe informacje ścieżki, alenie łańcuch zapytania. Odtworzony URL powinien wyglądać niemal identycznie jak URL wykorzystany przezklienta. Metoda ta może zostać wykorzystana do zgłaszania błędów, przekierowywania oraz tworzenia URL-i. Wprzypadku aplikacji, które muszą bezbłędnie zidentyfikować konkretne serwlety. Lepszym wyborem jestgeneralnie metoda getRequestURI() HttpServletRequest.

parsePostData()public static Hashtable parsePostData(int dlug, ServletInputStream in)

Analizuje dlug danych parametrów ServletInputStream (zazwyczaj wysyłanych jako część operacjiPOST). Zgłasza IllegalArgumentException, jeżeli dane parametrów okażą się nieprawidłowe.Większość serwletów wykorzystuje getParameterNames(), getParameter() igetParameterValues() zamiast tej metody.

parseQueryString()public static Hashtable parseQueryString(String s)

Zwraca tablicę asocjacyjną Hashtable, w której klucze są nazwami parametrów pobranymi z łańcuchazapytania, a każda wartość tablicy jest tablicą String zawierającą zdekodowaną wartość/wartości parametru.Zgłasza IllegalArgumentException, jeżeli łańcuch zapytania okaże się nieprawidłowy. Większośćserwletów wykorzystuje getParameterNames(), getParameter() i getParameterValues()zamiast tej metody. Użycie obu tych metod nie jest bezpieczne.

Page 458: Java Servlet - Programowanie

Dodatek C. Krótki opis

deskryptorów DTD

Plik Document Type Definition (Definicja Typu Dokumentu — DTD) określa prawidłową zawartość aplikacji wpoprzez plik XML. Niniejszy dodatek przedstawia oficjalny DTD Servlet API 2.2 dla deskryptora aplikacjiWWW. Pliki DTD nie są plikami XML, ale ich składnia nie jest skomplikowana — każdy znacznik<!ELEMENT> definiuje w nawiasach, jakie elementy potomne może posiadać, jaką ich ilość oraz w jakimporządku. Znak zapytania (?) po nazwie potomka oznacza, że potomek jest opcjonalny (0 – 1), gwiazdka (*)oznacza, że może pojawić się dowolna ilość potomków (0 – n), znak plus (+) oznacza, że pewna ilość potomkówmusi się pojawić (1 – n), a brak znaku po nazwie oznacza, że potomek jest po prostu konieczny (1). Składnia(x|y) oznacza x lub y. Elementy potomne musza pojawiać się dokładnie w porządku określonym wewnątrznawiasów.

Rysunki od C.1 do C.4 przedstawiają graficznie strukturę elementów Przykład C.1 przedstawia sam DTD. Każdyznacznik <!ATTLIST> kontroluje dozwolone atrybuty elementu. W niniejszym DTD znacznik ten jestwykorzystany jedynie w celu dostarczenia domyślnych wartości id. Pozostała część niniejszego dodatku to opiselementów DTD.

Rysunek C.1.

Struktura elementów deskryptora DTD

Page 459: Java Servlet - Programowanie

Rysunek C.2.

Rysunek C.3.

Page 460: Java Servlet - Programowanie

Rysunek C.4.

Przykład C.1.

Deskryptor DTD<!ELEMENT web-app (icon?, display-name?, description?, distributable?, context-param*, servlet*, servlet-mapping*, session-config?, mime-mapping*, welcome-file-list?, error-page*, taglib*, resource-ref*, security-constraint*, login-config?, security-role*, env-entry*, ejb-ref*)><!ELEMENT icon (small-icon?, large-icon?)><!ELEMENT small-icon (#DANE)><!ELEMENT large-icon (#DANE)><!ELEMENT display-name (#DANE)><!ELEMENT description (#DANE)><!ELEMENT distributable EMPTY><!ELEMENT context-param (param-name, param-value, description?><!ELEMENT param-name (#DANE)><!ELEMENT param-value (#DANE)><!ELEMENT servlet (icon?, servlet-name, display-name?, description?, (servlet-class|jsp-file), init-param*, load-on-startup?, security-role-ref*)>

Page 461: Java Servlet - Programowanie

<!ELEMENT servlet-name (#DANE)><!ELEMENT servlet-class (#DANE)><!ELEMENT jsp-file (#DANE)><!ELEMENT init-param (param-name, param-value, description?><!ELEMENT load-on-startup (#DANE)><!ELEMENT servlet-mapping (servlet-name, url-pattern)><!ELEMENT url-pattern (#DANE)><!ELEMENT session-config (session-timeout?)><!ELEMENT session-timeout (#DANE)><!ELEMENT mime-mapping (extension, mime-type)><!ELEMENT extension (#DANE)><!ELEMENT mime-type (#DANE)><!ELEMENT welcome-file-list (welcome-file+)><!ELEMENT welcome-file (#DANE)><!ELEMENT taglib (taglib-uri, taglib-location)><!ELEMENT taglib-uri (#DANE)><!ELEMENT taglib-location (#DANE)><!ELEMENT error-page ((error-code | exception-type), location)><!ELEMENT error-code (#DANE)><!ELEMENT exception-type (#DANE)><!ELEMENT location (#DANE)><!ELEMENT resource-ref (description?, res-ref-name, res-type, res-auth)><!ELEMENT res-ref-name (#DANE)><!ELEMENT res-type (#DANE)><!ELEMENT res-auth (#DANE)><!ELEMENT security-constraint (web-resource-collection+, auth-constraint?, user-data-constraint?)><!ELEMENT web-resource-collection (web-resource-name, description?, url-pattern*, http-method*)><!ELEMENT web-resource-name (#DANE)><!ELEMENT http-method (#DANE)><!ELEMENT user-data-constraint (description?, transport-guarantee)><!ELEMENT transport-guarantee (#DANE)><!ELEMENT auth-constraint (description?, role-name*)><!ELEMENT role-name (#DANE)><!ELEMENT login-config (auth-method?, realm-name?, form-login-config?)><!ELEMENT realm-name (#DANE)><!ELEMENT form-login-config (form-login-page, form-error-page)><!ELEMENT form-login-page (#DANE)><!ELEMENT form-error-page (#DANE)><!ELEMENT auth-method (#DANE)><!ELEMENT security-role (description?, role-name)><!ELEMENT security-role-ref (description?, env-entry-name, env-entry-value? env-entry-type)><!ELEMENT env-entry-name (#DANE)><!ELEMENT env-entry-value (#DANE)><!ELEMENT env-entry-type (#DANE)><!ELEMENT ejb-ref (description?, ejb-ref-name, ejb-ref-type, home, remote, ejb-link?)><!ELEMENT ejb-ref-name (#DANE)><!ELEMENT ejb-ref-type (#DANE)><!ELEMENT home (#DANE)><!ELEMENT remote (#DANE)><!ELEMENT ejb-link (#DANE)><!—Mechanizm identyfikacji umożliwia narzędziom tworzenie w łatwy sposóbspecyficznych dla narzędzia odwołań do elementów deskryptora. Pozwala tonarzędziom tworzącym dodatkowe informacje (to znaczy informacje poza standardowymiinformacjami deskryptora) przechowywanie niestandardowych informacji w osobnympliku i łatwe odwoływanie się z tych specyficznych dla narzędzia plików doinformacji zawartych w standardowym deskryptorze aplikacji WWW. --><!ATTLIST web-app id ID #SUGERUJ><!ATTLIST icon id ID #SUGERUJ><!ATTLIST small-icon id ID #SUGERUJ><!ATTLIST large-icon id ID #SUGERUJ><!ATTLIST display-name id ID #SUGERUJ><!ATTLIST description id ID #SUGERUJ><!ATTLIST distributable id ID #SUGERUJ><!ATTLIST context-param id ID #SUGERUJ><!ATTLIST param-name id ID #SUGERUJ>

Page 462: Java Servlet - Programowanie

<!ATTLIST param-value id ID #SUGERUJ><!ATTLIST servlet id ID #SUGERUJ><!ATTLIST servlet-name id ID #SUGERUJ><!ATTLIST servlet-class id ID #SUGERUJ><!ATTLIST jsp-file id ID #SUGERUJ><!ATTLIST init-param id ID #SUGERUJ><!ATTLIST load-on-startup id ID #SUGERUJ><!ATTLIST servlet-mapping id ID #SUGERUJ><!ATTLIST url-pattern id ID #SUGERUJ><!ATTLIST session-config id ID #SUGERUJ><!ATTLIST session-timeout id ID #SUGERUJ><!ATTLIST mime-mapping id ID #SUGERUJ><!ATTLIST extension id ID #SUGERUJ><!ATTLIST mime-type id ID #SUGERUJ><!ATTLIST welcome-file-list id ID #SUGERUJ><!ATTLIST welcome-file id ID #SUGERUJ><!ATTLIST error-page id ID #SUGERUJ><!ATTLIST error-code id ID #SUGERUJ><!ATTLIST exception-type id ID #SUGERUJ><!ATTLIST location id ID #SUGERUJ><!ATTLIST resource-ref id ID #SUGERUJ><!ATTLIST res-ref-name id ID #SUGERUJ><!ATTLIST res-type id ID #SUGERUJ><!ATTLIST res-auth id ID #SUGERUJ><!ATTLIST security-constraint id ID #SUGERUJ><!ATTLIST web-resource-collection id ID #SUGERUJ><!ATTLIST web-resource-name id ID #SUGERUJ><!ATTLIST http-method-id id ID #SUGERUJ><!ATTLIST user-data-constraint id ID #SUGERUJ><!ATTLIST transport-guarantee id ID #SUGERUJ><!ATTLIST auth-constraint id ID #SUGERUJ><!ATTLIST role-name id ID #SUGERUJ><!ATTLIST auth-method id ID #SUGERUJ><!ATTLIST basic-auth id ID #SUGERUJ><!ATTLIST form-auth id ID #SUGERUJ><!ATTLIST form-login-page id ID #SUGERUJ><!ATTLIST form-error-page id ID #SUGERUJ><!ATTLIST form-login-config id ID #SUGERUJ><!ATTLIST realm-name id ID #SUGERUJ><!ATTLIST login-config id ID #SUGERUJ><!ATTLIST security-role id ID #SUGERUJ><!ATTLIST security-role-ref id ID #SUGERUJ><!ATTLIST role-link id ID #SUGERUJ><!ATTLIST env-entry id ID #SUGERUJ><!ATTLIST env-entry-name id ID #SUGERUJ><!ATTLIST env-entry-value id ID #SUGERUJ><!ATTLIST env-entry-type id ID #SUGERUJ><!ATTLIST mutual-auth id ID #SUGERUJ><!ATTLIST ejb-ref id ID #SUGERUJ><!ATTLIST ejb-ref-name id ID #SUGERUJ><!ATTLIST ejb-ref-type id ID #SUGERUJ><!ATTLIST home id ID #SUGERUJ><!ATTLIST remote id ID #SUGERUJ><!ATTLIST ejb-ref id ID #SUGERUJ>

<auth-constraint>

Zestawienie<!ELEMENT auth-constraint (description?, role-name*)>

OpisElement <auth-constraint> wskazuje na role użytkowników, które mogą zostać dopuszczone do zbioruzasobów. Nazwy ról wykorzystane w tym miejscu muszą pojawić się w elemencie <security-role-ref>.

<security-constraint> <web-resource-collection> ... </web-resource-collection> <auth-constraint> <role-name>menedzer</role-name> </auth-constraint></security-constraint>

Page 463: Java Servlet - Programowanie

<auth-method>

Zestawienie<!ELEMENT auth-method (#DANE)>

OpisElement <auth-method> jest wykorzystywany do konfigurowania mechanizmu uwierzytelniającego aplikacjiWWW. Podstawową zasadą dostępu do dowolnych zasobów WWW chronionych przez ograniczenia dostępujest uwierzytelnienie użytkownika przy pomocy skonfigurowanego mechanizmu. Prawidłowe wartości tegoelementu to BASIC, DIGEST, FORM i CLIENT-CERT.

<login-config> <auth-method>BASIC</auth-method> <realm-name>Domyslny</realm-name></login-config>

<context-param>

Zestawienie<!ELEMENT context-param (param-name, param-value, description?>

OpisElement <context-param> zawiera deklarację parametrów inicjacji kontekstu serwletów aplikacji WWW.

<context-param> <param-name>rmihost</param-name> <param-value>localhost</param-value></context-param>

<description>

Zestawienie<!ELEMENT description (#DANE)>

OpisElement <description> jest wykorzystywany w celu dołączenia tekstu opisującego element przodka.

<description>Nie zapomnij się przywitać!</description>

<display-name>

Zestawienie<!ELEMENT display-name (#DANE)>

OpisElement <display-name> zawiera krótką nazwę serwletu, który powinien zostać wyświetlony przeznarzędzia graficzne.

<servlet> <servlet-name>witaj</servlet-name> <display-name>WitajSwiecie</display-name> <servlet-class>WitajSwiecie</servlet-class></servlet>

<distributable>

Zestawienie<!ELEMENT distributable (#DANE)>

Page 464: Java Servlet - Programowanie

OpisElement <distributable> poprzez swoją obecność w deskryptorze aplikacji WWW wskazuje, że aplikacjaWWW jest zaprogramowana odpowiednio dla rozproszonego kontenera serwletów.

<distributable> <description> Wszystkie serwlety i strony JSP są przygotowane do działania rozproszonego </description></distributable>

<ejb-link>

Zestawienie<!ELEMENT ejb-link (#DANE)>

OpisElement <ejb-link> jest wykorzystywany w elemencie <ejb-ref> w celu zaznaczenia, że odwołaniepołączone jest z komponentem EJB w pakiecie aplikacji Java 2, Enterprise Edition (J2EE). Wartość elementu<ejb-link> musi być równa <ejb-name> komponentu w pakiecie aplikacji J2EE.

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote> <ejb-link>KabinaElement</ejb-link></ejb-ref>

<ejb-ref>

Zestawienie<!ELEMENT ejb-ref (description?, ejb-ref-name, ejb-ref-type, home, remote, ejb-link?)>

OpisElement <ejb-ref> jest wykorzystywany do deklarowania odwołania do komponentu korporacyjnego EJB.

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote></ejb-ref>

<ejb-ref-name>

Zestawienie<!ELEMENT ejb-ref-name (#DANE)>

OpisElement <ejb-ref-name> zawiera nazwę odwołania do EJB. Jest to nazwa JNDI, którą wykorzystuje kodserwletu w celu pobrania odwołania do komponentu korporacyjnego.

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote></ejb-ref>

Page 465: Java Servlet - Programowanie

<ejb-ref-type>

Zestawienie<!ELEMENT ejb-ref-type (#DANE)>

OpisElement <ejb-ref-type> zawiera typ komponentu. Jego wartość musi wynosić Entity lub Session.44

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote></ejb-ref>

<env-entry>

Zestawienie<!ELEMENT env-entry (description?, env-entry-name, env-entry-value?, env-entry-type)>

OpisElement <env-entry-type> zawiera deklarację pozycji środowiskowej aplikacji, dostępnej poprzezwyszukiwanie JNDI w kontekście java:comp/env.

<env-entry> <description>Wysyłanie kodu PIN pocztą</description> <env-entry-name>pocztaPIN</env-entry-name> <env-entry-value>false</env-entry-value> <env-entry-type>java.lang.Boolean</env-entry-type> <!--PNK--></env-entry>

<env-entry-name>

Zestawienie<!ELEMENT env-entry-name (#DANE)>

OpisElement <env-entry-name> określa nazwę pozycji środowiskowej aplikacji służącą do poszukiwań.

<env-entry> <description>Wysyłanie kodu PIN pocztą</description> <env-entry-name>pocztaPIN</env-entry-name> <env-entry-value>false</env-entry-value> <env-entry-type>java.lang.Boolean</env-entry-type> <!--PNK--></env-entry>

<env-entry-type>

Zestawienie<!ELEMENT env-entry-type (#DANE)>

OpisElement <env-entry-type> zawiera pełną nazwę typu Javy pozycji środowiskowej, który jest oczekiwanyprzez kod aplikacji. Prawidłowe wartości <env-entry-type> są następujące: java.lang.Boolean,java.lang.String, java.lang.Integer, java.lang.Double, and java.lang.Float.

44 Oryginalny komentarz do tego znacznika błędnie deklaruje, że znacznik „zawiera oczekiwany typ klasy Javy EJB, doktórego występuje odwołanie”.

Page 466: Java Servlet - Programowanie

<env-entry> <description>Wysyłanie kodu PIN pocztą</description> <env-entry-name>pocztaPIN</env-entry-name> <env-entry-value>false</env-entry-value> <env-entry-type>java.lang.Boolean</env-entry-type> <!--PNK--></env-entry>

<env-entry-value>

Zestawienie<!ELEMENT env-entry-value (#DANE)>

OpisElement <env-entry-value> zawiera domyślną wartość pozycji środowiskowej aplikacji.

<env-entry> <description>Wysyłanie kodu PIN pocztą</description> <env-entry-name>pocztaPIN</env-entry-name> <env-entry-value>false</env-entry-value> <env-entry-type>java.lang.Boolean</env-entry-type> <!--PNK--></env-entry>

<error-code>

Zestawienie<!ELEMENT error-code (#DANE)>

OpisElement <error-code> zawiera kod błędu HTTP.

<error-page> <error-code>400</error-code> <location>/400.html</location></error-page>

<error-page>

Zestawienie<!ELEMENT error-page ((error-code | exception-type), localization)>

OpisElement <error-page> zawiera odwzorowanie pomiędzy kodem błędu lub typem wyjątku I ścieżką dozasobu w aplikacji WWW.

<error-page> <error-code>400</error-code> <location>/400.html</location></error-page>

<exception-type>

Zestawienie<!ELEMENT exception-type (#DANE)>

OpisElement <exception-type> zawiera pełną nazwę klasy typu wyjątku Javy.

<error-page> <exception-type>javax.servlet.ServletException</exception-type> <location>/servler/WyswietlBlad</location></error-page>

Page 467: Java Servlet - Programowanie

<extension>

Zestawienie<!ELEMENT exception-type (#DANE)>

OpisElement <extension> zawiera łańcuch opisujący rozszerzenie pliku.

<mime-mapping> <extension>wml</extension> <mime-type>text/vnd.wap.wml</mime-type></mime-mapping>

<form-error-page>

Zestawienie<!ELEMENT form-error-page (#DANE)>

OpisElement <form-error-page> definiuje lokalizację w aplikacji WWW strony błędu, która jest wyświetlana,jeżeli logowanie nie powiedzie się.

<form-login-config> <form-login-page>/stronalog.html</form-login-page> <form-error-page>/stronablad.html</form-error-page></form-login-config>

<form-login-config>

Zestawienie<!ELEMENT form-login-config (form-login-page, form-error-page)>

OpisElement <form-login-config> określa strony logowania i błędu, które powinny zostać wykorzystanepodczas logowania opartego na formularzu. Jeżeli logowanie oparte na formularzu nie zostanie zastosowane,elementy te będą zignorowane.

<login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/stronalog.html</form-login-page> <form-error-page>/stronablad.html</form-error-page> </form-login-config></login-config>

<form-login-page>

Zestawienie<!ELEMENT form-login-page (#DANE)>

OpisElement <form-login-page> definiuje lokalizację w aplikacji WWW strony logowania.

<form-login-config> <form-login-page>/stronalog.html</form-login-page> <form-error-page>/stronablad.html</form-error-page></form-login-config>

Page 468: Java Servlet - Programowanie

<home>

Zestawienie<!ELEMENT home (#DANE)>

OpisElement <home> zawiera pełną nazwę głównego interfejsu EJB.

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote></ejb-ref>

<http-method>

Zestawienie<!ELEMENT http-method (#DANE)>

OpisElement <http-method> określa metodę HTTP.

<web-resource-collection> <web-resource-name>TajnaOchrona</web-resource-name> <url-pattern>/servlet/SerwerPlac</url-pattern> <url-pattern>/servlet/sekret</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method></web-resource-collection>

<icon>

Zestawienie<!ELEMENT icon (small-icon?, large-icon?)>

OpisElement <icon> może zawierać elementy <small-icon> i <large-icon> określające położeniewewnątrz struktury plików aplikacji WWW obrazków — małego i dużego — wykorzystywanych doprzedstawiania aplikacji WWW w narzędziu graficznym.

<icon> <small-icon>/obrazki/maly.gif</small-icon> <large-icon>/obrazki/duzy.gif</large-icon></icon>

<init-param>

Zestawienie<!ELEMENT init-param (param-name, param-value, description?)>

OpisElement <init-param> zawiera parę nazwa-wartość określającą parametr inicjacji serwletu.

<init-param> <param-name>klucz</param-name> <param-value>-915314447111823249</param-value></init-param>

Page 469: Java Servlet - Programowanie

<jsp-file>

Zestawienie<!ELEMENT jsp-file (#DANE)>

OpisElement <jsp-file> zawiera ścieżkę do pliku JSP wewnątrz aplikacji WWW.

<servlet> <servlet-name>zamowienie-katalog</servlet-name> <servlet-class>Katalog</servlet-class> <jsp-file>/formzamow.jsp</jsp-file></servlet>

<large-icon>

Zestawienie<!ELEMENT large-icon (#DANE)>

OpisElement <large-icon> określa położenie wewnątrz aplikacji WWW pliku zawierającego duży (32 x 32piksele) obrazek ikony. Narzędzia przyjmują formaty przynajmniej GIF i JPEG.

<icon> <small-icon>/obrazki/maly.gif</small-icon> <large-icon>/obrazki/duzy.gif</large-icon></icon>

<load-on-startup>

Zestawienie<!ELEMENT load-on-startup (#DANE)>

OpisElement <load-on-startup> wskazuje, że serwlet powinien być ładowany podczas startu aplikacji WWW.Opcjonalną zawartością tego elementu musi być dodatnia liczba całkowita określająca porządek ładowaniaserwletów. Jeżeli nie jest określona żadna wartość lub nie jest ona dodatnią liczbą całkowitą, kontener możezaładować serwlet w dowolnym momencie sekwencji startowej.

<servlet> <servlet-name>ps</servlet-name> <servlet-class>PierwszeSzukanie</servlet-class> <load-on-startup/></servlet>

<location>

Zestawienie<!ELEMENT location (#DANE)>

OpisElement <location> zawiera położenie zasobu w aplikacji WWW.

<error-page> <error-code>400</error-code> <location>/400.html</location></error-page>

Page 470: Java Servlet - Programowanie

<login-config>

Zestawienie<!ELEMENT login-config (auth-method?, realm-name?, form-login-config?)>

OpisElement <login-config> jest wykorzystywany do konfigurowania metody uwierzytelniania i nazwy obszaru,które powinny zostać wykorzystane w aplikacji, podobnie jak atrybuty logowania opartego na formularzu, jeżelizostanie ono wykorzystane.

<login-config> <auth-method>BASIC</auth-method> <realm-name>Domyslny</realm-name></login-config>

<mime-mapping>

Zestawienie<!ELEMENT mime-mapping (extension, mime-type)>

OpisElement <mime-mapping> definiuje odwzorowanie pomiędzy rozszerzeniem i typem MIME.

<mime-mapping> <extension>wml</extension> <mime-type>text/vnd.wap.wml</mime-type></mime-mapping>

<mime-type>

Zestawienie<!ELEMENT mime-type (#DANE)>

OpisElement <mime-type> zawiera zdefiniowany typ MIME.

<mime-mapping> <extension>wml</extension> <mime-type>text/vnd.wap.wml</mime-type></mime-mapping>

<param-name>

Zestawienie<!ELEMENT param-name (#DANE)>

OpisElement <param-name> zawiera nazwę parametru.

<context-param> <param-name>rmihost</param-name> <param-value>localhost</param-value></context-param>

<param-value>

Zestawienie<!ELEMENT param-value (#DANE)>

Page 471: Java Servlet - Programowanie

OpisElement <param-value> zawiera nazwę parametru.

<init-param> <param-name>klucz</param-name> <param-value>-915314447111823249</param-value></init-param>

<realm-name>

Zestawienie<!ELEMENT realm-name (#DANE)>

OpisElement <realm-name> określa nazwę obszaru, która powinna zostać wykorzystana podczas podstawowegouwierzytelniania TTP.

<login-config> <auth-method>BASIC</auth-method> <realm-name>Domyslny</realm-name></login-config>

<remote>

Zestawienie<!ELEMENT remote (#DANE)>

OpisElement <remote> zawiera pełną nazwę zdalnego interfejsu EJB.

<ejb-ref> <description>Kabiny na statku wycieczkowym</description> <ejb-ref-name>ejb/KabinaPocz</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.tytan.kabina.KabinaPocz</home> <remote>com.tytan.kabina.Kabina</remote></ejb-ref>

<res-auth>

Zestawienie<!ELEMENT res-auth (#DANE)>

OpisElement <auth-res> wskazuje, czy komponent kodu aplikacji przeprowadza przypisywanie zasobówprogramowo, czy też kontener przypisuje zasoby w oparciu o ogólną zasadę odwzorowywania określoną przezprogramistę. Musi mieć wartość CONTAINER lub SERVLET.

<resource-ref> <description>Podstawowa baza danych</description> <res-ref-name>jdbc/podstBD</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>CONTAINER</res-auth></resource-ref>

<res-ref-name>

Zestawienie<!ELEMENT res-ref-name (#DANE)>

Page 472: Java Servlet - Programowanie

OpisElement <res-ref-name> określa nazwę fabryki zasobów, do której wykonywane jest odwołanie.

<resource-ref> <description>Podstawowa baza danych</description> <res-ref-name>jdbc/podstBD</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>CONTAINER</res-auth></resource-ref>

<res-type>

Zestawienie<!ELEMENT res-type (#DANE)>

OpisElement <res-type> określa typ (klasę Javy) źródła danych/

<resource-ref> <description>Podstawowa baza danych</description> <res-ref-name>jdbc/podstBD</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>CONTAINER</res-auth></resource-ref>

<resource-ref>

Zestawienie<!ELEMENT resource-ref (description?, res-ref-name, res-type, res-auth)>

OpisElement <resource-ref> zawiera deklarację odwołania aplikacji WWW do zasobu zewnętrznego, takiegojak komponent Enterprise JavaBeans lub obiektu DataSource JDBC.

<resource-ref> <description>Podstawowa baza danych</description> <res-ref-name>jdbc/podstBD</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>CONTAINER</res-auth></resource-ref>

<role-link>

Zestawienie<!ELEMENT role-link (#DANE)>

OpisElement <role-link> jest wykorzystywany do łączenia odwołania do roli bezpieczeństwa ze zdefiniowanąrolą. Element musi zawierać nazwę jednej z ról bezpieczeństwa określonej w elementach <security-role>.

<security-role-ref> <role-name>men</role-name> <role-link>menedzer</role-link></security-role-ref>

<role-name>

Zestawienie<!ELEMENT role-name (#DANE)>

Page 473: Java Servlet - Programowanie

OpisElement <role-name> zawiera nazwę roli bezpieczeństwa.

<auth-constraint> <role-name>menedzer</role-name></auth-constraint>

<security-constraint>

Zestawienie<!ELEMENT security-constraint (web-resource-collection+, auth-constraint?, user-data-constraint?)>

Opis<security-constraint> <web-resource-collection> <web-resource-name>TajnaOchrona</web-resource-name> <url-pattern>/servlet/SerwerPlac</url-pattern> <url-pattern>/servlet/sekret</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>menedzer</role-name> </auth-constraint></security-constraint>

<security-role>

Zestawienie<!ELEMENT security-role (description?, role-name)>

OpisElement <security-role> zawiera deklarację roli bezpieczeństwa, która jest wykorzystywana wograniczeniach dostępu umieszczonych w aplikacji WWW.

<security-role> <role-name>menedzer</role-name></security-role>

<security-role-ref>

Zestawienie<!ELEMENT security-role-ref (description?, role-name, role-link)>

OpisElement <security-role-ref> określa alias dla roli bezpieczeństwa tworząc odwzorowanie pomiędzynazwą wykorzystywaną przez serwlet i nazwą zastosowaną w deskryptorze.

<security-role-ref> <role-name>men</role-name> <role-link>menedzer</role-link></security-role-ref>

<servlet>

Zestawienie<!ELEMENT servlet (icon?, servlet-name, display-name?, description?, (servlet-class|jsp-file), init-param*, load-on-startup?, security-role-ref*)>

Page 474: Java Servlet - Programowanie

OpisElement <servlet> zawiera deklaratywne dane serwletu, takie jak jego zarejestrowana nazwa i plik klasy.

<servlet> <servlet-name>witaj</servlet-name> <display-name>witaj</display-name> <description>Nie zapomnij się przywitać!</description> <servlet-class>WitajSwiecie</servlet-class> <init-param> <param-name>domyslnaNazwa</param-name> <param-value>Świecie</param-value> </init-param> <load-on-startup/> <security-role-ref> <role-name>men</role-name> <role-link>menedzer</role-link> </security-role-ref></servlet>

<servlet-class>

Zestawienie<!ELEMENT servlet-class (#DANE)>

Opis<servlet-class> zawiera pełną nazwę klasy serwletu/

<servlet> <servlet-name>witaj</servlet-name> <servlet-class>WitajSwiecie</servlet-class></servlet>

<servlet-mapping>

Zestawienie<!ELEMENT servlet-mapping (#DANE)>

OpisElement <servlet-mapping> definiuje odwzorowanie pomiędzy serwletem i wzorem URL-a.

<servlet-mapping> <servlet-name>witaj</servlet-name> <url-pattern>/witaj.html</url-pattern></servlet-mapping>

<servlet-name>

Zestawienie<!ELEMENT servlet-name (#DANE)>

OpisElement <servlet-name> określa kanoniczną nazwę serwletu.

<servlet> <servlet-name>witaj</servlet-name> <servlet-class>WitajSwiecie</servlet-class></servlet>

<session-config>

Zestawienie<!ELEMENT session-config (session-timeout?)>

Page 475: Java Servlet - Programowanie

OpisElement <session-config> definiuje parametry sesji dla aplikacji WWW.

<session-config> <session-timeout>60 <!-- minuty --></session-timeout></session-config>

<session-timeout>

Zestawienie<!ELEMENT session-timeout (#DANE)>

OpisElement <session-timeout> definiuje domyślny czas przedawnienia sesji dla wszystkich sesji utworzonychw aplikacji WWW. Dany czas przedawnienia musi być określony w pełnych minutach.

<session-config> <session-timeout>60 <!-- minuty --></session-timeout></session-config>

<small-icon>

Zestawienie<!ELEMENT small-icon (#DANE)>

OpisElement <small-icon> określa położenie wewnątrz aplikacji WWW pliku zawierającego mały (16 x 16pikseli) obrazek ikony. Narzędzia przyjmują formaty przynajmniej GIF i JPEG.

<icon> <small-icon>/obrazki/maly.gif</small-icon> <large-icon>/obrazki/duzy.gif</large-icon></icon>

<taglib>

Zestawienie<!ELEMENT taglib (taglib-uri, taglib-location)>

OpisElement <taglib> jest wykorzystywany w celu opisania biblioteki znaczników JSP.

<taglib> <taglib-uri>/WEB-INF/struts.tld</taglib-uri> <taglib-location>/WEB-INF/struts.tld</taglib-location></taglib>

<taglib-location>

Zestawienie<!ELEMENT taglib-location (#DANE)>

OpisElement <taglib-location> zawiera umiejscowienie (jako zasobu względnego do katalogu macierzystegoaplikacji WWW) pliku biblioteki znaczników (TLD).

<taglib> <taglib-uri>/WEB-INF/struts.tld</taglib-uri> <taglib-location>/WEB-INF/struts.tld</taglib-location></taglib>

Page 476: Java Servlet - Programowanie

<taglib-uri>

Zestawienie<!ELEMENT taglib-uri (#DANE)>

OpisElement <taglib-uri> opisuje URI względny do położenia dokumentu web.xml, który identyfikujebibliotekę znaczników wykorzystaną w aplikacji WWW.

<taglib> <taglib-uri>/WEB-INF/struts.tld</taglib-uri> <taglib-location>/WEB-INF/struts.tld</taglib-location></taglib>

<transport-guarantee>

Zestawienie<!ELEMENT transport-guarantee (#DANE)>

OpisElement <transport-guarantee> 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 tensposób, aby nie mogły zostać zmienione podczas transportu. CONFIDENTIAL oznacza, że aplikacja wymagaprzesyłania danych w ten sposób, aby zawartość transmisji nie mogła zostać zaobserwowana przez żaden innyprogram lub egzemplarz programu. W większości przypadków obecność znacznika INTEGRAL lubCONFIDENTIAL oznacza konieczność wykorzystania SSL.

<user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>

<url-pattern>

Zestawienie<!ELEMENT url-pattern (#DANE)>

OpisElement <url-pattern> 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, „Podstawyserwletów HTTP”.

<servlet-mapping> <servlet-name>witaj</servlet-name> <url-pattern>/witaj.html</url-pattern></servlet-mapping>

<user-data-constraint>

Zestawienie<!ELEMENT user-data-constraint (description?, transport-guarantee)>

OpisElement <user-data-constraint> jest wykorzystywany w celu wskazania sposobu ochrony danych przesyłanychpomiędzy klientem i kontenerem.

<security-constraint> <web-resource-collection> ...

Page 477: Java Servlet - Programowanie

</web-resource-collection> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint></security-constraint>

<web-app >

Zestawienie<!ELEMENT web-app (icon?, display-name?, description?, distributable?, context-param*, servlet*, servlet-mapping*, session-config?, mime-mapping*, welcome-file-list?, error-page*, taglib*, resource-ref*, security-constraint*, login-config?, security-role*, env-entry*, ejb-ref*)>

OpisElement <web-app> jest podstawowym elementem deskryptora aplikacji WWW.

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"><web-app> <servlet> <servlet-name>witaj</servlet-name> <servlet-class>WitajSwiecie</servlet-class> </servlet></web-app>

<web-resource-collection>

Zestawienie<!ELEMENT web-resource-collection (web-resource-name, description?, url-pattern*, http-method*)>

OpisElement <web-resource-collection> jest wykorzystywany w celu zidentyfikowania podzbioru zasobówi metod HTTP tych zasobów, do których odnoszą się ograniczenia dostępu. Jeżeli nie zostały określone żadnemetody HTTP, ograniczenia dostępu odnoszą się do wszystkich metod HTTP.

<security-constraint> <web-resource-collection> <web-resource-name>TajnaOchrona</web-resource-name> <url-pattern>/servlet/SerwerPlac</url-pattern> <url-pattern>/servlet/sekret</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection></security-constraint>

<web-resource-name>

Zestawienie<!ELEMENT web-resource-name (#DANE)>

Opis<web-resource-name> określa nazwę zbioru zasobów WWW.

<web-resource-collection> <web-resource-name>TajnaOchrona</web-resource-name> <url-pattern>/servlet/SerwerPlac</url-pattern> <url-pattern>/servlet/sekret</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method></web-resource-collection>

Page 478: Java Servlet - Programowanie

<welcome-file>

Zestawienie<!ELEMENT welcome-file (#DANE)>

OpisElement <welcome-file> zawiera nazwę pliku, który powinien zostać wykorzystany jako domyślny plikpowitalny.

<welcome-file-list> <welcome-file>indeks.html</welcome-file> <welcome-file>indeks.htm</welcome-file></welcome-file-list>

<welcome-file-list>

Zestawienie<!ELEMENT welcome-file-list (welcome-file+)>

Opis<welcome-file-list> określa listę plików, które powinien wyszukać kontenener, kiedy przeglądarkazażąda katalogu, a nie strony WWW lub serwletu.

<welcome-file-list> <welcome-file>indeks.html</welcome-file> <welcome-file>indeks.htm</welcome-file></welcome-file-list>

Page 479: Java Servlet - Programowanie

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 odpowiedzi100-199 Informacyjne200-299 Żądanie klienta pomyślne300-399 Żądanie klienta przekierowane, konieczne następne działanie400-499 Żądanie klienta niekompletne500-599 Błąd serweraTabela D.2 wylicza stałe kodów stanu zdefiniowane w interfejsie HttpServletResponse i wykorzystywanejako parametry metod setStatus() i sendError(). Numer wersji w ostatniej kolumnie odnosi się dowersji protokołu HTTP, która pierwsza zdefiniowała dany kod stanu. Servlet API 2.0 dodał stałe dla kodówstanu HTTP w wersji 1.1, jak opisano w proponowanym standardzie RFC 2068. Servlet API 2.2 dodał kodystanu 416 i 417, jak opisano w próbnym standardzie RFC 2616. Proszę zauważyć, że kody stanu HTTP/1.1wymagają przeglądarki zgodnej z HTTP/1.1.

Większa ilość informacji na temat HTTP jest dostępna w książce „Web Client Programming” autorstwa ClintonaWonga (O'Reilly). Najnowsza specyfikacja HTTP/1.1 jest dostępna w dokumencie RFC 2616 pod adresemhttp://www.ietf.org/rfc/rfc2616.txt.

Tabela D.2.

Stałe kodów stanu HTTP

Stała Kod Domyślnawiadomość

Znaczenie WersjaHTTP

SC_CONTINUE 100 Continue Serwer otrzymał początkową częśćżądania, klient może kontynuować zdalszymi częściami.

1.1

SC_SWITCHING_PROTOCOLS 101 SwitchingProtocols

Serwer chce wypełnić żądanie klientazmiany protokołu na określony wnagłówku żądania Upgrade.Działanie to może zawieraćprzełączenie na nowszą wersję HTTPlub własny synchroniczny kanałwideo.

1.1

SC_OK. 201 OK Żądanie klienta zakończyło sięsukcesem i odpowiedź serwerazawiera żądane dane. Jest to domyślnykod stanu.

1.0

Page 480: Java Servlet - Programowanie

SC_CREATED 201 Created Na serwerze został utworzony zasób,przypuszczalnie w odpowiedzi nażądanie klienta. Główna częśćodpowiedzi powinna zawierać URL(-e), pod którym można ten nowyzasób odnaleźć, z najbardziejspecyficznym URL-em zawartym wnagłówku Location. Jeżeli zasóbnie może zostać utworzonynatychmiast, powinien zamiennezostać zwrócony kod stanuSC_ACCEPTED.

1.0

SC_ACCEPTED 202 Accepted Żądanie zostało przyjęte doprzetwarzania, ale nie zostało jeszczeukończone. Serwer powinien opisaćaktualny stan żądania w głównejczęści odpowiedzi. Serwer nie jestzobligowany do działania na anikończenia wykonywania żądania.

1.0

SC_NON_AUTHORITATIVE_INFORMATION

203 Non-AuthoritativeInformation

Nagłówki odpowiedzi HTTP nadeszłyz lokalnego lub niezależnego źródła, anie z oryginalnego serwera. Zwykłeserwlety nie mają powodów, bywykorzystywać ten kod stanu

1.1

SC_NO_CONTENT 204 No Content Żądanie powiodło się, ale nie istniejegłówna część nowej odpowiedzi.Przeglądarki przyjmujące ten kodpowinny zachować swój aktualnywidok dokumentów. Kod ten jestprzydatny serwletowi, kiedyotrzymuje on dane z formularza, alechce, aby przeglądarka zatrzymała sięna formularzu, w ten sposób unikająckomunikatu o błędzie „Dokument niezawiera żadnych danych”.

1.0

SC_RESET_CONTENT 205 Reset Content Żądanie powiodło się i przeglądarkapowinno wyczyścić (ponowniepobrać) aktualnie przeglądanydokument. Kod ten jest przydatnyserwletowi, który pobiera dane zformularza i chce, aby został onwyświetlony w czystej formie.

1.1

SC_PARTIAL_CONTENT 206 PartialContent

Serwer ukończył częściowe żądanieGET i zwrócił część dokumentuokreśloną w nagłówku Range klienta.

1.1

SC_MULTIPLE_CHOICES 300 MultipleChoices

Żądany URL odnosi się do więcej niżjednego zasobu. Na przykład, URLmoże odnosić się do dokumentuprzetłumaczonego na wiele języków.Główna część odpowiedzi powinnawyjaśniać klientowi opcje w formieodpowiedniej do typu zawartościżądania. Serwer może zasugerowaćwybór przy pomocy nagłówkaLocation.

1.1

Page 481: Java Servlet - Programowanie

SC_MOVED_PERMANENTLY 301 MovedPermanently

Żądany zasób został na stałeprzeniesiony do nowej lokacji.Przyszłe odwołania powinnywykorzystywać w żądaniach nowyURL. Nowe umiejscowienie jestpodane w nagłówku Location.Większość przeglądarekautomatycznie przekierowuje donowej lokacji.

1.0

SC_MOVED_TEMPORARILY 302 MovedTemporarily

Żądany zasób został czasowoprzeniesiony do innej lokacji, aleprzyszłe żądania powinny dalejwykorzystywać oryginalny URI wcelu uzyskania dostępu do zasobu.Nowe umiejscowienie jest podane wnagłówku Location. Większośćprzeglądarek automatycznieprzekierowuje do nowej lokacji.

1.0

SC_SEE_OTHER 303 See Other Żądany zasób przetworzył żądanie, aleklient powinien pobrać swojąodpowiedź poprzez wykonanie GETna URL-u określonym w nagłówkuLocation. Kod ten jest przydatnyserwletowi, który chce otrzymywaćdane POST, po czym przekierowywaćklienta do innego zasobu w celuwygenerowania odpowiedzi.

1.1

SC_NOT_MODIFIED 304 Not Modified Żądany dokument nie zostałzmieniony od daty wymienionej wnagłówku żądania If-Modified-Since. Zwykłe serwlety nie powinnybyć zmuszone do korzystania z tegokodu stanu. Zamiast tegoimplementują onegetLastModified().

1.0

SC_USE_PROXY 305 Use Proxy Dostęp do żądanego zasobu musi byćuzyskiwany poprzez serwer proxypodany w nagłówku Location.

1.1

SC_BAD_REQUEST 400 Bad Request Serwer nie mógł zrozumieć żądania,prawdopodobnie z powodu błęduskładni.

1.0

SC_UNAUTHORIZED 401 Unauthorized Żądaniu brakuje właściwegouwierzytelnienia. Wykorzystywany wpołączeniu z nagłówkami WWW-Authenticate iAuthorization.

1.0

SC_PAYMENT_REQUIRED 402 PaymentRequired

Zarezerwowany do przyszłego użytku.Istnieją propozycje, bywykorzystywać ten kod w połączeniuz nagłówkiem Charge-To, ale nie stałsię on jeszcze standardem w czasieoddawania niniejszej książki dodruku.

1.1

SC_FORBIDDEN 403 Forbidden Żądanie zostało zrozumiane, aleserwer nie chce go wypełnić. Serwermoże wyjaśnić powody swojegooporu 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

Page 482: Java Servlet - Programowanie

SC_METHOD_NOT_ALLOWED 405 Method NotAllowed

Metoda wykorzystywana przez klientanie jest obsługiwana w tym URL-u.Metody obsługiwane muszą zostaćwymienione w nagłówku odpowiedziAccept.

1.1

SC_NOT_ACCEPTABLE 406 NotAcceptable

Żądany zasób istnieje, ale w formacienie przyjmowanym przez klienta (jakwskazano w nagłówku (ach) żądaniaAccept.

1.1

SC_PROXY_AUTHENTICATION_REQUIRED

407 ProxyAuthentication Required

Serwer proxy musi dokonaćuwierzytelnienia, zanim umożliwiprzejście dalej. Wykorzystywany znagłówkiem Proxy-Authenticate. Zwykłe serwletynie powinny być zmuszone dokorzystania z tego kodu stanu.

1.1

SC_REQUEST_TIMEOUT 408 RequestTimeout

Klient nie dokończył swojego żądaniaw czasie, w którym serwer był skłonnygo słuchać.

1.1

SC_CONFLICT 409 Conflict Żądanie nie mogło zostać wypełnione,ponieważ nastąpił konflikt z innymżądaniem lub konfiguracją serwera.Kod ten występuje najczęściej wprzypadku żądań HTTP PUT. Wktórych plik jest poddawany kontrolioraz w przypadku konfliktów nowychwersji z poprzednimi. Serwer możewysłać opis konfliktu w głównejczęści odpowiedzi

1.0

SC_GONE 410 Gone Zasób nie jest już dostępny na danymserwerze, a żaden alternatywny adresnie jest znany. Kod ten powinien byćwykorzystywany jedynie wprzypadku, kiedy zasób został trwaleusunięty. Zwykłe serwlety niepowinny być zmuszone do korzystaniaz tego kodu stanu.

1.1

SC_LENGTH_REQUIRED 411 LengthRequired

Serwer nie przyjmie żądania beznagłówka Content-Length.

1.1

SC_PRECONDITION_FAILED 412 PreconditionFailed

Wynik wstępnego warunkuokreślonego w jednym lub więcejnagłówku If... wynosi false.

1.1

SC_REQUEST_ENTITY_TOO_LARGE

413 RequestEntity TooLarge

Serwer nie przetworzy żądania,ponieważ zawartość żądania jest zbytduża. Jeżeli ograniczenie to jesttymczasowe, serwer może dołączyćnagłówek Retry-After.

1.1

SC_REQUEST_URI_TOO_LONG 414 Request-URIToo Long

Serwer nie przetworzy żądania,ponieważ URI żądania jest dłuższe,niż może przyjąć. Może to nastąpić,kiedy klient przypadkowoprzekonwertował żądanie POST naGET. Zwykłe serwlety nie powinnybyć zmuszone do korzystania z tegokodu stanu.

1.1

SC_UNSUPPORTED_MEDIA_TYPE

415 UnsupportedMedia Type

Serwer nie przetworzy żądania,ponieważ jego główna część posiadaformat nieobsługiwany przez żądanyzasób.

1.1

Page 483: Java Servlet - Programowanie

SC_REQUESTED_RANGE_NOT_SATISFYABLE

416 RequestedRange NotSatisfiable

Zakres zawartości żądanej przezklienta poprzez nagłówek Rangekoliduje z wielkością żądanegozasobu

1.1 (RFC2616)

SC_EXPECTATION_FAILED 417 ExpectationFailed

Oczekiwanie podane przez klientapoprzez nagłówek Expect nie mogłozostać spełnione.

1.1 (RFC2616)

SC_INTERNAL_SERVER_ERROR 500 InternalServer Error

Wewnątrz serwera nastąpiłniespodziewany błąd, któryprzeszkodziła w wypełnieniu żądania.

1.0

SC_NOT_IMPLEMENTED 501 NotImplemented

Serwer nie obsługuje funkcjonalnościpotrzebnej do wypełnienia żądania.

1.0

SC_BAD_GATEWAY 502 Bad Gateway Serwer działający jako brama lubproxy nie otrzymał prawidłowejodpowiedzi od głównego serwera.

1.0

SC_SERVICE_UNAVAILABLE 503 ServiceUnavailable

Usługa (serwer) jest tymczasowoniedostępna, ale powinna zostaćprzywrócona w przyszłości. Jeżeliserwer wie, kiedy będzie ponowniedostępny, może dołączyć nagłówekRetry-After.

1.0

SC_GATEWAY_TIMEOUT 504 GatewayTimeout

Serwer działający jako brama lubproxy nie otrzymał prawidłowejodpowiedzi od głównego serwera wczasie oczekiwania.

1.1

SC_HTTP_VERSION_NOT_SUPPORTED

505 HTTPVersion NotSupported

Serwer nie obsługuje wersji protokołuHTTP wykorzystanej w żądaniu.Główna część odpowiedzi powinnaokreślać protokoły obsługiwane przezserwer. Zwykłe serwlety nie powinnybyć zmuszone do korzystania z tegokodu stanu.

1.1

Page 484: Java Servlet - Programowanie

Dodatek E. Encje znakowe

Poniższa tablica wymienia różne kody ucieczkowe Unicode, encje nazwane i numeryczne HTML dla wszystkichmożliwych do wyświetlenia znaków ISO-8859 (Latin-1).

Encje nazwane i numeryczne mogą zostać wykorzystane na stronach HTML; są one konwertowane na symboleprzez przeglądarki WWW. Kody ucieczkowe Unicode mogą zostać wykorzystane w kodzie serwletów; są oneinterpretowane przez kompilator Javy. Na przykład, znak funta (£) może zostać osadzony na stronie HTML jako&#163; lub &pound;. 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 poziomobsł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ą standarduHTML, ale większości przypadków są powszechnie obsługiwane. N w tej kolumnie oznacza, że wartości encjinie są standardem i w związku z tym ich obsługa jest ograniczona. W przypadku tych symboli najlepiej jestzastosować kody ucieczkowe Unicode.

Koducieczkowy

Unicode

Encjanumeryczna

Encja nazwana Symbol Opis Obsługa

\u0009 &#009; \t Tabulator poziomy S\u000A &#010; \n Koniec linii S\u000D &#013; \r Powrót karetki S\u0020 &#032; Spacja S\u0021 &#033; ! Wykrzyknik S\u0022 &#034; &quot; " Cudzysłów S\u0023 &#035; # Płotek S\u0024 &#036; $ Znak dolara S\u0025 &#037; % Znak procent S\u0026 &#038; &amp; & Znak łączący S\u0027 &#039; ' Apostrof S\u0028 &#040; ( Lewy nawias S\u0029 &#041; ) Prawy nawias S\u002A &#042; * Gwiazdka S\u002B &#043; + Znak dodawania S\u002C &#044; , Przecinek S\u002D &#045; - Myślnik S\u002E &#046; . Kropka S\u002F &#047; / Ukośnik S\u0030-\u0039

&#048;-&#057

0-9 Cyfry 0-9 S

\u003A &#058; : Dwukropek S

Page 485: Java Servlet - Programowanie

\u003B &#059; ; Średnik S\u003C &#060; &lt; < Znak mniejszości S\u003D &#061; = Znak równości S\u003E &#062; &gt; > Znak większości S\u003F &#063; ? Znak zapytania S\u0040 &#064; @ „Małpa” S\u0041-\u005A

&#065;-&#090;

A-Z Litery A-Z S

\u005B &#091; [ Lewy nawias kwadratowy S\u005C &#092; \ Lewy ukośnik S\u005D &#093; ] Prawy nawias kwadratowy S\u005E &#094; ^ Karetka S\u005F &#095; _ Kreska dolna S\u0060 &#096; ` Akcent grave S\u0061-\u007A

&#097;-&#122;

a-z Litery a-z S

\u007B &#123; { Lewy nawias klamrowy S\u007C &#124; | Kreska pionowa S\u007D &#125; } Prawy nawias klamrowy S\u007E &#126; ~ Tylda S\u0082 &#130; ‚ Dolny lewy nawias pojedynczy N\u0083 &#131; f Floren N\u0084 &#132; „ Lewy dolny nawias podwójny N\u0085 &#133; … Trzykropek N\u0086 &#134; † Sztylet N\u0087 &#135; ‡ Podwójny sztylet N\u0088 &#136; ˆ circumflex N\u0089 &#137; ‰ Promil N\u008A &#138; Š Duże S, caron N\u008B &#139; ‹ Znak mniejszości N\u008C &#140; Œ Duże OE, ligatura N\u0091 &#145; ‘ Lewy pojedynczy cudzysłów N\u0092 &#146; ’ Prawy pojedynczy cudzysłów N\u0093 &#147; “ Lewy podwójny cudzysłów N\u0094 &#148; ” Prawy podwójny cudzysłów N\u0095 &#149; • Pocisk N\u0096 &#150; – Krótki myślnik N\u0097 &#151; — Długi myślnik N\u0098 &#152; ~ Tylda N\u0099 &#153; ™ Znak towarowy N\u009A &#154; š Małe s, caron N\u009B &#155; › Znak większości N\u009C &#156; œ Małe oe, ligatura N\u009F &#159; Ÿ Duże Y, umlaut N\u00A0 &#160; &nbsp; Spacja niełamiąca P\u00A1 &#161; &iexcl; ¡ Odwrócony wykrzyknik P\u00A2 &#162; &cent; ¢ Znak centa P\u00A3 &#163; &pound; £ Znak funta P\u00A4 &#164; &curren; ¤ Ogólny znak waluty P\u00A5 &#165; &yen; ¥ Znak jena P\u00A6 &#166; &brvbar; ¦ Złamana pionowa kreska P\u00A7 &#167; &sect; § Paragraf P\u00A8 &#168; &uml; ¨ Umlaut P\u00A9 &#169; &copy; © Prawa autorskie P\u00AA &#170; &ordf; ª Żeński liczebnik porządkowy P\u00AB &#171; &laquo; « Lewy cudzysłów kątowy P\u00AC &#172; &not; ¬ Znak zaprzeczenia P\u00AD &#173; &shy; „Miękki” myślnik P\u00AE &#174; &reg; ® Zarejestrowany znak towarowy P\u00AF &#175; &macr; ¯ Akcent macron P

Page 486: Java Servlet - Programowanie

\u00B0 &#176; &deg; ° Znak stopnia P\u00B1 &#177; &plusmn; ± Plus lub minus P\u00B2 &#178; &sup2; ² Indeks górny 2 P\u00B3 &#179; &sup3; ³ Indeks górny 3 P\u00B4 &#180; &acute; ´ Akcent acute P\u00B5 &#181; &micro; µ Znak mikro (greckie mi) P\u00B6 &#182; &para; ¶ Znak akapitu P\u00B7 &#183; &middot; · Kropka centralna P\u00B8 &#184; &cedil; ¸ Cedilla P\u00B9 &#185; &sup1; ¹ Indeks górny 1 P\u00BA &#186; &ordm; º Męski liczebnik porządkowy P\u00BB &#187; &raquo; » Prawy cudzysłów kątowy P\u00BC &#188; &frac14; ¼ Ułamek jedna czwarta P\u00BD &#189; &frac12; ½ Ułamek jedna druga P\u00BE &#190; &frac34; ¾ Ułamek trzy czwarte P\u00BF &#191; &iquest; ¿ Odwrócony znak zapytania P\u00C0 &#192; &Agrave; À Duże A, akcent grave S\u00C1 &#193; &Aacute; Á Duże A, akcent acute S\u00C2 &#194; &Acirc; Â Duże A, akcent circumflex S\u00C3 &#195; &Atilde; Ă Duże A, tylda S\u00C4 &#196; &Auml; Ä Duże A, umlaut S\u00C5 &#197; &Aring; Å Duże A, okrąg S\u00C6 &#198; &Aelig; Æ Duże A, ligatura S\u00C7 &#199; &Ccedil; Ç Duże C, cedilla S\u00C8 &#200; &Egrave; È Duże E, akcent grave S\u00C9 &#201; &Eacute; É Duże E, akcent acute S\u00CA &#202; &Ecirc; Ê Duże E, akcent circumflex S\u00CB &#203; &Euml; Ë Duże E, umlaut S\u00CC &#204; &Igrave; Ì Duże I, akcent grave S\u00CD &#205; &Iacute; Í Duże I, akcent acute S\u00CE &#206; &Icirc; Î Duże I, akcent circumflex S\u00CF &#207; &Iuml; Ï Duże I, umlaut S\u00D0 &#208; &ETH; Ð Duże eth, islandzki S\u00D1 &#209; &Ntilde; Ñ Duże N, tylda S\u00D2 &#210; &Ograve; Ò Duże O, akcent grave S\u00D3 &#211; &Oacute; Ó Duże O, akcent acute S\u00D4 &#212; &Ocirc; Ô Duże O, akcent circumflex S\u00D5 &#213; &Otilde; Õ Duże O, tylda S\u00D6 &#214; &Ouml; Ö Duże O, umlaut S\u00D7 &#215; &times; × Znak mnożenia P\u00D8 &#216; &Oslash; Ø Duże O, ukośnik S\u00D9 &#217; &Ugrave; Ù Duże U, akcent grave S\u00DA &#218; &Uacute; Ú Duże U, akcent acute S\u00DB &#219; &Ucirc; Û Duże U, akcent circumflex S\u00DC &#220; &Uuml; Ü Duże U, umlaut S\u00DD &#221; &Yacute; Ý Duże Y, akcent acute S\u00DE &#222; &THORN; Þ Duże thorn, islandzki S\u00DF &#223; &szlig; ß Małe sz, ligatura, niemiecki S\u00E0 &#224; &agrave; à Małe a, akcent grave S\u00E1 &#225; &aacute; á Małe a, akcent acute S\u00E2 &#226; &acirc; â Małe a, akcent circumflex S\u00E3 &#227; &atilde; ã Małe a, tylda S\u00E4 &#228; &auml; ä Małe a, umlaut S\u00E5 &#229; &aring; å Małe a, okrąg S\u00E6 &#230; &aelig; æ Małe a, ligatura S\u00E7 &#231; &ccedil; ç Małe c, cedilla S\u00E8 &#232; &egrave; è Małe e, akcent grave S\u00E9 &#233; &eacute; é Małe e, akcent acute S\u00EA &#234; &ecirc; ê Małe e, akcent circumflex S\u00EB &#235; &euml; ë Małe e, umlaut S\u00EC &#236; &igrave; ì Małe i, akcent grave S

Page 487: Java Servlet - Programowanie

\u00ED &#237; &iacute; í Małe i, akcent acute S\u00EE &#238; &icirc; î Małe i, akcent circumflex S\u00EF &#239; &iuml; ï Małe i, umlaut S\u00F0 &#240; &eth; ð Małe eth, islandzki S\u00F1 &#241; &ntilde; ñ Małe n, tylda S\u00F2 &#242; &ograve; ò Małe o, akcent grave S\u00F3 &#243; &oacute; ó Małe o, akcent acute S\u00F4 &#244; &ocirc; ô Małe o, akcent circumflex S\u00F5 &#245; &otilde; õ Małe o, tylda S\u00F6 &#246; &ouml; ö Małe o, umlaut S\u00F7 &#247; &divide; ÷ Znak dzielenia P\u00F8 &#248; &oslash; ø Małe o, ukośnik S\u00F9 &#249; &ugrave; ù Małe u, akcent grave S\u00FA &#250; &uacute; ú Małe u, akcent acute S\u00FB &#251; &ucirc; û Małe u, akcent circumflex S\u00FC &#252; &uuml; ü Małe u, umlaut S\u00FD &#253; &yacute; ý Małe y, akcent acute S-\u00FE &#254; &thorn; þ Małe thorn, islandzki S\u00FF &#255; &yuml; ÿ Małe y, umlaut S

Page 488: Java Servlet - Programowanie

Dodatek F. Kodowania

Poniższa tabela zawiera sugerowane kodowania dla dużej ilości języków. Kodowania są wykorzystywane przezserwlety 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ójPrintWriter, na przykład:

odp.setContentType("text/html; charset=Shift_JIS"); // Kodowanie japońskiePrintWriter 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_JISPrintWriter wyj = odp.getWriter; // Zapisuje japoński Shift_JIS

Metoda setLocale() przypisuje odpowiedzi kodowanie zgodnie z poniższą tabelą. W przypadku, gdymoż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 znakiUnicode, 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 kodowaniaangielski en ISO-8859-1Albański sq ISO-8859-2Arabski ar ISO-8859-6białoruski be ISO-8859-5Bułgarski bg ISO-8859-5chiński (tradycyjny / Tajwan) zh (kraj TW) Big5chiński (uproszczony / kontynentalny zh GB2312Chorwacki hr ISO-8859-2czeski cs ISO-8859-2duński da ISO-8859-1holenderski nl ISO-8859-1estoński et ISO-8859-1fiński fi ISO-8859-1francuski fr ISO-8859-1grecki el ISO-8859-7hebrajski he (dawniej iw) ISO-8859-8hiszpański es ISO-8859-1islandzki is ISO-8859-1japoński ja Shift_JIS, ISO-2022-JP, EUC-JP45

kataloński (hiszpański) ca ISO-8859-1

45 Obsługiwany po raz pierwszy w JDK 1.1.6. Poprzednie wersje JDK znają zestaw znaków EUC-JP pod nazwą EUCJIS, takwięc w celu zapewnienia przenośności można ustawić kodowanie na EUC_JP i samodzielnie skonstruować PrintWriterEUCJIS.

Page 489: Java Servlet - Programowanie

koreański ko EUC-KR46

litewski lt ISO-8859-2łotewski lv ISO-8859-2macedoński mk ISO-8859-5niemiecki de ISO-8859-1polski pl ISO-8859-2portugalski pt ISO-8859-1rosyjski ru ISO-8859-5, KOI8-Rrumuński ro ISO-8859-2serbski sr ISO-8859-5, KOI8-Rserbsko-chorwacki sh ISO-8859-5, ISO-8859-2, KOI8-Rsłowacki sk ISO-8859-2słoweński sl ISO-8859-2szwedzki sv ISO-8859-1turecki tr ISO-8859-9ukraiński uk ISO-8859-5, KOI8-Rwęgierski hu ISO-8859-2włoski it ISO-8859-1

46 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.