Professional Documents
Culture Documents
Java Servlet - Programowanie
Java Servlet - Programowanie
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
Przetwarzanie danych formularzowych..........................................................................................................32
Obsługa zleceń POST..................................................................................................................................... 34
Obsługa zleceń HEAD.................................................................................................................................... 34
APLIKACJE WWW................................................................................................................................................. 35
Katalog WEB-INF...........................................................................................................................................36
Deskryptor wdrożenia.....................................................................................................................................37
PRZEJDŹMY DALEJ....................................................................................................................................................40
ROZDZIAŁ 3. CZAS ISTNIENIA (CYKL ŻYCIA) APLETU........................................................................ 41
ALTERNATYWA APLETU.............................................................................................................................................41
Pojedyncza maszyna wirtualna Javy.............................................................................................................. 41
Trwałość kopii.................................................................................................................................................42
Liczniki............................................................................................................................................................42
Liczniki zsynchronizowane..............................................................................................................................43
Liczniki całościowe......................................................................................................................................... 45
ODNAWIANIE (POWTÓRNE ŁADOWANIE) APLETU............................................................................................................ 46
METODY „INIT” I „DESTROY”...................................................................................................................................46
Licznik z metodą Init....................................................................................................................................... 47
Licznik z metodami Init i Destroy................................................................................................................... 49
Model jedno-wątkowy (Single-Thread Model)............................................................................................... 51
PRZETWARZANIE DRUGOPLANOWE...............................................................................................................................53
URUCHAMIANIE I ROZRUCH........................................................................................................................................54
BUFOROWANIE PODRĘCZNE PO STRONIE KLIENTA........................................................................................................... 55
BUFOROWANIE ZEWNĘTRZNE PO STRONIE SERWERA........................................................................................................57
ROZDZIAŁ 4. ODCZYTYWANIE INFORMACJI.......................................................................................... 65
APLET....................................................................................................................................................................66
Pobieranie parametru początkowego apletu.................................................................................................. 66
Pobieranie nazw parametrów początkowych apletów....................................................................................67
Pobieranie nazwy apletu.................................................................................................................................68
SERWER ................................................................................................................................................................ 68
Pobieranie informacji o serwerze...................................................................................................................68
Zapisywanie w pliku tymczasowym.................................................................................................................70
Ograniczanie działania apletu do serwera.....................................................................................................71
Pobieranie kontekstowego parametru początkowego.................................................................................... 72
Ustalanie wersji apletu................................................................................................................................... 73
KLIENT.................................................................................................................................................................. 75
Pobieranie informacji o komputerze klienta...................................................................................................75
Ograniczanie dostępu..................................................................................................................................... 76
Pobieranie informacji o użytkowniku............................................................................................................. 77
Personalizowane powitanie............................................................................................................................ 78
Zlecenie .......................................................................................................................................................... 79
Parametry zlecenia......................................................................................................................................... 79
Generowanie Klucza Licencji......................................................................................................................... 81
Informacje ścieżki........................................................................................................................................... 83
Pobieranie typów MIME.................................................................................................................................86
Podawanie plików...........................................................................................................................................87
Czytanie z zasobów oddzielonych................................................................................................................... 88
Podawanie zasobów........................................................................................................................................90
Podawanie zasobów do ściągnięcia............................................................................................................... 92
Określanie co było przedmiotem zlecenia...................................................................................................... 93
Sposób złożenia zlecenia.................................................................................................................................94
Nagłówki zleceniowe.......................................................................................................................................95
Strumień wyjściowy.........................................................................................................................................98
Ładowanie plików przy użyciu strumienia wejściowego................................................................................ 99
Dodatkowe atrybuty......................................................................................................................................106
ROZDZIAŁ 5. PRZESYŁANIE INFORMACJI HTTP..................................................................................108
STRUKTURA ODPOWIEDZI.........................................................................................................................................108
PRZESYŁANIE STANDARDOWEJ ODPOWIEDZI ............................................................................................................... 109
KORZYSTANIE ZE POŁĄCZEŃ STAŁYCH....................................................................................................................... 110
BUFOROWANIE ODPOWIEDZI..................................................................................................................................... 111
Regulowanie bufora odpowiedzi...................................................................................................................112
KODY STATUSU..................................................................................................................................................... 114
Ustanawianie kodu statusu........................................................................................................................... 114
Ulepszanie apletu „ViewFile” przy pomocy kodów statusu.........................................................................115
Nagłówki HTTP ........................................................................................................................................... 116
USTAWIANIE NAGŁÓWKÓW HTTP .......................................................................................................................... 117
Przekierowywanie zlecenia...........................................................................................................................118
Nadzorowanie łączników do innych stron.................................................................................................... 119
Klienckie ściąganie z serwera.......................................................................................................................120
ROZWIĄZYWANIE PROBLEMÓW................................................................................................................................. 121
Kody statusu..................................................................................................................................................122
Konfiguracja stron błędu.............................................................................................................................. 123
Rejestracja.................................................................................................................................................... 125
Raportowanie................................................................................................................................................126
Wyjątki.......................................................................................................................................................... 127
Wyjątki apletowe „ServletException”.......................................................................................................... 127
Wyjątki apletowe „UnavailableException”..................................................................................................128
Konfiguracja Stron Wyjątków.......................................................................................................................128
Jak 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..................................................................................................................................................... 134
WML..............................................................................................................................................................136
Symulatory urządzeń WAP............................................................................................................................137
Podawanie treści WAP................................................................................................................................. 137
Dynamiczna treść WAP.................................................................................................................................138
Poznajmy WAP..............................................................................................................................................142
OBRAZKI.............................................................................................................................................................. 142
Generowanie obrazków................................................................................................................................ 142
Obrazek „Hello World”................................................................................................................................143
Dynamicznie generowany schemat...............................................................................................................145
Składanie obrazu...........................................................................................................................................147
Rysowanie obrazków.....................................................................................................................................147
Obrazki — efekty...........................................................................................................................................152
TREŚĆ SKOMPRESOWANA.........................................................................................................................................155
SERWER CYKLICZNY............................................................................................................................................... 157
ROZDZIAŁ 7. ŚLEDZENIE SESJI ..................................................................................................................161
UWIERZYTELNIANIE UŻYTKOWNIKA........................................................................................................................... 162
UKRYTE POLA DANYCH FORMULARZA........................................................................................................................163
PRZEPISYWANIE URL-U......................................................................................................................................... 165
TRWAŁE COOKIES...................................................................................................................................................167
Praca z cookies............................................................................................................................................. 167
Robienie zakupów przy pomocy trwałych cookies........................................................................................169
API — ŚLEDZENIE SESJI.........................................................................................................................................171
Podstawy śledzenia sesji............................................................................................................................... 171
Wykorzystywanie śledzenia sesji — liczba wizyt...........................................................................................172
Czas istnienia (cykl życia) sesji.....................................................................................................................173
Ustawianie terminu ważności sesji............................................................................................................... 174
Metody czasu trwania................................................................................................................................... 176
Awaryjne zmiany trybu pracy — „nie-ciasteczkowe”.................................................................................. 178
Robienie zakupów przy użyciu śledzenia sesji.............................................................................................. 182
ROZDZIAŁ 8. BEZPIECZEŃSTWO............................................................................................................... 184
Uwierzytelnienie poprzez HTTP................................................................................................................... 185
KONFIGURACJA UWIERZYTELNIENIA HTTP................................................................................................................185
Uwierzytelnienie w oparciu o rolę................................................................................................................185
Ograniczanie dostępu do serwletu................................................................................................................186
WYSZUKIWANIE INFORMACJI UWIERZYTELNIENIA......................................................................................................... 188
Uwierzytelnienie w oparciu o formularz.......................................................................................................189
Uwierzytelnienie niestandardowe.................................................................................................................191
Certyfikaty cyfrowe....................................................................................................................................... 195
Protokół bezpiecznej transmisji danych (SSL)..............................................................................................197
ROZDZIAŁ 9. ŁĄCZNOŚĆ Z BAZĄ DANYCH.............................................................................................202
RELACYJNE BAZY DANYCH...................................................................................................................................... 203
JDBC API..........................................................................................................................................................205
Sterowniki JDBC...........................................................................................................................................205
Połączenie z bazą danych............................................................................................................................. 206
Otwieranie połączenia z serwletu................................................................................................................. 207
Wykonywanie zapytań SQL...........................................................................................................................208
Obsługa wyjątków SQL.................................................................................................................................209
Zestawy wyników w szczegółach...................................................................................................................210
Obsługa pól mających wartość null..............................................................................................................211
Aktualizacja baz danych............................................................................................................................... 212
Użycie gotowych zapytań..............................................................................................................................213
Ponowne użycie obiektów bazy danych........................................................................................................ 214
Ponowne użycie połączeń do baz danych..................................................................................................... 214
Ponowne użycie przygotowanych wyrażeń................................................................................................... 215
TRANSAKCJE......................................................................................................................................................... 215
Użycie transakcji w JDBC............................................................................................................................ 216
Optymalizacja transakcji.............................................................................................................................. 217
Pule połączeń................................................................................................................................................217
Serwlet księgi gości.......................................................................................................................................221
ZAAWANSOWANE TECHNIKI JDBC........................................................................................................................... 225
Przechowywane procedury........................................................................................................................... 225
Pliki binarne i księgi, czyli bardzo duże obiekty...........................................................................................226
CO DALEJ?........................................................................................................................................................... 227
ROZDZIAŁ 10. KOMUNIKACJA APLET-SERWLET................................................................................ 229
OPCJE KOMUNIKACJI...............................................................................................................................................229
Aplety godne i niegodne zaufania.................................................................................................................229
Połączenia przez HTTP i zwykłe porty......................................................................................................... 230
Serwlety i serializacja obiektu...................................................................................................................... 231
JDBC, RMI i CORBA....................................................................................................................................232
Podejście hybrydowe.................................................................................................................................... 233
SERWER GODZINY.................................................................................................................................................. 233
Aplet.............................................................................................................................................................. 233
Oparta na tekście komunikacja HTTP..........................................................................................................236
Oparta na obiektach komunikacja HTTP..................................................................................................... 240
Komunikacja przez port................................................................................................................................ 242
Komunikacja RMI......................................................................................................................................... 248
Wskazówka!!!!...............................................................................................................................................253
W którym miejscu uruchomić rejestr?.......................................................................................................... 253
SERWER POGAWĘDEK..............................................................................................................................................255
Projekt...........................................................................................................................................................256
Serwlet...........................................................................................................................................................257
Aplet HTTP................................................................................................................................................... 260
Aplet łączący się przez port...........................................................................................................................262
Aplet RMI...................................................................................................................................................... 263
Dyspozytor.................................................................................................................................................... 266
ROZDZIAŁ 11. WSPÓŁPRACA SERWLETÓW...........................................................................................268
DZIELENIE INFORMACJI............................................................................................................................................268
Współdzielenie przy pomocy ServletContext................................................................................................ 268
Współdzielenie z innym ServletContext........................................................................................................ 270
DZIELENIE KONTROLI..............................................................................................................................................270
Pobieranie dyspozytora żądań......................................................................................................................271
Dyspozycja przekazaniem............................................................................................................................. 271
Przekazanie czy przekierowanie................................................................................................................... 273
Dyspozycja dołączania................................................................................................................................. 274
ROZDZIAŁ 12. SERWLETY KORPORACYJNE I J2EE.............................................................................277
DYSTRYBUCJA ŁADUNKU.........................................................................................................................................277
Jak być dystrybuowalnym............................................................................................................................. 278
Wiele stylów dystrybucji................................................................................................................................279
INTEGRACJA Z J2EE.............................................................................................................................................. 279
Podział pracy w J2EE...................................................................................................................................280
Pozycje środowiskowe.................................................................................................................................. 280
Odwołania do elementów EJB......................................................................................................................282
Odwołania do zewnętrznych fabryk zasobów............................................................................................... 282
Dystrybucja serwletów w środowisku J2EE................................................................................................. 283
ROZDZIAŁ 13. INTERNACJONALIZACJA................................................................................................. 284
JĘZYKI ZACHODNIOEUROPEJSKIE................................................................................................................................284
Encje znakowe HTML................................................................................................................................... 285
Kody ucieczkowe Unicode............................................................................................................................ 286
HOŁDOWANIE LOKALNYM ZWYCZAJOM...................................................................................................................... 287
JĘZYKI SPOZA EUROPY ZACHODNIEJ......................................................................................................................... 288
Kodowanie.................................................................................................................................................... 288
Tworzenie wyników zakodowanych.............................................................................................................. 289
Odczyt i zapis wyników zakodowanych........................................................................................................ 290
WIĘKSZA ILOŚĆ JĘZYKÓW........................................................................................................................................291
UCS-2 i UTF-8..............................................................................................................................................291
Tworzenie UTF-8.......................................................................................................................................... 292
DYNAMICZNA NEGOCJACJA JĘZYKA........................................................................................................................... 293
Preferencje językowe.................................................................................................................................... 294
Preferencje kodowania................................................................................................................................. 294
Pakiety zasobów............................................................................................................................................294
Wyświetlanie odpowiednich informacji........................................................................................................ 295
Klasa LocaleNegotiator................................................................................................................................ 296
Lokalizacje dostarczane przez system...........................................................................................................299
FORMULARZE HTML............................................................................................................................................299
Ukryte kodowanie......................................................................................................................................... 300
ROZDZIAŁ 14. SZKIELET TEA......................................................................................................................304
JĘZYK TEA........................................................................................................................................................... 304
POCZĄTKI............................................................................................................................................................. 305
INFORMACJA O ŻĄDANIU..........................................................................................................................................306
Sięganie głębiej.............................................................................................................................................307
ADMINISTRACJA TEA..............................................................................................................................................308
Skompilowane szablony: szansa dla biznesu................................................................................................ 311
ZASTOSOWANIA TEA.............................................................................................................................................. 312
Przetwarzanie tekstu..................................................................................................................................... 312
Obsługa zawartości.......................................................................................................................................313
Obsługa żądań/odpowiedzi........................................................................................................................... 314
Tworzenie aplikacji Tea................................................................................................................................315
APLIKACJA „NARZĘDZIA”....................................................................................................................................... 317
OSTATNIE SŁOWO...................................................................................................................................................324
ROZDZIAŁ 15. WEBMACRO..........................................................................................................................325
SZKIELET WEBMACRO...........................................................................................................................................325
Powitanie przy pomocy WebMacro.............................................................................................................. 326
INSTALACJA WEBMACRO....................................................................................................................................... 328
Język szablonów WebMacro......................................................................................................................... 329
Narzędzia kontekstu WebMacro................................................................................................................... 330
INSTRUKCJE WEBMACRO....................................................................................................................................... 332
#if.................................................................................................................................................................. 332
#set................................................................................................................................................................ 332
#foreach........................................................................................................................................................ 332
#parse............................................................................................................................................................333
#include.........................................................................................................................................................333
#param.......................................................................................................................................................... 333
#use............................................................................................................................................................... 333
SZABLONY WEBMACRO......................................................................................................................................... 334
Gotowy do ponownego wykorzystania serwlet MacroPrzegl....................................................................... 335
Przetwarzanie szablonów..............................................................................................................................337
APLIKACJA „NARZĘDZIA”....................................................................................................................................... 337
FILTRY................................................................................................................................................................. 341
Filtry niestandardowe...................................................................................................................................341
ROZDZIAŁ 16. ELEMENT CONSTRUCTION SET.....................................................................................342
ELEMENTY STRONY JAKO OBIEKTY............................................................................................................................342
WYŚWIETLANIE ZBIORU WYNIKÓW............................................................................................................................ 343
Dostosowywanie wyświetlania......................................................................................................................345
ROZDZIAŁ 17. XMLC.......................................................................................................................................351
PROSTA KOMPILACJA XML.................................................................................................................................... 351
KLASA MANIPULACYJNA..........................................................................................................................................358
Modyfikacja listy...........................................................................................................................................359
APLIKACJA „NARZĘDZIA”....................................................................................................................................... 361
ROZDZIAŁ 18. JAVASERVER PAGES..........................................................................................................367
WYKORZYSTYWANIE JAVASERVER PAGES................................................................................................................. 368
ZASADY DZIAŁANIA................................................................................................................................................ 369
WYRAŻENIA I DEKLARACJE......................................................................................................................................371
INSTRUKCJE.......................................................................................................................................................... 371
Wykorzystanie instrukcji............................................................................................................................... 372
Unikanie kodu Javy na stronach JSP........................................................................................................... 374
JSP I JAVABEANS................................................................................................................................................. 375
Osadzanie komponentu Bean........................................................................................................................375
Kontrola parametrów komponentu...............................................................................................................377
Powitania przy pomocy komponentu............................................................................................................ 377
DOŁĄCZENIA I PRZEKAZANIA....................................................................................................................................379
APLIKACJA „NARZĘDZIA”....................................................................................................................................... 380
BIBLIOTEKI WŁASNYCH ZNACZNIKÓW.........................................................................................................................383
Stosowanie bibliotek własnych znaczników..................................................................................................383
Aplikacja „Narzędzia” wykorzystująca bibliotekę własnych znaczników....................................................385
ROZDZIAŁ 19. INFORMACJE DODATKOWE........................................................................................... 387
ANALIZA PARAMETRÓW...........................................................................................................................................387
Kod ParameterParser...................................................................................................................................388
WYSYŁANIE POCZTY ELEKTRONICZNEJ.......................................................................................................................391
Stosowanie klasy MailMessage.....................................................................................................................391
Wysyłanie pocztą danych formularza........................................................................................................... 392
STOSOWANIE WYRAŻEŃ REGULARNYCH......................................................................................................................392
Odnajdywanie łącz przy pomocy wyrażeń regularnych............................................................................... 393
URUCHAMIANIE PROGRAMÓW...................................................................................................................................396
Finger............................................................................................................................................................396
Uruchamianie polecenia finger.................................................................................................................... 396
Uruchamianie finger z argumentami............................................................................................................397
Uruchamianie finger z przekierowywanym wynikiem.................................................................................. 398
STOSOWANIE RDZENNYCH METOD............................................................................................................................. 398
WYSTĘPOWANIE JAKO KLIENT RMI..........................................................................................................................399
USUWANIE BŁĘDÓW............................................................................................................................................... 400
Sprawdzenie dzienników zdarzeń..................................................................................................................400
Wyświetlenie dodatkowych informacji..........................................................................................................401
Stosowanie standardowego programu uruchomieniowego..........................................................................401
Analiza żądania klienta.................................................................................................................................402
Utworzenie własnego żądania klienta.......................................................................................................... 403
Wykorzystanie niezależnego narzędzia......................................................................................................... 405
Ostatnie wskazówki.......................................................................................................................................405
POPRAWA WYDAJNOŚCI........................................................................................................................................... 406
Tworzyć, ale nie przesadzać......................................................................................................................... 406
Nie łączyć...................................................................................................................................................... 406
Ograniczać synchronizację...........................................................................................................................406
Buforować dane wprowadzane i wyświetlane.............................................................................................. 407
Spróbować wykorzystania OutputStream..................................................................................................... 407
Wykorzystać narzędzie profilujące............................................................................................................... 407
ROZDZIAŁ 20. ZMIANY W SERVLET API 2.3............................................................................................408
ZMIANY W SERVLET API 2.3................................................................................................................................. 408
Serwlety w J2SE i J2EE................................................................................................................................ 409
Filtry............................................................................................................................................................. 409
Zdarzenia okresu trwałości...........................................................................................................................411
Wybranie kodowania znaków....................................................................................................................... 413
Zależności plików JAR.................................................................................................................................. 413
Mechanizmy ładowania klas.........................................................................................................................414
Nowe atrybuty błędów.................................................................................................................................. 414
Nowe atrybuty bezpieczeństwa..................................................................................................................... 416
Niewielkie poprawki......................................................................................................................................417
Wyjaśnienia deskryptora DTD......................................................................................................................418
KONKLUZJA.......................................................................................................................................................... 418
DODATEK A. KRÓTKI OPIS SERVLET API...............................................................................................419
GenericServlet...............................................................................................................................................419
RequestDipatcher..........................................................................................................................................422
Servlet........................................................................................................................................................... 423
ServletConfig.................................................................................................................................................423
ServletContext............................................................................................................................................... 424
ServletException........................................................................................................................................... 428
ServletInputStream........................................................................................................................................429
ServletOutputStream..................................................................................................................................... 429
ServletRequest...............................................................................................................................................430
ServletResponse............................................................................................................................................ 434
SingleThreadModel.......................................................................................................................................436
UnavailableException ..................................................................................................................................437
DODATEK B. KRÓTKI OPIS HTTP SERVLET API................................................................................... 439
Cookie........................................................................................................................................................... 440
HttpServlet.................................................................................................................................................... 443
HttpServletRequest........................................................................................................................................445
HttpServletResponse..................................................................................................................................... 448
HttpSession................................................................................................................................................... 452
HttpSessionBindingEvent..............................................................................................................................454
HttpSessionBindingListener..........................................................................................................................455
HttpSessionContext....................................................................................................................................... 456
HttpUtils........................................................................................................................................................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
<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 F. KODOWANIA...........................................................................................................................488
O Autorach
Jason Hunter jest starszym technologiem w firmie CollabNet (http://collab.net), firmie dostarczającej narzędzia i
usługi dla współpracy Open Source. Oprócz bycia autorem książki „Java Servlet — programowanie” jest także
redaktorem witryny Servlets.com, twórcą biblioteki com.oreilly.servlet, współpracownikiem projektu Apache
Jakarta, który tworzy serwer Tomcat (od czasów, kiedy projekt był jeszcze wewnętrzną częścią firmy Sun),
członkiem grupy ekspertów odpowiedzialnej za tworzenie API Servlet/JSP i JAXP oraz jest członkiem Komitetu
Wykonawczego JCP nadzorującego platformę Javy, jako reprezentant Apache Software Foundation. Pisze
również artykuły dla JavaWorld oraz przemawia na wielu konferencjach programistycznych i Open Source. W
ostatnich czasach współtworzył bibliotekę Open Source JDOM (http://jdom.org), pozwalającą na optymalizację
integracji Javy i XML oraz przewodzi grupie ekspertów odpowiedzialnej za tworzenie JDOM.
Jason poprzednio pełnił funkcję głównego technologa w firmie K&A Software, specjalizującej się w treningach i
konsultacjach związanych z Javą i działał jako wynajęty ekspert dla wielu przedsiębiorstw włączając w to Sun
Microsystems. Jeszcze wcześniej pracował w Silicon Graphics, gdzie był odpowiedzialny za tworzenie (i
niszczenie) różnego rodzaju technologii WWW.
Jason ukończył z najwyższym wyróżnieniem kierunek nauki komputerowe w Willamette University (Salem,
Oregon) w 1995. Rozpoczął programowanie w Javie w lecie 1995, a z serwletami i innymi technologiami
programowania po stronie serwera jest związany od grudnia 1996. Jeżeli jakimś cudem nie pracuje,
przypuszczalnie można go znaleźć na górskiej wędrówce.
William „Will” Crawford związał się z tworzeniem stron WWW w 1995. Pracował przy programie
informatycznym szpitala Children's Hospital w Bostonie, gdzie pomagał przy tworzeniu pierwszego
elektronicznego systemu zapisów medycznych opartego na sieci WWW i był związany z jednymi z pierwszych
korporacyjnych zastosowań języka Java. Był konsultantem projektów sieci Intranet w między innymi Children's
Hospital w Massachusetts, General Hospital w Brigham, Women's Hospital, Boston Anesthesia Education
Foundation i Harvard Medical Center.
Will obecnie przewodzi zespołowi projektanckiemu w firmie Invantage, Inc. w Cambridge, Massachusetts, która
tworzy oparte na Javie narzędzia intranetowe dla przemysłu farmaceutycznego. W wolnym czasie jest zapalonym
amatorem fotografii, pisarzem i studentem ekonomii na Yale University.
Wstęp
Od czasu, kiedy napisane zostało pierwsze wydanie niniejszej książki, serwlety i platforma Javy działająca po
stronie serwera zyskała popularność, której nie można było spodziewać się w najśmielszych marzeniach.
Postępuje przyłączanie tych mechanizmów do istniejących. Producenci serwerów WWW oferują obecnie
obsługę serwletów jako standardową własność swojego oprogramowania. W specyfikacji Java 2, Enterprise
Edition (J2EE) serwlety istnieją jako podstawowy składnik, a niemożliwym jest obecnie znalezienie producenta
serwerów aplikacji, którego produkt nie zawierałby skalowalnej implementacji serwletów. Jest to jednak więcej
niż zjawisko napędzane przez producentów. Serwlety stały się podstawą dla JavaServer Pages (JSP) i innych
szkieletów tworzenia stron WWW, a technologia serwletów obsługuje aktualnie tak często odwiedzane witryny,
jak ESPN.com i AltaVista.com.
W związku z tym nie jest zaskakującym fakt, że krajobraz serwletów wygląda nieco inaczej niż w czasach
pierwszego wydania. Interfejs serwletów (Servlet API) został poddany dwóm przeglądom, a trzeci jest w trakcie
przygotowań. Znajome z początków istnienia serwletów firmy Live Software i New Atlanta, które niegdyś
zarabiały sprzedając mechanizmy serwletów (nazywane teraz kontenerami serwletów) Jrun i ServletExec, zostały
zauważone i wykupione przez większe firmy zorientowane na WWW, odpowiednio przez Allaire i Unify.
Oferują one teraz wiele własności wykraczających poza podstawową obsługę serwletów w celu odróżnienia się
od innych.
Co dziwne, oficjalne pakiety javax.servlet i javax.servlet.http były pierwszymi klasami Javy,
które zostały oficjalnie rozprowadzone jako Open Source. Zostały one przeniesione do projektu Apache
Software Foundation (ASF), i można je aktualnie odnaleźć pod adresem http://jakarta.apache.org. Pakiety te
dalej zgodne są ze specyfikacją Servlet API, jednak poprawa błędów i uaktualnianie specyfikacji znajduje się
teraz w rękach w zaufanych programistów Open Source — włączając autora, który miał niedawno okazję
poprawienia obsługi warunkowego żądania GET w HttpServlet. Dodatkowo, serwer, który jest traktowany
jako wzorcowa implementacja Servlet API, został również przeniesiony do ASF i udostępniony jako Open
Source pod nazwą Apache Tomcat. Od tego czasu Tomcat stał się jednym z najpopularniejszych kontenerów
serwletów. Większa ilość informacji na ten temat dostępna jest pod adresem http://opensource.org.
Świat serwletów zmienił się, a niniejsza książka zawiera uaktualnione informacje. Całą wiedzę potrzebną do
programowania serwletów Javy, od początku do końca. Pierwsze pięć rozdziałów opisuje podstawy — czym są
serwlety, jakie działania wykonują oraz w jaki sposób pracują. Następne 15 rozdziałów zawiera informacje
zaawansowane — opisuje działania podejmowane najczęściej przy pomocy serwletów oraz najpopularniejsze
narzędzia do tego służące. Można tam znaleźć wiele przykładów, kilka wskazówek i ostrzeżeń, a nawet opisy
kilku prawdziwych błędów, które umknęły uwagi korektorów technicznych.
• JavaServer Pages (JSP), standard firmy Sun, tworzony i udostępniany w połączeniu z serwletami
• Tea, technologia utworzona przez Walt Disney Internet Group (dawniej GO.com), zastosowany w wielu
bardzo często odwiedzanych stronach, takich jak ESPN.com, NFL.com, Disney.com, DisneyLand.com,
GO.com i Movies.com
• WebMacro, utworzony przez Semiotek i wykorzystywany przez wyszukiwarkę AltaVista
• XMLC, utworzony przez Lutris Technologies w celu udostępnienia mocy technologii XML sieci
WWW, wykorzystywany przez innowacyjne witryny takie jak customatix.com
• Element Construcion Set (ECS), utworzony przez Apache w celu obsługi najbardziej wymagających
potrzeb programistycznych
Niniejsze drugie wydanie opisuje również WAP, Wireless Application Protocol (Protokół Aplikacji
Bezprzewodowych) oraz wyjaśnia, jak tworzyć oparte na serwletach aplikacje WWW dla urządzeń
bezprzewodowych.
Czytelnicy
Dla kogo jest ta książka? Dla osób zainteresowanych tworzeniem aplikacji umieszczanych w sieci WWW.
Dokładniej rzecz biorąc, niniejszą książką powinni zainteresować się:
• Programiści J2EE — serwlety są integralną częścią standardu Java 2, Enterpise Edition. Programiści
tworzący aplikacje dla serwerów J2EE mogą nauczyć się jak najlepiej zintegrować serwlety z innymi
podobnymi technologiami.
• Programiści JSP — JavaServer Pages (JSP) tworzone są na podstawie serwletów. Wykorzystanie pełnej
mocy JSP wymaga zrozumienia serwletów, co też umożliwia niniejsza książka. Zawiera ona również
samouczek JSP oraz czterech podstawowych konkurencyjnych technologii.
• Programiści apletów Javy — porozumiewanie się apletów z serwerem zawsze sprawiało problemy.
Serwlety ułatwiają to zadanie poprzez dostarczenie apletom prostego w połączeniu agenta na serwerze.
• Programiści CGI — CGI jest popularną metodą rozszerzania funkcjonalności serwera WWW. Serwlety
są elegancką i wydajną alternatywą tej techniki.
• Programiści innych technik serwerów — istnieje wiele alternatyw dla CGI, między innymi FastCGI,
PHP, NSAPI, WAI, ISPAI, ASP, a teraz ASP+. Każda z nich posiada ograniczenia związane z
przenośnością, bezpieczeństwem, wydajnością i/lub integracją z innymi źródłami danych. Serwlety
przewyższają je w każdym z tych obszarów.
Co należy wiedzieć
Podczas rozpoczynania pracy z niniejszą książką, niespodzianką dla autorów okazało się, że jedną z
najtrudniejszych do określenia rzeczy jest docelowy czytelnik. Czy zna on Javę? Czy ma doświadczenie w
programowaniu CGI lub innych aplikacji WWW? Czy miał już kontakt z serwletami? Czy zna HTTP i HTML,
czy te skróty brzmią dla niego zupełnie niezrozumiale? Niezależnie od przyjmowanego poziomu doświadczenia,
zawsze okazywało się, że książka będzie zbyt uproszczona dla jednych użytkowników, a zbyt zaawansowana dla
drugich.
Ostatecznie zdecydowano się na zasadę, że niniejsza książka powinna zawierać w przeważającej części materiał
oryginalny — można pominąć obszerne opisy tematów i koncepcji dobrze opisanych w sieci lub innych
książkach. W tekście znaleźć można odwołania do tych zewnętrznych źródeł informacji.
Oczywiście zewnętrzne źródła informacji nie są wystarczające. Niniejsza książka zakłada, że czytelnicy dobrze
znają język Java oraz podstawowe techniki programowania obiektowego. Jeżeli nie spełnia się tych założeń,
polecane jest przygotowanie się poprzez przeczytanie ogólnej książki na temat programowania w Javie, takiej jak
„Learning Java” autorstwa Patricka Niemeyera i Jonathana Knudsena (O'Reilly). W książce tej można jedynie
krótko zapoznać się z rozdziałami na temat apletów i programowania Swing (graficznego), a skupić się na sieci i
programowaniu wielowątkowym. Aby zacząć od razu naukę serwletów i uczyć się Javy w trakcie, polecane jest
przeczytanie niniejszej książki równocześnie z „Java in a Nutshell” autorstwa Davida Flanagana (O'Reilly) lub
innym podręcznikiem.
Niniejsza książka nie wymaga od czytelników doświadczenia w programowaniu WWW, HTTP i HTML. Nie
zawiera jednak pełnego wprowadzenia lub wyczerpującego opisu tych technologii. Opisane zostaną podstawy
potrzebne do efektywnego programowania serwletów, a szczegóły (takie jak pełna lista znaczników HTML i
nagłówków HTTP 1.1) pozostawione zostaną innym źródłom.
Przykłady
W niniejszej książce znaleźć można ponad 100 przykładów serwletów. Ich kod jest całkowicie zawarty wewnątrz
tekstu, możliwe jest jednak także pobranie przykładów zamiast ręcznego ich wpisywania. Kod przykładów,
spakowany i gotowy do pobrania, można znaleźć pod adresem http://www.oreilly.com/catalog/jservlet2. Wiele z
tych serwletów można zobaczyć w działaniu pod adresem http://www.servlets.com.
Wszystkie przykłady zostały przetestowane przy pomocy serwera Apache Tomcat 3.2 działającego w trybie
samodzielnym, wirtualnej maszyny Javy (Java Virtual Machine — JVM) zawartej w Java Development KIT
1.1.8 i 1.2.2, zarówno pod Windows jak i Uniksem. Kilka zaawansowanych przykładów wymaga własności,
których nie obsługuje Tomcat w trybie samodzielnym. W tym przypadku przykłady były testowane na różnych
innych serwerach, jak opisano w tekście. Serwer Apache Tomcat jest oficjalną wzorcową implementacją Servlet
API, i jest dostępny w licencji Open Source pod adresem http://jakarta.apache.org.
Niniejsza książka zawiera również zbiór klas narzędziowych — wykorzystywane są one przez serwlety
przykładowe, mogą się także okazać przydatne przy tworzenie własnych. Klasy te zawarte są w pakiecie
com.oreilly.servlet. Między innymi są to klasy pomagające serwletom w analizie parametrów, obsłudze
wysyłania plików, generowaniu wieloczęściowych odpowiedzi (przepychanie serwera), negocjacji ustawień
lokalnych i internacjonalizacji, zwracaniu plików, zarządzaniu połączeniami i pracy jako serwer RMI. Pakiet te
zawiera również klasę wspomagającą komunikację apletów z serwletami. Od czasu pierwszego wydania dodane
zostały nowe klasy pomagające serwletom w wysyłaniu wiadomości poczty elektronicznej, przechowywaniu
odpowiedzi w pamięci podręcznej oraz automatycznym wykrywaniu obsługi Servlet API. Kod źródłowy
większości pakietu com.oreilly.servlet zawarty jest w tekście, a pełna, aktualna wersja jest dostępna w
formie elektronicznej (razem z dokumentacją javadoc) pod adresem http://www.servlets.com.1
Organizacja
Niniejsza książka składa się z 20 rozdziałów i 6 dodatków, są one następujące:
• Rozdział 1, „Wprowadzenie”. Wyjaśnia rolę i zalety serwletów Javy w tworzeniu aplikacji WWW. W
drugim wydaniu dodane zostały dodatkowe informacje na temat serwerów.
• Rozdział 2, „Podstawy serwletów HTTP”. Zawiera krótkie wprowadzenie do HTTP i funkcji, jakie
mogą pełnić serwlety HTTP. Przedstawia tworzenie prostej strony i wprowadza pojęcie dołączanej
aplikacji WWW. Drugie wydanie opisuje aplikacje WWW i ich deskryptory oparte na XML.
• Rozdział 3, „Cykl życia serwletów”. Wyjaśnia szczegółowe informacje na temat sposobu i czasu
ładowania serwletów, sposobu i czasu ich wykonywania, zarządzania wątkami oraz obsługi kwestii
synchronizacji w systemie wielowątkowym. Opisane są również stany trwałe. Drugie wydanie zawiera
nowe zasady kontekstowego przeładowywania i rejestracji serwletów, nowy podrozdział na temat
pamięci podręcznej po stronie serwera oraz uwagę na temat super.init(config).
• Rozdział 4, „Pobieranie informacji”. Wprowadza najpopularniejsze metody wykorzystywane przez
serwlety w celu pobrania informacji — na temat klienta, serwera, żądań klienta oraz samego siebie.
Przedstawia również działanie ogólnej klasy służącej do wysyłania plików. Drugie wydanie opisuje
ustawianie informacji w deskryptorze, pobieranie nazwy serwletu, dostęp do katalogów tymczasowych,
obsługę kontekstowych parametrów początkowych, określanie wersji Servlet API, przypisywanie
odwzorowania serwletów oraz dostęp do zasobów abstrakcyjnych. Przestawia również poprawiony,
bardziej elastyczny składnik służący do wysyłania plików.
• Rozdział 5, „Wysyłanie informacji HTML”. Opisuje sposoby tworzenia kodu HTML przez serwlet,
zwracania błędów, buforowania odpowiedzi, przekierowywania żądań, zapisywania danych w dzienniku
zdarzeń serwera oraz wysyłania dostosowanych nagłówków HTML. Drugie wydanie zawiera nowy opis
buforowania odpowiedzi, bardzo przydatny przykład przekierowywania oraz nowe podrozdziały na
temat konfiguracji stron zawierających błędy i obsługi błędów.
• Rozdział 6, „Wysyłanie zawartości multimedialnej”. Opisuje różne interesujące dane, które może
zwracać serwlet — zawartość WAP/WML dla urządzeń bezprzewodowych, dynamicznie tworzone
obrazki, zawartość skompresowana oraz odpowiedzi wieloczęściowe. W drugim wydaniu dodano opis
WAP/WML, listy plików powitalnych, dyskusję na temat PNG, usprawnioną pamięć podręczną
rysunków po stronie serwera oraz więcej szczegółów na temat tworzenia zawartości skompresowanej.
• Rozdział 7, „Śledzenie sesji”. Opisuje sposoby tworzenia śledzenia stanu w bezstanowym protokole
HTTP. Pierwsza część rozdziału opisuje tradycyjne techniki śledzenia sesji stosowane przez
programistów CGI. Druga część opisuje sposoby zastosowania wbudowanej w Servlet API obsługi
1
Niniejsza książka nie zawiera CD-ROM-u. Dołączenie CD-ROM-u podnosi koszty produkcji a w związku z tym cenę
książki. Założono, że każdy Czytelnik posiada dostęp do Internetu, a w związku z tym może oszczędzić pewną ilość
pieniędzy poprzez pobranie kodu przykładów przez sieć WWW. Nie uważa się również za sensowne dołączanie wersji
próbnych różnych serwerów WWW i aplikacji. Zważywszy na nieustanny szybki postęp na rynku serwletów, dołączone
serwery stałyby się przestarzałe jeszcze przed wydrukowaniem książki. Te same wersje próbne dostępne są w sieci i poleca
się pobranie ich własnoręcznie. Proszę pamiętać, że jeżeli zamierza się czytać niniejszą książkę offline, polecane jest
pobranie kodu przykładów i serwera WWW Apache Tomcata, kiesy tylko będzie to możliwe. Łącza do pobrań umieszczone
są pod adresem http://www.servlets.com.
śledzenia sesji. Drugie wydanie zawiera zasady tworzenia sesji aplikacji WWW, materiał na temat
nowych nazw metod sesji, dyskusję na temat zarządzania przekraczaniem czasu oraz śledzenie sesji
oparte na apletach.
• Rozdział 8, „Bezpieczeństwo”. Wyjaśnia kwestie bezpieczeństwa związane z programowanie
rozproszonym. Opisuje sposoby korzystania ze standardowych funkcji serwletów związanych z
zarządzaniem kontami użytkowników oraz sposoby tworzenia bardziej zaawansowanego systemu przy
pomocy dodatkowego uwierzytelniania i autoryzacji. Wyjaśni również rolę serwletów w bezpiecznej
komunikacji SSL. W drugim wydaniu całkowicie przeredagowany.
• Rozdział 9, „Łączność z bazami danych”. Opisuje sposoby wykorzystania serwletów w wysokowydajnej
łączności z bazami danych WWW. Zawiera samouczek JDBC. Drugie wydanie zawiera przykłady
konfiguracji połączeń z plikami właściwości, nowy przykład księgi gości oraz nowy podrozdział
opisujący JDBC 2.0.
• Rozdział 10, „Komunikacja aplet-serwlet”. Opisuje sposoby wykorzystania serwletów przez aplet, który
musi porozumieć się z serwerem. Uaktualniony w drugim wydaniu.
• Rozdział 11, „Współpraca serwletów”. Opisuje powody komunikacji serwletów i sposoby ich
współpracy przez dzielenie się informacjami lub wywoływanie sienie nawzajem. W drugim wydaniu
całkowicie przeredagowany.
• Rozdział 12, „Serwlety korporacyjne i J2EE”. Opisuje zaawansowane własności serwletów
wykorzystywane w witrynach korporacyjnych — dystrybucję ładunku i integrację składników J2EE.
Nowość w drugim wydaniu.
• Rozdział 13, „Internacjonalizacja”. Opisuje sposoby, dzięki którym serwlet może odczytywać i tworzyć
zawartość w różnych językach. Drugie wydanie opisuje zastosowanie javadoc w zarządzaniu
kodowaniem i sposoby wykorzystywania nowych metod API w zarządzaniu wersjami lokalnymi.
• Rozdział 14, „Szkielet Tea”. Przedstawia szkielet Tea, elegancki, ale zarazem potężny mechanizm
szablonów. Nowość w drugim wydaniu.
• Rozdział 15, „WebMacro”. Opisuje szkielet WebMacro, podobny do Tea lecz z kilkoma innymi
decyzjami projektanckimi. Nowość w drugim wydaniu.
• Rozdział 16, „Element Construction Set”. Zawiera krótki opis ECS, obiektowego podejścia do
tworzenia strony. Nowość w drugim wydaniu.
• Rozdział 17, „XMLC”. Przegląd XMLC, podejścia do tworzenia strony opartego na XML. Nowość w
drugim wydaniu.
• Rozdział 18, „JavaServer Pages”. Wyjaśnia JSP, standardową technologię firmy Sun, w której strony
WWW są automatycznie wkompilowane w serwer. Nowość w drugim wydaniu.
• Rozdział 19, „Informacje dodatkowe”. Przedstawia dodatkowe przykłady serwletów i podpowiedzi,
które nie zmieściły się w żadnym z poprzednich rozdziałów. Drugie wydanie zawiera analizator
parametrów zlokalizowanych, nową klasę poczty elektronicznej oraz uaktualniony podrozdział na temat
wyrażeń regularnych, nowy podrozdział na temat dodatkowych narzędzi oraz dodatkowe podpowiedzi
na temat wydajności.
• Rozdział 20, „Zmiany w Servlet API 2.3”. Opisuje zmiany w nadchodzącej wersji 2.3 Servlet API,
który ma zostać udostępniony w połowie 2001. Nowość w drugim wydaniu.
• Dodatek A, „Krótki opis Servlet API”. Zawiera pełny opis klas, metod i zmiennych w pakiecie
javax.servlet. W drugim wydaniu uaktualniony do Servlet API 2.2
• Dodatek B, „Krótki opis HTTP Servlet API”. Zawiera pełny opis klas, metod i zmiennych w pakiecie
javax.servlet.http. W drugim wydaniu uaktualniony do Servlet API 2.2
• Dodatek C, „Krótki opis deskryptorów DTD”. Przedstawia opis deskryptora Document Type Definition
(Definicja Typu Dokumentu) web.xml. Nowość w drugim wydaniu.
• Dodatek D, „Kody stanu HTTP”. Lista kodów stanu określonych przez HTTP, a także stałe
mnemoniczne wykorzystywane przez serwlety.
• Dodatek E, „Encje znakowe”. Lista encji znakowych zdefiniowanych w HTML, a także równoważne do
nich wartości kodów ucieczkowych Uniksa.
• Dodatek F, „Kodowania”. Lista sugerowanych kodowań wykorzystywanych przez serwlety w celu
tworzenia zawartości w różnych językach.
Proszę czuć się swobodnie i czytać rozdziały w niniejszej książce w dowolnej kolejności. Czytanie prosto od
początku do końca zapewnia uniknięcie wszelkich niespodzianek, jako że starano się unikać odwołań do
dalszych części książki. Przeskakiwanie jest jednak możliwe, zwłaszcza po rozdziale 5 — pozostała część
rozdziałów została zaprojektowana w celu oddzielonego istnienia. Jedna ostatnia sugestia — proszę przeczytać
podrozdział „Usuwanie błędów” w rozdziale 19, jeżeli kiedykolwiek napotka się fragment kodu pracujący
nieprawidłowo.
• Wszystkich danych pojawiających się dokładanie w programie Javy, takich jak słowa kluczowe, typy
danych, stałe, nazwy metod, zmienne, nazwy klas oraz nazwy interfejsów
• Wszystkich wydruków kodu Javy
• Dokumentów HTML, znaczników i atrybutów
Czcionka o stałej szerokości z kursywą wykorzystywana jest do:
Prośba o komentarze
Prosimy o pomoc w poprawieniu następnych wydań poprzez zgłaszanie wszystkich błędów, nieścisłości,
niejasnych lub niewłaściwych wyrażeń oraz zwykłych literówek, które można odnaleźć w dowolnym miejscu
niniejszej książki. Proszę wysyłać komunikaty o błędach i komentarze pod adres bookquestions@oreilly.com.
(Przed wysłaniem komunikatu o błędzie prosimy sprawdzić erratę na stronie
http://www.oreilly.com/catalog/jservlet2 w celu sprawdzenia, czy dany błąd nie został już opisany.)
Prosimy również o opinie, co powinno znaleźć się w tej książce, aby stała się ona bardziej przydatna.
Wydawnictwo traktuje takie komentarze bardzo poważnie i próbuje dołączyć rozsądne sugestie do przyszłych
wydań książki.
Podziękowania
Kiedy pracowałem nad niniejszą książką, przyjaciel powiedział mi „Łatwiej musi być pisać drugie wydanie;
napisałeś już raz tę książkę”. Pomyślałem nad tym przez chwilę, roześmiałem się i odpowiedziałem, „To jest
łatwiejsze, ale ani trochę nie aż tak łatwe, jak się spodziewałem!”.
Patrząc wstecz, myślę że powód tego ma niewiele wspólnego z książkami, a bardziej z technologią. Pierwsze
wydanie opisywało Servlet API 2.0, specyfikację tworzoną przez około dwa lata. Niniejsze drugie wydanie
przedstawia Servlet API 2.2 i 2.3, co daje mniej więcej dwa dodatkowe lata pracy projektantów. Tak więc
jedynie z tej perspektywy można dostrzec, że jeżeli pierwsze wydanie zabrało mniej więcej rok aktywnego
pisania, to drugie powinno zabrać mniej więcej tyle samo czasu. I rzeczywiście tak było — około 9 miesięcy.
Wiele osób pomogło mi w tworzeniu tej książki. Jestem im głęboko wdzięczny. Po pierwsze są to redaktorzy
techniczni książki — James Duncan Davidson, przewodniczący specyfikacji Servlet API 2.1 i 2.2, oraz Danny
Coward, przewodniczący nadchodzącej wersji 2.3. Wszystko, co można o nich powiedzieć dobrego, to za mało.
Nie tylko dostarczyli mi nieocenionej pomocy i rad w trakcie pisania książki, lecz stworzyli wszystkim doskonałą
platformę do programowania dla WWW.
Dziękuję również wielu programistom, którzy swoim doświadczeniem wspomogli tworzenie rozdziałów na temat
tworzenia zawartości (i w wielu przypadkach tworzyli opisywaną technologię) — Reece Wilton i Brian O'Neill
dla Tea, Justin Wells dla WebMacro, Jon Stevens dla ECS, Mark Diekhnas i Christian Cryder dla XMLC oraz
Hans Bergsten i Craig McClanahan dla JSP.
Chciałbym również podziękować Bobowi Ecksteinowi, redaktorowi książki, którego ręczne notatki były zawsze
celne, choć czasami niemożliwe do odcyfrowania. Bob przejął obowiązki redaktorskie od Pauli Ferguson, po
tym, jak zajęła się ona zarządzaniem książkami O'Reilly na temat WWW i skryptów.
Dziękuję również Jimowi Grishamowi, który pomógł zlokalizować wszystkie rodzaje komputerów i przeglądarek
wykorzystywane przy testowaniu przykładów; Magnusowi Stenmanowi z firmy Orion, który wyjaśnił mi
implementację J2EE w serwerze Orion; Justynie Horwat, zwanej przez niektórych Boginią Biblioteki
Znaczników, za odpowiedzi na pytania dotyczący biblioteki znaczników JSP oraz Ethanowi Henry, który pomógł
sugestiami na temat poprawiania wydajności serwletów.
Nie mogę zapomnieć o Brett'cie McLaughlinie, autorze książki „Java and XML” (O'Reilly) i współtwórcy
JDOM. Jego współpraca ze mną na temat JDOM właściwie spowolniła pisanie tej książki, lecz prędkość, z jaką
on pisze inspiruje mnie, a ponieważ wspomniał mnie on w swojej książce, muszę napisać coś tutaj.
I ostatecznie dziękuję mojej dziewczynie, Kathlyn Bautista, która nie narzekała, kiedy pracowałem w niedziele,
lecz sprawiała, że wcale pracować nie chciałem.
Jason Hunter
Listopad 2000
Rozwój aplikacji Javy działających po stronie serwera — wszystko od działających samodzielnie serwletów do
pełnej platformy Java 2, Enterprise Edition (J2EE) — był jednym z najbardziej ekscytujących trendów w
programowaniu Javy. Język Java został utworzony pierwotnie w celu zastosowania w małych, osadzonych
urządzeniach. Był on opisywany jako język do tworzenia zawartości WWW po stronie klienta w formie apletów.
Jednak aż do kilku ostatnich lat potencjał Javy jako platformy do programowania po stronie serwera był niestety
pominięty. Aktualnie Java jest uważana za język idealnie nadający się do programowania po stronie serwera.
Szczególnie szybko rozpoznały potencjał Javy w serwerach firmy biznesowe — Java idealnie pasuje do dużych
aplikacji typu klient-serwer. Niezależna od platformy natura Javy jest niezwykle użyteczna dla organizacji
posiadających heterogeniczny zbiór serwerów pracujących pod różnymi odmianami systemów operacyjnych
UNIX i Windows (oraz coraz bardziej Mac OS X). Nowoczesny, obiektowy i chroniący pamięć projekt Javy
pozwala programistom na skrócenie cyklów programistycznych i zwiększenie niezawodności. Dodatkowo,
wbudowana w Javę obsługa sieci i interfejsów korporacyjnych dostarcza możliwości dostępu do starych danych,
ułatwiając przejście ze starszych systemów klient-serwer.
Serwlety Javy są kluczowym składnikiem programowania Javy po stronie serwera. Serwlet to małe, dołączane
rozszerzenie serwera, które rozszerza jego funkcjonalność. Serwlety pozwalają programistom na rozszerzanie i
dostosowywanie każdego serwera WWW lub aplikacji z obsługą Javy do wcześniej nieznanego poziomu
przenośności, elastyczności i łatwości. Jednak przed przejściem do szczegółów, należy spojrzeć na sprawę z
pewnej perspektywy.
FastCGI
Firma o nazwie OpenMarket stworzyła alternatywę dla standardu CGI o nazwie FastCGI. W większości
aspektów, FastCGI działa podobnie do CGI — ważną różnicą jest tworzenie przez FastCGI jednego trwałego
procesu dla każdego programu FastCGI, jak przedstawiono na rysunku 1.2. Eliminuje to konieczność tworzenia
nowego procesu dla każdego żądania.
mod_perl
Przy korzystaniu z serwera WWW Apache, inną opcją zwiększenia wydajności CGI jest wykorzystanie
mod_perl. mod_perl jest modułem serwera Apache osadzającym kopię interpretatora Perla w pliku
wykonywalnym Apache'a, dostarczającym pełnej funkcjonalności Perla wewnątrz Apache'a. Jego efektem jest
prekompilowanie skryptów CGI przez serwer i wykonywanie ich bez rozdzielania, a związku z tym ich praca jest
dużo szybsza i wydajniejsza. Jego wadą jest możliwość wykorzystania jedynie w serwerze Apache. Większa
ilość informacji na temat mod_perl jest dostępna pod adresem http://perl.apache.org.
Inne rozwiązania
CGI/Perl posiada zaletę bycia mniej lub bardziej niezależnym od platformy sposobem na tworzenie dynamicznej
zawartości WWW. Inne dobrze znane technologie tworzenia aplikacji WWW takie jak ASP i JavaScript
działający po stronie serwera, są opatentowanymi rozwiązaniami pracującymi jedynie z określonymi serwerami
WWW.
JavaServer Pages
JavaServer Pages, często nazywana po prostu JSP, jest opartą na Javie alternatywą dla ASP, utworzoną i
wystandaryzowaną przez firmę Sun. JSP wykorzystuje składnię podobną do ASP poza tym, że językiem
skryptowym w tym przypadku jest Java. Odwrotnie niż ASP, JSP jest otwartym standardem implementowanym
przez wielu producentów na wszystkich platformach. JSP jest ścisłe związana z serwletami, ponieważ strona JSP
jest przetwarzana do serwletu, co stanowi część jej wykonania. JSP jest bardziej szczegółowo opisana w
dalszych częściach niniejszej książki. Większa ilość informacji na temat JSP jest dostępna pod adresem
http://java.sun.com/products/jsp.
Serwlety Javy
W tym miejscu pojawiają się serwlety Javy. Jak wspomniano wcześniej, serwlet jest ogólnym rozszerzeniem
serwera — klasą Javy, która może być dynamicznie ładowana w celu rozszerzenia funkcjonalności serwera.
Serwlety są często używane w serwerach WWW, gdzie zajmują miejsce skryptów CGI. Serwlet jest podobny do
poprzednio omawianego rozszerzenia serwera, poza tym, że działa on wewnątrz wirtualnej maszyny Javy (Java
Virtual Machine — JVM) na serwerze (proszę spojrzeć na rysunek 1.4), tak więc są one bezpieczne i przenośne.
Serwlety działają wyłącznie w domenie serwera — inaczej niż aplety, nie wymagają one obsługi Javy przez
przeglądarkę WWW.
Obsługa serwletów
Podobnie jak sama Java, serwlety zostały zaprojektowane w celu zapewnienia maksymalnej przenośności.
Serwlety obsługiwane są przez wszystkie platformy obsługujące również Javę, oraz pracują w większości
podstawowych serwerów WWW2. Serwlety Javy zdefiniowane przez dział Java Software firmy Sun
Microsystems (dawniej znany jako JavaSoft), stanowią Pakiet Opcjonalny (Optional Package) dla Javy (dawniej
znany jako Rozszerzenie Standardowe — Standard Extension). Oznacza to, że serwlety zostały oficjalnie
pobłogosławione przez Sun'a i stanowią część języka Java, lecz nie są częścią podstawowego API Javy. Zamiast
tego, są one znane jako część platformy J2EE.
W celu ułatwienia tworzenia serwletów, Sun i Apache udostępniły klasy API niezależnie od żadnego
mechanizmu WWW. Pakiety javax.servlet i javax.servlet.http składają się na Servlet API.
Najnowsza wersja tych klas jest dostępna do pobrania pod adresem
http://java.sun.com/products/servlet/download.html3. Wszystkie serwery WWW obsługujące serwlety muszą
wykorzystywać te klasy wewnętrznie (chociaż mogą stosować alternatywną implementację), tak więc generalnie
ten plik JAR może zostać znaleziony gdzieś wewnątrz dystrybucji serwera WWW obsługującego serwlety.
Nie jest ważne, skąd pobiera się klasy serwletów, należy posiadać je jednak w swoim systemie w celu
kompilowania serwletów. Dodatkowo konieczny jest program uruchamiający serwlety (technicznie nazywany
kontenerem serwletów, czasami mechanizmem serwletów), w celu przetestowania i udostępnienia serwletów.
Wybór kontenera serwletów zależy po części od działającego w danym systemie serwera(ów) WWW. Istnieją
trzy odmiany kontenerów serwletów — samodzielne, dołączane i osadzane.
2
Proszę zauważyć, że niektórzy producenci serwerów WWW posiadają swoje własne implementacje Javy działającej po
stronie serwera, niektóre z nich noszą również nazwę serwletów. Są one generalnie niekompatybilne z serwletami Javy
utworzonymi przez Sun'a. Większość z tych producentów konwertuje swoją obsługę Javy do standardowych serwletów lub
wprowadzają obsługę standardowych serwletów równolegle, w celu zapewnienia wstecznej kompatybilności.
3
W pewnym momencie planowano dołączenie tych klas do JDK 1.2. Później jednak zdecydowano na utrzymanie ich
niezależności od JDK w celu ułatwienia dokonywania poprawek do Servlet API.
4
Implementacja javax.servlet i javax.servlet.http w standardzie Open Source spowodowała naprawienie
wielu błędów (na przykład, autor miał okazję poprawić obsługę warunkowego GET w HttpServlet) i kwestii
niekompatybilności. Istnieje nadzieja, że przykład ten wspomoże w udostępnieniu większej ilości oficjalnych pakietów Javy
jako Open Source
• WebSite Professional O'Reilly, o podobnej funkcjonalności do Enterprise Server iPlanet, lecz za niższą
cenę. Proszę zobaczyć http://website.oreilly.com.
• Zeus Web Server, serwer WWW uważany przez niektórych za najszybszy z dostępnych. Jego lista
własności jest dość długa i zawiera obsługę serwletów. Proszę zobaczyć http://www.zeus.co.uk.
• Resin Caucho, kontener Open Source, uważany za bardzo wydajny. Może być uruchamiany w trybie
samodzielnym lub jako dodatek do wielu serwerów. Proszę zobaczyć http://www.caucho.com.
• LiteWebServer Gefion Software, niewielki (nieco ponad 100 KB) kontener serwletów utworzony dla
zastosowań, takich jak dołączanie do wersji demonstracyjnych, gdzie niewielki rozmiar ma znaczenie.
Proszę zobaczyć http://www.gefionsoftware.com/LiteWebServer.
• Jigsaw Server World Wide Web Consortium Open Source, napisany całkowicie w Javie. Proszę
zobaczyć http://www.w3.org/Jigsaw.
• Java Web Server firmy Sun, serwer, od którego wszystko się rozpoczęło. Serwer ten był pierwszym
serwerem implementującym serwlety oraz działał jako efektywna wzorcowa implementacja dla Servlet
API 2.0. Jest on napisany całkowicie w Javie (poza dwoma bibliotekami kodu macierzystego, które
powiększają funkcjonalność, lecz nie są konieczne). Sun nie kontynuuje już prac nad serwerem,
koncentrując się na produktach iPlanet/Netscape w ramach sojuszu Sun-Netscape. Proszę zobaczyć
http://java.sun.com/products.
Serwery aplikacji są rosnącym obszarem tworzenia. Serwer aplikacji oferuje obsługę po stronie serwera dla
tworzenia aplikacji korporacyjnych. Większość aplikacji opartych na Javie obsługuje serwlety i pozostałą część
specyfikacji Java 2, Enterprise Edition (J2EE).Serwery te to miedzy innymi:
• WebLogic Application Server BEA System, jeden z pierwszych i najsłynniejszych opartych na Javie
serwerów aplikacji. Proszę zobaczyć http://www.beasys.com/products/weblogic.
• Orion Application Server, wysoko wydajny serwer o stosunkowo niskiej cenie, napisany całkowicie w
Javie. Proszę zobaczyć http://www.orionserver.com.
• Enhydra Application Server, serwer Open Source firmy Lutris. Proszę zobaczyć
http://www.enhydra.org.
• Borland Application Server 4, serwer ze specjalnym naciskiem na technologię CORBA. Proszę
zobaczyć http://www.borland.com/appserver.
• WebSphere Application Server IBM, wysokowydajny serwer oparty w części na kodzie Apache'a.
Proszę zobaczyć http://www-4.ibm.com/software/webservers.
• Dynamo Application Server 3 ATG, kolejny wysokowydajny serwer napisany całkowicie w Javie.
Proszę zobaczyć http://www.atg.com.
• Application Server Oracle, serwer zaprojektowany do integracji z bazą danych Oracle. Proszę zobaczyć
http://www.oracle.com/appserver.
• iPlanet Application Server, zgodny z J2EE większy brat iPlanet Web Server Enterprise Edition. Proszę
zobaczyć http://www.iplanet.com/products/infrastructure/app_servers/nas.
• GemStone/J Application Server, serwer Javy stworzony przez firmę poprzednio znaną z serwera
Smalltalk. Proszę zobaczyć http://www.gemstone.com/products/j.
• Jrun Server Allaire (poprzednio Live Software), prosty kontener serwletów, który rozrósł się do
zaawansowanego kontenera dostarczającego wiele technologii J2EE włączając w to EJB, JTA i JMS.
Proszę zobaczyć http://www.allaire.com/products/jrun.
• Silverstream Application Server, w pełni zgodny z J2EE serwer, który rozpoczął również od skupienia
się na serwletach. Proszę zobaczyć http://www.silverstream.com.
Uwagi dodatkowe
Przed przejściem do następnej części należy zapamiętać, że nie wszystkie kontenery serwletów są tworzone w
jednakowy sposób. Tak więc przed wybraniem kontenera serwletów (i prawdopodobnie serwera) przy pomocy
którego udostępniane będą serwlety, należy go wypróbować, prawie do granic możliwości. Sprawdzić listy
dystrybucyjne. Należy zawsze sprawdzać, czy serwlety zachowują się tak, jak we wzorcowej implementacji
Tomcata. Można również sprawdzić, jakie narzędzia programistyczne są dostarczane, które technologie J2EE są
wspierane i jak szybko można uzyskać pomoc u producenta. W przypadku serwletów nie trzeba się martwić o
zgodność z najsłabszą implementacją, tak więc należy pobrać kontener serwletów, który posiada wszystkie
pożądane właściwości.
Kompletna, aktualna lista dostępnych kontenerów serwletów razem z ich obecnymi cenami jest dostępna pod
adresem http://www.servlets.com.
Potęga serwletów
Jak dotychczas serwlety zostały opisane jako alternatywa dla innych technologii dynamicznej zawartości WWW,
lecz nie zostało tak naprawdę powiedziane, dlaczego powinny być one, zdaniem autorów, stosowane. Co
sprawia, że serwlety są jednym z najlepszych sposobów programowania WWW? Zdaniem autorów posiadają one
kilka zalet ponad innymi podejściami, włączając w to przenośność, moc, wydajność, wytrzymałość,
bezpieczeństwo, elegancję, integrację, rozszerzalność i elastyczność. Każda z tych własności zostanie kolejno
omówiona.
Przenośność
Ponieważ serwlety są pisane w Javie według dobrze zdefiniowanego i szeroko akceptowanego API, są one w
dużym stopniu przenośne pomiędzy systemami operacyjnymi i implementacjami serwerów. Można stworzyć
serwlet na komputerze pod Windows NT i z serwerem Tomcat, po czym bez problemu udostępnić go na wysoko
wydajnym serwerze Uniksowym z iPlanet/Netscape Application Server. Stosując serwlety można naprawdę
„napisać raz, udostępniać wszędzie”.
Przenośność serwletów nie jest tak ważną sprawą jak w przypadku apletów, z dwóch powodów. Po pierwsze,
przenośność serwletów nie jest obowiązkowa. Inaczej niż w przypadku apletów, które muszą zostać
przetestowane na wszystkich możliwych platformach klientów, serwlety muszą pracować jedynie na serwerach,
które są wykorzystywane do tworzenia i udostępniania. Dopóki nie sprzedaje się swoich serwletów, nie trzeba się
martwić o kompletną przenośność. Po drugie, serwlety unikają najbardziej pełnej błędów i niespójnie
zaimplementowanej części języka Java — Abstract Windowing Toolkit (AWT), który stanowi bazę graficznych
interfejsów Javy, takich jak Swing.
Moc
Serwlety mogą wykorzystywać pełną moc jądra API Javy — pracę w sieci i dostęp do URL-i, wielowątkowość,
kompresję danych, łączność z bazami danych (JDBC), serializację obiektów, zdalne wywoływanie metod (RMI)
oraz integrację ze starymi programami (CORBA). Serwlety mogą również korzystać z platformy J2EE, która
zawiera obsługę Enterprise JavaBeans (EJBs), transakcji rozproszonych (JTS), standaryzowanych wiadomości
(JMS), wyszukiwania katalogów (JNDI) oraz zaawansowanego dostępu do baz danych (JDBC 2.0). Lista
standardowych API dostępnych serwletom rośnie, czyniąc tworzenie aplikacji WWW szybszym, łatwiejszym i
bardziej niezawodnym.
Jako autor serwletów, można wykorzystać dowolną z mnóstwa niezależnych klas Javy i składników JavaBeans.
Serwlety mogą używać niezależnego kodu w celu obsługi zadań takich jak wyszukiwanie według wyrażeń
regularnych, tworzenie wykresów danych, dostosowany dostęp do baz danych, zaawansowana praca w sieci,
analiza składniowa XML oraz tłumaczenia XSLT.
Serwlety są również sprawne w umożliwianiu komunikacji klient-serwer. Posiadając oparty na Javie aplet i
oparty na Javie serwlet, można wykorzystać RMI i serializację obiektu w komunikacji klient-serwer, co oznacza,
że ten sam kod można wykonać zarówno na maszynie klienta, jak i na serwerze. Wykorzystywanie po stronie
serwera języków innych niż Java jest znacznie bardziej skomplikowane, jako że konieczne jest tworzenie swoich
własnych protokołów do obsługi komunikacji.
Wydajność i wytrzymałość
Wywoływanie serwletów charakteryzuje się bardzo wysoką wydajnością. Kiedy serwlet zostaje załadowany,
pozostaje w pamięci serwera jako pojedynczy egzemplarz obiektu. Następnie serwer wywołuje serwlet do
obsługi żądania przy pomocy prostego wywołania metody. Inaczej niż w przypadku CGI, nie trzeba wywoływać
procesu ani interpretatora, tak więc serwlet może rozpocząć obsługę żądania niemal natychmiast. Wielokrotne,
równoległe żądania są obsługiwane przez osobne wątki, tak więc serwlety są w wysokim stopniu skalowalne.
Serwlety są obiektami z natury trwałymi. Ponieważ serwlet zostaje w pamięci serwera jako pojedynczy
egzemplarz obiektu, automatycznie zachowuje swój stan i może utrzymywać kontakt z zasobami zewnętrznymi,
takimi jak połączenia z bazami danych. W innym przypadku przywrócenie połączenia mogłoby zabrać
kilkanaście sekund.
Bezpieczeństwo
Serwlety obsługują bezpieczne praktyki programowania na różnych poziomach. Ponieważ są one pisane w Javie,
dziedziczą po niej silne bezpieczeństwo typów. Podczas gdy większość wartości w programie CGI, włączając w
to element numeryczny taki, jak numer portu serwera, są traktowane jako łańcuchy, wartości w Servlet API są
manipulowane przy pomocy ich naturalnych typów, tak więc numer portu serwera jest reprezentowany jako
integer. Automatyczne zbieranie śmieci przez Javę i brak wskaźników oznaczają, że serwlety są generalnie
bezpieczne od problemów z zarządzaniem pamięcią, takich, jak uszkodzone wskaźniki, niewłaściwe odwołania
do wskaźników oraz uszczerbki pamięci.
Serwlety mogą bezpiecznie obsługiwać błędy, dzięki mechanizmowi obsługi wyjątków lub kontrolerowi dostępu
Javy. Jeżeli serwlet wykona dzielenie przez zero lub inne nieprawidłowe działanie, wyrzuca wyjątek, który może
być bezpiecznie wychwycony i obsłużony przez serwer, który zapisze błąd w dzienniku zdarzeń i przeprosi
użytkownika. Jeżeli podobny wyjątek napotkałoby rozszerzenie serwera oparte na C++, przypuszczalnie
nastąpiłoby załamanie serwera.
Serwer może chronić siebie w większym stopniu poprzez zastosowanie menedżera bezpieczeństwa lub kontrolera
dostępu Javy. Serwer może wykonywać swoje serwlety pod ochroną dokładnego kontrolera dostępu który, na
przykład wymusza politykę bezpieczeństwa zaprojektowaną do strzeżenia przed złośliwym lub źle
zaprojektowanym serwletem dążącym do zniszczenia systemu plików serwera.
Elegancja
Elegancja kodu serwletów jest uderzająca. Kod serwletów jest czysty, obiektowy, modularny i zadziwiająco
prosty. Jednym z powodów tej prostoty jest sam Servlet API, który zawiera metody i klasy obsługujące wiele
rutynowych elementów programowania serwletów. Nawet zaawansowane operacje, takie jak obsługa cookies i
śledzenie sesji, są rozkładane na odpowiednie klasy. Kilka bardziej zaawansowanych, lecz także popularnych
zadań zostało pozostawione poza API, i w tych przypadkach autorzy próbowali to naprawić i tak powstał zbiór
przydatnych klas w pakiecie com.oreilly.servlet.
Integracja
Serwlety są ściśle zintegrowane z serwerem. Ta integracja pozwala serwletowi na współpracę z serwerem w
sposób niedostępny dla programów CGI. Na przykład, serwlet może wykorzystywać serwer w celu
przetłumaczenia ścieżek plików, dokonania logowania, sprawdzenia uwierzytelnienia oraz wykonania
odwzorowania typu MIME. Właściwe dla konkretnego serwera rozszerzenia mogą wykonać większość tej pracy,
lecz proces ten jest zazwyczaj znacznie bardziej złożony i obfity w błędy.
Rozszerzalność i elastyczność
Servlet API jest zaprojektowany w celu zapewnienia łatwej rozszerzalności. W obecnym czasie, API zawiera
klasy z wyspecjalizowaną obsługą serwletów HTTP. Lecz w późniejszym okresie może być ona rozszerzona i
zoptymalizowana dla innego typu serwletów, czy to produkcji Suna, czy innej firmy. Jest również możliwe, że
jego obsługa serwletów HTTP może być dalej rozwijana.
Serwlety cechują się również elastycznością w tworzeniu zawartości. Mogą tworzyć prostą zawartość przy
pomocy wyrażeń out.println(), lub generować skomplikowany zbiór stron przy pomocy mechanizmu
szablonów. Mogą tworzyć stronę HTML przez traktowanie strony jako zestawu obiektów Javy, lub tworzyć
stronę HTML przez wykonanie transformacji XML do HTML. Serwlety mogą być nawet łączone w celu
utworzenia całkowicie nowych technologii takich jak JavaServer Pages. Nie wiadomo, do czego jeszcze zostaną
wykorzystane.
Rozdział 2. Aplety Http —
wprowadzenie
Zwróćmy uwagę, iż możliwe jest ściągnięcie kodów dla każdego z przykładów zamieszczonych w tym oraz
innych rozdziałach tej książki, zarówno w formie źródłowej jak i skompilowanej (jak zostało to opisane w
przedmowie). Jednakże, co się tyczy tego rozdziału, wydaje się, iż rzeczą najbardziej pomocną w nauce będzie
zapisanie przykładów ręcznie (za pomocą klawiatury). Lektura tego rozdziału może prowadzić do wniosku, iż
niektóre zagadnienia zostały potraktowane zbyt ogólnie. Aplety to narzędzia dające wiele możliwości i czasem
bywają skomplikowane, dlatego też rozdział ten ma na celu wprowadzenie w ogólne zasady ich działania i
zorientowania się w temacie. Czytelnik po lekturze niniejszej książki będzie w stanie samodzielnie tworzyć
najrozmaitsze aplety.
Podstawy HTTP
Zanim przejdziemy do omawiania prostych apletów HTTP, musimy sprawdzić znajomość podstaw działania
protokołu HTTP. Będąc doświadczonym w programowaniu w CGI (lub mając doświadczenie w tworzeniu stron
WWW na serwerach) można z powodzeniem pominąć czytanie tego podrozdziału. W takim przypadku
korzystnym wydaje się zatrzymanie się na istotnych zagadnieniach metod GET i POST. Jednakże będąc osobą
stawiającą pierwsze kroki w tworzeniu stron WWW na serwerach, należy przeczytać wspomniany materiał
uważnie, ponieważ zrozumienie dalszej części książki wymaga znajomości protokołu HTTP. Protokół HTTP
został szczegółowo omówiony w „Pocket Reference” Clintona Wong’a (wydawnictwo O’Reilly).
W tym zleceniu chodzi o uzyskanie dokumentu o nazwie intro.html za pomocą wersji 1.0 HTTP. Po przesłaniu
zlecenia klient może przesłać informacje nagłówkową w celu dostarczenia serwerowi dodatkowych informacji o
zleceniu, takich jak: jakie oprogramowanie jest używane przez klienta oraz jaka forma informacji potrzebna jest
klientowi do jej zrozumienia. Takie informacje nie odnoszą się bezpośrednio do tego, co było przedmiotem
zlecenia, jednakże mogą być wykorzystane przez serwer w tworzeniu odpowiedzi. Oto dwa przykłady
nagłówków zleceń:
Nagłówek User-Agent dostarcza informacji o oprogramowaniu klienta, podczas gdy nagłówek Accept określa
rodzaj nośnika (MIME) najkorzystniejszy dla klienta (nagłówki zleceń zostaną omówione szerzej przy
omawianiu apletów, w rozdziale 4 „Odczytywanie informacji”). Celem zaznaczenia końca sekcji nagłówkowej,
po przesłaniu nagłówków, klient przesyła nie zapisany wiersz. Jeżeli wymaga tego używana metoda, klient może
również przesłać inne dodatkowe dane, tak jak w przypadku metody POST, która zostanie zaraz omówiona. Jeżeli
zlecenie nie zawiera żadnych danych, to kończy się nie zapisanym wierszem. Po przesłaniu przez klienta
zlecenia, serwer przetwarza je i przesyła odpowiedź. Pierwszy wiersz odpowiedzi zawiera wiersz statusu oraz
jego opis. Oto przykład:
HTTP/1.0 200 OK.
Powyższy wiersz statusu zawiera kod statusu 200, co oznacza że zlecenie zostało wykonane, stąd opis OK.
Kolejny często spotykany kod to 404 z opisem Not Found (nie znaleziono), jak łatwo się domyśleć opis ten
oznacza, że dokument nie został odnaleziony. W rozdziale 5 „Przesyłanie informacji HTML” zostały omówione
najczęściej spotykane kody statusu oraz w jaki sposób można je wykorzystać w apletach. Dodatek D „Kody
statusu HTTP” zawiera kompletną listę kodów statusu HTTP. Po przesłaniu wiersza statusu serwer przesyła
nagłówki odpowiedzi, które są informacją dla klienta taką jak np.: jakiego oprogramowania używa serwer oraz
nośnika informacji użytego do zapisania odpowiedzi serwera. Oto przykład:
Aplet HTTP zwykle nie ignoruje metody service(), tylko metodę doGet() (do obsługi zleceń GET) i metodę
doPost() (do obsługi zleceń POST). Aplet HTTP może zignorować jedną z powyższych metod lub obie, w
zależności od tego, jaki jest typ zlecenia, które ma obsłużyć. Metoda service() HttpServlet obsługuje
instalację oraz transfer do wszystkich metod doXXX(), dlatego właśnie zwykle nie powinna być ignorowana. Na
rysunku 2.2 został ukazany sposób, w jaki aplet HTTP obsługuje zlecenia metod GET i POST.
Tworzenie strony
Najbardziej podstawowy typ apletu HTTP tworzy pełną stronę HTML. Taki aplet ma zwykle dostęp do takich
samych informacji, co przesyłane do skryptu CGI oraz do pewnej partii innych. Aplet, który tworzy strony
HTML może zostać użyty do wykonywania wszystkich zadań, tych które obecnie są wykonywane przy pomocy
CGI, jak np. przetwarzanie formularzy HTML, tworzenie listy z bazy danych, przyjmowanie zamówień,
sprawdzanie zamówień, sprawdzanie identyfikacji, itd.
*
Pierwszy przykład zarejestrowanego programu „Hello World” pojawił się w „A Tutorial Introduction to the Language B”,
napisanym przez Braiana Kernighana w 1973. Dla tych z czytelników, zbyt młodych, by pamiętać, język B był prekursorem
języka C. Więcej informacji o języku programowania B oraz link do tej książki można znaleźć na stronie: http://cm.bell-
labs.com/who/dmr/bintro.html.
Przykład 2.1. Aplet, który wyświetla „Hello World”
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
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(). Kiedy
serwer WWW otrzymuje zlecenie GET z tego apletu, każdorazowo wywołuje metodę doGet(), przekazując mu
obiekty HttpServletRequest oraz HttpServletResponse. Obiekt HttpServletRequest reprezentuje
zlecenie klienta. Obiekt ten daje apletowi dostęp do informacji o kliencie, parametrach zlecenia, nagłówkach
HTTP przekazywanych razem ze zleceniem oraz inne. W rozdziale 4. omówione zostały wszystkie możliwości
obiektu zlecenia. Dla tego przykładu możemy jednak je pominąć jako, ze niezależnie od typu zlecenia aplet ten
wyświetla „Hello World”.
Obiekt HttpServletResponse reprezentuje odpowiedź apletu. Aplet może wykorzystać ten obiekt do
dostarczenia danych klientowi. Mogą to być dane różnego typu, typ ten jednak powinien być określony jako
część odpowiedzi. Aplet może również użyć tego obiektu do ustalenia nagłówków odpowiedzi HTTP. W
rozdziałach 5. i 6 „Przesyłanie treści multimedialnej” zostało omówione wszystko co może zrobić aplet jako
część odpowiedzi.
Nasz aplet ustala najpierw za pomocą metody setContentType() typ zawartości swojej odpowiedzi do
text/html, standardowego typu zawartości MIME dla stron HTML. Następnie używa metody getWriter() w
celu odczytania PrintWriter, międzynarodowego odpowiednika PrintStream. PrintWriter przekształca
kod UNICODE Javy na kodowanie specyficzne lokalnie. Dla kodowania lokalnego — angielskiego zachowuje się
tak jak PrintStream. Aplet używa wreszcie PrintWriter do wysłania swojego HelloWorld HTML do
klienta.
<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.
Rysunek 2.4. Formularz HTML
Formularz ten powinien znaleźć się w pliku HTML, w katalogu serwera document_root. Jest to miejsce, w
którym serwer szuka plików statycznych. Dla serweru „Tomcat” katalog ten to server_root/webapps/ROOT.
Dzięki umieszczeniu pliku w tym katalogu, może być on dostępny bezpośrednio pod adresem:
http://server:8080/form.html.
Kiedy użytkownik przesyła ten formularz, jego imię (i nazwisko) jest przesyłane do apletu „Hello” ponieważ
uprzednio zainstalowaliśmy atrybut ACTION celem wskazania ich apletowi. Formularz używa metody GET, więc
jakiekolwiek dane są dodawane do URL-u zlecenia jako pasmo zapytań. Jeżeli np. użytkownik wprowadzi imię,
nazwisko „Inigo Montoya”, URL zlecenia będzie wyglądał następująco:
http://server:8080/servlet/Hello?name=Inigo+Montoya. Przerwa pomiędzy imieniem a nazwiskiem jest
wyjątkowo kodowana przez przeglądarkę jako znak „+”, ponieważ URL nie może zawierać spacji.
Obiekt apletu: HttpServletRequest umożliwia dostęp do danych formularzowych w paśmie zapytań. Na
przykładzie 2.2 została ukazana zmodyfikowana wersja naszego apletu „Hello”, która używa swojego obiektu
zlecenia do odczytywania parametru name.
Przykład 2.2. Aplet, który „wie” kogo pozdrawia
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
res.setContentType ("text/html");
PrintWriter out = res.getWriter ( );
Aplikacje WWW
Aplikacje 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 w
taki sposób , aby mogły być łatwo zainstalowane na każdym serwerze WWW udostępniającym aplety. Dzięki
standaryzacji umiejscowienia plików w aplikacji WWW oraz standaryzacji formatu konfiguracji pliku, aplikacja
WWW 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 stronic
zawierają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: web
application 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 w
formie skompresowanej oraz opatrywanie ich zawartości podpisem elektronicznym. Zamiast rozszerzenia .war
wybrano rozszerzenie .jar, żeby zasygnalizować użytkownikom oraz narzędziom, których używają, iż pliki te
należy traktować inaczej.
Struktura organizacji plików w aplikacji WWW została ściśle określona. Na przykładzie 2.4 został ukazany
listing plików.
Przykład 2.4. Struktura organizacji plików w aplikacji WWW
index.html
feedback.jsp
images/banner.gif
images/jumping.gif
WEB-INF/web.xml
WEB-INF/lib/bhawk4j.jar
WEB-INF/classes/Myservlet.class
WEB-INF/classes/com/mycorp/frontend/CorpServlet.class
WEB-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 z
jakimkolwiek przedrostkiem pliku URI na serwerze. Aplikacja WWW obsługuje wtedy wszystkie zlecenia
mają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.
Katalog WEB-INF
Katalog WEB-INF jest wyjątkowy — pliki nie są przesyłane bezpośrednio do klienta, zamiast tego zawierają one
klasy Java oraz informacje o konfiguracji dla aplikacji WWW. Katalog ten zachowuje się jak katalog META-INF
pliku 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 maja
automatyczny wgląd do WEB–INF/classes oraz do WEB–INF/lib podczas ładowania klas, wiec nie trzeba
wykonywać 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żde
zlecenie 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 nazwy
pliku. Oznacza to, iż dostęp do apletów znajdujących się w server_root/webapps/ROOT/WEB-INF/classes
moż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 nazwie
serwer_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 o
konfiguracji aplikacji WWW, w której się znajduje. Jest to plik XML ze standaryzowanym DTD. DTD zawiera
ponad 50 znaczników, pozwalając tym samym na pełną kontrolę nad aplikacją WWW. Plik deskryptora
wdrożenia kontroluje rejestrację apletu, odwzorowanie URL, przyjmuje pliki i typy MIME jak również funkcje
zaawansowane, 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żenia
pozwala na wyszczególnienie informacji konfiguracyjnych w sposób niezależny od serwera, upraszczając tym
samym znacznie proces wdrażania. Deskryptory wdrożenia nie tylko umożliwiają przenoszenie prostych apletów
lecz 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 prawdopodobnie
integralna częścią sieci. Prawdopodobnie będzie je można również ładować i instalować i będą od razu gotowe
do pracy — bez względu na to, jaki system operacyjny jest używany przez nas lub przez nasz serwer WWW.
!!!!!!!!!!początek ramki
XML i DTD
XML to angielski skrót (Extensible Markup Language)a , który można przetłumaczyć jako rozszerzalny język
oznaczania znaczników, oznaczający rozszerzenie HTML-a. Jest to uniwersalna składnia (syntaktyka) tworzenia
struktury danych, stworzona przez Konsorcjum „World Wide Web Consortium” (W3C), mająca swój początek w
1996 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łnia
nieco inną rolę niż HTML — załączniki w dokumentach XML nie określają sposobu wyświetlania tekstu, lecz
raczej wyjaśniają jego znaczenie. XML jest to „rozszerzalny” język, oznaczania znaczników, ponieważ nowe
znaczniki mogą być tworzone ze swoim własnym znaczeniem, właściwym dla tworzonego dokumentu. XML
działa najlepiej w formacie pliku jednorodnego, ponieważ jest to standardowa, dobrze opisana i niezależna od
platformy technika stworzona dla danych hierarchicznych, istnieje także wiele programów narzędziowych
wspomagających odczytywanie, tworzenie oraz operowanie plikami XML.
Zasady pisania w XML-u są bardziej restrykcyjne niż w przypadku HTML-u. Przede wszystkim znaczniki XML
reagują 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ć. Tak
np. jeżeli rozpoczniemy znacznikiem rozpoczynającym <servlet> to musimy zakończyć znacznikiem
końcowym </servlet>. Dla ułatwienia pusty znacznik składniowy <servlet> może zostać użyty jako
substytut oznaczenia jednoczesnego rozpoczęcia i zakończenia, pary znaczników <servlet></servlet>.
Trzecia zasada jest taka, że elementy zagnieżdżone nie mogą się na siebie nakładać. Tak więc właściwym będzie
układ <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"/> a
niewłaściwy <servlet id=O/>. Dokumenty (zwane well-formed) spełniające powyższe zasady, będą
całkowicie przetwarzane przez automatyczne programy narzędziowe.
Poza tymi zasadami, istnieją także sposoby szczegółowego określania struktury znaczników w pliku XML.
Specyfikacja tego typu jest określana jako Dokumentacja typu dokumentu (DTD). DTD szczegółowo precyzuje
jakie znaczniki mogą znaleźć się w pliku zgodności XML, jaki rodzaj danych mają zawierać te znaczniki oraz to
gdzie w hierarchii mają (lub muszą) znajdować się te znaczniki. Każdy plik XML może być zadeklarowany do
pewnego DTD. Pliki które całkowicie dostosowują się do DTD, do których są zadeklarowane nazywamy valid.
XML jest używany w apletach jako format pamięci przy konfiguracji plików. XML może być również używany
przez aplety przy tworzeniu treści , jak to zostało opisane w rozdziale 17 „XMLC” Więcej informacji o XML-u
można znaleźć na stronie http://www.w3.org/XML/ oraz w książce Bretta McLaughlin’a „Java and XML”
(wydawnictwo O’Relly).
!!!!!!!!!!!!!!!!koniec ramki
Deskryptory wdrożenia z prostym sposobem obsługi wielu użytkowników na tym samym serwerze są również
oferowane przez firmy zajmujące się obsługują sieci WWW. Użytkownicy mogą uzyskać kontrole nad swoimi
domenami — mogą oni nadzorować rejestrację apletu, odwzorowanie URL-u, rodzaj MIME oraz poziom
zabezpieczenia strony — bez potrzeby ogólnego dostępu do serwera.
Deskryptor wdrożenia
Prosty deskryptor wdrożenia został pokazany na przykładzie 2. Żeby sprawić, by plik ten opisywał domyślną
aplikacją WWW serwera „Tomcat” musimy go umieścić w server_root/webapps/ROOT/WEB-INF/web.xml.
Przykład 2.5.
Prosty deskryptor wdrożenia
<?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 na
jego weryfikację (czy jest valid) oraz sprawdzenie czy odpowiada zasadom DTD. Wszystkie pliki deskryptora
wdrożenia zaczynają się opisanymi dwoma wierszami lub bardzo podobnymi do nich.
Pozostała część tekstu — pomiędzy <web-app> a </web-app> dostarcza serwerowi informacji o tej aplikacji
WWW. Ten prosty przykład rejestruje nasz aplet „Hello World” przed nazwą hi (otaczający biały obszar jest
wycinany). Zarejestrowana nazwa jest tu umieszczana pomiędzy znacznikami <servlet-class>. Znacznik
<servlet> „spina” znaczniki <servlet-name> oraz <servlet-class>. Składnia deskryptora wdrożenia
XML wydaje się być lepiej zoptymalizowana dla odczytu automatycznego, raczej niż dla bezpośredniego (przez
człowieka).
Dlatego też większość producentów oferuje środki graficzne pomocne w procesie tworzenia web.xml. Na rynku
dostępnych jest również wiele edytorów XML, które można wykorzystać przy tworzeniu XML-u.
!!!!!!!!!!!!!!!!!!!!początek ramki
Uwaga na kolejność znaczników
Nie wszyscy uświadamiają sobie fakt, iż znaczniki w web.xml są uwarunkowane co do kolejności. Dla przykładu,
żeby wszystko działało poprawnie znacznik <servlet-name> musi znajdować się przed znacznikiem
<servlet-class>. Taka jest ich kolejność, w której zostały zdeklarowane w DTD. Analizatory sprawdzające
poprawność składni wprowadzają taką kolejność oraz deklarują dokument jako nieprawidłowy (invalid) jeżeli
kolejność nie jest zachowana. Niektóre serwery, nawet te bez analizatorów poprawności składni, mogą być po
prostu przygotowane na przyjęcie takiej kolejności, mogą więc być zdezorientowane w przypadku otrzymania
innej. Dla pewności należy upewnić się, ze wszystkie znaczniki <web-app> zostały uporządkowane we
właściwej kolejności. Niektóre znaczniki mogą, lecz nie muszą być obecne, lecz te, które zostały przez nas
zapisane muszą być we właściwym porządku. Na szczęście istnieją narzędzia, które upraszczają to zadanie
(więcej informacji znajduje się w dodatku C — pod DTD).
!!!!!!!!!!koniec ramki
Po rejestracji, oraz po ponownym uruchomieniu serwera możemy wejść do apletu „Hello World” pod URL-em:
http://server:8080/servlet/hi. Może zastanawiać fakt dlaczego ktoś miałby zadawać sobie trud rejestracji apletu
pod specjalna nazwą. Odpowiedź jest taka, że procedura ta pozwala na „zapamiętanie” przez serwer szczegółów
związanych z apletem i traktować go w szczególny sposób. Jednym z przykładów takiego traktowania jest
możliwość ustalenia wzorców URL, które wywołują zarejestrowany aplet. Klient może postrzegać URL będący
przedmiotem zlecenia , tak jak każdy inny, jednakże serwer jest w stanie stwierdzić , że zlecenie jest zgodne z
odwzorowaniem modelu (wzoru) i dzięki temu może być obsłużone przez odpowiedni aplet. Możemy dla
przykładu sprawić, że http://server:8080/hello.html wywoła aplet „HelloWorld”. Użycie odwzorowań apletu w
ten sposób, pomaga ukryć , że strona używa apletów. Użycie to również pozwala apletowi na płynne zastąpienie
istniejącej pod jakimkolwiek URL-em adresem strony, tak więc wszystkie zakładki oraz łączniki (linki) do strony
nadal będą działać. Wzory URL konfigurowane są przy użyciu deskryptora wdrożenia, tak jak zostało to
zaprezentowane na przykładzie 2.6.
Przykład 2.6.
Dołączanie odwzorowania apletu
<?xml version="1.0" kodowanie="ISO-8859-1"?>
<!DOCTYPE web-app
<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 hi
powinien obsłużyć wszystkie URL-e zgodne ze wzorem dostępu do ścieżki głównej /hello.html. Jeżeli aplikacja
WWW zostanie przekształcona do głównej ścieżki „/” pozwoli to apletowi „Hello World” obsłużyć zlecenie na
http://server:8080/hello.html. Jeżeli zamiast powyższego aplikacja WWW zostanie przekształcona do ścieżki
dostępu przedrostka /greeting: aplet „Hello” obsłuży zlecenia złożone na http://server:8080/greeting/hello.html.
Różne zasady odwzorowania URL-u mogą zostać wyszczególnione w deskryptorze wdrożenia. Istnieją cztery
rodzaje (style) odwzorowania, wyszukiwane w następującej kolejności:
• Odwzorowania jawne, takie jak /hello.html lub /images/chart.gif, które nie zawierają symboli
wieloznacznych. Ten styl odwzorowania jest pomocny w zastępowaniu istniejącej strony.
• Odwzorowania ścieżki dostępu przedrostka takie jak /lite/*, /dbfile/* lub /catalog/item/* Odwzorowania te
zaczynają się od a /, a kończą na a/* i obsługują wszystkie zlecenia rozpoczynające się takimi przedrostkami
(nie licząc kontekstu ścieżki dostępu). Ten styl odwzorowania pozwala apletowi na kształtowanie całej
hierarchii sieciowej. Dla przykładu, aplet obsługujący /dbfile/* może serwować pliki z bazy danych, podczas
gdy aplet obsługujący /lite/* może przesyłać pliki z systemu plików automatycznie skompresowanych.
• Odwzorowania rozszerzenia takie jak *.wm lub .jsp. Takie odwzorowania zaczynają się od a* i obsługują
wszystkie zlecenia kończące się tym przedrostkiem. To odwzorowanie pozwala apletowi na operowanie na
wszystkich plikach określonego rozszerzenia. Dla przykładu aplet może zostać przypisany do obsługi plików
kończących się na *.jsp celem obsługi stron JavaServer (jest to de facto odwzorowanie niejawne będące
domeną specyfikacji apletu).
• Odwzorowanie domyślne /. Odwzorowanie to określa serwer domyślny dla aplikacji WWW — jeżeli inne
nie będą pasowały. Jest ono identyczne z ograniczonym odwzorowaniem ścieżki dostępu przedrostka (/*),
wyjątek stanowi fakt iż odwzorowanie to jest brane pod uwagę po odwzorowaniach rozszerzenia.
Odwzorowanie to daje kontrolę nad tym, w jaki sposób są wysyłane są podstawowe pliki — znacząca
możliwość, która jednak powinna być wykorzystywana z rozwagą. W sytuacji kolizji odwzorowań dokładne
(ścisłe) dopasowania mają pierwszeństwo przed dopasowaniami przedrostków, a dopasowanie ścieżki
dostępu przedrostka przed dopasowaniami rozszerzenia. Odwzorowanie domyślne jest wywoływane tylko
wtedy, kiedy nie występują żadne inne dopasowania. Dopasowania o dłuższym ciągu znaków w grupie
traktowane są priorytetowo przed dopasowaniami o krótszym ciągu.
Deskryptor „snippet” zaprezentowany na przykładzie 2.7 prezentuje różne odwzorowania, które mogą być użyte
do wchodzenia do apletu „Hello World”.
Aplet „Hello World” może być wywołany poprzez użycie jednego z odwzorowań znajdujących sie na poniższej
liście:
/servlet/HelloWorld
/servlet/hi
/hello.html
/well.hello
/fancy/meeting/you/here.hello
/hello/to/you
Bardziej praktyczne odwzorowania zostaną zaprezentowane w dalszych rozdziałach niniejszej książki.
Przejdźmy dalej
Duża ilość informacji o apletach, aplikacjach WWW oraz plikach konfiguracyjnych XML zwartych w
niniejszym wprowadzeniu powinna być wystarczająca do uzyskania pojęcia jak tworzyć proste aplety, instalować
je na serwerach oraz informować serwery o ścieżkach, dla których chcemy wykonywać wspomniane aplety.
Możliwości apletów znacznie przekraczają wyświetlanie „Hello World” czy pozdrawianie użytkowników ich
imionami i nazwiskami — właśnie o tym będzie traktować dalsza część książki.
Rozdział 3. Czas istnienia (cykl
życia) apletu
Czas istnienia (cykl życia) apletu jest jednym z bardziej interesujących aspektów apletów. Czas istnienia jest
hybrydą czasów istnienia używanych w środkach programowania CGI oraz środkach programowania niskiego
poziomu WAI/NSAPI i ISAPI, tak jak zostało to omówione w rozdziale1 „Wprowadzenie”.
Alternatywa apletu
Czas istnienia (cykl życia) apletów pozwala ich pojemnikom na odniesienie się zarówno do wydajności, jak i do
problemów związanych z CGI oraz do problemów dotyczących bezpieczeństwa nisko-poziomowych środków
programowania serwerów API. Pojemniki apletów uruchamiają zwykle aplety wszystkie razem, w jednej
maszynie wirtualnej Javy (JVM). Dzięki umiejscowieniu wszystkich apletów w tej samej JVM mogą one
skutecznie wymieniać dane między sobą, jednak co się tyczy ich danych „prywatnych” — język Java nie daje
możliwości wglądu jednemu apletowi w dane znajdujące się na drugim. Aplety mogą istnieć w JVM-ie pomiędzy
zleceniami — jako kopie obiektów. Dzięki temu zajęte jest mniej pamięci niż w przypadku pełnej procedury, a
aplety są nadal w stanie utrzymać odniesienia do zewnętrznych zasobów. Cykl życia apletów nie jest wielkością
stałą. Jedyną rzeczą niezmienną i konieczną w tym cyklu jest to, iż pojemnik apletu musi przestrzegać
następnych zasad:
Jest rzeczą całkowicie naturalną w przypadku apletów, iż są one ładowane, tworzone konkretyzowane w swojej
własnej maszynie wirtualnej Javy — tylko po to aby być usuniętymi i odtworzonymi nie obsłużywszy żadnych
zleceń od klientów lub po obsłużeniu tylko jednego takiego zlecenia. Jednakże aplety zachowujące się w taki
sposób nie utrzymają się długo na rynku. W tym rozdziale omówimy najbardziej popularne oraz czułe realizacje
czasów istnienia apletów HTTP.
• Wieloprocedurowy serwer WWW (który uruchamia kilka procedur, aby obsłużyć zlecenia) właściwie nie
może zawrzeć JVM w swojej procedurze ponieważ takiej nie posiada. Ten typ serwerów zwykle uruchamia
zewnętrzny JVM, którego procedury może współdzielić. Taki sposób oznacza, iż każde wejście do apletu
wiązać się będzie ze skomplikowanym połączeniem kontekstu przypominającym FastCGI. Jednakże
wszystkie aplety będą nadal dzieliły tą samą zewnętrzną procedurę.
Na szczęście, z perspektywy apletów (a tym samym z naszej — jako ich twórców) wdrażanie serwerów nie ma
większego znaczenia ponieważ zachowują się one zawsze w te sam sposób.
Trwałość kopii
Tak jak to zostało opisane wcześniej, aplety istnieją pomiędzy zleceniami jako kopie obiektów. Inaczej mówiąc
w czasie ładowania kodu dla apletu, serwer tworzy pojedynczą kopię. Ta pojedyncza kopia obsługuje wszystkie
zlecenia, utworzone z apletu. Poprawia to wydajność w trzy następujące sposoby:
Liczniki
W celu przedstawienia cyklu życia (czasu istnienia apletu) posłużymy się prostym przykładem. Przykład 3.1
ukazuje serwer, który zlicza i wyświetla liczbę połączeń się z nim. Dla uproszczenia wynik przedstawiany jest
jako zwykły tekst (kod dla wszystkich przykładów dostępny jest w internecie — patrz Wstęp)
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
int count = 0;
Liczniki zsynchronizowane
Z punktu widzenia projektantów apletów każdy klient, to kolejny wątek, który wywołuje aplet poprzez metody
takie jak: service(), doGet (), doPost(), tak jak to pokazuje przykład 3.1*.
Jeżeli nasze aplety odczytują tylko zlecenia, piszą w odpowiedziach i zapisują informacje w lokalnych
zmiennych, (czyli w zmiennych określonych w metodzie) nie musimy obawiać się interakcji pomiędzy wątkami.
Jeżeli informacje zostają zapisane w zmiennych nielokalnych (czyli w zmiennych określonych w klasie, lecz
poza szczególną metodą) musimy być wtedy świadomi, iż każdy z wątków klienckich może operować tymi
zmiennymi apletu. Bez odpowiednich środków ostrożności sytuacja taka może spowodować zniszczenie danych
oraz sprzeczności. I tak np. jeżeli aplet SimpleCounter założy fałszywie, że przyrost na liczniku oraz
wyprowadzenie są przeprowadzanie niepodzielnie (bezpośrednio jeden po drugim, nieprzerwanie), to jeżeli dwa
zlecenia zostaną złożone do SimpleCounter prawie w tym samym czasie, możliwe jest wtedy, że każdy z nich
wskaże tą samą wartość dla count. Jak? Wyobraźmy sobie, że jeden wątek zwiększa wartość dla count i zaraz
po tym, zanim jeszcze pierwszy watek wypisze wynik count, drugi wątek również zwiększa wartość. W takim
przypadku, każdy z wątków wskaże tą samą wartość, po efektywnym zwiększeniu jej o 2أ.
count++ // Wątek 1
count++ // Wątek 2
out.println // Wątek 3
out.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ę tak
prawdopodobnie dlatego, że kiedy obrazujemy program uruchamiający zwykle obserwujemy jak kopie obiektów wykonują
zadanie wywołując nawzajem swoje metody. Mimo, że przedstawiony model działa w prostych przypadkach nie jest on
dokładnym przedstawieniem rzeczywistości. Prawdziwa sytuacja wygląda tak, że wszystkie zadania wykonują wątki. Kopie
obiektów nie są niczym więcej jak tylko strukturami danych, którymi operują wątki. Dlatego możliwa jest sytuacja, w której
dwa działające wątki używają w tym samym czasie tego samego obiektu.
أ
Ciekawostka: jeżeli wartość count byłaby zamiast 32-bitowego int, 64-bitowym long, teoretyczna możliwa byłaby
sytuacja, że przyrost będzie dokonany tylko w połowie, do czasu, gdy przerwie mu inny wątek. Dzieje się tak dlatego,
ponieważ w Jawie używana jest 32=bitowa kaskada
W tym przypadku ryzyko sprzeczności nie stanowi poważnego zagrożenia, jednakże wiele innych apletów
zagrożonych jest poważniejszymi błędami. W celu zapobieżenia temu typowi błędów oraz sprzecznościom, które
im towarzyszą, możemy dodać jeden lub więcej synchronicznych bloków do kodu. Jest gwarancja, że wszystko,
co znajduje się w bloku synchronicznym lub w metodzie synchronicznej nie będzie wywoływane przez inny
wątek. Zanim jakikolwiek z wątków rozpocznie wywoływanie kodu synchronicznego musi otrzymać monitor
(zamek) na określoną kopie obiektu. Jeżeli monitor ma już inny wątek np. z powodu tego, że wywołuje on ten
sam blok synchroniczny, lub inny tym samym monitorem, wtedy pierwszy wątek musi zaczekać. Działa to na
zasadzie łazienki na stacji benzynowej, zamykanej na klucz (zawieszany zwykle na dużej, drewnianej desce),
którym w naszym przypadku będzie monitor. Wszystko to dzieje się dzięki samemu językowi tak więc obsługa
jest łatwa. Synchronizacja jednakże powinna być używana tylko w ostateczności. W przypadku niektórych
platform sprzętowych otrzymanie monitora za każdym razem, kiedy wchodzimy do kodu synchronicznego
wymaga wiele wysiłku, a co ważniejsze w czasie, kiedy jeden wątek wywołuje kod synchroniczny, pozostałe
mogą być blokowane do zwolnienia monitora.
Dla SimpleCounter istnieją cztery sposoby rozwiązywania potencjalnych problemów. Po pierwsze możemy
dodać hasło zsynchronizowane z sygnaturą doGet():
Taka sytuacja gwarantuje zgodność synchronizacji całej metody, używa się w tym celu kopii apletu, jako
monitora. Nie jest to w rzeczywistości najlepsza metoda, ponieważ oznacza to, iż aplet może w tym samym
czasie obsłużyć tylko jedno zlecenie GET.
Drugim sposobem jest zsynchronizowanie tylko dwóch wierszy, które chcemy wywołać niepodzielnie.
Powyższa technika działa lepiej, ponieważ ogranicza czas, który aplet spędza w swoim zsynchronizowanym
bloku, osiągając ten sam cel zgodności w wyniku liczenia. Prawdą jest, iż technika ta nie różni się specjalnie od
pierwszego sposobu.
Trzecim sposobem poradzenia sobie z potencjalnymi problemami jest utworzenie synchronicznego bloku, który
wykonywał będzie wszystko, co musi być wykonane szeregowo, a następnie wykorzystanie poza blokiem
synchronicznym. W przypadku naszego apletu, liczącego możemy zwiększyć wartość liczoną (count) w bloku
synchronicznym, zapisać zwiększoną wartość do lokalnej zmiennej (zmiennej określonej wewnątrz metody), a
następnie wyświetlić wartość lokalnej zmiennej poza blokiem synchronicznym:
PrintWriter out = res.getWriter();
int local_count;
synchronized(this) {
local_count= ++count;
}
out.println ("Od załadowania z apletem łączono się" +
localcount + " razy.");
Powyższa zmienna zawęża blok synchroniczny do najmniejszych, możliwych rozmiarów zachowując przy tym
zgodność liczenia.
Celem zastosowania czwartej, ostatniej z metod musimy zadecydować, czy chcemy ponieść konsekwencje
zignorowania wyników synchronizacji. Czasem bywa i tak, że konsekwencje te są całkiem znośne. Dla
przykładu, zignorowanie synchronizacji może oznaczać, że klienci otrzymają wynik trochę niedokładny. Trzeba
przyznać, iż to rzeczywiście nie jest wielki problem. Jeżeli jednak oczekiwano by od apletu liczb dokładnych,
wtedy sprawa wyglądałaby trochę gorzej.
Mimo, iż nie jest to opcja możliwa do zastosowania na omawianym przykładzie, to na innych apletach możliwa
jest zamiana kopii zmiennych na zmienne lokalne. Zmienne lokalne są niedostępne dla innych wątków i tym
samym nie muszą być dokładnie strzeżone przed zniszczeniem. Jednocześnie zmienne lokalne nie istnieją
pomiędzy zleceniami, tak więc nie możemy ich użyć do utrzymywania stałego stanu naszego licznika.
Liczniki całościowe
Model „jeden egzemplarz na jeden aplet” jest sprawą do omówienia ogólnego. Prawda jest taka, że każda
zarejestrowana nazwa (lecz nie każde URL-owe dopasowanie do wzorca) dla apletu jest związana z jedną kopią
apletu. Nazwa używana przy wchodzeniu do apletu określa, która kopia obsłuży zlecenie. Taka sytuacja wydaje
się być sensowna, ponieważ klient powinien kojarzyć odmienne nazywanie apletów z ich niezależnym
działaniem. Osobne kopie są ponadto wymogiem dla apletów zgodnych z parametrami inicjalizacji, tak jak to
zostało omówione dalej w tym rozdziale.
Nasz przykładowy SimpleCounter posługuje się kopią liczenia zmiennej przy zliczaniu liczby połączeń z nim
wykonanych. Jeżeli byłaby potrzeba liczenia wszystkich kopii (a tym samym wszystkich zarejestrowanych nazw)
możliwe jest użycie klasy zmiennej statycznej.
Zmienne takie są wspólne dla wszystkich kopii klasy. Przykład 3.2 ukazuje liczbę wejść na aplet, liczbę kopii
utworzonych przez serwer (na jedną nazwę) oraz całkowitą liczbę połączeń z tymi kopiami.
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
count++;
out.println ("Od załadowania z apletem łączono się" +
count + " razy.");
classCount++
out.println ("Licząc wszystkie kpoie, z apletem tym " + "łączono
"łączono się" + classCount + "razy")
}
}
Przedstawiony licznik całościowy — Holistic Counter, śledzi liczbę połączeń własnych przy pomocy
zmiennej kopii count, liczbę połączeń wspólnych za pomocą zmiennej klasy oraz liczbę kopii za pomocą tablicy
asocjacyjnej — instances (kolejny wspólny element, który musi być zmienną klasy). Widok przykładu ukazuje
rysunku 3.2.
Rysunek 3.2. Widok licznika całościowego
*
Specyfikacji projektów mającego ukazać się na rynku apletu API 2.3 (Servlet API 2.3), zakładają, że dodane zostaną
metody cyklu życia (czasu istnienia), które umożliwią apletom oczekiwanie na sygnały, kiedy kontekst lub sesja są tworzone
lub zakańczane oraz podczas wiązania i rozwiązywania atrybutu z kontekstem lub sesją.
• podczas uruchamiania apletu
• podczas pierwszego łączenia się z apletem, przed wywołaniem metody service()
• na żądania administratora serwera
W każdym przypadku metoda init() zostanie wywołana i zakończona ani zanim jeszcze aplet obsłuży swoje
pierwsze zlecenie.
Metoda init() jest zwykle wykorzystywana do inicjalizacji apletu — ładowania obiektów używanych przez
aplet w procesie obsługi zleceń. W czasie wykorzystywania metody init() aplet może „chcieć” odczytać swoje
parametry inicjalizacji (init). Parametry te są dostarczane samemu apletowi i nie są w jakikolwiek sposób
związane z jednym zleceniem. Mogą one określać takie wartości początkowe jak: odkąd licznik powinien zacząć
liczyć lub wartości domyślne takie jak np. szablon, który powinien zostać użyty w przypadku nie określenia tego
w zleceniu. * Specyfikacji projektów mającego ukazać się na rynku interfejsu API 2.3 (Servlet API 2.3),
zakładają, że dodane zostaną metody cyklu życia (czasu istnienia), które umożliwią apletom oczekiwanie na
sygnały, kiedy kontekst lub sesja są tworzone lub zakańczane oraz podczas wiązania i rozwiązywania atrybutu z
kontekstem lub sesją.
Parametry początkowe dla apletu można znaleźć w deskryptorze wdrożenia, niektóre serwery mają graficzne
interfejsy mogące zmodyfikować ten plik (patrz przykład 3.3).
Przykład 3.3. Ustalanie wartości parametrów w deskryptorze rozmieszczenia
<web-app>
<servlet>
<servlet-name>
counter
</servlet-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>
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
int count;
catch (numberFormatException e) {
count = 0;
}
}
Metoda init() wykorzystuje metodę getinitParameter() w celu uzyskania wartości dla parametru
początkowego zwanego initial. Metoda ta przyjmuje tą nazwę jako String i oddaje wartość również jako
String. Nie ma możliwości uzyskania wartości jako innego typu. Dlatego aplet ten przekształca wartość
String na wartość int lub w razie problemów zamienia na wartość 0. Należy pamiętać, że jeżeli chcemy
wypróbować ten przykład może się okazać konieczne powtórne „zastartowanie” serwera celem wprowadzenia
zmian w web.xml, oraz odniesienie się do apletu, używając zarejestrowanej nazwy.
ServletConfig_config = null;
// itd. ...
}
-—kontynuacja–
Zwróćmy uwagę, iż serwer wywołuje w czasie inicjalizacji metodę apletu init(ServletConfig config).
Zmiana w 2.1 dotyczyła tego, iż obecnie GenericServlet przekazuje to wywołanie do bez-argumentowego
init(), którą to metodą można zignorować nie martwiąc się o config.
Co się tyczy zgodności z poprzednimi wersjami należy nadal ignorować init(ServletConfig config) i
wywoływać super.init(config). W przeciwnym wypadku może być tak, iż nie będziemy mogli wywoływać
metody bez-argumentowej init().
Niektórzy z programistów uważają, iż dobrze jest wywołać najpierw super.destroy() podczas gdy wdrażamy
destroy() powoduje to, że GenericServlet wdraża destroy(), która to metoda „pisze” wiadomość do
rejestru zdarzeń, że aplet jest niszczony.
!!!!!!!!! koniec ramki
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
int count;
public void init() throws ServletException {
//Spróbuj załadować liczenie początkowe z naszego zachowanego stałego stanu
FileReader fileReader = null;
BufferedReader bufferedReader = null;
try {
fileReader = new fileReader ("InitDestroyCounter.initial");
bufferedReader = new BufferedReader(fileReader);
String initial = bufferedReader.readLine();
count = Integer.parseInt(initial);
return;
}
catch (FileNotFoundException ignored) {} // nie ma stanu zapisanego
catch (IOException ignored) {} // problem podczas czytania
catch (NumberFormatException ignored) {} //zniekształć stan zapisany
finally {
// Nie zapomnij zamknąć plik
try {
if(bufferedReader ! = null) {
bufferedReader.close()
}
}
catch (IOException ignored) {}
}
// W razie braku powodzenia ze stanem zapisanym,
// sprawdź dla parametru init
String initial = getInitParameter("początkowy");
try {
count = Integer.parseInt(initial);
return;
}
res.setContentType ("text/zwykły");
finally {
// Nie zapomnij zamknąć plik
if (printWriter ! = null) {
printWriter.close();
}
}
}
}
Za każdym razem, kiedy aplet jest usuwany, stan jego jest zachowywany w pliku o nazwie
InitDestroyCounter.initial. Jeżeli nie ma dostarczonej ścieżki dostępu, plik jest zapisywany w procedurze
serweru bieżącego katalogu, zwykle jest to katalog auto-startowy. Sposoby alternatywnej lokalizacji opisano w
rozdziale 4 „Odzyskiwanie informacji”. Plik ten również zawiera liczbę całkowitą, zapisana jako ciąg znaków,
reprezentujący ostatnie liczenie.
Za każdym ładowaniem serwera jest próba odczytu z pliku, zachowanego liczenia. Jeżeli z jakiegoś powodu
próba odczytu nie powiedzie się (jak ma to miejsce podczas pierwszego uruchomienia apletu — ponieważ plik
jeszcze wtedy nie istnieje), aplet sprawdza, czy parametr początkowy określa liczenie początkowe. Jeżeli i to nie
da efektu zaczyna od zera. Podczas stosowania metody init() zalecana jest najwyższa ostrożność.
Aplety mogą zachowywać swój stan na wiele różnych sposobów. Niektóre z nich mogą posłużyć się w tym celu
formatem użytkowym pliku, tak jak to zostało tutaj zrobione przez nas. Inne serwery zapisują swój stan jak
serializowane obiekty Java lub „umieszczają” go w bazie danych. Niektóre serwery nawet wykorzystują technikę
journaling, powszechnie stosowaną przy bazach danych oraz przy kopiach zapasowych taśm, gdzie pełny stan
apletu jest zapisywany rzadko, podczas gdy plik dziennika wprowadza do pamięci przyrostowe aktualizacje w
trakcie zmian. To, którą metodę użyje aplet zależy od sytuacji. Powinniśmy być zawsze świadomi tego, iż
zapisywany stan nie podlega żadnym zmianom na drugim planie.
Teraz może nasuwać się pytanie: co się stanie, jeżeli aplet ulegnie awarii? Odpowiedź brzmi: metoda destroy
() nie zostanie wywołanaأ. Nie jest to jednakże problem dla metod destroy(), które muszą tylko zwolnić
zasoby; przeładowany serwer równie dobrze się do tego nadaje i (czasem nawet lepiej). Jednakże sytuacja taka
jest problemem dla apletu, który musi zapisywać swój stan w swojej metodzie destroy(). Ratunkiem dla tych
apletów jest częstsze zapisywanie swojego stanu. Aplet może „zdecydować się” na zapisanie swojego stanu po
obsłudze każdego ze zleceń tak jak powinien to zrobić aplet „chess server” (serwer szachowy), tak że nawet
kiedy serwer jest ponownie uruchamiany, gra może zostać wznowiona z ostatnią sytuacją na szachownicy. Inne
aplety mogą potrzebować zapisać strony tylko po zmianie jakiejś ważnej wartości — aplet „shopping cart” (lista
zakupów) musi zapisać swój stan tylko wtedy, gdy klient doda lub usunie pozycję z listy. I w końcu niektóre
aplety mogą tracić niektóre ze swoich ostatnich zmian stanu. Takie aplety mogą zapisywać stan po pewnej
określonej liczbie zleceń. Dla przykładu, w naszym przykładzie InitDestoyCounter, wystarczającym
powinno być zapisywanie stanu co dziesięć połączeń. Celem wdrożenia powyższego można dodać prosty wiersz
na końcu doGet():
if (count % 10 == 0) saveState();
Można zapytać czy jest to istotna zmiana. Wydaje się, że tak, biorąc pod uwagę zagadnienia związane z
synchronizacją. Stworzyliśmy możliwość utraty danych (jeśli saveState() zostanie uruchomionym przez dwa
wątki, w tym samym czasie) oraz ryzyko, że saveState() nie będzie w ogóle wywołane i jeżeli liczenie
zostanie zwiększone przez kilka wątków z rzędu przed sprawdzeniem. Załóżmy, że taka możliwość nie istniała
kiedy saveState() było wywoływane tylko z metody destroy() ponieważ metoda destroy() jest
wywoływana tylko raz na jedną kopię apletu. Jednakże teraz, kiedy saveState() jest wywoływana w metodzie
do Get() musimy ponownie się nad tym zastanowić. Jeżeli zdarzyłoby się kiedyś, że aplet ten byłby odwiedzany
tak często, iż byłoby więcej niż 10 niezależnie wywołujących wątków, jest prawdopodobne, że dwa aplety (10
osobnych zleceń) będą w saveState() w tym samym czasie. Może to spowodować zniszczenie pliku z danymi.
Może to również doprowadzić do jednoczesnego zwiększenia liczenia przez dwa watki, zanim któryś „zorientuje
się”, iż minął czas wywoływania saveState(). Rozwiązanie jest proste: przemieśćmy kontrolę liczenia do
bloku zsynchronizowanego, tam gdzie liczenie jest zwiększane:
int local_count ;
synchronizeed(this) {
local_count = ++count ;
if (count % 10 == 0) saveState();
}
out.println ("Od załadowania z apletem łączono się" +
count + " razy.");
Wniosek z powyższych rozważań jest jeden: bądźmy przezorni i chrońmy kod apletu od problemów związanym
z wielowątkowym dostępem.
أ
Jeżeli nie mamy pecha i nasz serwer nie będzie miał awarii podczas stosowania metody destroy(). W przeciwnym
wypadku możemy zostać z częściowo zapisanym plikiem stanu — pozostałościami napisanymi na górze naszego
poprzedniego stanu. Celem osiągnięcia całkowitego bezpieczeństwa aplet zapisuje swój stan w pliku roboczym, kopiując go
następnie na górze oficjalnego pliku stanu w jednej komendzie.
Rysunek 3.3. Model jedno-wątkowy
Czas istnienia SingleThreadModel (Modelu jedno-wątkowego) nie ma zastosowania dla liczników lub innych
aplikacji apletu, które wymagają obsługi centralnego stanu. Czas istnienia może mieć pewne zastosowanie,
jednak tylko w unikaniu synchronizacji, ciągle obsługując sprawnie zlecenie.
Dla przykładu aplety, które łączą się z bazami danych muszą czasem wykonać kilka poleceń bazy danych,
niepodzielnie jako część pojedynczej obsługi transakcji. Każda transakcja bazy danych wymaga wydzielonego
obiektu połączenia bazy danych, dlatego więc aplet musi jakoś zagwarantować, że żadne dwa wątki nie będą
próbowały „wchodzić” na to samo połączenie w tym samym czasie. Można tego dokonać poprzez użycie
synchronizacji, pozwalając apletowi na obsługę tylko jednego zlecenia w jednym momencie. Poprzez wdrożenie
SingleThreadModel oraz poprzez fakt, iż jest tylko jedno „połączenie” kopii zmiennej, aplet może w prosty
sposób obsługiwać konkurencyjne (jednoczesne) zlecenia ponieważ każda kopia będzie miała swoje połączenie.
Zarys kodu pokazano na przykładzie 3.6.
Przykład 3.6. Obsługa połączeń bazy danych przy użyciu Modelu jedno-wątkowego
import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
try {
// Użyj połączenia utworzonego specjalnie dla tej kopii
Przetwarzanie drugoplanowe
Aplety potrafią więcej niż tylko po prostu trwać pomiędzy wejściami na nie. Potrafią także wtedy wywoływać.
Każdy wątek uruchomiony przez aplet może kontynuować wywoływanie nawet po wysłaniu odpowiedzi.
Możliwość ta najlepiej sprawdza się przy dłuższych zadaniach, których wyniki przyrostowe są udostępniane
wielu klientom. Wątki drugoplanowe, uruchomione w init() wykonują pracę w sposób ciągły podczas gdy
wątki obsługujące zlecenia wyświetlają stan bieżący za pomocą doGet(). Jest to technika podobna to tej
używanej w apletach animacyjnych, gdzie jeden wątek dokonuje zmian na rysunku, a drugi nanosi kolory.
Przykład 3.7 ukazuje aplet wyszukujący liczb pierwszych, większych od kwadryliona. Tak wielka liczba
wybierana jest celowo, żeby uczynić liczenie powolnym tak, aby zademonstrować efekty buforujące (które będą
nam potrzebne przy omawianiu dalszej części materiału). Algorytm używany przez aplet jest bardzo prosty: aplet
selekcjonuje wszystkie liczby nieparzyste i następnie próbuje podzielić je przez liczby nieparzyste całkowite z
przedziału od 3 do ich pierwiastka kwadratowego. Jeżeli dana liczba, nie jest podzielna bez reszty przez żadną
tych liczb, wtedy jest ona uznawana za liczbę pierwszą.*
*
Można zapytać dlaczego sprawdzane są tylko czynniki mniejsze od pierwiastka kwadratowego. Odpowiedź jest prosta:
ponieważ jeżeli liczba miałaby dzielniki wśród liczb większych od tego pierwiastka, to musiałaby je także mieć wśród liczb
od niego mniejszych.
searcher.setPriority(Thread.MIN_PRIORITY); // bądź dobrym obywatelem
searcher.start();
}
}
candidate += 2; // liczby parzyste nie są liczbami pierwszymi
Wątek wyszukujący rozpoczyna wyszukiwanie w metodzie init(). Ostatnia liczba przez niego znaleziona
zostaje zapisana w lastprime, a czas, w którym to się stało w lastprimeModified. Za każdym razem kiedy
klient łączy się z apletem, metoda doGet() informuje go jaka największa liczba pierwsza została znaleziona do
tej pory oraz o czasie, w którym to się stało. Wątek niezależnie przetwarza połączenia klientów; nawet kiedy
żaden klient nie jest połączony, wątek nadal kontynuuje poszukiwania liczb pierwszych. Jeżeli kilku klientów
połączy się w tym samym czasie z apletem, aktualny stan wyświetlony dla nich wszystkich będzie jednakowy.
Zwróćmy uwagę na fakt, iż metoda destroy() zatrzymuje wątek wyszukujący. Jest to bardzo istotne ponieważ
jeżeli aplet nie zatrzyma swoich drugoplanowych wątków, będą one działały tak długo jak długo będzie istniała
maszyna wirtualna. Nawet jeżeli aplet zostanie powtórnie załadowany (odnowiony) (jawnie bądź z powodu
zmiany pliku klasy) jego wątki nie przestaną działać. Zamiast tego jest prawdopodobne, że nowy aplet utworzy
kopie dodatkowe wątków drugoplanowych.
Uruchamianie i rozruch
Aby sprawić, że PrimeSearcher zacznie szukać liczb pierwszych tak szybko jak to możliwe, możemy
skonfigurować aplikację WWW apletu w taki sposób, że będzie ładowała aplet przy startowaniu serweru.
Dokonuje się tego poprzez dodanie znacznika <load-on-startup> do hasła <servlet>, deskryptora
wdrożenia, tak jak na przykładzie 3.8.
<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 ładowana
przy 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ów
kontekstu. Aplety z niższymi liczbami ładowane są przed tymi z liczbami większymi. Aplety z wartościami
ujemnymi lub nie-całkowitymi mogą być ładowane w każdym momencie sekwencji uruchamiania, dokładna
kolejność zależna jest wtedy od serwera. Dla przykładu, web.xml ukazany na przykładzie 3.9 gwarantuje, że
first będzie załadowany przed second, podczas gdy anytime może zostać załadowany w każdym momencie
rozruchu serwera.
Przykład 3.9. Pokaż możliwości apletu
<web-app>
<servlet>
<servlet-name>
first
</servlet-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>
*
Aplet może wstawić bezpośrednio swój nagłówek LastModified, w doGet(), przy użyciu technik omówionych w
rozdziale 5.(„Przesyłanie informacji HTML”). Jednakże do czasu kiedy nagłówek zostanie wstawiony w doGet() jest już
zbyt późno by zdecydować o tym czy wywoływać doGet() czy nie.
Thu, 17-Jul-97 09:17:22 GMT
Niektóre serwery przyjmują ten czas, przeliczają go na dokładnie 869 127 442 000 milisekundy, uznają iż jest on
359 milisekund wcześniejszy od odesłanego przez getLastModified(), a następnie fałszywie zakładają, że
treść apletu uległa zmianie. Dlatego właśnie, żeby zachować bezpieczeństwo („to play it safe”),
getLastModified() powinna zawsze zaokrąglać do najbliższego tysiąca milisekund. Obiekt
HttpServletRequest jest przekazywany do getLastModified() w razie jakby aplet potrzebował oprzeć
swoje rezultaty na informacjach specyficznych dla określonego zlecenia. Standardowy aplet elektronicznego
biuletynu informacyjnego może to wykorzystać np. do określenia który biuletyn jest przedmiotem zlecenia.
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.oreilly.servlet.CacheHttpServlet;
printHeader(out);
printForm(out);
printMassages(out);
printFooter(out);
}
out.println ("</FORM>);
out.println ("<HR>");
}
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) { }
}
}
package com.oreilly.servlet;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
CacheHttpServletResponse cacheResponse;
long cacheLastMod = -1;
String cacheQueryString = null;
String cachePathInfo = null;
String cacheServletPath = null;
Object lock = new Object();
req.getDateHeader("If-Modified-Since")) {
res.SetStatus(res.S.C._NOTMODIFIED);
return ;
}
synchronized (lock) {
cacheResponse = localResponseCopy;
cacheLastMod = servletLastMod;
cacheQueryString();
cachePathInfo = req.getPathInfo();
cacheServletPath = req.geServletPath();
}
}
}
catch (IOExcepion e) {
System out.println (
" Otrzymałeś IOException tworząc odpowiedź z pamięci podręcznej:"
+ e.getMassage());
}
internalReset() {
}
headers.put(name, v);
}
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());
}
}
catch (IOException e) {
System.out.println(
" Otrzymałeś IOException pisemną odpowiedź tekstową z pamięci
podręcznej:" + e.getMessage());
}
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
}
contentType = type;
}
return delegate.getBufferSize();
}
internalReset();
}
/**@deprecated*/
public void setStatus(int sc, String sm) {
delegate.setStatus(sc, sm);
status = sc;
}
internalSetHeader(name, value);
}
/**@deprected*/
public String encodeUrl(String url) {
return this.encodeURL(url);
}
/**@deprected*/
public String encodeRedirectUrl(String url) {
return this.encodeRedirectURL(url);
}
}
ServletOutputStream delegate;
ByteArrayOutputStream cache;
CacheServletOutputStream(ServletOutputStream out) {
delegate = out;
cache = new ByteArrayOutputStream(4096);
}
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);
}
}
Rozdział 4. Odczytywanie
informacji
Do utworzenia udanej aplikacji WWW niejednokrotnie potrzebna jest gruntowna znajomość środowiska, w
którym będzie ona uruchamiana. Dla przykładu konieczne może okazać się zaznajomienie z serwerem, który
wywoł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 o
zleceniach, 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żda
metoda 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 w
wył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ć problemy
wykonawcze. 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:
• Opóźnione obliczanie. W momencie uruchamiania programu CGI przez serwer wartość dla wszystkich
zmiennych ś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 ma
możliwość poprawienia wydajności poprzez opóźnienie podobnych kalkulacji oraz wykonywanie ich na
żądanie wtedy kiedy jest to potrzebne.
• 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 port
wyjściowy. Jednakże co się tyczy apletu, to może on nadal współpracować z serwerem. Tak jak to zostało
omówione w poprzednim rozdziale, aplet działa albo (jeżeli to możliwe) w ramach serwera albo (jeżeli jest
to konieczne) jako przyłączona procedura, poza nim. Korzystając z takiej możliwości podłączenia aplet
może utworzyć zlecenia ad hoc dla informacji przeliczonych, których może dostarczyć tylko serwer. Dla
przykł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
Tabela 4.1.
Zmienne środowiskowe CGI oraz odpowiadające im metody apletowe
W pozostałej części tego rozdziału pokażemy kiedy i jak używa się tych metod oraz wielu
innych, które nie mają odpowiedników CGI. Dalej będziemy również używali metod w
konkretnych apletach.
Aplet
Z każdą zarejestrowaną nazwą apletu mogą być związane inne, specyficzne dla niej parametry początkowe. Aplet
może mieć w każdej chwili dostęp do tych parametrów: są one określone w deskryptorze wdrożenia web.xml
oraz stosowane w metodzie init() celem ustalenia wartości początkowych i domyślnych dla apletu lub
dostosowania jego zachowania w określony sposób. Parametry początkowe zostały szerzej opisane w rozdziale 3
„Czas istnienia (Cykl Życia) Apletu”.
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 metody
getInitParameter(). 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ów
połączenia. Celem odseparowania szczegółów JDBC możemy użyć metody dostosowanej establishConnection(), tak jak
został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
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 to
zastosowanie 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 ukazuje
aplet, 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.*;
Zauważmy, że aplet ten jest bezpośrednio podrzędny do GenericServlet, dowodząc tym samym, że parametry
początkowe dostępne są także dla apletów, które nie są apletami HTTP. Standardowy aplet może zostać użyty w
aplikacji WWW serwera nawet w sytuacji kiedy nie realizuje zespołu funkcji. HTTP-specyficznych.
Jeżeli aplet jest niezarejestrowany metoda odsyła nazwę klasy apletu. Metoda ta znajduje zastosowanie przy
pisaniu do dzienników zdarzeń wprowadzaniu informacji o stanie kopii obiektów apletów do wspólnych zasobów
takich 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ęści
klucza 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 innymi
poznać 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 celu
dostosowania 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ł.
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ący
sposób:www.servlets.com, przykładem odesłanego portu może być 8080.
Metody getServerInfo() oraz getAtribut() ServletContext dostarczają informacji o
oprogramowaniu 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łany
ciąg znaków mógłby wyglądać w następujący sposób: Tomcat Web Server/3.2. Niektóre serwery
zamieszczają 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 by
powiedzieć, że jest furtką przez którą aplet może uzyskać dodatkowe informacje o swoim serwerze. Dla
przykładu serwer mógłby udostępnić statystyki dotyczące obciążenia, odwołanie do puli wspólnych zasobów lub
każ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 do
katalogu poufnego dla tego kontekstu.
Aplety mogą również dodawać do kontekstu swoje własne atrybuty używając do tego celu metody
setAttribute(), tak jak to zostało omówione w rozdziale 11 „Współpraca Serwerów”. Nazwy atrybutów
powinny być zapisywane zgodnie z tą samą konwencją co nazwy pakietów. Nazwa pakietów java.* oraz
javax* 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ępna
jest 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ć je
za pomocą poniższego obiektu:
Najprostszym zastosowaniem informacji o serwerze jest aplet About This Server, sytuacja taka została
zaprezentowana na przykładzie 4.3.
Przykład 4.3.
„Podsłuchiwanie” serwera
import java.io.* ;
import java.uti1.*;
import javax.servlet.* ;
Aplet ten jest bezpośrednio podrzędny (jest bezpośrednią podklasą) do GenericServlet,dowodząc tym
samym, ż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(): localhost
req.getServerPort(): 8080
context.getServerInfo(): Tomcat Web Server/3.2 (JSP 1.1; Servlet 2.2; ...)
get Server Info() name: Tomcat Web Server
getServerInfo() version: 3.2
context .getAttributeNames () :
context. getAttribute (" javax. servlet. context. tempdir") : work/
localhost_8080
Przykład 4.4.
Tworzenie pliku tymczasowego w katalogu tymczasowym
// ...
}
Serwer ten najpierw lokalizuje swój katalog tymczasowy. Następnie używa metody createTempFile() celem
utworzenia w tym katalogu pliku tymczasowego o nazwie i rozszerzeniu odpowiednio xxx i .tmp i w końcu
konstruuje FileOutputStream, ażeby stworzyć możliwość zapisywania w pliku tymczasowym. Pliki, które
muszą pozostawać pomiędzy kolejnymi, ponownymi uruchomieniami serwera nie powinny być umieszczane w
katalogu tymczasowym.
Przykład 4.5 ukazuje aplet, który ogranicza się (swoje działanie) do określonego
serwerowego adresu IP oraz numeru portu. Apletowi temu do usunięcia podobnego
ograniczenia i do obsługi zlecenia, potrzebny jest klucz parametru początkowego, właściwy
dla adresu IP oraz portu jego serwera. Jeżeli aplet nie otrzyma takiego klucza wstrzyma
dalsze działanie.Algorytm używany do odwzorowywania klucza do adresu IP oraz portu (i
vice versa) musi być bezpieczny.
Przykład 4.5.
Aplet ograniczony do serwera
import java. io. *;
import java.net. *;
import java.util.* ;
import javax.servlet.*;
byte hostIP[] ;
try {
hostIP = InetAddress.getByName(host) . getAddress ();
}
catch (UnknownHostException e) {
return false;
}
// logiczna negacja
long accesscode = -numericKey;
Aplet, dopóki nie otrzyma właściwego klucza nie wykona żadnego polecenia. Jednakże celem zapewnienia
cał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ędziemy
testowali 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.
Przykład 4.6.
Ustalanie kontekstowych parametrów początkowych w deskryptorze wdrożenia
<?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ładzie
4.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.* ;
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) {
// ...
}
}
}
Przykład 4.8.
Klasa VersionDetector
package corm.oreilly.servlet;
Klasa na zasadzie przeprowadzania prób ładowania klas oraz zmiennych dostępu — do momentu, w którym
NoClassDefFoundError lub NoSuchFieldException, zawiesi wyszukiwanie. Do tego czasu aktualna
wersja jest już znana. Przykład 4.9 prezentuje aplet, który „podsłuchuje” serwer oraz wersję Javy.
Przykład 4.9.
„Podsłuchiwanie” wersji
Klient
Przy każdym zleceniu aplet ma możliwość uzyskania informacji o komputerze klienta a w przypadku stron
wymagających uwierzytelnienia — informacji o aktualnym użytkowniku. Informacja takie mogą zostać
wykorzystane przy rejestracji danych dostępu, kojarzeniu informacji z indywidualnymi użytkownikami lub przy
ograniczaniu do poszczególnych klientów.
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 komputerem
dist.engr.sgi.com.
Adres IP oraz nazwa węzła odległego mogą zostać konwertowane na obiekt java.net.InetAddress przy
użyciu InetAddress.getByName():
InetAddressremoteInetAddress= InetAddress.getByName(req.getRemoteAddr());
Ograniczanie dostępu
Z 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 o
komputerach klientów, dobrze nadają się do tego celu. Aplety te mogą sprawdzić komputer
klienta i udostępnić łącza do pobierania określonego oprogramowania, tylko wtedy jeżeli
klient 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 eksportu
były tylko Stany Zjednoczone i Kanada, a aplet ten mógł być ściągany tylko przez
użytkowników pochodzących z tych dwóch krajów. Od czasu tamtej edycji rząd Stanów
Zjednoczonych 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 ze
wszystkich 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ł pokazany
aplet, 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.*;
Niemal każdy serwer HTTP ma wbudowaną zdolność do ograniczania dostępu do pewnej liczby lub wszystkich
swoich 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ą z
takich 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ę, tym
razem 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 ponownie
odmawiany 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():
Rysunek 4.1.
Okno logowania dla strony z ograniczonym dostępem
Aplet, w celu ustalenia jaki rodzaj uwierzytelnienia został użyty, może użyć metody
getAuthType():
Do czasu kiedy aplet wywoła getRemoteUser(), serwer określi już, że użytkownik jest uprawniony do
wywołania apletu, jednakże nie oznacza to, że nazwa użytkownika zdalnego jest bezwartościowa. Aplet mógłby
przeprowadzić 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żeli
osoba 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ńcu
odległ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ólnie
rzecz 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 o
użytkownikach. Jednakże w przypadku gdy aplet jest już chroniony i ma łatwo dostępną nazwę, może jej równie
dobrze użyć.
Za pomocą nazwy użytkownika zdalnego aplet może zapisać informacje o każdym kliencie. Po dłuższym okresie
aplet może zapamiętać preferencje każdego z użytkowników. Na krótszą metę może on zapamiętać serię stron
przeglądanych przez klienta i użyć jej w celu dodania odczytu stanu do bezstanowego protokołu HTTP. Efekty
związane ze śledzeniem sesji (opisane w rozdziale 7) mogą okazać się niepotrzebne w przypadku kiedy aplet zna
już nazwę użytkownika klienta.
Personalizowane powitanie
Prosty 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.*;
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.
}
else {
out.println("Twoja ostatnia wizyta miała miejsce
"+accesses.get(remoteUser));
}
if (remoteUser.equals("PROFESSOR FALKEN")) {
out.println("Zagramy?");
}
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 oraz
poinformowanie jej kiedy miała jej ostatnia wizyta. Następnie rejestruje czas wizyty (w celu przedstawienia go
następnym razem), poczym kontynuuje obsługę zlecenia.
Zlecenie
Po tym jak zapoznaliśmy się ze sposobem, w jaki aplet uzyskuje informacje o serwerze i o
kliencie, nadszedł czas na omówienia naprawdę ważnej sprawy: w jaki sposób aplet
dowiaduje się o tym czego chce klient.
Parametry zlecenia
Każdemu wywołaniu apletu może towarzyszyć dowolna liczba parametrów zlecenia. Parametrami są zwykle pary
nazwa/wartość, które dostarczają apletowi wszystkich dodatkowych informacji, które są mu potrzebne do obsługi
zlecenia. Nie należy mylić parametrów zlecenia z parametrami początkowymi apletu, które są związane z samym
apletem.
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 w
obu formach. Na szczęście każdy aplet odczytuje swoje parametry w ten sam sposób — przy
uż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łoby
jakiekolwiek prawdopodobieństwo, że parametr będzie miał więcej niż jedną wartość powinniśmy użyć w
zamian metody getParameterValues(). Metoda ta odsyła wszystkie wartości parametrów nazwanych jako
układ obiektów String lub jako null — jeżeli parametr nie został określony. Wartość pojedyncza odsyłana jest
jako 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ą one
już 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ólnego
zastosowania poinformowania apletu co ma robić, jak to ma zrobić, lub jedno i drugie. Dla przykładu
przyjrzyjmy 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 listy
wycofanych metod. Była to pierwsza metoda Javy, z której nie zrezygnowano.
Plik HTML mógłby zawierać poniższy formularz, w którym użytkownik jest proszony o podanie słówka do
wyszukania:
<FORM METHOD=GET ACTION="/aplet/Słownik">
Word to look up: <INPUT TYPE=TEXT NAME="słowo"><P>
< INPUT TYPE=SUBMIT><P>
</FORM>
Ten kod obsługuje tylko wartość na parametr. Niektóre parametry maja wartości wielokrotne, tak jak w
przypadku używania <SELECT>:
Poza możliwością uzyskania wartości parametrów, aplet może mieć dostęp do nazw parametrów. używając do
tego celu metody getParameterNames():
Metoda odsyła wszystkie nazwy parametrów jako Enumeration (wyliczenie) obiektu String lub puste
Enumeration — jeżeli aplet nie ma żadnych parametrów. Metoda ta jest najczęściej stosowana do
debagowania. Kolejność nazw nie koniecznie będzie zgodna z kolejnością w formularzu.
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 danych
formularzowych. Najlepiej sprawdza się przy obsłudze pojedynczych, nienazwanych wartości, tak jak w
przypadku /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 swoich
parametrów.
Przykład 4.12.
Podpatrywanie parametrów
import java.io. *;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http. * ;
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]);
}
}
}
}
}
import java.io. *;
import java.net. *;
import java.util.* ;
import javax.servlet.*;
import javax.servlet.http.*;
// 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());
}
}
}
class KeyGenerationException extends Exception {
public KeyGenerationException() {
super() ;
}
<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 rzeczywistym
użytkowaniem.
Informacje ścieżki
Dodatkowo 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 taka
informacja dodatkowej ścieżki używana jest do wskazywania pliku na serwerze, który
powinien zostać użyty przez aplet w jakimś określonym celu. Taka informacja ścieżki jest
kodowana 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że
mieć dostęp do tych informacji ścieżki, może również przesunąć łańcuch /index.html ścieżki rzeczywistej
pliku index.html. Czym zatem jest ścieżka rzeczywista /index.html? Jest to pełna ścieżka systemu plików do
pliku — to co serwer by odesłał jeżeli klient poprosiłby bezpośrednio o /index.html. Tym „czymś” okazałoby
się z pewnością document_root/index.html, lecz oczywiście serwer mógłby mieć specjalny aliasing zmieniający
to.
Poza tym, podobna informacja dodatkowej ścieżki będąc wyraźnie określoną w URL-u, może być kodowana w
parametrze ACTION, formularza HTML:
Formularz ten wywołuje aplet Dictionary w celu obsłużenia swoich przedłożeń oraz przekazuje mu informacje
dodatkowej ś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.
Metoda ta odsyła informacje dodatkowej ścieżki związane ze zleceniem (URL jest dekodowany jeżeli jest taka
potrzeba) 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ę w
systemie plików, pliku podanego w informacji ścieżki, tam gdzie zwykle ma miejsce getPathTranslated():
Przykład 4.14.
Pokazywanie dokąd prowadzi ścieżka
import java.io. *;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
if (req.getPathInfo() != null) {
Metoda ta odsyła ścieżkę rzeczywistą jakiejkolwiek ścieżki wirtualnej (virtual path) lub null — jeżeli
tłumaczenie nie może zostać wykonane. Jeżeli daną ścieżką jest /, metoda odsyła źródło dokumentu (miejsce, w
któ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żywana
przez aplety standardowe jak również aplety HTTP. Nie istnieje żaden odpowiednik CGI.
Metoda ta odsyła ty MIME danego pliku, na podstawie jego rozszerzenia lub null — jeżeli
jest ono nieznane. Najczęściej spotykane typy MIME to: text/html, text/plain, image/gif
oraz 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ą one
być ulepszane bądź ignorowane przez hasła w deskryptorze wdrożenia web.xml, dając każdemu kontekstowi
możliwe do skonfigurowania zachowanie, tak jak to zostało pokazane na przykładzie 4.15.
Przykład 4.15.
Każdy kocha Mime
<!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>
Podawanie plików
Serwer „Tomcat” jako taki, używa apletów do obsługi każdego zlecenia. Poza tym
prezentowanie możliwości apletów powoduje, że serwer ma budowę modularną, co z kolei
umożliwia „hurtową” wymianę (zastąpienie) pewnych aspektów jego sposobu działania. Dla
przykł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 programem
obsługującym zlecenie). Jednak nie jest tak, że nie można zastąpić DefaultServlet. Można
tego dokonać poprzez zmianę wzoru URL i użycie innego apletu. Nie jest również problemem
napisanie 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 metody
getPathTranslated() oraz getMimeType(), ażeby odesłać plik podany w informacji
dodatkowej ś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;
// 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ści
odpowiedzi. I w końcu odsyła plik, używając do tego metody returnFile() znajdującej się w klasie usługowej
com.oreilly.servlet.ServletUtils:
// Prześlij zawartość pliku do strumienia wyjściowego
public 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 opisuje
błą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ępnym
rozdziale 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.
Rzeczą o której należy pamiętać podczas korzystania z obiektu kontekstowego, jest nie zawieranie ścieżki
kontekstu 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ąc
rekompilacji. Poniższy kod pobiera oraz drukuje plik /includes/header.html dla bieżącego kontekstu:
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 klasy
com.oreilly.servlet.Servlet.Utils:
Tak jak to zostało pokazane w drugiej metodzie returnURL(), w poprzednim kodzie, możliwe jest stworzenie
obiektu URL do badania atrybutów zasobu oddzielonego. Nie wszystkie serwery oraz fazy wykonywania Javy
obsł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 // kompletne
String contentType = con.getContentType(); // rozpoznawanie nie // kompletne
long expiration = con.getExpiration(); // rozpoznawanie nie // kompletne
long lastModified = con.getLastModified(); // rozpoznawanie nie // kompletne
// itd...
Pamiętajmy, że treść podana dla ścieżki /URI jest całkowicie determinowana przez serwer. W
celu uzyskania dostępu do zasobów z innego kontekstu możemy użyć metody getContext():
public ServletContext ServletContext.getContext(String uripath)
Poniższa metoda — getResourceAsStream() jest wygodna dla odczytywania zasobów jako strumień:
Zachowuje się ona zasadniczo tak samo jak get().openStream(). Celem utrzymania zgodności z
poprzednimi wersjami oraz aby złagodzić przejście na aplety (dla programistów CGI), Interfejs API będzie nadal
zawierał metody dostępu do plików, takie jak np. getPathTranslated(). Trzeba tylko pamiętać, że za
każdym razem kiedy uzyskujemy dostęp do zasobu używając obiektu File, „wiążemy się” z określonym
komputerem.
Podawanie zasobów
Uż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 serwerze
innym 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;
// 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;
}
// 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 ochrony
zasobów, więc pliki pod WEB-INF i pod źródłem .jsp mogą być podawane bezpośrednio. Przykład 4.18
prezentuje bezpieczniejszą wersję klasy, przy użyciu metody ServletUtils.getResource() z
com.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 ;
// Połącz z zasobem
URLConnection con = url.openConnection() ;
con.connect();
// 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());
}
}
}
if (resource.endsWith("/") ||
resource.endsWith("\\")||
resource.endsWith(".")) {
throw new MalformedURLException(‘Ścieżka nie może się kończyć
ukośnikiem ani kropką”);
}
if (upperResource.endsWith(".JSP")) {
throw new MalformedURLException(
"Ścieżka nie może kończyć się na .jsp") ;
}
return url;
}
Żadna metoda nie odsyła bezpośrednio oryginalnego Jednolitego Lokalizatora Zasobów (Uniform Resource
Locator — 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.*
Przez większość czasu jednakże apletowi tak naprawdę nie jest potrzebny URL zlecenia. Co jest mu potrzebne to
zleceniowe 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 — jeszcze
przed dekodowaniem URL-u. Normalne aplety HTTP mogą postrzegać URI zlecenia jako URL nie zawierający
schematu, 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.2
zaprezentowano kilkanaście URI zleceniowych oraz ich odpowiedniki URL-owe.
Tabela 4.2.
URI oraz ich URL-owe odpowiedniki
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 celu
realizacji połączenia HTTP lecz nie jest zawarty w zleceniu składanym serwerowi odpowiadającemu na tym porcie.
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():
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
decoded(getRequestURI) ==
decoded(getContextPath) + getServletPath + getPathInfo
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óra
zawiera schemat). W przypadku apletów HTTP metoda ta informuje o tym czy zlecenie zostało złożone przez
połączenie bezpieczne, przy użyciu SSL — Warstwy Bezpiecznych Gniazdek (Secure Sockets Layer), tak jak w
przypadku 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:
Protokół i numer wersji oddzielone są ukośnikiem. Metoda odsyła null jeżeli nie można ustalić protokołu. Dla
apletów HTTP protokół to zwykle HTTP/1.0 lub HTTP/1.1. Aplety HTTP mogą wykorzystać wersję protokołu
w celu określenia czy mogą podczas współpracy z klientem nowych funkcji zawartych wersji 1.1 HTTP.
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, oraz
HEAD. Metoda service(), wdrożenia HttpServlet wykorzystuje tą metodę przy wysyłaniu zleceń.
Nagłówki zleceniowe
Zlecenia 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ątki
możliwych nagłówków; wersja 1.1 zawiera ich nawet więcej. Opisu wszystkich nagłówków nie byliśmy w stanie
zamieś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 Roberta
Ecksteina „Webmaster in a Nutshell”
Aplet rzadko musi odczytywać nagłówki HTTP, które towarzyszą zleceniu. Wiele nagłówków towarzyszących
zleceniu obsługiwanych jest przez sam serwer. Rozważmy dla przykładu sposób, w jaki serwer ogranicza dostęp
do swoich dokumentów. Serwer wykorzystuje nagłówki HTTP, a aplet nie musi znać szczegółów. Kiedy serwer
otrzymuje zlecenie na stronę, do której dostęp jest ograniczony, sprawdza czy zlecenie zawiera właściwy
nagłówek Authorization (Uwierzytelnianie), który powinien z kolei zawierać poprawną nazwę użytkownika
oraz 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 daje
wywoł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ć para
Last-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 w
tabeli 4.4.
Tabela 4.4.
Lista przydatnych nagłówków zleceniowych HTTP
Nagłówek Zastosowanie
Accept 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śnika
podzielony 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 klient
akceptuje wszystkie typy nośników
Accept- Określa język lub języki, które preferuje klient, używając w tym celu skrótów języka
Language standardowego ISO-639 wraz z (wariantowym) kodem kraju ISO-3166. Na przykład:
Accept-Language: en, es, de, ja, zh-TW
Taki 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. Stosuje
się 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.
User-Agent Dostarcza informacji na temat oprogramowania klienta. Format odsyłanego strumienia jest
względnie dowolny, zwykle jednak zawiera nazwę oraz wersję przeglądarki oraz informacje o
komputerze na którym jest wykonywany. Netscape 4.7 na SGI Indy uruchamiający IRIX 6.2
relacjonuje:
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 swojej
odpowiedzi 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 tego
dokumentu)*) Dla przykładu:
Referer: http://developer.java.sun.com/index.html
Aplet może posługiwać się tym nagłówkiem w celu prowadzenia statystyk lub w przypadku
istnienia jakiegoś błędu w zleceniu, utrzymywać ścieżkę dokumentu z błędami.
Authorizatio Zapewnia uprawnienie klienta do dostępu do żądanego URI, włącznie z nazwą użytkownika i
n hasł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 pisowni
zawartej w specyfikacji HTTP, determinuje taką a nie inną pisownię tego wyrazu.
getHeader() odsyła wartość nagłówka nazwanego, jako String (ciąg znaków) lub null (zero) — w wypadku
kiedy nagłówek nie był przesłany jako część zlecenia. W wypadku w nazwie nie są rozróżniane małe i wielkie
litery, przeciwnie do wszystkich tych metod. Przy pomocy tej metody można odczytać wszystkie typy
nagłówków.
getDateHeader() odsyła wartość nagłówka nazwanego, jako long (reprezentujący Date), określające ile
milisekund upłynęło od „epoki”) lub — 1 jeżeli nagłówek nie został przesłany jako część zlecenia. Metoda ta
zgłasza wyjątek IllegalArgumentException, kiedy wywoływany jest nagłówek, którego wartość nie może
zostać 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łany
jako część zlecenia. Metoda zgłasza wyjątek NumberFormatException przy wywoływaniu nagłówka, którego
wartość nie może być przekształcona na int.
Aplet może również, używając metody getHeaderNames(), uzyskać nazwy wszystkich nagłówków:
Niektóre nagłówki takie jak np. Accept czy Accept-Language, obsługują wartości wielokrotne. Zwykle
wartości te są przekazywane w jednym nagłówku, oddzielonym odstępami, jednak niektóre przeglądarki
preferują przesyłanie wartości wielokrotnych przez nagłówki wielokrotne:
Accept-Language: en
Accept-Language: fr
Accept-Language: ja
W celu odczytania nagłówka za pomocą wartości wielokrotnych, aplety mogą posłużyć się metodą getHeaders
():
Omawiana metoda odsyła wszystkie wartości dla danego nagłówka jako Enumeration (wyliczenie)obiektów
String, lub puste Enumeration — jeżeli nagłówek nie został przesłany jako część zlecenia. W przypadku
gdy 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świetla
informacje 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 {
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);
Request Headers;
Connection: Keep-Alive
If-Modified-Since: Thursday, 17-Feb-OO 23:23:58 GMT; length=297
User-Agent: Mozilla/4.7 [en] (WiriNT; U)
Host: localhost:8080
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*
Accept-Language: en
Accept-Language: es
Accept-Charset: iso-8859-1,*,utf-8
Cookie: JSESSIONID=ql886xlc31
Strumień wyjściowy
Z każdym zleceniem, obsługiwanym przez aplet związany jest strumień wejściowy. Tak jak w
przypadku PrintWriter czy OutpurStream związanych z związanych z obiektem odpowiedzi
apletu, do których może on pisać, w przypadku Reader czy InputStream związanych z
obiektem zlecenia apletu, może on z nich czytać. Dane odczytywane ze strumienia
wejściowego mogą mieć dowolny typ treści i dowolną długość. Strumień wejściowy ma dwa
zasadnicze cele:
• Przekazanie apletowi HTTP treści związanej ze zleceniem POST
• Przekazanie apletowi innemu niż HTTP dane nieprzetworzone, przesłane przez klienta
W celu odczytania danych znakowych ze strumienia wejściowego, powinniśmy użyć metody
getReader(), pobierając strumień wejściowy jako obiekt BufferReader:
Przewaga używania BufferReader dla odczytywania danych znakowych, to poprawne tłumaczenie zestawów
znakó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ątek
UnsupportedEncodingException jeżeli kodowanie znaków sygnału wejściowego jest nieobsługiwane lub
nieznane.
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:
Obiekt ServletInputStream jest bezpośrednią podklasą InputStream i może być traktowany jako normalny
InputStream, 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 tego
samego zlecenia. Kiedy już mamy ServletInputStream, możemy odczytać z stamtąd wiersz przy użyciu
readLine():
Metoda ta czyta bity ze strumienia wejściowego do tablicy bitów b, począwszy od pozycji w tablicy podanej
przez 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ęty
koniec strumienia.
W
drożenia readLine Interfejsu API 2.0 były obciążone błędem, przekazany parametr len był ignorowany, stwarzając
problemy 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.
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:
getContentType() odsyła typ nośnika treści przesyłanej przez strumień wejściowy, lub null — jeżeli typ ten
nie 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żeli
długość ta nie jest znana.
Napisanie klientowi połowę ładowania pliku jest rzeczą całkiem prostą. HTML w przykładzie
4.20 generuje formularz, który prosi o podanie nazwy użytkownika oraz pliku do
załadowania. Warte odnotowania jest dodanie atrybutu ENCTYPE oraz zastosowanie typu FILE
sygnału wejściowego.
Przykład 4.20.
Formularz do wybierania ładowanego pliku
Użytkownik, który otrzymuje podobny formularz widzi stronę, która wygląda podobnie jak ta
przedstawiona na rysunku 4.3. Nazwa pliku może zostać umieszczona w obszarze tekstowym
lub wybrana poprzez przeglądanie. Wielokrotne pola danych <INPUT TYPE =FILE> mogą być
stosowane, jednakże obecnie stosowane przeglądarki obsługują ładowanie tylko jednego
pliku na pole. Po selekcji użytkownik przedkłada formularz standardowo.
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” Jason
napisał klasę użyteczności, która nie działa w naszym przypadku. Jej nazwa brzmi MultipartRequest i została
zaprezentowana na przykładzie 4.22, dalej w tym podrozdziale.
MultipartRequest zawija ServletRequest i przedstawia proste dla programistów apletów API. Klasa ta ma
dwóch konstruktorów:
Każda z tych metod tworzy nowy obiekt MultipartRequest aby obsłużyć określone zlecenie, zapisując
wszystkie 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 aplety
używając tej klasy nie mogą czytać strumienia wejściowego). Konstruktor, który przyjmuje parametr
MaxPostSize 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: po
pierwsze, spróbować przesłać stronę błędu, poczekać aż klient się rozłączy i podczas „cichego” czekania
zniszczyć całą załadowaną treść. Postępowanie takie jest zgodne z opisem HTTP / 1.1 w RFC2616, sekcja 8.2.2,
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 wielu
przeglądarek, które nie oczekują na status błędu oznacza marnowanie przepustowości serwera ponieważ klient
bę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ów
pozostaje bez komunikatu błędu, lecz zapewnia to bezwzględne przerwanie ładowania.
Metoda ta odsyła nazwy wszystkich parametrów jako Enumeration (wyliczenie) obiektów String, lub puste
Enumeration jeżeli nie ma żadnych parametrów.
W celu uzyskania nazwy parametru nazwanego należy zastosować metodę getParameter() lub metodę
getParameterValues():
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, tylko
ostatnia z nich jest odsyłana.
Metoda ta odsyła wszystkie wartości parametrów nazwanych jako String, lub null — jeżeli parametr nie
został 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():
Powyższa metoda odsyła nazwy wszystkich ładowanych plików jako Enumeration obiektów String, lub puste
Enumeration — 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ę pliku
możemy uzyskać jego nazwę systemu plików, wykorzystując w tym celu metodę getFilesystemName():
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ą plik
jest 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():
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 aplet
jest 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 ;
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>") ;
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) {
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 do
katalogu macierzystego serwera, gdzie będą zapisane ładowane pliki (ponieważ obszerne pliki mogą nie zostać
umieszczone w pamięci) oraz z maksymalnym rozmiarem POST 5 MB. Aplet używa następnie
MultipartRequest do przechodzenia kolejno przez parametry, które zostały przesłane. Zwróćmy uwagę, iż
MultipartRequest API dla manipulowania parametrami zgadza się z ServletRequest. I wreszcie aplet
wykorzystuje swojego MultipartRequest do przechodzenia przez przesłane pliki. Dla każdego pliku
uzyskiwana jest nazwa pliku (zgodnie z formularzem), nazwa systemu plików (określona przez użytkownika)
oraz typ treści. Aplet uzyskuje również odwołanie File, którego używa w celu wyświetlenia zapisywanego
pliku. W razie jakichkolwiek problemów aplet powiadamia użytkownika o sytuacji wyjątkowej.
Należy mieć świadomość tego, iż wielu producentów serwerów nie testuje należycie swoich
wyrobów pod kątem ładowania plików, nie jest rzeczą niezwykłą w przypadku tej klasy, fakt iż
serwery bywają obarczone błędami. Jeżeli spotkamy się z problemami podczas korzystania tej
klasy, powinniśmy spróbować innego serwera (jak np. „Tomcat’a”) w celu ustalenia czy
rzeczywiście są one związane z serwerem — jeżeli okaże się, że to prawda należy
skontaktować się z producentem serwera.
Przykład 4.22.
Klasa MultipartRequest
package com.oreilly.servlet ;
import 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 ;
// Zapisz katalog
File dir = new File(saveDirectory) ;
catch (Exception e) {
return null;
}
}
return null;
}
}
}
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 interfejsu
HttpServletRequest? Odpowiedź brzmi: ponieważ ograniczałoby to jej kompatybilność z przyszłymi
wersjami. 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ów
używających tej klasy.
Dodatkowe atrybuty
Czasem aplety potrzebują informacji na temat zlecenia, dostępu do których nie można uzyskać poprzez metody
wymienione 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ła
specyficzne-serwerowo atrybuty związane z samym serwerem. ServletRequest również ma metodę
getAttribute():
Metoda ta odsyła wartość serwerowo-specyficznego atrybutu dla zlecenia, lub null — w przypadku gdy serwer
nie obsługuje nazwanego atrybutu zlecenia. Metoda ta pozwala również serwerowi na dostarczanie apletowi
niestandardowych informacji o zleceniu. Serwery mają wolną rękę w dostarczaniu jakichkolwiek atrybutów lub
mogą ich w ogóle nie dostarczać. prostu nie. Jedynym wymogiem jest to, że nazwy atrybutów powinny być w tej
samej 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”. Opis
atrybutó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() (tak
jak to zostało omówione w rozdziale 11). Wydruk wszystkich aktualnych atrybutów, ustalonych przez serwer lub
umieszczonych przez aplety, można otrzymać przy pomocy metody getAttributeNames():
HTTP
W poprzednim rozdziale dowiedzieliśmy się, że aplet ma dostęp do różnego rodzaju informacji — informacji o
kliencie, o serwerze, o zleceniu i nawet o sobie samym. Czas więc abyśmy się zapoznali z tym, co aplet może
zrobić 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 w
poprzednich przykładach. Dalej powiemy jak zmniejszyć obciążenie związane z utrzymywaniem połączenia z
klientem, 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ów
statusu, 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 do
dziennika zdarzeń serwera.
W przeciwieństwie do swojego odpowiednika z poprzedniego wydania niniejszej książki, rozdział ten nie opisuje
szczegółowo generowania treści HTML-owej. Jest on wprowadzeniem do następnych rozdziałów książki, w
których omówionych zostanie szereg oddzielnych osnów.
Struktura odpowiedzi
Aplety HTTP odsyłają trzy, różne rodzajowo kwestie: pojedynczy kod statusu, dowolną liczbę nagłówków
HTTP 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. Numerycznemu
kodowi 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órych
przypadkach, kiedy pojawiają się problemy, przeglądarka może pokazać kod statusu użytkownikowi. Najbardziej
chyba znanym kodem statusu jest kod 404NotFound, przesyłany przez serwer WWW, kiedy ten nie może
znaleźć żądanego URL-u.
W poprzednim rozdziale zapoznaliśmy się ze sposobem, w jaki klient wykorzystuje nagłówki HTTP w celu
przesłania dodatkowych informacji razem ze zleceniem. W tym rozdziale zobaczymy jak aplet może przesyłać te
nagłó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 odpowiedzi
może mieć różny typ oraz różną długość; klient czytając oraz interpretując nagłówki HTTP zawarte w
odpowiedzi, wie czego może się spodziewać.
Standardowe aplety są znacznie mniej skomplikowane od apletów HTTP — odsyłają tylko korpus odpowiedzi
do swojego klienta. Co się jednak tyczy podklasy GenericServlet, to może ona przedstawiać API, które dzieli
pojedynczy korpus odpowiedzi na bardziej skomplikowaną strukturę, dając wrażenie odsyłania wielokrotnych
pozycji 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ą kody
statusu 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, że
protokół 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 zanim
jeszcze prześle klientowi jakikolwiek korpus odpowiedzi. Aplet może umieścić swój korpus odpowiedzi w
pamię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.
Przykład 5.1.
Jeszcze raz „Hello”
import java.io.* ;
import javax.servlet.* ;
import javax.servlet.http. *;
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 tylko
skrótowo. Metoda setContentType() ServletResponse (odpowiedzi serwera) ustala typ
treści odpowiedzi, aby był on typem określonym.
public void ServletResponse.setContentType(String typ)
Metoda getWriter() odsyła PrintWriter w przypadku pisania danych odpowiedzi opartych na znakach:
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óra
została zdefiniowana w javax.servlet. Klasę ServletOutputStream można uzyskać za pomocą metody
getOutputStream():
Metoda ta odsyła ServletOutputStream dla pisania binarnych danych odpowiedzi. Nie jest w takim
przypadku wykonywane żadne kodowanie. Metoda ta zgłasza wyjątek IllegalStateeException jeżeli
getWriter() 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że
w wersji 2.0 Interfejsu API oraz późniejszych, została ograniczona tylko do obsługi danych binarnych. Będąc
bezpoś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() i
println() klasy ServletOutputStream, umożliwiające pisanie większości elementarnych Javowskich typów
danych (w celu uzyskania kompletnego zestawienia patrz Uzupełnienie A: „Interfejs API — charakterystyka
ogólna”). Jedyna różnica pomiędzy interfejsem ServletOutputStream a interfejsem klasy PrintStream, jest
taka, że metody print() oraz println() klasy servletOutputStream nie mogą (nie jest dokładnie jasne
dlaczego) drukować bezpośrednio parametrów typu Object czy char[].
Kiedy klient np. przeglądarka, chce złożyć do serwera zlecenie na określony dokument sieci WWW, rozpoczyna
poprzez ustanowienie połączenia do portu serwera. Właśnie poprzez to połączenie klient składa swoje zlecenie
oraz 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, kiedy
wyszukana strona zawierałaby znaczniki <IMG> lub znaczniki <APPLET >, które obligują klienta do odczytania
większej ilości treści z serwera? W takim przypadku tworzone jest jeszcze jedno połączenie portowe. Jeżeli
strona 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ługimi
czasami oczekiwań w Internecie. Taką sytuacją można porównać do zamawiania pizzy poprzez składanie
zamówienia na każdą jej warstwę.
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 taki
jak np. czysta linia. Co by się jednak stało gdyby już sama odpowiedź zawierała czystą linię? Idea trwałego
połą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ędzie
zgodne 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że
ustanowić odpowiedź Content-Length i zyskać w ten sposób korzyści płynące ze stosowania trwałego
połączenia dla treści dynamicznej poprzez użycie metody setContentLength():
Metoda ta ustala długość (w bitach) treści odsyłanej przez serwer. W przypadku apletu HTTP, metoda ustanawia
nagłówek HTTP Content-Length. Zauważmy, że stosowanie tej metody jest opcjonalne. Jeżeli jednak
zdecydujemy się na jej zastosowanie, nasze aplety będą mogły czerpać korzyści płynącyce ze stosowania
trwałych połączeń, jeżeli takie się pojawią. Klient będzie również w stanie wyświetlać dokładne monitorowanie
stopnia bieżącego zaawansowania podczas ładowania.
Jeżeli wywołujemy metodę setContentLength(),musimy pamiętać o dwóch bardzo ważnych sprawach: aplet
musi 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ę z
potencjalnymi problemami.
Buforowanie odpowiedzi
Począwszy od wersji 2.2 Interfejsu API aplety maja kontrolę nad tym czy serwer buforuje swoją odpowiedź czy
nie 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ła
determinowana 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ź nie
będzie od razu zatwierdzona. Jeżeli aplet wykryje błąd, kod statusu oraz nagłówki będą mogły być jeszcze
zmienione (do czasu opróżnienia bufora).
Buforowanie odpowiedzi jest również prostym sposobem uniknięcia stosunkowo trudnego szacowania długości
treści. Aplet może wykorzystać buforowanie aby automatycznie obliczyć długość treści, tak jak to zostało
zaprezentowane 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.*;
Aplet wywołuje metodę setBufferSize() aby poprosić o minimum 16kB bufor odpowiedzi, a następnie
wysyła jak zwykle, swoją odpowiedź. Serwer wykorzystuje bufor wewnętrzny o pojemności co najmniej 16.384
bajtów aby przechować korpus odpowiedzi, po czym czeka z wysłaniem treści do klienta do momentu
wypeł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.
Warto również zauważyć, iż nie wszystkie serwery i nie wszyscy klienci obsługują trwałe
połączenia. Oznacza to, iż dla apletu jest nadal ważne ustalanie swojej długości oraz
buforowanie 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órzy
obsługują trwałe połączenia a ignorowana przez innych.
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 bufor
pozwala na napisanie większej ilości treści zanim jeszcze cokolwiek zostanie wysłane, dając
tym samym apletowi więcej czasu na ustawienie kodów statusu oraz nagłówków. Mniejszy
bufor zmniejsza obciążenie pamięci serwera i pozwala na wcześniejsze rozpoczęcie
przesyłania danych do klienta. Metoda ta musi zostać wywołana zanim jeszcze zostanie
napisana jakakolwiek treść korpusu odpowiedzi; w przypadku gdy treść została już napisana
metoda ta zgłasza wyjątek IllegalStateException. W celu określenia rozmiaru bufora można
posł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ło
prawdopodobnym) przypadku nie użycia żadnego buforowania.
Ażeby określić czy jakaś część zlecenia została już wysłana możemy wywołać metodę
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ć bufor
odpowiedzi, aktualnie przypisany kod statusu oraz nagłówki odpowiedzi:
Metoda ta musi zostać wywołana zanim jeszcze odpowiedź nie zostanie zatwierdzona, w przeciwnym wypadku
jest zgłaszany wyjątek IllegalStateException. Metody sendError() oraz sendRedirect() (które
zostaną omówione później) zachowują się podobnie i opróżniają bufor odpowiedzi, metody te jednak nie
modyfikują nagłówków odpowiedzi.
Możemy również wymusić zapisanie określonej treści do klienta w buforze, poprzez wywołanie metody
flushBuffer(), pozwalając tym samym na natychmiastowe rozpoczęcie przesyłania danych do klienta:
Wywołanie tej metody powoduje automatycznie zatwierdzenie odpowiedzi, oznacza to, iż kod
statusu 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żliwe
jest wyzerowanie (za pomocą metody reset()).
Przykład 5.3.
Zarządzanie buforem odpowiedzi
import javax.servlet.* ;
import javax.servlet.http.*;
import java.io.*;
Kody statusu
Do 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 do
przodu” 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ąc
kod statusu aplet może zrobić ze swoją odpowiedzią więcej. Dla przykładu może on
przekierować zlecenie lub zawiadomić o błędzie.
Najczęściej spotykane liczby kodu statusu są określane jako stałe skrótów mnemonicznych (pola danych public
final 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ótu Kod Komunikat Znaczenie
mnemonicznego domyślny
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_CONTE 204 Nie ma treści Zlecenie zakończyło się sukcesem, lecz nie było żadnego korpusu
NT odpowiedzi do odesłania. Przeglądarki, które otrzymają ten kod powinny
zachować swój aktualny widok dokumentu. Kod ten jest bardzo przydatny
apletowi, kiedy przyjmuje dane z formularza, lecz chce aby widok
przeglądarki pozostał na formularzu, ponieważ unika komunikatu błędu
„Dokument nie zawiera żadnych danych”.
S.C._MOVED_PE 301 Stale Zamawiany zasób ma stale przenoszone miejsce lokalizacji. Przyszłe
RMANENTLY przenoszone odwołania powinny użyć w zleceniu nowy URL. Nowa lokalizacja podawana
jest przez nagłówek Location. Większość przeglądarek automatycznie
ustanawia połączenie z nową lokalizacją.
S.C._MOVED_TE 302 Czasowo Zamawiany zasób ma tymczasowo zmienioną lokalizację, jednak przyszłe
MPORARILY przeniesione odniesienia powinny nadal używać oryginalnego URL-u, aby ustanowić
połączenie z nową lokalizacją.
S.C._UNAUTHOR 401 Nieupoważni Zleceniu brak było właściwego upoważnienia. Używane razem z
IZED ono nagłówkami WWW-Authenticate i Authorization
S.C._NOT_FOUN 404 Nie znaleziono Zamówiony zasób nie został odnaleziony lub jest niedostępny
D
S.C._INTERNAL 500 Błąd Na serwerze zdarzył się nieoczeki wany błąd, który uniemożliwił mu
_SERVER_ERROR wewnętrzny wykonanie zlecenia.
serwera
S.C._NOT_IMPL 501 Nie Serwer nie obsługuje zestawu funkcji potrzebnych do zrealizowania zlecenia.
EMENTED wprowadzono
S.C._SERVICE_ 503 Obsługa Obsługa (serwer) jest tymczasowo niedostępna, lecz powinna być dostępna w
UNAVAILABLE niedostępna niedalekiej przyszłości. Jeżeli serwer wie kiedy wznowi obsługę, nagłówek
Retry-After może zostać użyty
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.
Jeżeli aplet ustala kod statusu, który informuje o błędzie w czasie obsługi zlecenia, zamiast setStatus()może
on wywołać metodę sendError():
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 kody
statusu, aplet może wykonać dokładnie to co robi DefaultServlet: ustawić kod odpowiedzi na
S.C._NOT_FOUND, w celu zasygnalizowania, że plik będący przedmiotem zlecenia nie został odnaleziony i nie
może zostać odesłany. Oto ulepszona wersja apletu:
// Odeślij plik
try {
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, jednak
bardzo przypomina standardową stronę błędu serwera. Dla serwera „Apache/Tomcat” generowana jest jego
wł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 na
współdziałanie z serwerem.
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 wszystkich
możliwych nagłówków HTTP 1.0 i HTTP 1.1 wybiega poza zakres tej książki. Na tabeli 5.2
wyszczególniono nagłówki HTTP — ustawiane najczęściej przez aplety jako część
odpowiedzi.
Tabela 5.2.
Nagłówki HTTP odpowiedzi
Nagłówek Zastosowanie
Cache-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 do
poinformowania, że ten dokument nie powinien być buforowany), no-store (służąca do
poinformowania, że ten dokument nie powinien być -buforowany ani nawet przechowywany przez
serwer 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ęć utrzymywania
otwartego (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 automatycznie
ustalając wartość nagłówka na keep-alive — w przypadku gdy aplet ustawia swój nagłówek
Content_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 statusu
S.C._SERVICE_UNAVAILABLE. Jego wartość to int, które reprezentuje
liczbę sekund lub ciąg danych reprezentujący czas rzeczywisty.
Expires Określa czas, w którym dokument może się zmienić lub kiedy jego
informacje 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ć w
pełni kwalifikowany URL (włącznie z „http://”)
WWW- Określa schemat autoryzacji dziedzinę autoryzacji wymaganą przez klienta podczas wywoływania
Authenticate URL-u będącego przedmiotem zlecenia. Używany z kodem statusu S.C._UNAUTHORIZED.
Content- Określa schemat użyty do kodowania korpusu odpowiedzi. Przykładowe
Encoding wartości to gzip (lub x-gzip) compress (lub x-compress). Wartości
wielokrotne powinny być przedstawiane jako oddzielone przecinkami
listy, 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 do
apletu powinno wykorzystywać trochę inny URL, np. z dodatkiem pewnej ilości
informacji parametrów drugorzędnych
Metoda ta ustala wartość nagłówka nazwanego, jako String. Nazwa nie uwzględnia wielkości liter, tak jak ma
to 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
():
Metoda ta ustala wartość nagłówka nazwanego na określoną datę i czas. Metoda akceptuje wartość daty jako
long, 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:
Metoda ta ustala wartość nagłówka nazwanego na int. Jeżeli nagłówek został już uprzednio ustalony stara
wartość jest zastępowana przez nową.
Metoda containsHeader() demonstruje sposób, w jaki można sprawdzić czy nagłówek już istnieje:
Metoda ta odsyła true w przypadku gdy nagłówek nazwany został już ustalony, w przeciwnym razie metoda ta
odsyła false.
Dla względnie małej liczby przypadków kiedy to aplet musi odesłać wartości wielokrotne dla tego samego
nagłówka, stosuje się dodatkowe metody.
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:
Znacznik ten musi zostać przesłany jako część sekcji <HEAD> strony HTML. Powyższa technika nie przynosi
jakichś specjalnych korzyści dla apletów; została ona stworzona z myślą o dokumentach statycznych, które nie
mają dostępu do swoich nagłówków.
Przekierowywanie zlecenia
Jednym z pożytecznych zastosowań, do których aplet może wykorzystać kody statusu i nagłówki jest
przekierowywanie zleceń. Jest to realizowane poprzez przesłanie do klienta instrukcji, zalecających użycie
innego 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 miejsca
przeznaczenia).
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, aplet
podobny do poniższego mógłby mieć wiele zastosowań. W stanie obecnym jest to punkt
wyjściowy do selekcji ciekawych stron apletów. W przypadku listy stron zawierającej obrazki
reklamowe, 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.*;
res.setStatus(res.SC_MOVED_TEMPORARILY) ;
res.setHeader("Lokalizacja",site);
}
}
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 zanim
jeszcze zostanie zatwierdzona odpowiedź. Pamiętajmy, że protokół HTTP przesyła kody statusu oraz nagłówki
przed 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 metod
podstawowych: sendRedirect():
res.sendRedirect(site);
Metoda ta przekierowuje odpowiedź do określonej lokalizacji, automatycznie ustalając kod statusu oraz
nagłówek Location. Poza obsługą klientów nie wyposażonych w zdolność przekierowania czy tych nie
rozpoznających kodu statusu S.C._MOVED_TEMPORARILY, metoda ta pisze krótki korpus odpowiedzi
zawierający hiperłącze do nowej lokalizacji. Tak więc kiedy używamy tej metody nie piszemy własnego korpusu
odpowiedzi. 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 jednak
przekształca URL-e względne na formę bezwzględną (automatycznie protokół bieżący, serwer oraz port — lecz
nie ś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, w
przeciwnym wypadku zostanie zgłoszony wyjątek IllegalStateException. Metoda ta wykonuje niejawne
zerowanie buforu odpowiedzi przed generowaniem przekierowywanej strony. Nagłówki ustalone przed
sendRedirect() nadal pozostają ustalone.
<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 jako
informację dodatkowej ścieżki, przekierowując następnie klienta do lokalizacji po uprzednim zapisaniu adnotacji
w rejestrze zdarzeń serwera. Kod apletu GoTo został zaprezentowany na przykładzie 5.5.
Przykład 5.5
Jak myślisz dokąd się udajesz?”
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
Ktoś mógłby zapytać: „czy jest to metoda skuteczna?” Odpowiedź brzmi: „tak”. Po pierwsze, treść pierwszej
strony może wyjaśnić klientowi, że strona będąca przedmiotem zlecenia została przeniesiona przed
automatycznym 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łówek
ten zawiera dodatkowo ciąg URL, określający z którego URL-u ma zostać przeprowadzone ściąganie. W
przypadku gdy żaden URL nie jest podany używany jest ten sam. Poniżej przedstawiamy wywołanie do metody
setHeader(), które informuje klienta, o tym, że ma on powtórnie załadować ten sam aplet, po uprzednim
pokazaniu 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:
Przykład 5.6 ukazuje aplet, który wykorzystuje klienckie ściąganie z serwera w celu wyświetlenia czasu
rzeczywistego, uaktualnianego co 10 sekund.
Przykład 5.6.
Czas rzeczywisty uaktualniany
import java.io.*;
import java.util.*;
import javax.servlet.* ;
import javax.servlet.http.*;
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óćmy
uwagę, iż nagłówek Refresh nie powtarza się. Nie ma konieczności aby ładować dokument w sposób
powtarzający się. Nagłówek Refresh jest jednakże określany przy każdym pobraniu, tworząc wyświetlanie
ciągłe.
Wykorzystanie klienckiego ściągania z serwera w celu pobrania drugiego dokmentu zostało zaprezentowane na
przykładzie 5.7. Aplet ten przekierowuje zlecenia kierowane do jednego komputera centralnego, na inny
komputer 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.* ;
Rozwiązywanie problemów
Nadszedł czas abyśmy zmierzyli się ze sprawami najtrudniejszymi — błędami, problemami, awariami. Jest wiele
przyczyn występowania problemów: nieprawidłowe parametry, brakujące zasoby i bieżące błędy. To co jest na
ten moment istotne, to to, że aplet musi być przygotowany na problemy i to zarówno na przewidywalne jak i na
te, których przewidzieć nie sposób. Dwie najważniejsze sprawy podczas występowania problemów to:
• Ograniczenie uszkodzeń serwera
• Właściwe poinformowanie klienta
Ponieważ aplety są pisane w Javie, potencjalne uszkodzenia serwera są minimalne. Serwer
może bezpiecznie osadzić aplety (nawet w swojej formalnej procedurze postępowania), tak
jak 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ądzanie
bezpieczeń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ątku
pozwala serwerowi na wychwycenie każdego wyjątku zgłoszonego przez aplet. Nawet kiedy
aplet przypadkowo podzieli przez zero lub wywoła metodę na obiekt null, serwer może dalej
sprawnie działać. Mechanizm zarządzania bezpieczeństwem Javy umożliwia serwerom
umieszczania niewiarygodnych apletów w „piaskownicy”, ograniczając ich możliwości i nie
dopuszczając aby powodowały problemy.
Kody statusu
Najprostszym (i prawdopodobnie najlepszym) sposobem, w który aplet może powiadomić o błędzie jest
wykorzystanie metody sendError(), aby ustawić poprawnie 400 lub 500 szeregów kodu statusu. Dla
przykł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 on
S.C._NOT_IMPLEMENTED. W razie wystąpienia okoliczności całkowicie nieprzewidywalnych, aplet może
odesłać S.C._INTERNAL_SERVER_ERROR.
Poprzez zastosowanie metody sendError() w celu ustalenia kodu statusu, serwer może zamienić korpus
odpowiedzi apletu, serwerowo-specyficzną stroną wyjaśniającą błąd. Jeżeli błąd ma taką naturę, iż aplet
powinien wystosować swoje własne wyjaśnienie do klienta w korpusie odpowiedzi, może on ustalić kod statusu
za pomocą metody setStatus() i następnie wysłać właściwy korpus odpowiedzi — który może być zarówno
tekstowym, wygenerowanym obrazkiem jak i też czymkolwiek właściwym.
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 oraz
nagłówki HTTP muszą zostać wysłane przed korpusem odpowiedzi. W momencie kiedy jakiekolwiek dane
zostały już przesłane do klienta, jest już za późno aby zmienić nasz kod statusu czy nagłówki HTTP. Najlepszym
sposobem uniknięcia takiej sytuacji jest jak najwcześniejsze sprawdzenie poprawności oraz zastosowanie
buforowania w celu opóźnieni przesłania danych do klienta.
Przykład 5.8.
Konfiguracja stron błędu 400 i 404
<!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 metody
sendError() z kodem statusu 400 powinno wyświetlić treści z zasobu /400.html oraz, że jakiekolwiek
wywołanie do tej metody z kodem 404 powinno wyświetlić zasób /404.html. Obsługa adaptacji stron błędu
jest praktyką powszechnie stosowana przez serwery WWW, jednakże pola wpisywania w pliku web.xml
zastępują domyślną konfiguracji serwera i udostępniają mechanizm, umożliwiający aplikacjom WWW
dopuszczanie 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 jako
umiejscowiona w kontekstowym katalogu macierzystym i musi odwoływać się do zasobu w kontekście. Aby
odnieść się do zasobu poza bieżącym kontekstem, możemy wskazać na plik HTML wewnątrz kontekstu, który
zawiera bezpośrednie przekierowanie poza kontekstem:
URL=http://www.errors.com/404.html">
Przeznaczenie <location> może być zasobem dynamicznym, takim jak aplet czy JSP. Dla zasobów
dynamicznych serwer udostępnia dwa specjalne atrybuty zlecenia, dostarczające informacji o błędzie:
javax.servlet.error.status_code
Integer (liczba całkowita) podająca kod statusu błędu. Typ był początkowo nie określony, dlatego
niektóre wczesne wdrożenia serwerowe mogą odsyłać kod jako String.
javax.servlet.error.message
String (strumień) podający komunikat statusu, na ogół przekazywany jako drugi
argument 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.*;
Object codedbj =
req.getAttribute("javax.servlet.error.status_code");
Object messageObj = req. getAttribute
("javax.servlet.error.message");
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óre
potrzebują 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 tego
samego URI, wskazując z dużym praedopodobieństwem, nowo przesłany błędny łącznik gdzieś, gdzie powinien
być poprawiony.
Podsumowując jest to fałszywa strona dla przesyłania kodów statusu błędu. Metoda setStatus() pozwala na
ustalenie kodu statusu błędu przy zatrzymaniu pełnej kontroli nad odpowiedzią. Metoda sendError() pozwala
z kolei na ustalenie kodu statusu błędu, przy przekazaniu kontroli nad tworzeniem strony do serwera. Serwer
przesyła domyślnie swoją standardową stronę błędu dla tego kodu. Za pomocą pozycji <error-page> możemy
polecić serwerowi aby przesłał specjalną stronę błędu.
Rejestracja
Aplety mają zdolność zapisywania, czynności które wykonują oraz błędów, które popełniają, do pliku rejestru
zdarzeń. Używają w tym celu metody log():
Metoda jedno-argumentowa zapisuje dany komunikat w dzienniku zdarzeń apletu, którym jest
zwykle plik rejestru zdarzeń. Dwu-argumentowa wersja metody zapisuje dany komunikat oraz
ślad kaskady Throwable w dzienniku zdarzeń apletu. Dokładny format wydruku wyjściowego
oraz 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 operacji
apletu. Zapewnia ona również możliwość zapisywania kompletnego opisu wszystkich błędów, z którymi miał do
czynienia aplet. Opis ten może być taki sam, jak ten przesłany do klienta lub bardziej wyczerpujący i
szczegółowy.
Teraz już możemy wrócić do dalszego ulepszania apletu ViewFile, tak że będzie używał metodę log() do
rejestrowania na serwerze w sytuacji kiedy plik będący przedmiotem zlecenia nie będzie istniał, odsyłając do
klienta prostą stronę 404NotFound:
// Odeślij plik
try {
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 to
zostało przedstawione poniżej:
// Odeślij plik
try {
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) ;
}
Raportowanie
Poza 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 śladu
kaskady jako String (strumień) — może on tylko drukować swój ślad kaskady do PrintStream lub do
PrintWriter. W celu odczytania śladu kaskady jako String, musimy pokonać pewne trudności. Musimy
pozwolić 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:
Oto sposób w jaki aplet ViewFile jest w stanie dostarczyć informacji, zawierających ślad kaskady
IOException:
// Odeślij plik
try {
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)) ;
}
Rysunek 5.2.
Rzetelne informowanie klienta „na bieżąco”
Wyjątki
Tak jak powiedzieliśmy wcześniej, każdy wyjątek, który jest zgłaszany lecz nie wyłapywany przez aplet, jest
wył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. Lecz
równie dobrze może tego nie robić.
Aplety zaprojektowane i skonstruowane do tego aby działać na określonym serwerze mogą optymalizować w tym
celu 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 lub
RuntimeException. 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() i
doPost() które wywołuje) zgłoszenie wyjątku i nie wyłapanie niczego więcej powoduje błąd związany z
czasem 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łoszenie
wyjątków tylko ServletException, metoda destroy(), nie deklaruje z kolei zgłaszania żadnych wyjątków.
javax.servlet.ServletException()
javax.servlet.ServletException(String msg)
Klasa ServletException może również obsługiwać „główną przyczynę” obiektu Throwable. Pozwala to
dział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ługi
powyż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 lub
błą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 metody
getRootCause():
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ątek
UnavailableException podczas obsługi zlecenia zostaje wyłączony z pracy, a na jego miejsce zostaje
utworzona nowa kopia kontynuująca obsługę zleceń. Jeżeli nie zostanie utworzony żadna „dostępna” kopia
apletu, klient otrzyma błąd. Aplet który zgłasza trwały wyjątek UnavailableException (lub regularnie
wyją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 powodu
ogólnosystemowych problemów. Dla przykładu, mogłaby zdarzyć się sytuacja, że trójpoziomowy serwer nie
był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, że
administrator będzie zmuszony się nim zająć.
Podczas okresu niedostępności zlecenia apletu obsługuje serwer — poprzez odsyłanie kodu statusu
S.C._SERVICE_UNAVAILABLE (503) z nagłówkiem Retry-After, informując o przybliżonym czasie
niedostępności apletu. Jeżeli aplet zgłosi tymczasowy wyjątek UnavailableException podczas stosowania
swojej 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łą.
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, z
wyjaśnieniem podanym przez msg. Natomiast wersja dwu-argumentowa tworzy nowy wyjątek informujący o
tym, że aplet jest niedostępny tymczasowo, z wyjaśnieniem podanym również przez msg. Poprawnie napisany
aplet powinien zamieścić w swoim wyjaśnieniu powód powstania problemu oraz działania, które powinien
podjąć administrator systemu aby przywrócić dostępność apletu. Wspomniany czas trwania niedostępności apletu
podawany 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.
<?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>
Dla przeznaczeń <location> (lokalizacji) dynamicznych, aplet udostępnia dwa atrybuty zleceniowe, służące do
opisania zgłoszonego wyjątku:
javax.servlet.error.exception_type
Kopia java.lang.Class podająca typ wyjątku. Typ atrybutu był początkowo nieokreślony, dlatego
niektóre wczesne wdrożenia serwera mogą odsyłać formularz String nazwy klasy.
javax.server.error.message
String (strumień) podający komunikat wyjątku, przekazywany do konstruktora wyjątku. Nie ma żadnej
moż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ędzie
działał jako zasób monitora ogólnego błędu, obsługujący wyświetlanie zarówno kodów statusu błędów i
wyją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.*;
typeObj = req.getAttribute("javax.servlet.error.exception_type") ;
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 kaskady
wyją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ów
mamy pewność, że obsługa błędu będzie spójna i właściwa. Oczekuje się, że w Interfejsie API 2.3 zostanie
dodany nowy atrybut zlecenia, zawierający sam wyjątek, ma to zostać wykonane przy użyciu nazwy
javax,servlet.error.exception.
Niestety aplet nie jest bezpośrednio informowany, że użytkownik kliknął przycisk „stop”, aplet nie wie o tym, że
powinien 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 wydruk
wyjś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 wydruk
wyjściowy, poprawnie napisane aplety uwalniają swoje zasoby w bloku finally. (Blok finally jest
dodatkową 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żej
przedstawiamy wersję metody returnFile() z apletu ViewFile, gdzie do zagwarantowania zamknięcia jej
FileInputStream, wykorzystywany jest blok finally:
Aplet przesyłający dane znakowe, wykorzystujący w tym celu PrintWriter, nie otrzymuje wyjątku
IOException w czasie kiedy próbuje napisać wydruk wyjściowy, ponieważ metody PrintWriter’a nigdy nie
zgłaszają wyjątków. Zamiast tego aplet przesyłający dane znakowe musi wywołać metodę PrintWriter’a
checkError(). Metoda ta opróżnia wydruk wyjściowy — zatwierdzając odpowiedź, a następnie odsyła
boolean, informując czy pojawił się problem przy pisaniu do stosowanego OutputStream. Jeżeli klient
zatrzyma zlecenie, odsyłane jest true.
Długo-wywołujacy aplet, który nie „ma nic przeciwko” wczesnemu zatwierdzaniu odpowiedzi, powinien
regularnie wywoływać metodę checkError() aby określić czy przetwarzanie może zostać zatrzymane przed
zakoń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:
preliminaryCalculation() ;
out.print(" "); // treść wypełniająca, dodatkowe światło na stronie
//jest ignorowane w HTML-u
if (out.checkError()) return ;
additionalCalculation() ;
Należy tutaj zaznaczyć, że serwer nie jest zobligowany do zgłaszania wyjątku IOException lub do ustawienia
znacznika błędu PrintWriter’a, po rozłączeniu się klienta. Serwer może zdecydować, że pozwoli odpowiedzi
na zakończenie, przy zignorowaniu jej wydruku wyjściowego. Ogólnie rzecz biorąc sytuacja taka nie stwarza
problemów, jednak oznacza to, iż aplet wywołując w takim serwerze powinien zawsze mieć ustawiony punkt
końcowy i nie powinien być pisany do ciągłego wykonywania pętli, aż do naciśnięcia „stopu” przez użytkownika.
Rozwiązania takie są bardzo potrzebne ponieważ proste podejście do generowania treści znaczników, zmusza
programistó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.
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 lepszym
określeniem byłoby przetwarzanie danych, jednakże „oddzielanie przetwarzania danych od prezentacji” nie
byłoby najtrafniejszym określeniem. Termin prezentacja oznacza reprezentację danych, podanych ostatniemu
użytkownikowi (często są to dane HTML, jednakże przy obecnym wzroście urządzeń podłączanych do sieci
WWW, 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 jej
obsługa, jako że treść strony (determinowana przez programistę) może być konstruowana i zmieniana niezależnie
od jej prezentacji (determinowanej przez projektanta lub producenta). Czasem określa się takie oddzielenia jako
strukturę 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 apletowego
tworzenia 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 to
coś więcej niż tylko pięć implementacji tej samej idei. Każda alternatywa traktuje o problemie tworzenia treści z
innej perspektywy, w związku z tym techniki rozwiązywania problemów różnią się znacznie. Narzędzie, które
sprawdza się w jednej sytuacji może w ogóle nie działać w innej. Tak więc nie czytajmy przykładów w
poniższych rozdziałach, z myślą o poznaniu najlepszej technologii — starajmy się w zamian rozglądać za
najlepszą technologią dla naszych projektów.
Dotąd unikaliśmy dyskusji na temat swoistych, komercyjnych rozwiązań, z powodu związanego z tym ryzyka
ograniczania się do rozwiązań jednego producenta, nacechowanych motywami finansowymi.*Alternatywy, które
zostaną omówione w dalszej części książki są następujące:
JavaServer Pages
JavaServer Pages (JSP) to technologia stworzona przez „Sun Microsystems”, ściśle
związana z apletami. Tak jak w przypadku apletów, „Sun” wydaje specyfikację JSP, a
sprzedawcy nie związani z tą firmą starają się wprowadzić swoje wdrożenia jako
standardowe. Fakt, iż JSP zostało wydane przez „Sun” stawia tą technologię w
uprzywilejowanej pozycji i gdyby JSP było rozwiązało wystarczającą liczbę problemów
użytkowników, prawdopodobnie zdobyłoby rynek jeszcze przed pojawieniem się na nim
innych 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.
Tea
Tea jest nowym autorskim produktem firmy „Walt Disney Internet Group” (wcześniej
znanej jako GO.com), rozwijanym przez lata na potrzeby wewnętrzne, do zaspokojenia
ich ogromnego zapotrzebowania na produkcję sieciową, dla stron takich jak
ESPN.com. Jest to technologia przypominająca JSP, jednak pozbawiona wielu jego
błędów, ma już ponadto znaczną obsługę narzędziową. Więcej na ten temat w rozdziale
14 „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 htmlKona
wyprodukowany przez „WebLogic” ponieważ nie było w tym czasie żadnej dostępnej alternatywy, ponadto zostaliśmy
zapewnieni przez „WebLogic”, że cena zestawu pozostanie na rozsądnym poziomie. Po przejęciu „WebLogic” przez „BEA
Systems” , zapewnienie nie było już aktualne, i w rzeczywistości htmlKona stał się mało znaczącym szczegółem w ofercie
cenowej „BEA Systems”. Szczęśliwie htmlKona został ponownie wdrożony jako otwarte źródło projektu „Element
Construction Set” (ECS) przez „Apache Jakarta Project” i teraz będzie dostępny tak długo jak długo będzie cieszył się
zainteresowaniem.
WebMarco
WebMarco 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 wiele
mechanizmów wzorcowych wartych omówienia, jednak WebMarco jest najbardziej popularny, jest
używany na wyjątkowo często uczęszczanych stronach, takich jak AltaVista.com., został wbudowany w
osnowach o otwartym dostępie do kodu źródłowego takich jak „Turbine” czy „Melati”, ponadto jest
uż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.org
i http://jakarta.apache.org/velocity.
XMLC
XMLC wykorzystuje XML, uzyskując prawie wszystkie możliwości ECS, unikając wielu jego
ograniczeń. XMLC został wyprodukowany przez „Lutris” jako część ich projektu otwartego dostępu do
kodu ź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.
Cocoon
Cocoon to kolejna, oparta na XML-u alternatywa stworzona przez „XML Apache Project”. Cocoon
wykorzystuje XML oraz XSLT do obsługa treści tworzenia czegoś, co określa się jako osnowa
publikacji WWW. Cocoon jest sterowaną serwerowo osnową przez co XML, tworzony statycznie lub
dynamicznie, uruchamiany jest przez filtr XSLY, jeszcze przed prezentacją klientowi. Filtr ten może
sformatować treść jako każdy typ treści włącznie z HTML, XML, WML czy PDF — a osnowa może
wybrać, 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 (przynajmniej
w chwili obecnej) na ogół nie jest to często wybierane narzędzie dla pisania interaktywnych aktywacji
WWW. 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źć na
stronie http://xml.apa.cheorg oraz w książce Brett’a McLaughlin’a „Java and XML”. Rozdział tej
książki traktujący o Cocoon’ie dostępny jest na stronie
http://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 niczym
nadzwyczajnym. Przyjrzyjmy się dacie produkcji tego narzędzia: jest prawdopodobnie
późniejsza od terminu oddania tej książki do druku. Tempo wprowadzania w tym obszarze
innowacji jest ogromne, z XML-em dostarczającym nam nowych możliwości oraz
dołączanych do Internetu urządzeń wymagających nowych właściwości, dziedzina ta to
eldorado dla nowych rozwiązań. Miejmy również świadomość tego, że ulepszenia tych
narzę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 czytelnik
będzie go czytał. Aby być na bieżąco zorientowanym w temacie należy zaglądać na stronę
http://www.servlets.com.
Rozdział 6. Przesyłanie treści
multimedialnej
Do tej pory wszystkie aplety, które pisaliśmy odsyłały standardową stronę HTML. Sieć WWW
nie 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łu
WAP oraz języka znaczników WML używanego w telefonii komórkowej oraz urządzeniach
przenośnych i poznamy jak aplety mogą wykorzystywać tą technologię. Następnie pokażemy
jak 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ę skompresowane
odpowiedzi, ponadto zbadamy użycie odpowiedzi wieloczęściowych w celu wdrażania
serwera cyklicznego.
WAP i WML
Protokół Aplikacji Bezprzewodowej (WAP) jest de facto standarem dla dostarczania łączności internetowej do
telefonów komórkowych, pejdżerów oraz do Osobistych Asystentów Cyfrowych (PDA) w sieciach komunikacji
bezprzewodowej na całym świecie. Protokół ten został stworzony przez Ericssona, Nokię, Motorolę oraz
Phone.com (wcześniej „Unwired Planet”), które to firmy rozsądnie wyraziły chęć stworzenia protokołu
standardowego, a nie własnych, konkurujących między sobą protokołów. Owe cztery firmy stworzyły razem
forum WAP (http://www.wapforum.org), demokratycznie zarządzaną organizację, która dzisiaj liczy już 400
członków.
Protokół WAP składa się z szeregu specyfikacji, opisujących projektowanie aplikacji przeznaczonych do
uruchamiania 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 Aplikacji
WWW 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 rysunku
6.1.
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 struktur
odpowiedzi i zlecenia, zamiast formatu tekstowego przez WWW.
Brama WAP odgrywa rolę pośrednika pomiędzy siecią WAP a siecią WWW. Zadaniem bramy jest
konwertowanie zleceń WAP na zlecenia WWW oraz odpowiadające im odpowiedzi WWW na odpowiedzi
WAP. O Bramie WAP można powiedzieć, iż jest ona „konwertorem kaskady protokołu”* Bramy WAP
udostępniają urządzeniom WAP dostęp do standardowych serwerów WWW. Zostało to zaprezentowane na
rysunku 6.2.
Rysunek 6.2.
Rola Bramy WAP
*
Bramy WAP są zwyczajowo dostarczane przez właściciela sieci bezprzewodowej. Czytelników zainteresowanych
utworzeniem swojej własnej bramy lub tym jak działają bramy odsyłamy do Bramy WAP zwanej „Kannel” dostępnej na
stronie http://www.wapgateway.org.
Prostą sprawą jest konstruktor bezprzewodowy — możemy zignorować protokół warstwy transportowej. Brama
WAP „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.
WML
Urządzenia przenośne są znacznie bardziej ograniczone niż komputery osobiste. Mają one zwykle wolne
procesory, małe pojemności pamięci, małe wyświetlacze oraz niezwykle ograniczoną przepustowość. Z powodu
podobnych ograniczeń urządzenia WAP nie współdziałają ze zwykłymi treściami HTML i treściami
obrazkowymi. Zamiast tego używają one języka znaczników telefonii komórkowej (WML) — dla treści
tekstowej, języka WMLScript dla wykonywania skryptów oraz Bezprzewodowej Mapy Bitowej (Wireless
Bitmap) 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, minimalizuje
opóź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 do
wyboru, a następnie składniki wybranego drinka.* Zwróćmy uwagę, iż jest to dokument XML z dobrze znanym
DTD.
Przykład 6.1.
„Minibarman WML”, drinks.xml
<?xml versions"1.0"?>
<!DOCTYPE wml PUBLIC
<anchor>
Kamikaze <go href="#Kamikaze" />
</anchor><br/>
<anchor>
Margarita <go href="#Margarita" />
</anchor><br/>
<anchor>
Boilermaker <go href="#Boilermaker" />
</anchor><br/>
</P>
</card>
*
Pełna implementacja tej idei dostępna jest na stronie http://www.wap2bar.com/index.wml
</p>
</card>
Dokument zawiera cztery karty. Pierwsza karta, wyświetlana domyślnie, ukazuje krótką listę drinków. Każda
nazwa 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 do
urządzenia podczas jednej operacji, mimo iż w danym momencie będą dostępne tylko jego części.
Dodatkowo niektórzy programiści aplikacji WWW uważają, że dobrze jest dodać index.wml do domyślnej listy
plików wyszukiwania w katalogu, znanej jako welcome file list. Blok deskryptora wdrożenia w przykładzie 6.3
ustawia welcome file list do index.html, index.htm oraz do index.wml, w takiej właśnie kolejności. Taka kolej
rzeczy daje znać serwerowi o zleceniu na http://localhost/wap/index.wml jeżeli index.html oraz index.htm nie
istnieją.
Przykład 6.3.
Dodawanie plików WAP Welcome Files do web.xml
Aplety nie pozostają oczywiście bez użyteczne. Aplet (lub inny skrypt serwera) jest potrzebny do generowania
talii kart zawierających informacje dynamiczne pobrane z bazy danych. Aplety są również potrzebne do obsługi
zleceń na dane zbyt duże aby zmieścić się na zestawie kart. Urządzenia są często ograniczone do przechowania
tylko 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ące
telefony obsługujące WAP mogą wykorzystać tą aplikację do fizycznej lokalizacji jakiegokolwiek numeru
identyfikacyjnego rozmówcy.
Przykład 6.4.
Wykorzystanie WML-u w celu uzyskania o kodu kierunkowego
<?xml version="1.0"?>
<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 kod
kierunkowy 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.*;
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>") ;
}
Aplet ten otrzymuje zlecenie WAP jako zwykłe zlecenie HTTP GET. W swojej metodzie doGet() aplet ustawia
typ 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 pochodzi
z ręcznie wprowadzanej tablicy, która jest konwertowana na tabelę Properties po inicjalizacji apletu. „Zrzut”
ekranu wydruku wyjściowego, pokazano na rysunku 6.4.
Rysunek 6.4.
Wykorzystanie WAP-u do lokalizacji rozmówców
Poznajmy WAP
Jeż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 nas
strony źródłowe takie jak: http://www.wap-resources.net, http://AnywhereYouGo.com oraz
http://www.wirelessdevnet.com. Istniej również administrowana przez „Sun” lista adresowa archiwizująca na
http://archives.java.sun.com/archives/web-wireless.html.
Obrazki
Ludzie to istoty postrzegające świat głównie poprzez obraz — chcą oglądać, nie tylko czytać informacje. W
konsekwencji 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ętamy
aplet ten może odesłać wszystkie pliki pod dokumentem katalogu macierzystego serwera. Jeżeli zdarzy się tak, że
plik będzie plikiem obrazu, aplet wykrywa ten fakt za pomocą metody getMimeType() a następnie ustawia
swoją treść odpowiedzi za pomocą metody setContentType(), jeszcze przed przesłaniem nieprzetworzonych
bitó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ąć plik
przed 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 12
godzin) na dysku, a następnie wykorzystać aplet do wysłania jednego — właściwego. Lecz z pewnością nie
powinniśmy tego robić. Zamiast tego powinniśmy napisać aplet, który będzie generował dynamicznie obrazek
cyferblatu oraz wskazówek zegara lub wariantowo — aplet, który ładuje obrazek cyferblatu dodając tylko
wskazó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ęki
generowaniu 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) czy
graficzną 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ów
Załóż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-bitowy
obrazek prawdziwych kolorów (3 bity na jeden piksel), oraz że
ma on wymiary 100x100 pikseli. Moglibyśmy zrobić rzecz, która
nasuwa się od razu — przesłać jeden piksel na jeden raz, w
strumieniu 30.000 bitów. Jednak czy to wystarczy? Skąd
odbiorca 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 pikselowe
prawdziwych kolorów, o tym że zaczynamy od lewego, górnego
rogu, o tym że będziemy przesyłali wiersz po wierszu oraz o
tym, iż każdy wiersz ma długość 100 pikseli. A co, jeżeli
zdecydujemy się na przesłanie mniejszej liczby bitów stosując
kompresję? W takim przypadku musimy poinformować jaki rodzaj
kompresji 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 format
obrazu (GIF, JPEG, PNG, TIFF, itd.) odpowiada jednemu rozwiązaniu. Każdy format obrazu określa jeden
standardowy sposób kodowania obrazków, tak że może on później zostać zdekodowany dla oglądania lub
manipulowania. Każda z technik kodowania ma swoje zalety i ograniczenia. Dla przykładu kompresja używana
dla kodowania GIF lepiej obsługuje obrazki generowane komputerowo, jednak forma GIF ograniczony jest do
256 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 zastosowaniem
kompresji „stratnej”, która może rozmyć szczegóły zdjęcia. Kodowanie PIN (wymawiane jako „ping”) jest
relatywnie nowym kodowaniem, którego celem jest zastąpienie GIF, jako że jest ono mniejsze, obsługuje miliony
kolorów, wykorzystuje „bezstratną” kompresją, ponadto ma kanał alfa dla efektów przezroczystości — jest
ró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łanie
swojej zakodowanej reprezentacji — niezmienionej do klienta, przeglądarka dekoduje wtedy obrazek do
oglądania. Jednak aplet, który generuje lub modyfikuje obrazek musi skonstruować przedstawienie wewnętrzne
obrazka, rozmieścić go, a następnie, przed przesłaniem go do klienta, zakodować go.
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;
try {
// Utwórz niepokazywaną ramkę
frame = new Frame () ;
frame. addNotify ();
*
Więcej informacji na temat PNG można znaleźć na stronie: http://graphicswiz.com/png.
// Rysuj "Hello World!" do poza-ekranowego kontekstu grafiki
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 kontekstu
grafiki, 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 jest
reprezentowany 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 obrazek
używamy do tego celu metody createImage(). Zwróćmy uwagę, iż zanim komponent butworzyć obrazek, jego
wł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 przez
skonstruowanie 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życiu
wywoł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óry
serwer X będzie użyty) nie jest ustawiona lub ustawiona na lokalny serwer X, który może wymagać zastosowania komputera
centralnego x lub uwierzytelniania x.
Serwery „Headless”, mogą bez działającego serwera X, mogą użyć Xvfb (wirtualnego bufora ramki X) obsłużyć prace
pomocnicze grafiki; należy tylko pamiętać, żeby wskazać DISPLAY na serwerze Xvfb.
Po rysowaniu do kontekstu grafiki, wywołujemy setContentType() aby ustawić typ nośnika na image/gif
ponieważ mamy zamiar użyć kodowania GIF. Dla przykładów w tym rozdziale użyliśmy kodera GIF napisanego
przez 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 do
bezpłatnego oprogramowania generującego format GIF. Koder Acme GIF wykorzystuje kompresję LZ, pozwoli
to 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 na
każ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 jest
koder 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 Java
Image Management Interface (JIMI), wcześniej komercyjny produkt „Activated Intelligence”, obecnie „Sun
library” dostępny bezpłatnie na stronie http://java.sun.com/products/jimi.
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, jednak
zwolnienie 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 aplet
zgłasza wyjątek.
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;
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));
chart.getBackground().setTitleString("Porównywanie Jabłek i
Pomarańczy");
chart.addDataSet("Jabłka", appleData) ;
// Nazwij osie
chart.getXAxis().setTitleString("Rok") ;
chart.getYAxis().setTitleString("Tony Skonsumowane") ;
// Nadaj odpowiedni rozmiar
chart.resize(WIDTH, HEIGHT);
Na rysunku 6.6 zostały zaprezentowane rezultaty. Właściwie nie ma potrzeby, aby wykres ten był generowany
dynamicznie, mimo to pozwala osiągnąć wiele, bez zbędnego kodowania.
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 tego
kontekstu, a następnie zakodować rysunek do transmisji dla klienta. Różnica polega na tym, że aplet konstruuje
obiekt BarChart do rysowania. W Javie dostępnych jest kilkanaście zestawów tworzących wykresy. Klasa
BarChart, 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 dla
czytelników niniejszej książki został przyznany bezpłatny dostęp do „porcji” API zaprezentowanej w naszym
przykładzie. Zestaw „KavaChart” zawiera również bezpłatny pakiet apletów generujących wykresy, ze strony
http://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 obrazu
Jak 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. Przeanalizujemy
również obsługę błędu w apletach odsyłających obrazki.
Rysowanie obrazków
Czasami aplet może czerpać korzyści z rysowania na górze istniejącego obrazka. Dobrym przykładem może tutaj
być 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ów
za pomocą Yoolkit.getDefaultToolkit().getImage(imagename), pobranie jego kontekstu grafiki przy
pomocy wywołania do metody Image – getGraphics(), a następnie wykorzystanie odesłanego kontekstu
grafiki 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 metody
Component createImage(). W przypadku pracy z narzędziami wzorowanymi na Windows AWT, konieczny
jest 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 obrazka
poprzez metodę Toolkit.getDefaultToolkit().getImage(imagename), a następnie „powiedzenie” mu,
aby narysował się na inny kontekst grafiki utworzony za pomocą metody createImage() Component, tak jak
to zostało w poprzednich dwóch przykładach. Teraz możemy już wykorzystać omawiany kontekst grafiki do
rysowania 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;
frame.addNotify() ;
}
try {
// Pobierz lokalizację obrazka z informacji ścieżki
// Dla bezpieczeństwa użyj ServletUtils (Rozdział 4)
URL source =
ServletUtils.getResource(getServletContext(), req.getPathInfo()) ;
}
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;
}
}
finally {
// Wyczyść zasoby
if (g != null) g.dispose();
}
}
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().Tworzenie
Frame, a następnie powtórne wykorzystywanie to optymalizacja, nie przeprowadzona uprzednio z powodów
estetyczno-porządkowych. Dla każdego zlecenia aplet zaczyna od odczytania nazwy wcześniej istniejącego
obrazka z informacji dodatkowej ścieżki, a następnie konwertuje ścieżkę do zasobu używając metody
getResource(). Następnie pobiera odwołanie do obrazka za pomocą metody Toolkit — getImage() i
fizycznie ładuje go do pamięci korzystając z pomocy MediaTracker.Zwykle ładowanie asynchroniczne nie jest
problemem dla obrazka, ponieważ jego częściowe rezultaty malowane są wraz z postępem ładowania, jednak w
tym przypadku obrazek jest malowany tylko raz i musimy mieć pewność, że zostanie on z góry załadowany w
całości. W następnej kolejności aplet pobiera szerokość i długość ładowanego obrazka i tworzy poza-ekranowy
obrazek 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 koduje
obrazek do transmisji. Zauważmy w jaki sposób aplet radzi sobie z warunkami występowania błędu, wywołując
metodę sendError(). Podczas odsyłania obrazków trudno o coś bardziej wyszukanego. Takie ujęcie sprawy
pozwala apletowi na wykonanie tego, co uważa za słuszne.
Zestawianie obrazków
Aplet 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 czerwonej
kropki nad jego biurem. Technika używana przy zestawianiu obrazków, przypomina używaną do rysowania na
górze obrazka: odpowiednie obrazki są ładowane, rysowane na utworzonym w odpowiedni sposób obiekcie
Image, 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 wspomniany
aplet, 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;
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];
// Ładuj rysunki
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;
}
g = image.getGraphics() ;
// Rysuj obrazki
int xindex = 0;
for (int i = 0; i < countlen; i++) {
g. drawlmage(images[i], xindex, 0, frame) ;
xindex += images [i].getWidth (frame);
}
Rysunek 6.8.
Kombinowanie obrazków do graficznego licznika
Aplet ten pobiera liczbę do wyświetlenia poprzez odczytanie nieprzetworzonego ciągu
zapytań. Dla każdej liczby w liczeniu, aplet wyszukuje i ładuje odpowiadający jej obrazek
liczbowy z katalogu podanego przez DIR. (DIR znajduje się zawsze pod serwerowym
katalogiem macierzystym dokumentu. Jest on podany jako ścieżka wirtualna i konwertowany
na 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 koduje
obrazek 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ładu
aplet 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ę w
poprawny sposób.
Obrazki — efekty
Dowiedzieliśmy się już w jaki sposób aplety mogą tworzyć i kombinować obrazki. W tym
podrozdziale 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 rozmiaru
przed transmisją. Może on również dodać specjalne cieniowanie do obrazka, aby
przypominał on przycisk, który można nacisnąć. Dla przykładu przyjrzyjmy się w jaki sposób
aplet może konwertować obrazek kolorowy na czarno biały.
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.* ;
if (source == null) {
res.sendError(res.SC_NOT_FOUND,
"Informacja dodatkowej ścieżki musi wskazywać na dodatkowy
obrazek");
return;
}
// Ładuj obrazek
Image image = Toolkit.getDefaultToolkit().getImage(source);
MediaTracker mt = new MediaTracker(frame);
mt. addImage (image, 0);
try {
mt .waitForAll<) ;
}
catch (InterruptedException e) {
Kod dla tego apletu pokrywa się w większości z kodem przykładu Confidentialer. Główna różnica została
zaprezentowana poniżej:
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 poprzez
GrayscaleImageFilter. Filtr konwertuje każdy kolorowy piksel na jego czarno biały odpowiednik. Dzięki
temu obrazek jest konwertowany na skalę czarno białą w czasie kiedy jest tworzony. Kod dla
GrayscaleImageFilter został pokazany na przykładzie 6.11.
Przykład 6.11.
Klasa „GrayscaleImageFilter”
import java.awt.* ;
import java.awt.image.*;
public GrayscaleImageFilter() {
canFilterIndexColorModel = true;
}
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 filtr
ten działa na mapie kolorów a nie na poszczególnych wartościach pikselowych. Wartość pikselowa podana jest
jako 32-bitowe int, gdzie pierwszy bajt reprezentuje wartość (przezroczystości) alfa, drugi bajt natężenie
czerwieni, 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 takie
same 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 na
skalę czarno białą. Jeżeli jednak weźmiemy pod uwagę sposób, w jaki ludzie postrzegają kolory (i inne
czynniki), 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 Systemu
Telewizyjnego USA (National Television Systems Committee) dla telewizji czarno białej. Więcej na ten temat
można znaleźć w książce Charlesa A. Poyntona pt. „A Technical Introduction to Digital Video” oraz na stronie
http://www.color.org.
W celu wypełnienia tej tablicy-przewodnika musimy utrwalić zakodowane grafiki. Tak więc zamiast podawania
GifEncoder ServletOutputStream, podajemy mu ByteArrayOutputStream. Następnie kodujemy
obrazek za pomocą metody encode(), zakodowany obrazek jest przechowywany w
ByteArrayOutputStream. Potem składujemy utrwalone bity w tablicy-przewodniku, by w końcu napisać je do
ServletOutputStream i odesłać obrazek do klienta. Oto nowy kod do kodowania, składowania oraz odsyłania
przefiltrowanego obrazka:
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 celu
jakiejś 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ę, że
oryginalny obrazek nie zmienił się od czasu buforowania czarno białej wersji.
Treść skompresowana
Zestaw java.util.zip został wprowadzony w JDK 1.1. Zestaw ten zawiera klasy, które
obsł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 on
ró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ż prezentuje
się tak samo, może przysłużyć się użytkownikowi poprzez ograniczanie czasu ładowania treści z serwera. Dla
bardzo „kompresowalnych” treści, takich jak HTML, kompresja może zredukować czas transmisji poprzez
dyrektywę rozmiaru. Uświadomijmy sobie, że dynamiczne kompresowanie treści zmusza serwer do
wykonywania dodatkowej pracy, tak więc ewentualne przyspieszenie transmisji musi wiązać z obniżeniem
wydajności serwera.
Jak pamiętamy, aplet może przesyłać nagłówek Content-Type jako część swojej odpowiedzi, ażeby
poinformować klienta o tym jaki typ ma przesyłana informacja. Aby przesłać treść skompresowaną, aplet musi
również przesłać nagłówek Content-Encoding, aby poinformować klienta o schemacie, zgodnie z którym
treść 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 kodowania
jest rozumiany przez klienta, klient może przesłać nagłówek Accept-Encoding, który określa akceptowane
przez 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 musi
zdecydować, ż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, popularnych
przeglądarek (jednak nie wszystkie) obsługuje kodowanie gzip, żadna nie obsługuje jednakże kodowania
kompresji, a tylko MIcrosoft Internet Explorer 4 lub 5 (w Windowsie) obsługuje kodowanie typu deflate. W
prostym 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 operacyjny
klienta, zainstalowane moduły dodatkowe, szerokość i wysokość przeglądarki, szerokość i wysokość monitora
klienta, 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 po
prostu 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 dla
formatu 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 na
nim nic ciekawego do oglądania. Tak jak powiedzieliśmy wcześniej, użytkownik końcowy nie jest w stanie
rozpoznać 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 ;
5
((ZipOutputStream)out),putNextEntry(new ZipEntry("nazwa pusta"));
}
else {
// Nie kompresuj
out = res.getOutputStreamO ;
}
res.setHeader("Vary", "Accept-Encoding") ;
e.getMessaget));
}
// Połącz z zasobem
URLConnection con = url.openConnection();
con.connect() ;
// Odeślij zasób
try {
ServletUtils.retumURL(url, out);
}
catch (IOException e)
res.sendError(res.Sc_INTERNAL_SERVER_ERROR,
"Problem przy przesyłaniu zasobu: " + e.getMessage());
}
Serwer cykliczny
Do 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ę w
jednej odpowiedzi? To może wydawać się nie do zrealizowania, jednak przy użyciu techniki zwanej serwerem
cyklicznym (server push) jest to całkiem proste.
W przypadku stosowania tej techniki serwer umieszcza na stosie lub przesyła sekwencję stron odpowiedzi do
klienta. Porównajmy tą technikę do techniki z poprzedniego rozdziału — ściągania klienckiego, w przypadku
której to techniki klient musi otrzymać lub ściągnąć każdą stronę z serwera. Mimo iż rezultaty zastosowania obu
technik są podobne dla użytkownika — pojawienie się sekwencji stron, szczegóły wdrażania oraz ich poprawne
zastosowania są odmienne dla każdej z nich.
Przy stosowaniu serwera cyklicznego, połączenie gniazdowe pomiędzy serwerem a klientem pozostaje otwarte
do czasu przesłania ostatniej ze stron. Takie rozwiązanie daje serwerowi możliwość szybkiego przesyłania
uaktualnień strony oraz kontrolę nad tym, w jakim czasie wspomniane uaktualnienia będą przesyłane. Z takimi
moż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ą kontrolowanych
przez serwer, jednak nie częstych uaktualnień (takich jak uaktualnienia aktywnego statusu). Zwróćmy jednak
uwagę, 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ęc
odpowiedzialność 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ć do
serwera cztery najświeższe pogodowe mapy satelitarne, tworząc animację poziomu podstawowego. Spróbujmy
sobie 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świetlania
odliczania czasu pozostałego do startu rakiety. Rozpoczyna się to wysłaniem serii stron odliczających od 10 do
1. Każda strona zastępuje swoją poprzedniczkę. Kiedy odliczanie dotrze do 0, aplet przesyła zdjęcie startu
rakiety. Aplet używa klasy użyteczności com.oreilly.servlet.MultipartResponse (pokazanej na
przykł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;
Najpierw tworzy się obiekt MultipartResponse, przekazując mu obiekt odpowiedzi apletu. Klasa
MultipartResponse wykorzystuje obiekt odpowiedzi aby załadować wydruk wyjściowy apletu oraz aby
ustawić typ treści odpowiedzi. Następnie, dla każdej strony treści, zaczynamy od wywołania metody
startResponse() i przekazania jej typu treści dla tej strony. Treść dla tej strony przesyłamy, jak zwykle do
strumienia wyjściowego. Wywołanie do endResponse() kończy stronę i wysyła treść z bufora, tak że klient
może ją zobaczyć. Na tym etapie możemy dodać wywołanie do metody sleep(), lub jakiś inny rodzaj
opóźnienia, stosowanego do czasu w którym następna strona będzie gotowa do wysłania. Wywołanie do metody
endResponse() nie jest konieczne, ponieważ metoda startResponse() rozpoznaje czy poprzednia
odpowiedź została zakończona i zakańcza ją w razie potrzeby. W przypadku gdy spodziewamy się opóźnienia
pomię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.
Klasa „MultipartResponse”
package com.oreilly.servlet;
import java.io.* ;
import javax.servlet.* ;
import jvax.servlet.http.* ;
// Ustaw wszystko
res. setContentType ("mulltipart/x-mixed-replace; boundary=End") ;
out.println() ;
out.println (" --End") ;
}
*
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ługuje
wielokrotnych użytkowników.
naszych apletach. Wszystkie omówienia zawarte w tym rozdziale opierają się na założeniu, że
używany jest serwer pojedynczy. Rozdział 12 „Aplety Przedsiębiorstw i J2EE” wyjaśnia jak
jest realizowana obsługa stanu wspólnej sesji w serwerach wielo-wejściowych.
Uwierzytelnianie użytkownika
Jednym ze sposobów przeprowadzenia śledzenia sesji jest wykorzystanie informacji
pochodzącej z uwierzytelniania użytkownika. Uwierzytelnianie użytkownika zostało
omówione w rozdziale 4 „Odczytywanie Informacji”, jednak w celu przypomnienia,
uwierzytelnianie użytkownika stosowane jest kiedy serwer WWW ogranicza dostęp do
niektórych ze swoich zasobów tylko do tych klientów, którzy logują się przy użyciu
określonego hasła i nazwy użytkownika. Po zalogowaniu się klienta, nazwa użytkownika jest
dostę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żytkownik
przegląda kolejne strony witryny WWW. Aplet może zidentyfikować użytkownika
wykorzystując jego nazwę, tym samym może również wyśledzić jego sesję. Dla przykładu
kiedy 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 w
przyszł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:
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ów
toleruje 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
podstawowe uwierzytelnianie HTTP nie zapewnia żadnego mechanizmu wylogującego; użytkownik musi
zamknąć swoją przeglądarkę żeby się wylogować. Problemy, w przypadku uwierzytelniania użytkownika stwarza
niemoż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.
W pewnym sensie ukryte pola danych formularza definiują zmienne stałe dla formularza. Dla
apletu otrzymującego przedłożony formularz nie ma żadnej różnicy pomiędzy ukrytym polem
danych a polem, które jest widoczne.
Przy zastosowaniu ukrytych pól danych formularzowych, możemy przepisać nasze aplety
koszyka zakupów, tak że użytkownicy będą mogli dokonywać zakupów anonimowo, do czasu
zakończenia. Na przykładzie 7.1 została zaprezentowana technika wykorzystująca aplet, który
wyświetla zawartość koszyka zakupów użytkownika i pozwala użytkownikowi na dodanie
wię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.*;
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 wykorzystaniu
ukrytych pól danych. Następnie aplet wyświetla użytkownikowi bieżące artykuły i pyta go czy chce jeszcze
jakieś dodać, czy też chce już zakończyć zakupy. Pytania zadawane są za pomocą formularza zawierającego
ukryte 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ól
danych 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 sesji
moż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ż klient
znają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, nie
upubliczniajmy loginu dostępu serwera, ponieważ zarejestrowane URL-e mogą zawierać identyfikacje sesji dla
formularzy, przedłożonych za pomocą zleceń GET.
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 uwierzytelniony
przez serwer, jego tożsamość może zostać powiązana z jego określonym ID sesji. Przy wylogowywaniu się ID
sesji może zostać usunięte (poprzez nie przesyłanie do klienta późniejszych formularzy) lub po prostu nie
zapamię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 danych
są 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 utrzymuje
się 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 czy
za pomocą zamknięć przeglądarki.
Przepisywanie URL-u
Przepisywanie URL-u jest kolejnym sposobem na obsługę śledzenia anonimowego konta. W
przypadku stosowania przepisywania URL-u, każdy lokalny URL, na który kliknie użytkownik,
jest dynamicznie modyfikowany lub przepisywany — w celu dołączenia dodatkowych
informacji. Te dodatkowe informacje mogą być w formie informacji dodatkowej ścieżki, w
formie parametrów dodanych lub w formie jakiejś własnej, serwerowo — specyficznej zmiany
URL-u. Z powodu ograniczonej przestrzeni dostępnej dla przepisywania URL, dodatkowe
informacje są zwykle ograniczane do ID określonej sesji. Dla przykładu, poniższe URL-e
zostały przepisane aby przekazać identyfikację sesji 123:*
http://server:port/servlet/Rewritten original
http://server:port/servlet/Rewritten/123 extra path infonnation
http://server:port/servlet/Rewritten?sessionid=123 added parameter
http://server:port/servlet/Rewritten;jsessionid=123 custom change
Każda technika przepisywania ma swoje złe i dobre strony. Informacja dodatkowej ścieżki
dział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 nazw
parametrów. Standardowa, serwerowo-specyficzna zmiana działa we wszystkich warunkach
dla apletów, które obsługują tą zmianę.
Przykład 7.2 prezentuje poprawioną wersję naszego „shopping cart viewer”, który
wykorzystuje przepisywanie URL-u (w formie informacji dodatkowej ścieżki) w celu
anonimowego ś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.*;
*
Pamiętajmy, że wartości ID sesji powinny być trudne do odgadnięcia i sfałszowania. Identyfikacja 123 nie jest dobrym
przykładem ID sesji, nadaje się tylko jako przykład książkowy.
out.println("<HEAD><TITLE>Current Shopping Cart
Items</TITLE></HEAD>");
out.println("<BODY>");
// 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>") ;
Pierwszą rzeczą wykonywaną przez ten aplet jest pobranie aktualnej identyfikacji sesji przy użyciu metody
IDgetPathInfo(). Jeżeli ID sesji nie jest określone, aplet wywołuje generateSessionId(), aby
wygenerować nową, niepowtarzalną identyfikację sesji, wykorzystując w tym celu specjalnie do tego celu
zaprojektowaną klasę RMI. ID sesji jest również wykorzystywane do załadowania i wyświetlenia aktualnych
artykułó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óry
wywołuje aplet Help. Coś takiego nie było możliwe przy ukrytych polach danych formularza, ponieważ aplet
Help 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 danych
formularza. Obie techniki są stosowane we wszystkich typach przeglądarek, pozwalają na dostęp anonimowy
obie również mogą zostać użyte do wdrożenia uwierzytelniania z wylogowaniem się. Główna różnica między
tymi technikami polega na tym, że przepisywanie URL-u działa dla wszystkich dynamicznie utworzonych
dokumentó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.
Trwałe cookies
Czwarta 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ępnie
może zostać odczytany z tej przeglądarki. Kiedy przeglądarka otrzymuje cookie, zapisuje go, i
następnie odsyła go z powrotem do serwera, za każdym razem kiedy wchodzi na jakąś jego
stronę, zgodnie z pewnymi zasadami. Ponieważ wartość cookie może jednoznacznie
zidentyfikować 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łącznie
z Netscape 0.94 Beta (i późniejszych) oraz Microsoft Internet Explorer’ze 2 (i późniejszych). Obecnie grupa
robocza Internet Engineering Task Force (IETF) — HTTP Working Group, pracuje nad przekształceniem
cookies w oficjalny, napisany w RFC 2109, standard. Więcej informacji o cookies można znaleźć w specyfikacji
Netscape'a — Netscape's cookie specification, na stronie http://home.netscape.com/newsref/sts/cookie_spec.html
i 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 cookies
Interfejs API ofreuje klasę javax.servlet.http.Cookie, dla pracy z cookies. Szczegóły nagłówka HTTP
dla cookies, są obsługiwane przez Interfejs API. „Ciasteczko” (cookie) tworzymy przy pomocy konstruktora
Cookie():
Konstruktor ten tworzy nowe cookie z początkową wartością i nazwą. Zasady dla
poprawnych nazw i wartości podane są w specyfikacji „Netscape’s cookie specyfication” i w
RFC 2109.
metoda ta dodaje określone cookie do odpowiedzi. Dodatkowe „ciasteczka” są dodawane poprzez kolejne
wywołania do metody addCookie(). Ponieważ cookie są wysyłane przy użyciu nagłówków HTTP, powinny
zostać dodane do odpowiedzi zanim jeszcze zostanie ona zatwierdzona. Od przeglądarek wymaga się, żeby
przyjmowały 20 cookies na witrynę WWW, 300 (wszystkich) na użytkownika, mogą one ponadto ograniczyć
rozmiar każdego „ciasteczka” do 4096 bajtów.
Niestety, w standardowym Interfejsie API nie ma żadnej metody umożliwiającej ściągnięcie wartości cookie
poprzez nazwę. Poza jego wartością i nazwą, możemy ustawić wiele atrybutów dla cookie. Poniższe metody
używane są do ustawiania tych atrybutów. Jak można się przekonać przeglądając uzupełnienie B „HTTP Interfejs
API — 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ła
do skutku, powinniśmy również dopilnować, aby wszystkie atrybuty z wyjątkiem nazwy, wartości i wersji zostały
wyzerowane, podobnie jak cookie.
ustala wersję „ciasteczka”. Aplety mogą przesyłać i otrzymywać cookies sformatowane, tak
aby pasować zarówno do „Netscape persistent cookies” (Wersja 0) lub nowszego, jednak
trochę eksperymentalnego RFC 2109 cookies (Wersja 1). Nowo zaprojektowane cookies
domyś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 powinny
zobaczyć cookie. Domyślnie „ciasteczka” są odsyłane tylko do komputera centralnego który
je 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 z
upload.foo.com, lecz nie z www.upload.foo.com.* Więcej o wzorach domen można znaleźć w
specyfikacji „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ć” kiedy
przeglądarka „wychodzi”. Wartość zerowa informuje przeglądarkę, że ma natychmiast
wyzerować cookie.
public void Cookie.setPath(String uri)
*
Technicznie rzecz ujmując, zgodnie ze specyfikacją „Netscape cookie specyfication”, dla domen najwyższej klasy
wymagane są dwie kropki, domenami tymi mogą być .com, .edu, .net, .org, .gov, .mil, i .int, natomiast dla pozostałych domen
wymagane są trzy kropki. Reguła nie pozwala serwerowi na przypadkowe lub szkodliwe ustawianie cookie dla domen .co.uk
i przekazywanie ich do wszystkich przedsiębiorstw w Wielkiej Brytanii, dla przykładu. Niestety prawie wszystkie
przeglądarki ignorują metodę trzech kropek. Więcej na ten temat na stronie
http://homepages.paradise.net.nz~glineham/cookiemonster.html.
— 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 tym
katalogiem. 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 cookie
musi zawierać aplet, który ustawia cookie.
informuje czy cookie powinno zostać przesłane wyłącznie przez bezpieczny kanał, taki jak SSL. Wartością
domyślną jest false.
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.
— 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 sam
sposób na wszystkich przeglądarkach.
Przykład 7.3.
Śledzenie sesji wykorzystujące trwałe cookies
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
// 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>") ;
out.println( "</BODY></HTML>") ;
}
*
Takie, a nie inny układ materiału niniejszej książki może przypominać sytuację nauczyciela trzeciej klasy szkoły
podstawowej, który tłumaczy swoim uczniom rachunkowe zasady dzielenia, tylko po to, aby powiedzieć im później, że
dzielenie najlepiej wykonuje się za pomocą kalkulatora. Wierzymy jednak, iż poznanie tradycyjnych metod pozwala na
lepsze zrozumienie problemu.
public void HttpSession.setAttribute(String name. Object value)
Metoda wiąże określoną wartość obiektu z określoną nazwą. Wszystkie wcześniejsze
powiązania z tą nazwą są usuwane. Aby pobrać obiekt z sesji, wykorzystujemy metodę
getAttribute():
Przykład 7.4.
Sesja śledząca liczbę wizyt
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
Rysunek 7.2.
Liczenie liczby wizyt
Aplet pobiera najpierw obiekt HttpSession, związany z aktualnym klientem. Poprzez wykorzystanie
bezargumentowej 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 aktualne
pary nazwa-wartość, w sesji.
<!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. Dla
przykładu, ustaliliśmy, że jeżeli użytkownik nie złoży żadnego zlecenia do aplikacji WWW, wtedy serwer może
unieważnić sesję użytkownika i „rozwiązać” obiekty w niej przechowywane. Specyfikacja apletu wymaga, aby
wartość terminu ważności wyrażona była liczbą całkowitą, co wyklucza wartości ujemne, jednak niektóre
serwery 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 HttpSession
dysponuje metodą setMaxInactivateInterval() do takiego precyzyjnego sterowania:
Przykład 7.6.
Ustawianie terminu ważności
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
out.println("<HTML><HEAD><TITLE>SessionTimer</TITLE></HEAD>") ;
out.println("<BODY><Hl>Session Timer</Hl>") ;
out.println("</BODY></HTML>") ;
}
}
Przykład 7.7.
Unieważnianie starej sesji
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
}
}
Metoda ta odsyła niepowtarzalny identyfikator String, przypisany do sesji. Dla przykładu, ID serwera
„Tomcat” mogłoby wyglądać w następujący sposób awj4gyhsn2. Metoda zgłasza wyjątek
IllegalStateException w przypadku gdy sesja nie jest ważna.
Pamiętajmy, iż identyfikacja sesji powinna być traktowana jako tajna informacja serwera. Zwracajmy uwagę na
to, co robimy z tą wartością.
Poniższy fragment kodu ukazuje aplet piszący łącznik do samego siebie, który jest kodowany tak, aby zawierał bieżącą identyfikację sesji:
Na serwerach, które nie obsługują przepisywania URL-u lub mają wyłączoną tą funkcję,
końcowy URL pozostaje bez zmian. Poniżej przedstawiamy fragment kodu, na którym jest
zaprezentowany aplet przekierowujący użytkownika do URL-u zakodowanego tak, aby
zawierał ID sesji:
res.sendRedirect(res.encodeRedirectURL("/servlet/NewServlet")) ;
Aplet jest w stanie wykryć czy identyfikacja sesji, wykorzystywana do zidentyfikowania aktualnego obiektu
HttpSession, pochodzi od cookie czy od zakodowanego URL-u wykorzystującego metody
isRequestedSessionIdFromCookie() i isRequestedSessionIdFromURL():
public boolean HttpServletRequest.isRequestedSessionIdFromCookie()
public boolean HttpServletRequest.isRequestedSessionIdFromURL()
Określenie czy identyfikacja sesji pochodzi z innego źródła, takiego jak sesja SSL, nie jest aktualnie możliwe.
ID sesji będące przedmiotem zlecenia może nie pokrywać się z identyfikacją sesji, odesłaną przez metodę
getSession(), tak jak ma to miejsce kiedy ID sesji jest nieważne. Aplet może jednak ustalić czy identyfikacja
sesji będąca przedmiotem zlecenia jest ważna, przy pomocy metody isRequestedSessionIdValid():
Miejmy również świadomość, iż kiedy używamy śledzenia sesji opartego na przepisywaniu URL-u, wielokrotne
okna przeglądarki mogą należeć do różnych sesji lub do tej samej sesji, w zależności od tego w jaki sposób okna
te zostały utworzone oraz czy tworzący je łącznik miał zastosowane przepisywanie URL-u.
Aplet „SessionSnoop”
Zaprezentowany na przykładzie 7.8 aplet SessionSnoop wykorzystuje większość opisanych w rozdziale metod,
aby „podsłuchać” informację o aktualnej sesji. Rysunek 7.3 prezentuje przykładowy wydruk wyjściowy apletu.
Przykład 7.8.
„Podsłuchiwanie” informacji sesji
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
out.println("<HTML><HEAD><TITLE>SessionSnoop</TITLE></HEAD>");
out.println("<BODY><Hl>Session Snoop</Hl>");
out.println("<P>") ;
out.println("</BODY></HTML>") ;
}
}
Aplet rozpoczyna tym samym kodem, który został zamieszczony w przykładzie 7.4, w aplecie
SessionTracker. Następnie przechodzi do dalszego wyświetlania bieżącego ID sesji, tego czy
jest to sesja nowa, wartości limitu czasu, czasu utworzenia sesji oraz czas ostatniego
połączenia z sesją. Później aplet wyświetla czy identyfikacja sesji (jeżeli jest taka), będąca
przedmiotem zlecenia, pochodzi od cookie czy od URL-u oraz czy to ID jest ważne. W końcu
aplet drukuje zakodowany URL, który może zostać wykorzystany do powtórnego załadowania
tej strony, w celu sprawdzenia czy przepisywanie URL-u działa nawet wtedy kiedy cookies nie
są obsługiwane.
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.
Obiekt HttpSessionBindingEvent zapewnia również dostęp do obiektu, do którego jest wiązany (lub od
którego jest „odwiązywany”) odbiornik, wykorzystując w tym celu metodę getSession():
Przykład 7.9.
Zdarzenia wiążące śledzenie sesji
import java.io.*;
import java.util.*;
import javax.servlet.* ;
import javax.servlet.http.*;
// Dodaj CustomBindingListener
session.setAttribute("bindings.listener",
new CustomBindingListener(getServletContext()));
Za każdym razem kiedy obiekt CustomBindingListner jest wiązany z sesją, jego metoda valueBound(),
jest wywoływana i zdarzenie jest rejestrowane. Za każdym razem kiedy obiekt ten jest „odwiązywany” od sesji,
wywoływana jest jego metoda valueUnbound(), tak że zdarzenie jest również rejestrowane. Możemy śledzić
sekwencję zdarzeń poprzez obserwowanie dziennika zdarzeń serwera.
Załóżmy, iż aplet ten jest wywołany jeden raz, powtórnie ładowany 30 sekund później i następnie nie
wywoływany co najmniej przez następne pół godziny. Dziennik zdarzeń mógłby wyglądać w następujący sposób:
Pierwsza pozycja występuje podczas zlecenia pierwszej strony, kiedy odbiornik jest związany z nową sesją.
Pozycje: druga oraz trzecia występują podczas ponownego ładowania, ponieważ odbiornik jest „odwiązywany” i
powtórnie wiązany z sesją, w czasie tego samego wywołania setAttribute(). Czwarta pozycja ma miejsce
pół godziny później, kiedy limit czasu sesji kończy się, a ona sama jest unieważniana.
Przykład 7.10.
Wykorzystanie Śledzenia Sesji API
import java.io.*;
import javax.servlet.* ;
import javax.servlet.http.*;
public class ShoppingCartViewerSession extends HttpServlet {
out.println("<HTML><HEAD><TITLE>SessionTracker</TITLE></HEAD>");
out.println("<BODY><Hl>Session Tracking Demo</Hl>");
// 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>") ;
Bezpieczeństwo danych jest jednym z najważniejszych zagadnień programowania sieciowego, ponieważ coraz
więcej firm decyduje się na obsługę klientów za pośrednictwem sieci i ich Intranety zapełniane są tajnymi
danymi. 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.
Załóżmy, że posiadamy bazę danych użytkowników naszego serwera, zawierającą listę identyfikatorów, haseł i
ró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 (nasza
wersja 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ój
komputer do sieci, powinien być jedyną osobą mającą dostęp do naszego tajnego serwletu. Określimy to w pliku
web.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ży
zawsze stosować znaczniki w następującym porządku: <security-constraint>, <login-config>, a
następnie <security-role>. Nie bez znaczenia jest również kolejność występowania elementów wewnątrz
tych znaczników. Tworząc pliki web.xml najłatwiej skorzystać z pomocy narzędzi graficznych, które
automatycznie „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-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>
secret
</servlet-name>
<servlet-class>
SalaryServer
</servlet-class>
</servlet>
<security-constraint>
<web-resource-collection>
<web-resource-name>
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ą uwierzytelnienia
podstawowego. 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 tylko
uż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 o
ograniczonym dostępie. Dla wzorców adresów URL można użyć identycznych symboli wieloznacznych, jakich
uż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>, to
wszystkie metody zostaną uznane za chronione. Znacznik <auth-constraint> zawiera nazwy ról
uż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 przypadku
okreś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. O
uwierzytelnieniach w oparciu o formularz i przy użyciu certyfikatów klienckich będzie mowa w dalszej części
rozdziału. Znacznik <realm-name> określa zakres użycia loginu (tu zawarta jest informacja określająca
uprawnienia poszczególnych ról). Ten znacznik jest używany tylko w uwierzytelnieniu podstawowym i
szyfrowanym.
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żących
do „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 jest
zabroniony, a okno dialogowe przeglądarki poprosi użytkownika o kolejną próbę zalogowania się.
Wyszukiwanie informacji uwierzytelnienia
Serwlet może odczytać informację na temat uwierzytelnienia dzięki dwóm metodom przedstawionym w rozdziale
4: 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. Metoda
IsUserInRole() została także wprowadzona w Servlet API 2.2. Ta metoda zwraca wartość true, gdy
uż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 rozmieszczenia
pozwala uzyskać dostęp wielu różnym rolom. Wywołanie tej metody pozwala serwletowi na zróżnicowanie
dostępu do danych zależności od uwierzytelnionej roli użytkownika.
W deskryptorze rozmieszczeń (pliku opisu rozmieszczenia) można utworzyć pseudonimy, (można sprawić, by
zasięg roli mgr będzie taki sam, jak zasięg roli manager). Ma to zastosowanie podczas integracji serwletów
pochodzą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ątrz
znacznika <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.*;
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, że
w 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 logowania
używane w procesie uwierzytelnienia. Obie strony powinny być dobrze zaprojektowane i opisowe, przy czym
strona logująca powinna prosić użytkownika o podanie danych, a strona błędnego logowania informować serwer
o 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 obiektu
Principal. Jeśli serwer odnajdzie ten obiekt, porównuje zawarte w nim role do tych, które mają dostęp do
danych. Dostęp przyznawany jest tylko temu użytkownikowi, którego rola jest umieszczona w obiekcie
Principal. 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 obiekcie
uż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żą do
uprawnionych 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ściwych
danych 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żytkownika
przesł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.
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ć jakimkolwiek
plikiem 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ących
użytkownikowi stronę, za pomocą której powinien spróbować zalogować się ponownie. Przykład 8.7 ilustruje
prostą 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żytkownik
może wejść do witryny poprzez przyjazną i przejrzystą stronę logującą. W obu uwierzytelnieniach hasło jest
przesyłane jako tekst jawny chyba, że kanał komunikacyjny zostanie zabezpieczony innymi metodami.
Oba omówione uwierzytelnienia nie zapewniają mechanizmu wylogowania. W przypadku uwierzytelnienia
opartego o formularz można wywołać metodę session.Invalidate(), ale nie gwarantuje to zamierzonego
efektu. W obu sposobach serwer sprawdza ważność użytkowników nawet wówczas, gdy kontrola nie powinna
być dokonywana przez serwer (na przykład: niektóre banki wymagają podania numeru konta, hasła i PIN-u, by
umożliwić dostęp). Aby rozwiązać ten problem, należy skorzystać z uwierzytelnienia niestandardowego.
Uwierzytelnienie niestandardowe
Zwykle uwierzytelnienie klienta odbywa się w serwerze sieciowym. Deskryptor rozmieszczenia przekazuje
serwerowi 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ć zaimplementowany
na serwerze. Może być to spowodowane tym, że lista użytkowników jest zapisana w formacie nieznanym
serwerowi 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żytkownikach
ze specjalnie sformatowanego pliku lub relacyjnej bazy danych. Dodatkowo można zastosować określony system
ochrony (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 otrzymuje
zakodowane dane użytkownika w nagłówku Authorization. Odmawia dostępu danemu użytkownikowi
poprzez przesłanie statusu SC_UNAUTHORIZED i nagłówka WWW-Authenticate, opisującego określone
dane użytkownika. Serwer zazwyczaj obsługuje wyżej opisane zdarzenia bez udziału serwletu, ale nie ogranicza
dostępu do danych, gdy autoryzacja jest przeprowadzona w serwlecie.
Nagłówek Authorization przesłany przez klienta zawiera jego nazwę i hasło. W podstawowym schemacie
autoryzacji, nagłówek Authorization zawiera łańcuch tekstowy username:password, zakodowany za
pomocą kodera Base64. Na przykład, nazwa webmaster i hasło try2gueSS są przesyłane w nagłówku
Authorization o wartości:
Authorization: BASIC d2VibWFzdGVyOnRyeTJndWVTUw
Serwlet może przesłać nagłówek WWW-Authenticate, aby poinformować klienta o sposobie autoryzacji i o
obszarze weryfikacji użytkowników. Obszar stanowi zbiór kont użytkowników i chronionych zasobów. Na
przykł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łówek
Authorization i wysyłając status SC_UNAUTHORIZED oraz nagłówek WWW-Authenticate. Tak
skonfigurowany serwlet ogranicza dostęp do „ściśle tajnego zbioru” tylko do grona użytkowników, których
rozpozna na liście. W tym przykładzie, lista jest przechowywana w prostej tablicy mieszającej, a jej
zawartość 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ęście
jest on dostępny w darmowej wersji. W naszym serwlecie użyjemy własnej klasy
com.oreilly.servlet.Base64Decoder dostępnej na stronie http://www.servlets.com. Szczegóły
dotyczą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 autoryzacji
String auth = req.GetHeader("Authorization");
//Czy zezwalamy na dostęp temu użytkownikowi?
if(!allowUser(auth)) {
//nie zezwalamy, nie jest autoryzowany
res.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 rzeczy
out.println("Ściśle tajne rzeczy");
}
}
*
Można także użyć klasy sun.misc.BASE64Decoder, która towarzyszy JDK.
else
return false;
}
Dostęp do serwletu mają wszyscy użytkownicy. W serwlecie dokonuje się proces uwierzytelnienia: dostęp do
chronionych zasobów jest przydzielany użytkownikom których dane uwierzytelniające znajdują się na liście
uprawnionych. Po dokonaniu kilku modyfikacji za pomocą tego serwletu można udostępnić dane użytkownikom
posiadającym hasło grupowe albo, podobnie do anonimowego FTP, można udostępnić zasoby użytkownikowi o
nazwie 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 URL
mogł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. W
metodzie niestandardowej autoryzacji sposób ograniczenia zabezpieczeń serwera nie wpływa na sposób
zabezpieczenia jego serwletów.
Przykład 8.9
Plik 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>
</FORM>
</BODY></HTML>
//pobierz sesję
HttpSession session = req.getSession();
//czy sesja rozpozna użytkownika jako już zalogowanego?
Object done=sesion.getAttribute("logon.isDone");//zaznacz obiekt
if (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ć towary
out.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 obiekt
istnieje, serwlet ma pewność, że klient już się zalogował, w związku z czym pozwala mu przeglądać tajne
informacje. Brak tego obiektu oznacza, że klient nie jest zalogowany, wobec czego serwlet zapisuje żądany adres
URL w zmiennej login.target i przekierowuje klienta na stronę logującą. W niestandardowej autoryzacji
opartej o formularz należy zapewnić takie zachowanie. Użycie podklas lub klas użytkowych może uprościć to
zadanie.
W naszym przykładowym serwlecie sprawdzana jest ważność numeru konta, hasła i kodu PIN. Jeśli
wprowadzone przez klienta dane są nieprawidłowe, dostęp do informacji jest zabroniony. W przeciwnym
wypadku, 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żytkownika
String account = req.getparametr("account");
String password = req.getParametr("password");
String pin = req.getParametr("pin");
Certyfikaty cyfrowe
Prawdziwe 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. To
właśnie oferuje technologia certyfikatów cyfrowych.
Główną koncepcją jest system kryptografii oparty o klucze publiczne. W tym systemie każdy uczestnik wymiany
informacji posiada unikatową parę kluczy używanych do kodowania i dekodowania informacji. Jednym z nich
jest ogólnie dostępny klucz publiczny, a drugim — tajny klucz prywatny. Aby zademonstrować zasadę działania
systemu, załóżmy, że Jaś chce przesłać Krzysiowi tajną informację. Jaś znajduje w swoich zasobach klucz
publiczny Krzysia i za jego pomocą koduje informację. Gdy Krzyś otrzyma wiadomość, użyje swojego klucza
prywatnego do jej zdekodowania. Informacja staje się bezwartościowa dla każdego, kto ją przechwyci i nie
posiada 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
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 do
wpisywania klucza za każdym razem. Zamiast tego, klucze publiczne są przechowywane na dysku w formie
certyfikatów cyfrowych, a klucze prywatne w zakodowanej formie. Certyfikaty cyfrowe mogą być generowane za
pomocą specjalnych programów (takich jak np. pakiet PGP) lub są wydawane przez specjalistyczną firmę. Pliki z
certyfikatami mogą być ładowane przez najbardziej chronione aplikacje (takie jak serwery, przeglądarki i
programy obsługujące pocztę elektroniczną).
Kryptografia kluczy publicznych rozwiązuje problem poufności, ponieważ cały proces komunikacji jest
zakodowany. 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śmy
jeszcze problemu wierzytelności: Krzyś nie ma pewności, że wiadomość, którą odebrał została wysłana przez
Jasia. 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 jedynym
nadawcą otrzymanej wiadomości mógł być tylko Jaś, bo wiadomość zakodowana jego kluczem prywatnym może
być zdekodowana jedynie za pomocą jego klucza publicznego.
Prostszym systemem jest użycie kluczy symetrycznych, czyli jednego klucza kodującego i dekodującego. Klucze
asymetryczne mają dużą zaletę: przy przesyłaniu informacji nie trzeba zapewniać bezpieczeństwa kanału
komunikacyjnego. Mają też ogromną wadę: kodowanie i dekodowanie informacji za pomocą tych kluczy
wymaga sporej mocy obliczeniowej. Jako kompromis, wiele systemów kryptograficznych korzysta z
asymetrycznych kluczy prywatnych i publicznych do wzajemnej identyfikacji i poufnego przekazania
oddzielnego klucza symetrycznego do kodowania właściwej informacji. Klucz symetryczny jest zwykle tworzony
w 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ą trudne
do zdekodowania, ale nie ma rzeczy niemożliwych — wyspecjalizowane maszyny potrafią złamać kod w ciągu
kilku 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 DES
w sposób brutalny, przekazywanie wiadomości z wykorzystaniem kluczy o dużych rozmiarach jest bardzo
bezpieczne.
Pozostaje jeszcze jeden problem: w jaki sposób jeden użytkownik może upewnić się, że ten drugi jest faktycznie
tym, za kogo się podaje? Jaś i Krzyś znają się, więc Krzyś wierzy, że klucz publiczny dostarczony mu osobiście
przez Jasia jest prawdziwy**. Z drugiej strony, jeśli np. Małgosia, nie znając wcześniej Jasia, chciałaby mu
udostępnić swój klucz publiczny, Jaś może podejrzewać, że Małgosia jest w rzeczywistości Markiem. Jeśli
założymy, że Krzyś zna Małgosię, możemy poprosić Krzysia, by za pomocą swojego klucza prywatnego
podpisał klucz publiczny Małgosi. Wówczas, Jaś dostanie klucz Małgosi potwierdzony przez Krzysia, do
którego ma zaufanie.
W rzeczywistości, ta „trzecia strona” potwierdzająca autentyczność nazywana jest Centrum Certyfikacji. Takim
centrum jest np. korporacja VeriSign. Ponieważ VeriSign jest ogólnie znaną organizacją (z powszechnie znanym
kluczem publicznym) klucze zweryfikowane przez to centrum można uznać za pewne. VeriSign oferuje kilka
klas identyfikatorów (wraz ze wzrostem numeru klasy rośnie jego poziom zaufania i cena). Certyfikaty pierwszej
klasy 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, przy
użyciu specjalnych metod weryfikacyjnych.
Wybierając centrum autoryzacji należy zwrócić uwagę na jego prestiż. Certyfikaty VeriSign są dołączone do
przeglądarek MS IE i Netscape, więc każdy użytkownik Internetu będzie je akceptował. Poniżej wypisano
najpopularniejsze 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ą klucze
w cyfrowy sposób (np. za pomocą email) i rozpoznają klucz po kilku pierwszych znakach.
• 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łych
użytkowników. O powiązaniu kryptografii z Javą traktuje książka Jonathana Knudsena Java Cryptography
(O'Reilly).
*
Połączenie klient-serwer poprzez bezpieczną sieć VPN może spotkac się z ograniczeniem CONFIDENTIAL nawet bez użycia SSL.
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 aplikacji
będą wykonywane przy użyciu certyfikatów klienckich zamiast tradycyjnych metod: podstawowej lub w oparciu
o formularz. Klient nigdy nie ujrzy strony logującej, mimo że przeglądarka poprosi o hasło do odblokowania
certyfikatu przed przesłaniem do serwera. Jeśli przeglądarka nie otrzyma certyfikatu klienckiego, dostęp do
danych będzie zabroniony.
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 {
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, numeru
seryjnego, podpisu i tak dalej. Wydruk certyfikatu jako String pokazano w przykładzie 8.14. Pierwszy
certyfikat 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: EmailAddress=rmandava@talentportal.com, CN=Ramesh babu mandava,
OU=Digital
ID Class 1 –
Netscape, OU=Persona Not Validated, OU="www.verisign.com/repository/RPA Incorp. By
Ref., LIAB.LTD©98", OU=VeriSign Trust Network, O="VeriSign, INC."
Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4
Key: com.sun.net.ssl.internal.ssl.JSA_RSAPublicKey@5b5870e3
Validity: [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=VeriSign
Trust Network, O="VeriSign, Inc."
SerialNumber: [ 1ef11638 5ab8aaa1 bfa2b11b3 c0fb9cd9]
Certificate Extensions: 4
[1]: ObjectId: 2.16.840.1.113730.1.1 Criticality=false
NetscapecertType [
SSL client
]
[2]: ObjectId: 2.5.29.32 Criticality=false
Extension 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.v
0030: 65 72 69 73 69 67 6E 2E 63 6F 6D 2F 72 70 61 erisign.com/rpa
]
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ów
internetowych i programów wysyłających wiadomości online. Współpraca aplikacji sieciowych z bazami danych
ma 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 danych
coraz 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 kursem
wprowadzającym interfejs programistyczny JDBC.
Największą zaletą serwletów jest ich czas istnienia (szeroko opisany w rozdziale 3,”Cykl Życia Serwletu”), który
pozwala 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 trzeba
ponownie 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. Serwlet
obsługujący np. bazę danych Sybase może korzystać z bazy Oracle po niewielkiej zmianie w pliku właściwości
lub 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 do
różnorodnych baz danych, łącznie z bazami danych ODBC, takimi jak MS Access, Oracle, czy Sybase.
Serwlety w warstwie pośredniej
Warstwa pośrednia służy do połączenia aplikacji klienckich z aplikacjami serwerowymi (np. aplety
z 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łem
danych, ponieważ oprogramowanie warstwy pośredniej (nazywane middleware) zawiera logikę
biznesową. Logika biznesowa oddziela skomplikowane zadania niskiego poziomu (takie jak
aktualizacja tabel baz danych) od zadań wysokiego poziomu (np. składanie zamówienia), co
sprawia, ż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 z
serwerem 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 bazy
danych, itp.) może spowodować przerwanie połączenia z aplikacją klienta. Jeśli dokonamy zmiany
w kliencie (zamierzonej lub przypadkowej), baza danych może błędnie zapisać informacje o
zamówieniach złożonych za pośrednictwem aplikacji klienta.
Do Middleware przesyłane są informacje o zamówieniu (np. nazwisko, adres, towar, ilość i numer
karty kredytowej) i za pomocą tych informacji dokonywana jest weryfikacja użytkownika
skł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ówienia
bazy 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 procesu
zapisywania informacji w bazie danych na kilka wewnętrznych serwerów. Middleware potrafi
wykorzystać przepustowość sieci: zamiast powolnego połączenia klient-serwer, klient może
przekazać wszystkie informacje middleware, który użyje szybkiego połączenia sieciowego z puli
połą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 lub
formularzy HTML. Klient przesyła żądania do serwletu za pomocą HTTP, a logika biznesowa w
serwlecie przekazuje żądanie do wewnętrznego serwera (więcej informacji na temat komunikacji
aplet-serwlet znajduje się w rozdziale 10, „Komunikacja aplet-serwlet”).
Serwlety często używają dodatkowej warstwy pośredniej poza serwerem sieciowym (takiej jak
Enterprise Java Beans) do łączenia się z bazą danych. Jeśli przeglądarka prześle formularz HTML z
zamówieniem do serwletu, to może on przekształcić tą informację i wykonać wywołanie do EJB
innej maszyny odpowiedzialnej za obsługę wszystkich zamówień — pochodzących zarówno z
serwletów, jak i niezależnych programów. W omówionych przypadkach mamy do czynienia ze
strukturą czterowarstwową.
JDBC API
Wcześniej zakładaliśmy, że czytelnik posiada ogólną wiedze dotyczącą Java API. Ponieważ nawet wprawieni
programiści Javy mogą mieć małe doświadczenie z bazami danych, ten podpunkt wprowadza JDBC na
podstawie 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ólnych
założeniach baz danych i JDBC, takie jak: Database Programming with JDBC and Java George'a Reese'a i
JDBC Database Access with Java Grahama Hamiltona, Ricka Cattela, Maydene Fisher. Krótki przegląd znajduje
się w Java Enterprise in a Nutshel Davida Flangana. Oficjalna specyfikacja JDBC znajduje się na stronie
http://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 z
jakąkolwiek bazą danych. Rysunek 9.2 pokazuje schemat struktury JDBC.
Sterowniki JDBC
JDBC API, znajdujący się w pakiecie java.sql zawiera zestaw klas służących do implementacji określonych
interfejsów. Większa część API jest rozprowadzana jako klasy interfejsów neutralnych dla baz danych, a
współ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 interfejs
java.sql.Driver. Sterowniki istnieją prawie dla wszystkich popularnych systemów RDBMS, ale nie
wszystkie 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 danych
Microsoft 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 w
treś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 łączenia
klienta Javy i serwisu bazy danych ODBC.JDBC-ODBC dostarczony z JDK jest najpopularniejszym
sterownikiem tego typu. Te sterowniki są implementowane przy użyciu kodu własnego i wymagają instalacji
dodatkowego oprogramowania po stronie klienta.
• Typ 2: Native-API Partly Java Driver — sterowniki typu 2 są napisane w Javie, ale korzystają z bibliotek
napisanych w innych językach. Dla Oracle, biblioteki kodu własnego mogłyby bazować na bibliotekach
OCI, 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 wszystkie
odpowiedniki w całości napisane w Javie. Użycie tych sterowników może być jednak ryzykowne: błąd w
sekcji kodu własnego sterownika może zniszczyć cały serwer. Sterowniki tego typu wymagają instalacji
dodatkowego oprogramowania po stronie klienta.
Właściwie dostępne są trzy formy metody getConnection(). Najprostszą z nich jest ta, która pobiera tylko
adres URL: getConnection (String url). Ta metoda ma zastosowanie w przypadku, gdy nie
przewidziano konieczności zalogowania się (podania nazwy użytkownika i hasła) lub umieszczenia informacji
logują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) zawiera
standardowo nazwę użytkownika i hasło, a także dodatkowe informacje przekazywane odpowiedniemu
sterownikowi bazy danych. Na przykład, niektóre sterowniki respektują właściwość cacherows, która określa
jak dużo wierszy ma trafić do pamięci cache w jednostce czasu. Używanie tej metody ułatwia otwarcie
połą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 pliku
sql.properties. Format nazwa=wartość jest formatem standardowym w plikach właściwości Javy:
connection.driver=sun.jdbc.odbc.JdbcOdbcDriver
connection.url=jdbc:odbc:somedb
user=user
password=passwd
Za pomocą kodu przedstawionego w przykładzie 9.1 otwiera się połączenie z bazą danych używając wartości
zgromadzonych wewnątrz pliku sql.properties. Należy zauważyć, że nazwa użytkownika i hasło to informacja
standardowo wymagana do zalogowania się, natomiast właściwości connection.driver i
connection.url są specjalnymi nazwami użytymi w poniższym kodzie do rozpoznania sterownika i
odpowiadają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 bazodanowego
Properties props = new Properties();
InputStream in = new FileInputStream("sql.properties");
props.load(in);
in.close(); ta instrukcja powinna znaleźć się w bloku finally
// załadowanie sterownika
Class.forName(props.getProperty("connection.driver");
//otwarcie połączenia
con = DriverManager.getConnection("connection.url"),props);
Utworzony obiekt Properties jest wypełniany wartościami odczytanymi z pliku sql.properties, a następnie
zawarte w tym pliku właściwości są użyte do otwarcia połączenia z bazą danych. Wykorzystanie pliku
właściwości pozwala na zmianę wszystkich informacji o połączeniach z bazą danych bez zbędnego
przekompilowania kodu Javy.
Przykład 9.3.
Serwlet JDBC-enabled
import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
res.setContentType("text/html");
Printwriter out = res.getWriter();
try{
//ładujemy sterownik oracle
Class.forName("oracle.jdbc.driver.OracleDriver");
try {
ResultSetMetaData rsmd = rs.getMetaData();
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(" ");
}
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();
}
}
Ten przykład pokazuje w jaki sposób użyć dwóch podstawowych metod pochodzących od obiektu
ResultSetMetaData: 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, że
obiekty 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 w
ten sposób jest bardziej wydajny i przenośny, jeśli właściwie napiszemy wyrażenie SQL. Używamy tu
getObject().toString() zamiast getString(), aby uprościć obsługę pól mających wartość null, co
omówiono w następnym podrozdziale.
Tabela 9.1 pokazuje metody Javy, które można użyć do wyszukania najbardziej powszechnych typów danych
SQL. Metoda getObject() obiektu ResultSet zwróci obiekt Javy którego typ zależy od typu obiektu
SQL, co pokazano w tabeli. Można również użyć innej metody getXXX(). Metody te pokazano w trzeciej
kolumnie, wraz ze zwracanymi typami danych. Należy pamiętać, że dostępne typy danych SQL są różne w
zależności od bazy danych.
Tabela 9.1.
Metody otrzymywania danych z obiektu ResultSet
Typ danych SQL Typ Java zwracany przez Zalecana alternatywa do
getObject() getObject()
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 ()
InputStream
getUnicodeStream
NUMERIC java.math.BigDecimal java.math.BigDecimal
getBigDecimal()
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()
Innym sposobem pozwalającym na odnajdywanie wartości typu null jest użycie metody getObject(). Jeśli
kolumna 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.
try {
Statement stmt = con.createStatement();
if (stmt.execute(sql)) {
//?????
ResultSet rs = stmt.getResultSet();
out.append("<TABLE>\n");
while (rs.next()) {
out.append("<TR>"); // rozpocznij nowy wiersz
for (int i = 1; i <= numcols; i++) {
out.append("<TD>"); // rozpocznij nowy element danych
Object obj = rs.getObject(i);
if (obj != null)
out.append(obj.toString());
else
out.append(" ");
}
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 konstruktora
HtmlSQLResult. Potem, w zależności od zwróconej wartości, wywołuje getResultSet() lub
getUpdateCount(). Należy zauważyć, że ani getResultSet(), ani getUpdateCount() nie powinny
być wykonane więcej niż raz w trakcie jednego zapytania.
// Inny kod
*
„Prepared statements” to w dosłownym tłumaczeniu „gotowe zapytania”, ale ponieważ te zapytania są interpretowane przed
użyciem, lepszym ich określeniem jest „preinterpretowane zapytania”
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, gdy
zaistnieje taka potrzeba:
PreparedStatement pstmt = con.prepareStatement(
" INSERT INTO MUSKETEERS (NAME) VALUES (?)");
pstmt.setString (1, "John d'Artagan");
pstmt.executeUpdate();
Przykład 9.6.
Ulepszony serwlet z numerami telefonów.
import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
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);
Niestety, takie rozwiązanie ma również wady. Wprowadzenie bloku synchronizacji na pewnych platformach
zajmuje 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 jest
wcześniejsze utworzenie połączeń z obiektami (takimi jak PreparedStatement), które mogą być szybko
użyte wewnątrz bloków synchronizacji.
Powyższe metody nie mają zastosowania w przypadku serwletów używających interfejsu
SigleThreadModel, a ponadto ograniczają wydajność poprzez ładowanie naraz wielu kopii serwletu.
Transakcje
Transakcje są ważnym zagadnieniem związanym ze współczesnymi systemami relacyjnych baz danych. W wielu
witrynach 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 musi
wykonać operację składającą się z dwóch oddzielnych, ale powiązanych ze sobą akcji: skredytować jedno konto i
obciążyć drugie. Wyobraźmy sobie, że z pewnych powodów powiodła się tylko operacja uznania pierwszego
konta, 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 informacje
dotyczące ilości środków dostępnych na koncie. Prawdopodobieństwo takiego zdarzenia jest niewielkie, ale jego
wystąpienie nie jest wykluczone. Ten rodzaj problemu jest zbliżony do zagadnienia synchronizacji omówionego
w rozdziale 3. Tym razem, zamiast zajmować się ważnością danych zgromadzonych w serwlecie, omówimy
ważność danych zapisywanych w bazie. Prosta synchronizacja nie jest dostatecznym rozwiązaniem omawianego
problemu, ponieważ z zasobów jednej bazy danych może korzystać wiele serwletów. W aplikacjach bankowych
prawdopodobne jest korzystanie z zasobów bazy danych przez wiele aplikacji nie napisanych w Javie. Na
szczęście tym problemem zajęto się na długo przed zaistnieniem Javy. Większość głównych systemów RDMBS
wspiera koncepcję transakcji. Technologia transakcji pozwala na grupowanie wielu wyrażeń SQL: transakcja to
zbiór operacji wykonywanych przez serwer bazodanowy na bazie. Za pomocą systemu RDMBS, obsługującego
tę technologię, można rozpocząć transakcję (czyli wykonać określoną liczbę operacji) i przesłać wyniki do bazy
danych. Jeśli wykonanie wszystkich operacji wchodzących w skład transakcji jest niemożliwe, cała transakcja
jest anulowana i żadne zmiany nie zachodzą w bazie danych. Gdybyśmy naszą aplikację bankową zbudowali w
oparciu 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 (operacje
wchodzące w skład transakcji są traktowane jako logiczna całość). To oznacza, że użytkownicy korzystający z
bazy zawsze mają dostęp do aktualnych, choć nie widzą procesu aktualizacji. Jeśli użytkownik zażąda raportu na
temat sprzedanych produktów w trakcie wykonywania transakcji sprzedaży, raport nie uwzględni wyników tej
transakcji.
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();
}
Uwagi do powyższego przykładu: po pierwsze, logika transakcji zamówienia znajduje się w doPost(), dopóki
klient nie będzie mógł powtórzyć akcji. Po drugie, celem przykładu jest przedstawienie logiki transakcji, więc
dla uproszczenia założono, że użytkownik kupuje 10 sztuk towaru nr 7 i nie wyświetla formularza zawierającego
informacje o karcie kredytowej i zamówieniu. Ponadto, zgłoszenie jakiegokolwiek wyjątku podczas inicjacji
sterownika, łączenia z bazą danych, wykonywania SQL lub obsługi karty kredytowej spowoduje wykonanie
skoku do bloku catch(), gdzie wywoływana jest metoda rollback() anulująca całą transakcję.
Optymalizacja transakcji
W poprzednim przykładzie umieściliśmy obiekt Connection wewnątrz metody doPost(). Zrezygnowaliśmy
wię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śnie
mogłyby być wykonane dwa polecenia: pobranie dziesięciu sztuk towaru i zatwierdzenie transakcji (wywołanie
metody 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:
Pule połączeń
Za pomocą puli połączeń możemy podwajać tylko te zasoby, których aktualnie potrzebujemy (w tym wypadku
obiekty 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óre
z nich, np. DbConnectionBroker są dostępne za darmo na stronie http://javaexchange.com. Pakiety te
tworzą obiekty, których zadaniem jest wydawanie połączeń na żądanie. Są dostępne również sterowniki puli
połączeń (pool drivers), które implementują nowy sterownik JDBC obsługujący połączenia innego (wcześniej
załadowanego) sterownika. Użycie sterownika jest najprostszym sposobem na utworzenie puli połączeń w
serwletach. Sterownik obsługujący pulę połączeń jest bardziej przeciążony niż sterownik obsługujący jedno
połączenie.
Przykład 9.8 przedstawia prosty system puli połączeń. Najpierw tworzone są połączenia, które będą otwierane w
miarę potrzeb. Gdy wszystkie połączenia zostaną otwarte i nie będzie już wolnych połączeń, serwlet utworzy
nowe połączenia.
Nasz klasa ConnectionPool jest w pełni funkcjonalna, ale można stworzyć klasy korzystające z pakietów
komercyjnych służących do tworzenia puli połączeń.
Przykład 9.8.
Klasa ConnectionPool
import java.sql.*;
import java.util.*;
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;
}
}
Klasa ConnectionPool korzysta z tablicy mieszającej (Hashtable) przy użyciu obiektów Connection
jako 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 metody
returnConnection().Przedstawiony tu model puli połączeń jest bardzo prosty, ale w praktyce często
wymaga się bardziej rozbudowanego modelu puli połączeń, w którym jest możliwość sprawdzenia spójności
połą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.*;
res.setContentType("text/html");
PrintWriter out = res.getWriter();
try {
con = pool.getConnection();
// Rozpocznij transakcję
con.setAutoCommit(false);
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");
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 (ClassNotfoundException e ) {
throw new UnavailableException ("Nie można zaladowac sterownika bazy Oracle");
}
}
// zsynchronizuj: Bez tego dwa uchwyty mogą być utworzone dla jednego
klienta.
synchronized (session) {
// spróbuj otrzymać dla tego klienta uchwyt połączenia
ConnectionHolder holder =
(ConnectionHolder) session.getAttribute("servletapp.connection");
try {
con.rollback();
session.removeAttribute("servletapp.connection");
}
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;
catch(Exception e) {
throw new UnavailableException("
Nie można pobrać puli połączeń z kontekstu: " + e.getMessage());
}
}
printHeader(out);
printForm(out);
printMessages(out);
printFooter(out);
}
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>");
}
}
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 danych
private HandleForm (HttpServletRequest req,
HttpServletResponse res) throws ServletException {
String name = req.getParameter("name");
String email = req.getParameter("email");
String comment = req.getParameter("comment");
Przechowywane procedury
Większość baz danych posiada pewien rodzaj wewnętrznego języka programowania służącego do tworzenia i
przechowywania procedur działających wewnątrz bazy danych. Utworzone procedury mogą być wywoływane
przez zewnętrzne aplikacje. Jednym z przykładów jest język PL/SQL dla Oracle. Bazodanowe języki
programowania 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:
END;
Ta procedura, oprócz instrukcji SQL, wykonuje tylko pewne obliczenie. Ten program jest łatwy do napisania w
SQL (transakcja z wcześniejszego przykładu działa w podobny sposób) i stanowi tylko prosty przykład
procedury przechowywanej. Istnieje kilka powodów, dla których warto używać procedur przechowywanych:
*
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ło
zapamiętane.
*
RDBMS — maszyna bazodanowa
• Wiele starszych baz danych posiada dużą ilość przechowywanych procedur, więc dobrze byłoby to
wykorzystać.
Procedura Oracle PL/SQL w tym przykładzie pobiera początkowe wartości, np. identyfikator konta i zwraca
zaktualizowane saldo. Ponieważ każda baza danych ma swoją własną składnię dostępu do przechowywanych
procedur, w JDBC utworzono interfejs pozwalający na korzystanie z przechowywanych procedur niezależnie od
rodzaju bazy danych. Ten interfejs to java.sql.CallableStatement. Składnia wywołania procedury nie
zwracającej wyniku to np. {call procedure_name(?,?)}, a zwracającej wynik to np.{?=call
procedure_name(?,?)}. Parametry wewnątrz nawiasów są opcjonalne.
Sposób użycia klasy CallableStatement jest podobny do sposobu, w jaki używa się klasy
PreparedStatement.
Podczas odczytywania z InputStream, serwlet nie otrzymuje wartości z żadnej innej kolumny zbioru
wyników. To bardzo ważne, bo wywołanie każdej innej metody getXXX() obiektu ResultSet zamyka
poprzedni strumień InputStream.
Dane binarne mogą być pobrane w ten sam sposób używając ResultSet.getBinaryStream(). W tym
wypadku należy ustalić właściwy typ danych i przesłać na wyjście w formie strumienia bajtowego. Przykład 9.12
pokazuje serwlet zwracający plik GIF pobrany z bazy danych.
Przykład 9.12.
Wczytywanie binarnego obrazka GIF z bazy danych
import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
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");
}
}
try{
res.setContentType("image/gif");
ServletOutputStream out = res.getOutputStream()
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że
obsłużyć zaawansowane metody zawarte w interfejsie oraz sterownika, który zapewnia dostęp do tych metod. Na
szczęście, wsparcie JDBC 2.0 jest gwarantowane przez wszystkie serwery J2EE, bo każdy serwer zgodny z J2EE
wymaga zapewnienia dostępu do sterownika i bazy danych współpracującej z JDBC2.0. Instrukcja implementacji
firmy 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 bazy
danych i sterownika zgodnych z JDBC 2.0. Druga część jest umieszczona w pakiecie javax.sql, który
zawiera 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 obiekcie
ResultSet kursor można przesuwać do przodu, do tyłu lub można ustawić go na dowolnym rekordzie (w
interfejsie JDBC 1.2 można poruszać się tylko o jeden element do przodu). Dodano możliwość obsługi typów
danych SQL3 (BLOB — dla dużych obiektów binarnych, CLOB dla dużych obiektów tekstowych i ARRAY dla
tablic).
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 na
podstawie nazwy z serwera JNDI. Kolejną cechą jest wbudowany mechanizm puli połączeń (to bardzo różni
interfejs JDBC 2.0 od JDBC 1.2, ale nie wpływa na działanie aplikacji napisanych pod starszy interfejs). Bardzo
ważną opcjonalną cechą w JDBC 2.0 jest wspomaganie transakcji rozproszonych. Pozwala to na rozmieszczenie
transakcji w różnych oddzielnych bazach danych i aktualizowanie obu baz jedną niepodzielną operacją. W
dodatku, JDBC 2.0 wspiera obiekty RowSet, które „opakowują”wiersze danych ResultSet. Ułatwia to
zapamię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 with
JDBC and Java (O'Reilly) i JDBC API Tutorial and Reference autorstwa Seth White oraz na stronie internetowej
http://java.sun.com/products/jdbc.
Dla tych, którzy oczekują czegoś więcej niż oferuje JDBC, firma ClearInk sponsoruje otwartą bibliotekę z
kodami ź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 Village
stworzono bardziej rozbudowaną wersję o nazwie Town. Oba produkty Village i Town są dostępne na stronie
http://www.working-dogs.com (jak na razie nie ma żadnych informacji o pakiecie City).
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ć, że
istnieje 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 dobrych
przykł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 serwer
muszą być w niemal stałym kontakcie. Inny przykład, proszę spojrzeć na jeden z popularnych apletów
pogawędek. Jeden klient coś mówi, a cała reszta to widzi. Jak to działa? Na pewno aplety nie komunikują się ze
sobą. 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ę zbioru
akcji i dokonuje ciągłego uaktualniania. Jak aplet poznaje aktualne ceny i, co ważniejsze, skąd wie, kiedy się one
zmieniają? Odpowiedzią jest, że komunikuje się on z serwerem.
Opcje komunikacji
Zainteresowanie handlem akcjami wzrasta razem z indeksami giełdowymi, więc kontynuowany będzie
hipotetyczny aplet, odpowiadający za akcje. Należy ostrzec, że aplet ten pozostanie hipotetyczny. Zostanie on
wykorzystany 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 tu
opisane, 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 innym
komputerem daje wynik w SecurityException, tak więc należy przyjąć, że aplet pobiera ceny z serwera, z
którego został pobrany6. Pytanie pozostaje — jak aplet i serwer mogą się ze sobą porozumiewać?
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.0
przyjmował, że wszystkie aplety są niegodne zaufania i uruchamiał je pod kontrolą SecurityManager, który
w 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 pewnych
wł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ły
klienta, 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ła
to magia
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ądarce
użytkownika). Podpis uwierzytelnia pochodzenie apletu i gwarantuje spójność podczas transferu tak, aby
klient wiedział, że kod apletu nie został złośliwie zmieniony. Pozwoliło to na tworzenie bardziej użytecznych
apletó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 nowym
systemie, 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 apletom
nieznanego pochodzenia niewielkich przywilejów (takich jak zapis w jednym, konkretnym katalogu), bez
dostarczania im możliwości przeglądania twardego dysku klienta. Producenci przeglądarek powoli
wprowadzają obsługę nowych wersji JDK, ale na szczęście możliwe jest uaktualnienie JVM przeglądarki przy
pomocy modułu rozszerzającego Java Plug-in, bezpłatnego produktu dostępnego pod adresem
http://java.sun.com/products/plugin.
• 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ńcucha
zapytania 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 danym
porcie 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 z
następujących powodów:
• 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 i
inicjalizacją programu CGI w celu obsługi żądania.
• Zazwyczaj wymaga, aby żądania były utworzone w formie okropnej tablicy par nazwa-wartość. Na
przykład, kiedy przykładowy aplet akcji pyta o najwyższy dzienny kurs akcji Sun Microsystems, musi
zadać pytanie przy pomocy okropnego łańcucha takiego jak akcja=sun&zapytanie=maxdzien.
• Wymaga, aby wszystkie odpowiedzi były sformatowane według arbitralnego, wcześniej uzgodnionego
standardu. Na przykład, kiedy przykładowy aplet akcji otrzymuje odpowiedź zawierającą najwyższy
dzienny kurs akcji, musi dokładnie znać sposób analizy danych. Czy zwrócona cena rozpoczyna się
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ć jedynie
wtedy, gdy zada właściwe pytanie.
Aplet i serwlet mogą się również porozumiewać poprzez połączenie zwykłym portem apletu z procesem serwera
nie-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 (a
nawet 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ę z
portem serwera na komputerze-serwerze, lecz po ustanowieniu połączenia przez port, każda strona
moż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że
być utworzony w celu obsługi żądania bezpośrednio bez uruchamiania zewnętrznego programu CGI w
celu 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 na
połączenia przez zwykły port, tak więc nie pozwala na ten typ komunikacji aplet-serwlet. Tak więc
mechanizm ten powinien zostać wykorzystywany jedynie wtedy, gdy pewne jest, że aplet nie będzie
uruchomiony 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. Tworzenie
takiej 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óry
wykorzystają 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ądarki
posł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 programami
CGI na serwerze. Jest to łatwe oraz działa we wszystkich typach przeglądarek, nawet tych pracujących za
firewallami. Wykorzystanie połączenia przez zwykły port zostało generalnie zarezerwowane dla sytuacji, w
któ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 symulacji
komunikacji dwukierunkowej, co ma służyć przejściu przez firewalle, jak przedstawione to będzie w
późniejszym przykładzie.
• Jest on skomplikowany. Komunikacja RMI wykorzystuje specjalne klasy szkieletowe dla każdego
zdalnego obiektu, a także wymaga rejestru nazw, w którym klienty mogą otrzymać odwołania do tych
zdalnych obiektów.
• Jest on obsługiwany przez niewielką ilość przeglądarek. Z wszystkich popularnych przeglądarek
dostę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 Explorera
Microsoftu 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ółdzielony
przez 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 42
podpowiedzi 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 i
socksProxyPort 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ąc
swoje aplety bez sposobu określenia właściwych ustawień i bez możliwości wykorzystanie RMI przez firewall.
CORBA (Common Object Request Broker Architecture — Wspólna Architektura Żądań Obiektów) to
technologia podobna do RMI, która pozwala na komunikację pomiędzy obiektami rozproszonymi napisanymi w
róż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 niniejszej
książki. Większa ilość informacji dostępna jest pod adresami http://www.omg.org i
http://java.sun.com/products/jdk/idl.
Podejście hybrydowe
Teraz, kiedy przeanalizowane zostały już wszystkie opcje, pytanie pozostaje: jak przykładowy aplet akcji
powinien 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 temat
Star Treka. Może to być prawda przy dokładnym wybieraniu przyjaciół (lub klientów), lecz generalnie nie zdarza
się to w prawdziwym świecie.
Kiedy RMI nie jest dostępny, dwukierunkowe możliwości połączenia przez port nie-HTTP sprawiają, że
wygląda ono atrakcyjnie. Niestety, ta dwukierunkowa komunikacja staje się nieistniejącą komunikacją, kiedy
aplet kończy się na firewallu.
Zawsze jest stary sposób, komunikacja HTTP. Jest ono proste w implementacji i pracuje na każdym kliencie z
obsługą Javy. A jeżeli można zagwarantować, że klient obsługuje JDK 1.1 (jest to łatwiejsze do
zagwarantowania, 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żliwe
jest połączenie technik komunikacji aplet-serwlet HTTP, nie-HTTP i RMI, umieszczając obsługę ich wszystkich
w jednej aplikacji. Dlaczego ktoś miałby chcieć to zrobić? Dlatego, że jest to poręczna technika, kiedy aplet chce
porozumieć 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 samego
serwera do obsługi wielu klientów, podstawowa logika serwera i stan serwera mogą być zebrane w jednym
miejscu. Kiedy środowisko jest pod kontrolą można usunąć jeden lub więcej z tych protokołów. Lecz czy nie jest
miło wiedzieć, że nie jest to konieczne?
Dla skomplikowanych aplikacji pracujących na serwerze aplikacji standardowym projektem jest udostępnienie
zdalnego obiektu RMI klientom RMI, urządzenia słuchającego na portach klientom portów a serwletu klientom
HTTP. 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ą). Jednak
w pozostałej części niniejszego rozdziału zostanie to nieco uproszczone, i przedstawiona zostanie komunikacja
RMI, przez porty i HTTP obsługiwana przez pojedynczy serwlet. Jeden serwlet, wiele protokołów dostępu.
Serwer godziny
W celu prostego przedstawienia każdej z technik komunikacji, napisany zostanie aplet proszący serwer o
aktualną 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, na
którym pracuje. Aby nadać temu przykładowi cień praktyczności, można przyjąć, że aplet potrzebuje dokładnego
znacznika czasu dla jakiegoś zdarzenia i nie może polegać na tym, że komputer klienta posiada prawidłowo
ustawiony zegar.
Aplet
W 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 interfejs
użytkownika, na którym wyświetlane będą pobrane przez niego czasy, jak przedstawiono na rysunku 10-1. W
dalszej części tego rozdziału zaimplementowane zostaną metody pobierzDataHttpTekst(),
pobierzDataHttpObiekt(), pobierzDataPortTekst(), pobierzDataPortObiekt() i
pobierzDataRMIObiekt().
Rysunek 10.1.
Interfejs użytkownika apletu ApletGodziny
Proszę zauważyć, że przykłady w niniejszym rozdziale korzystają z kilku metod JDK 1.0, które zostały
zarzucone w JDK 1.1. Zostało to uczynione w celu poprawienie przenośności. Podczas kompilowania
przykł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.*;
setLayout(new BorderLayout());
add("Centrum", centrum);
Serwlet
Aby 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 i
GET tekstową reprezentacją aktualnego czasu.
Przykład 10.2.
Serwlet SerwletGodziny obsługujący prosty dostęp HTTP
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
Powrót do apletu
W tym momencie, aby ApletGodziny mógł porozumiewać się z serwerem, musi zachowywać się tak, jak
przeglądarka i ustanowić połączenie HTTP z URL-em serwletu, jak to przedstawia implementacja
pobierzDataHttpTekst() w przykładzie 10.3.
Przykład 10.3.
ApletGodziny pobierający czas przy pomocy HTTP
import java.net.URL; // nowy dodatek
import com.oreilly.servlet.HttpMessage; // klasa wspierająca, przedstawiona dalej
// Zamknięcie InputStream
in.close();
import java.io.*;
import java.net.*;
import java.util.*;
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);
return con.getInputStream();
}
// Wyłączenie buforowania
con.setUseCaches(false);
// Wysłanie nagłówków
sendHeaders(con);
return con.getInputStream();
}
// Wyłączenie buforowania
con.setUseCaches(false);
// Wysłanie nagłówków
sendHeaders(con);
return con.getInputStream();
}
8
Tak właściwie można pozostawić wyłączenie buforowania serwletowi, przez ustawienie nagłówka serwletu Pragma na
no-cache, Nie zaszkodzi jednak wyłączenie jej również w aplecie.
miejsca, w którym pakiet com.oreilly.servlet został oryginalnie zainstalowany (zazwyczaj
katalog_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. Metoda
setHeader(String nazwa, String wartość) dodaje do żądania nagłówek o odpowiedniej nazwie i
wartoś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ątrz
Java 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ęp
do stron chronionych hasłem. Wykorzystuje klasę com.oreilly.servlet.Base64Encoder, która nie
jest przedstawiona w niniejszej książce, lecz jest dostępna pod adresem http://servlets.com. Dla wszystkich
powyższych metod, ustawienia pozostają niezmienione pomiędzy żądaniami, a wywołujący jest odpowiedzialny
za 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óc
w wynegocjowaniu połączenia HTTPS. W przypadku klienta nie będącego apletem konstruktor URL zwróci błąd
nieznanego protokołu HTTPS. Można to ominąć wykorzystując klasę
com.oreilly.servlet.HttpsMessage dostępną pod adresem http://www.servlets.com. Zawiera ona
samodzielną 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 na
tekście komunikacji HTTP aplet-serwer. Po wypróbowaniu jego działania można zobaczyć, że data „Tekst
HTTP” jest wypełniona, podczas gdy inne są oznaczone jako „niedostępny”.
Serwlet
W celu zachowania wstecznej kompatybilności, można zmienić SerwletGodziny tak, aby zwracał
zserializowany obiekt jedynie, gdy wykonane zostanie odpowiednie żądanie przez przekazanie parametru
format 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 obiekcie
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
Aplet
Kod apletu odczytującego zserializowany obiekt Data jest bardzo podobny do kodu odczytującego zwykły
tekst. Metoda pobierzDataHttpObiekt() jest przedstawiona na przykładzie 10.6.
Przykład 10.6.
ApletGodziny wykorzystujący HTTP do odczytania obiektu
private String pobierzDataHttpObiekt() {
try {
// Tworzenie URL-a odnoszącego się do serwletu
URL url = new URL(getCodeBase(), "/servlet/SerwletGodziny");
// Wyłączenie buforowania
con.setUseCaches(false);
// Wysłanie nagłówków
sendHeaders(con);
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 czasie
oraz, że pobiera jedynie obiekty Serializable (to znaczy, żadnych prymitywnych typów).
9
Zazwyczaj kiedy aplet wywoła nieobsługiwany wyjątek, przeglądarka zapisuje ścieżkę do wyjątku w konsoli Javy. Ten kod
zawiera aplet naprawdę wyrzucający wyjątek na serwer, gdzie może być przechowywany w dzienniku zdarzeń. W dzienniku
tym zapisywane są ścieżki, mogące być później przeglądane przez twórcę apletu
Serwlet
Rola 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 do
rozpoczęcia nasłuchu począwszy od metody init(), a skończywszy na metodzie destroy(). Pomiędzy tymi
dwoma 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 ten
prowadzi nasłuch przychodzących żądań HTTP i odpowiednio je rozsyła, wywołując odpowiednio metody
serwletu 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 w
zwią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 przejmie
na 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 metodzie
destroy(). 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 jest
podklasą DaemonHttpServlet.
Przykład 10.8 przedstawia sposób, w jaki SerwletGodziny wykorzystuje klasę DaemonHttpServlet i
implementuje handleClient() w celu stania się dostępnym przez połączenie przez port nie-HTTP.
Przykład 10.8.
Serwer HTTP
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.oreilly.servlet.DaemonHttpServlet;
10
Nazwa daemon (demon) został wprowadzona w odniesieniu do demonów Uniksa, programów działających w tle i
obsługujących konkretne zdarzenia. W jaki sposób otrzymały one nazwę demonów? Według słownika „New Hacker's
Dictionary”, początkowo pochodziło „ze znaczenia mitologicznego, później zracjonalizowanego do akronimu 'Disk And
Execution Monitor' (Monitor Dysku i Wykonania)”.
public void destroy() {
// W tym miejscu, inaczej niż poprzednio, po przejęciu destroy() należy
wywołać
// super.destroy()
super.destroy();
}
SerwletGodziny serwlet;
Socket klient;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
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ące
połą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 za
nasłuchiwanie połączeń przychodzących. Metoda destroy() zatrzymuje wątek. Sprawia to że imperatywem
staje się, żeby każdy serwlet będący podklasą DaemonHttpServlet wywoływał super.init() i
super.destroy(), jeżeli serwlet ten wykorzystuje własne metody init() i destroy(). (Serwlet
napisany 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 konkretnym
porcie 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 implementacji
getSocketPort, jeżeli jest to konieczne.
Po utworzeniu ServerSocket, wątek Daemon czeka na nadchodzące żądania przy pomocy wywołania
serverSocket.accept(). Metoda jest blokująca — zatrzymuje wykonywanie tego wątku do czasu
połączenia się przez klienta. Kiedy się to dzieje, metoda accept() zwraca obiekt Socket, który wątek
Daemon natychmiast przekazuje do metody serwletu handleClient(). Ta metoda zazwyczaj tworzy wątek
obsługujący i natychmiast powraca, powodując przejście wątku Daemon w stan gotowości do przyjęcia
następnego połączenia.
Oczyszczenie portu jest tak samo ważne, jak jego konfiguracja. Należy upewnić się, że port połączenia działa tak
długo jak serwlet, ale nie dłużej. W tym momencie metoda destroy() DaemonHttpServlet wywołuje
metodę stop() wątku Daemon. Wywołanie to nie zatrzymuje jednak natychmiast wątku Daemon. Powoduje
jedynie wystąpienie wyjątku ThreadDeath wątku Daemon w jego aktualnym miejscu wykonania. Wątek
Daemon 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 absolutnie
konieczne staje się, aby każdy serwlet-demon wybierał swój własny port połączenia — poprzez ustawienie
swojego parametru inicjacji socketPort, ustawiając zmienną DEFAULT_PORT przed wywołaniem
super.init(config) lub bezpośrednim przejęciem getSocketPort(). Po drugie, serwlet-demon musi
być 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ęp
bezpośredni.
Aplet
Kod apletu łączącego się z serwletem przy pomocy komunikacji nie-HTTP, przede wszystkim metody
pobierzDataPortTekst() 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 dodatek
Komunikacja RMI
We wcześniejszej części tego rozdziału powiedziano, że jednym z powodów nie wykorzystywania komunikacji
RMI jest jej skomplikowanie. Chociaż jest to prawda, jest również prawdą, że przy pomocy innej superklasy
serwletu, kod wymagany, aby serwlet był dostępny przez komunikację RMI, może być aż śmiesznie prosty. Po
pierwsze, dokładnie opisany zostanie proces nadawania serwletowi właściwości obiektu zdalnego. Następnie, po
udowodnieniu prostoty tego działania wyjaśniona zostanie cała praca mająca miejsce w tle.
Serwlet
Wszystkie 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 rozszerza
interfejs Remote w celu wskazania, że jest to interfejs obiektu zdalnego. W przypadku przykładowego serwletu
SerwletGodziny, można stworzyć interfejs SerwerGodziny przedstawiony w przykładzie 10.11.
Przykład 10.11. Interfejs SerwerGodziny
import java.util.Date;
import java.rmi.Remote;
import java.rmi.RemoteException;
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 serwletu
należy skompilować klasę serwletu przy pomocy kompilatora RMI rmic. Kompilator RMI pobiera plik klasy
obiektu zdalnego i tworzy szkieletową i końcową wersję klasy. Klasy te pracują w tle w celu umożliwienia
komunikacji RMI. Nie należy przejmować się szczegółami, trzeba jednak wiedzieć, że końcówka pomaga
klientowi 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 SerwletGodziny
Proszę zauważyć, że rmic należy dostarczyć nazwę klasy Javy — nie pliku — do skompilowania. W związku z
tym, jeżeli kompilowany serwlet jest częścią pakietu powinien zostać podany rmic jako
nazwa.pakietu.NazwaSerwletu. Program rmic może pobierać ścieżkę klasy do poszukiwania przy
pomocy parametru –classpath, a także katalog docelowy dla plików szkieletu i końcówki przy pomocy
parametru –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
przedstawione zostanie ich zastosowanie. Po pierwsze, należy pamiętać, że nie jest konieczne ponowne
uruchamianie 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 interfejsu
do niego równoważnego).
Teraz następuje ostatni krok w tworzeniu zdalnego serwletu — kopiowanie kilku plików klas do katalogu
macierzystego 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 sama
końcówka wykorzystuje interfejs zdalnej klasy. Proszę pamiętać, że serwlet również wykorzystuje te klasy, więc
należy skopiować je do katalogu macierzystego dokumentów serwera oraz pozostawić je w ścieżce klas
serwera11. 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 czasie
działającego zdalnego serwletu. Poniżej przedstawiona jest klasa RemoteHttpServlet i opisane działania
zachodzące w tle.
Superklasa
Obiekt 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 serwerowi
rejestrującemu swoją nazwę i numer portu tak, aby klient mógł go zlokalizować (a zwłaszcza poznać numer jego
portu) 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 systemie
Uniksowym 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 ona
katalog_macierzysty/webapps/ROOT/classes. (Proszę zauważyć, że nie jest to WEB_INF/classes). W tym miejscu należy
pozostawić klasy końcówki i szkieletu. Następnie serwer może je odnaleźć w nowej ścieżce klas, a baza kodu apletu może
zostać ustawiona na /classes, aby aplet również mógł je odnaleźć.
Przykład 10.13.
Superklasa RemoteHttpServlet
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.*;
Wskazówka!!!!
Możliwe jest również całkowite ominięcie uruchomienia rejestru, przy pomocy serwletów. Klient może
połączyć się ze specjalnym serwletem przy pomocy HTTP i zażądać danego obiektu zdalnego (przy pomocy
parametru), 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 na
zaimplementowanie polityki bezpieczeństwa ograniczającej prawa przeglądania obiektów, pozwala
serwletowi na nadanie każdemu klientowi odwołania do różnych obiektów zdalnych oraz (ponieważ serwlet
może tworzyć zdalny obiekt w ramach obsługi żądań) powoduje, że obiekt zdalny jest dostępny nawet przed
jego 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ży
nakazać serwerowi ładowanie go przy uruchamianiu lub upewnić się, że jest on zawsze dostępny przez HTTP
zanim będzie dostępny bezpośrednio.
Aplet
Teraz należy odwrócić uwagę od serwera i skupić się na kliencie. Kod wykorzystywany przez ApletGodziny
do wywołania metody GetDate() nowego SerwletGodziny() jest przedstawiony w przykładzie 10.14.
Przykład 10.14.
ApletGodziny pobierający czas przy pomocy RMI
import java.rmi.*; // Nowy dodatek
import java.rmi.registry.*; // Nowy dodatek
import com.oreilly.servlet.RemoteDaemonHttpServlet;
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.*;
Serwer pogawędek
Przykład serwera godziny z poprzedniego podrozdziału przedstawiał wady i zalety stosowania wszystkich trzech
technik komunikacji aplet-serwer. Nie korzystał jednak z zalet utrzymywania połączenia w przypadku połączenia
przez port. Nie przedstawiał również prostoty komunikacji RMI i elegancji wywołań wstecznych RMI (w których
serwlet może wywoływać metody apletu). Nie przedstawiał poważnego powodu, dla którego jeden serwlet
powinien 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
obsługuje RMI, serwlet może być traktowany jako obiekt zdalny, a (gdzie to możliwe) może traktować aplet
ró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 nie
będącego HTTP. I, oczywiście, jeżeli inne techniki zawiodą, serwer pogawędek wykorzystać HTTP. Nie jest to
polecane, ponieważ HTTP jako protokół bezstanowy wymaga od klienta aktywności w celu dokonania
uaktualnień. 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. Podzielenie
go na trzy klasy, jedną dla każdego protokołu, wymagałoby nadmiernej komunikacji między nimi i trzykrotnego
powtarzania podstawowego kodu serwera pogawędek. Implementacja serwera pogawędek jako serwletu
dostarcza 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 klasy
RemoteDaemonHttpServlet, może również łatwo uzyskać możliwość obsługi portów nie-HTTP i
komunikacji RMI.
Proszę zauważyć, że chociaż kod zostanie przedstawiony w całości, nie zostanie wyjaśniona każda pojedyncza
linia. 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 zrozumienie
wszystkich szczegółów będą pozostawione Czytelnikowi.
Projekt
Rysunek 10.3 przedstawia aplet pogawędek w działaniu. Proszę zauważyć, że wykorzystuje on duży element
TextArea w celu wyświetlenia toczącej się konwersacji. Mały element TextArea poniżej służy
użytkownikowi do wpisywania i wysyłania jednolinijkowych wiadomości. Kiedy dowolny użytkownik wpisze
wiadomość 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 jest
sposó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: nie
wie on, kiedy pojawia się nowa wiadomość do odebrania. Jest to problem związany z paradygmatem
jednokierunkowej 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, że
nadszedł czas na zwrócenie jakiejś informacji. W niniejszym przykładzie zaimplementowana zostanie ta
symulowana komunikacja dwukierunkowa.
Klienci uzyskujący dostęp przez port, w celu zachowania spójności, wysyłają swoje wiadomości w identyczny
sposób jak klienci HTTP, przy pomocy metody HTTP POST. Mogą oni wysyłać swoje wiadomości przy pomocy
połączeń przez zwykły port, lecz daje to jedynie minimalny zysk w wydajności, który, przynajmniej w tym
przypadku, nie przeważa zwiększonej złożoności. Klienci korzystający ze zwykłych portów wykorzystują je
jednak 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ść jest
odbierana przez serwlet, jest wysyłana bezpośrednio z serwletu do klientów korzystających z portów przy
pomocy 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 o
wywołanie swojej własnej metody ustawNastepnaWiadomosc(String) za każdym razem, kiedy nadana
zostaje 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ór
techniki komunikacji aplet-serwlet (HTTP, port lub RMI), któryą chce on wykorzystać i, w oparciu o jego
wybó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, lecz
działanie takie w tym miejscu jedynie niepotrzebnie skomplikowałoby przykład. Serwlet-dyspozytor podaje
również apletowi nazwę jego użytkownika, zostanie to jednak opisane później.
Serwlet
Pełne wydruki interfejsu SerwerPogaw i klasy SerwletPogaw, która go wykorzystuje są przedstawione w
przykładach 10.15 i 10.16.
Przykład 10.15.
Interfejs SerwerPogaw, wykorzystywany przez SerwletPogaw
import java.rmi.Remote;
import java.rmi.RemoteException;
import com.oreilly.servlet.RemoteDaemonHttpServlet;
// 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();
// Zwrócenie następnej wiadomości (blokowanie)
wyj.println(pobierzNastepnaWiadomosc());
}
Aplet HTTP
Kod pierwszego apletu, apletu pogawędek HTTP, jest przedstawiony w przykładzie 10.17.
Przykład 10.17.
Klient pogawędek wykorzystujący komunikację HTTP
import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import com.oreilly.servlet.HttpMessage;
TextArea tekst;
Label etykieta;
TextField wpis;
Thread watek;
String uzyt;
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 {
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 i
oczekiwanie
// 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";
}
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) { }
}
}
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ę. Po
pierwszym wywołaniu tej metody ustanawia ona połączenie z portem po czym wykorzystuje je do pobrania
DataInputStream, przy pomocy którego może odczytywać z portu jedną linię za każdym razem. Przy
każ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 ona
połączenie.
Aplet RMI
Kod interfejsu KlientPogaw jest przedstawiony w przykładzie 10.19. Aplet pogawędek oparty na RMI, który
wykorzystuje ten interfejs jest przedstawiony w przykładzie 10.20.
Przykład 10.19.
Interfejs KlientPogaw, wykorzystywany przez ApletPogawRMI
import java.rmi.Remote;
import java.rmi.RemoteException;
TextArea tekst;
Label etykieta;
TextField wpis;
Thread watek;
String uzyt;
SerwerPogaw serwerPogaw;
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";
}
Dyspozytor
Ostatni przykład kodu w niniejszym rozdziale, serwlet DyspozytorPogaw przedstawiony jest w przykładzie
10.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 on
użyć, jak przedstawiono na rysunku 10.4. Po drugie, kiedy wywoływany jest z parametrem żądania, wyświetla
stronę zawierającą właściwy aplet, jak przedstawiono wcześniej na rysunku 10.3. Proszę pamiętać, że URL
wykorzystywany 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-dyspozytor
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
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);
}
}
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>");
}
// 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>");
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;
}
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ąco
prosto 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żda
informacja, jaką serwlet chce przesłać nowemu apletowi, może być wysłana w ten właśnie sposób. W
powyższym przykładzie, serwlet wysyła nazwę zwróconą przez odp.getRemoteUser(). W innym
przykładzie serwlet mógłby przekazać apletowi jego typ przeglądarki poprzez wysłanie mu łańcucha zwróconego
przez req.getHeader("User-Agent"). Lub, aby okazać się bardziej pomocnym, serwlet mógłby
wykorzystać 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.
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ór
serwletów zarządzających sklepem online może współdzielić zbiór inwentarza sklepu lub połączenie z
baza 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 serwlet
może otrzymywać żądanie, ale pozwalać innemu serwletowi na przejęcie części lub całej
odpowiedzialnoś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; metoda
getServlet() została wyłączona i zdefiniowana tak, by zwracała null w przypadku Servlet API 2.1 i
późniejszych. Powód — serwlet może zostać zniszczony przez serwer WWW w dowolnym momencie, tak więc
nic poza serwerem nie powinno posiadać bezpośredniego odwołania do serwletu. Wszystkie działania, które
mogły być wykonywane przy pomocy getServlet() mogą być wykonane w sposób lepszy i bezpieczniejszy
przez alternatywy opisane w niniejszym rozdziale.
Dzielenie informacji
Serwlety często współpracują poprzez dzielenie pewnych informacji. Informacje te mogą być informacjami o
stanie, współdzielonymi zasobami, źródłem zasobów, lub czymkolwiek innym. W Servlet API 2.0 i
wcześniejszych nie istniały wbudowane mechanizmy, przy pomocy których serwlety mogły dzielić informacje, a
w pierwszym wydaniu niniejszej książki (napisanej według Servlet API 2.0) musiały zostać przedstawione pewne
kreatywne sposoby pracy, między innymi umieszczanie informacji na liście właściwości System! Na szczęście
sposoby te nie są już potrzebne, ponieważ począwszy od Servlet API 2.1 rozszerzona została klasa
ServletContext, która działa na zasadzie składnicy współdzielonych informacji.
DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM);
String dzisiaj = df.format(dzien);
Dzielenie kontroli
W celu bardziej dynamicznej współpracy, serwlety mogą dzielić kontrole żądania. Po pierwsze, serwlet może
przekazać całe żądanie wykonując pewne wstępne przetwarzanie, po czym przekazać żądanie innemu
elementowi. Po drugie, serwlet może dołączyć do swojej odpowiedzi pewną zawartość generowaną przez inny
składnik, właściwie tworząc programowe dołączenie po stronie serwera. Patrząc pojęciowo, jeżeli myśli się o
stronie wynikowej takiej, jak ekran, przekazanie daje innemu serwletowi pełną kontrole nad ekranem, podczas
gdy 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 pomocy
delegowania, serwlet może skonstruować swoją odpowiedź jako zbiór zawartości tworzonych przez różne
składniki serwera WWW. Funkcjonalność ta jest szczególnie ważna dla JavaServer Pages, w której to technice
często zdarza się, że jeden serwlet przetwarza żądanie, po czym przekazuje je stronie JSP w celu dokończenia
zachowania spójności (proszę zobaczyć rozdział 18, JavaServer Pages).
Dyspozycja przekazaniem
Metoda forward() przekazuje żądanie od serwletu do innego zasobu na serwerze. Metoda pozwala jednemu
serwletowi na wykonanie wstępnego przetwarzania żądania, a innemu elementowi na wygenerowanie
odpowiedzi. Inaczej niż sendRedirect(), forward() działa jedynie wewnątrz serwera, a klient nie jest w
stanie rozpoznać, że nastąpiło przekazanie. Informacja może zostać przekazana delegatowi przy pomocy
dołączonego łańcucha zapytania lub przy pomocy atrybutów żądania ustawionych przy pomocy metody
setAttribute(). Przykład 11.3 przedstawia serwlet wykonujący poszukiwanie, po czym przekierowujący
jego wyniki innej stronie, która je wyświetla.
Przykład 11.3.
Wewnętrzny mechanizm wyszukiwarki
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class LogikaSzukaj extends HttpServlet {
if (wyniki == null) {
wyj.println("Brak wyników.");
wyj.println("Czy przypadkiem serwlet nie został wywołany bezpośrednio?");
}
else {
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. Nie
jest ona taka, jak oryginalna informacja o żądaniu; zamiast tego pasuje do ścieżki wykorzystywanej do dotarcia
do dyspozytora:
URI żądania: /servlet/WyswietlSzukaj
Ścieżka kontekstu:
Ścieżka serwletu:/ servlet/WyswietlSzukaj
Informacje o ścieżce: null
Łańcuch zapytania: null
Powód tego uaktualnienia jest taki, że jeżeli wykonane zostaje przekazanie serwletowi, serwlet ten powinien
otrzymać tę samą informację o ścieżce, jaką otrzymałby, gdyby został wywołany bezpośrednio; od tej pory
posiada on pełną kontrolę nad żądaniem. Jest to również powód, dla którego ważne jest, żeby bufor odpowiedzi
został wyczyszczony przed jego wywołaniem i żaby odpowiedź nie została zatwierdzona.
Dyspozycja dołączania
Metoda include() RequestDispatcher dołącza zawartość zasobu do aktualnej odpowiedzi. Pozwala na
coś, 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 do
aktualnej odpowiedzi.
Przykład 11.6.
Dołączanie egzemplarza z katalogu
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
wyj.println("<HTML><HEAD><TITLE>Witamy</TITLE></HEAD>");
wyj.println("<BODY>");
RequestDispatcher dyspozytor =
zad.getRequestDispatcher("/servlet/Egzemplarz?egzemplarz=0596000405");
dyspozytor.include(zad, odp);
wyj.println("</BODY></HTML>");
}
}
Podobnie jak w przypadku forward(), informacje mogą być przesyłane do wywołanego zasobu przy pomocy
dołączonego łańcucha zapytania lub przy pomocy atrybutów żądania ustawionych za pomocą metody
setAttribute(). Wykorzystanie atrybutów zamiast parametrów umożliwia przekazywanie obiektów zamiast
prostych łańcuchów, jak przedstawiono w przykładzie 11.7.
Przykład 11.7.
Dołączanie kilku egzemplarzy z katalogu
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
wyj.println("<HTML><HEAD><TITLE>Witamy</TITLE></HEAD>");
wyj.println("<BODY>");
wyj.println("</BODY></HTML>");
}
}
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 nie
posiada ona możliwości zmiany kodu stanu ani nagłówków HTTP wysłanych w odpowiedzi. Każda próba
dokonania zmiany jest ignorowana.
Inaczej niż w przypadku forward(), elementy ścieżki i parametry żądania pozostają niezmienione w
porównaniu z elementami serwletu wywołującego. Jeżeli dołączony składnik wymaga dostępu do elementów
swojej własnej ścieżki, może on je odczytać przy pomocy następujących preasygnowanych atrybutów żądania:
javax.servlet.include.request_uri
javax.servlet.include.context_path
javax.servlet.include.servlet_path
javax.servlet.include.path_info
javax.servlet.include.query_string
Parametry żądania i odpowiedzi muszą być tymi samymi obiektami, które zostały przekazane metodzie usług
serwletu 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żeli
mechanizm 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ątek
IllegalStateException. Jeżeli jest to możliwe, należy się upewnić, co do stosowania PrintWriter
dla każdego wyświetlanego tekstu, a nie stanie się to problemem.
Rozdział 12. Serwlety
korporacyjne i J2EE
Niniejszy rozdział opisuje serwlety korporacyjne. Termin korporacyjny (enterprise) jest w obecnych czasach
mocno związany z Javą, co jednak oznacza? Według starszego wydania słownika „American Heritage
Dictionary” 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 i
korporacyjnych 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 witryn
WWW o dużej skali — witryny o wysokim obciążeniu i niezawodności wiążą się z dodatkowymi wymaganiami
na temat skalowalności, rozkładu ładunku, obsługi błędów i integracji z innymi technologiami Java 2, Enterprise
Edition (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 pomocy
serwletów. Tworzenie serwletów dla takich witryn różni się od tworzenia ich dla witryn tradycyjnych, i w tym
rozdziale omówione zostaną specjalne wymagania i możliwości serwletów korporacyjnych.
Dystrybucja ładunku
W przypadku witryn o wysokiej wydajności i/lub niezawodności, często korzystna jest dystrybucja zawartości i
przetwarzania witryny pomiędzy większą ilością serwerów wspierających. Dystrybucja ta pozwala kilku
serwerom 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ć w
znaczący sposób trudniejsze niż wykonanie tych samych czynności w przypadku witryny samodzielne, oraz
bardziej kosztowne w kwestii sprzętu dzielącego ładunek i/lub wymagań programów. Dystrybucja nie przynosi
również znaczącej poprawy wydajności, jeżeli serwer nie znajduje się pod maksymalnym obciążeniem. Po
wystą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 o
niewystarczającej wydajności.
Istnieje jednak wiele witryn, które wymagają skalowalności poza osiągami dowolnego pojedynczego komputera i
poziomu niezawodności niemożliwego do uzyskania na pojedynczym komputerze. W witrynach tych należy
zastosować dystrybucję.
Jak być dystrybuowalnym
Wymagania programistyczne dla serwletu dystrybuowalnego są dużo bardziej restrykcyjne niż wymagania
odnoszące się do innych serwletów. Serwlet dystrybuowalny musi zostać napisany według konkretnych zasad
tak, aby różne egzemplarze serwletu mogły być wykonywane na wielu serwerach wspierających. Każdy
programista, który przyjmuje, że istnieje tylko jedna kopia serwletu, jeden kontekst serwletu, jedna wirtualna
maszyna 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ów
biznesowych, który jest sercem J2EE. EJB jest zaprojektowany od początku jako zbiór obiektów
dystrybuowalnych. ELB implementuje logikę biznesową i pozwala kontenerowi (zwykle serwerowi), na którym
pracuje, 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ć przenoszony
pomię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 jako
frontowych elementów po stronie serwera, wykorzystywanych do komunikacji z klientem i wywoływania
dystrybuowanych EJB, podczas, gdy same serwlety nie są przeznaczone do dystrybucji. Jednak w przypadku
witryn 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ę implementacji
dystrybuowalnych 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/lub
komputerach. W związku z tym zmienne egzemplarza i zmienne statyczne nie powinny być
wykorzystywane do przechowywania stanu. Każdy stan powinien być przechowywany w obiekcie
zewnę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 kopie
ServletContext. W związku z tym kontekst nie powinien być wykorzystywany do
przechowywania stanu aplikacji. Każdy stan powinien być przechowywany w obiekcie
zewnę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że
wykorzystywać 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 jej
dowiązania!
• Należy pamiętać, że pliki nie muszą występować na wszystkich komputerach wspierających. W
związku z tym powinno się unikać wykorzystywania pakietu java.io w celu uzyskania dostępu
do pliku i zamiast tego wykorzystać mechanizm getServletContext().getResource()
— lub upewnić się, że wszystkie wykorzystywane pliki zostały umieszczone na wszystkich
komputerach 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 to
oznaczenie pozwala serwerowi na uruchomienie jej na kilku serwerach wspierających. Znak dystrybuowalności
jest umieszczany w deskryptorze web.xml jako pusty znacznik <distribuable/> umieszczony pomiędzy
opisem 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 w
książce „Enterprise JavaBeans” autorstwa Richarda Monson-Haefela (O'Reilly).
</web-app>
Aplikacje z definicji nie są dystrybuowalne w celu zapewnienia niedoświadczonym programistom serwletów
możliwości tworzenia ich bez potrzeby martwienia się o dodatkowe zasady związane z dystrybucją. Zaznaczenie
aplikacji jako dystrybuowalnej niekoniecznie oznacza, że aplikacja zostanie podzielona pomiędzy kilkoma
różnymi komputerami. Wskazuje to jedynie, że aplikacja ma możliwość bycia dystrybuowaną. Należy myśleć o
tym jako o wystawionym przez programistę świadectwie aplikacji.
Serwery nie wymuszają większości podanych powyżej zasad dystrybucji aplikacji. Na przykład, serwlet może
wykorzystywać zmienne egzemplarza i statyczne, a także przechowywać obiekty w swoim ServletContext
oraz uzyskiwać bezpośredni dostęp do plików przy pomocy pakietu java.io. Od programisty zależy
zabezpieczenie tych własności przed nadużyciem. Jedynym działaniem, jakie może podjąć serwer jest wywołanie
wyją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).
Szczegóły implementacji klastrowania różnią się między serwerami i są miejscem rywalizacji poszczególnych
producentów serwerów. Proszę spojrzeć do dokumentacji własnego serwera w celu uzyskania szczegółów na
temat obsługiwanego poziomu klastrowania. Inną przydatną własnością jest trwałość sesji, czyli zapisywanie w
tle informacji sesji na dysk lub do bazy danych, co pozwala informacjom na przetrwanie załamań i ponownych
uruchomień serwera.
Integracja z J2EE
W poprzednich częściach książki serwlety były wykorzystywane jako samodzielna technologia utworzona na
standardowej podstawie Javy. Serwlety posiadają także inne życie, w którym działają jako integralna część
czegoś, co nazywane jest Java 2, Enterprise Edition, w skrócie J2EE14. J2EE 1.2 zbiera razem kilka interfejsów
pracują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 Directory
Interface (JNDI). J2EE integruje te elementy w coś więcej niż prostą sumę części poprzez zdefiniowanie sposobu
współpracy tych technologii, ich wykorzystywania siebie nawzajem oraz dostarczania zaświadczeń, że konkretne
serwery aplikacji są zgodne z J2EE, co oznacza, że obsługują wszystkie potrzebne usługi, a także mechanizmy
ich integracji.
Pozycje środowiskowe
Parametry inicjacji kontekstu są użyteczne dla serwletów, ale w modelu J2EE istnieje z nimi problem— każda
zmiana 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”
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 go
umieszczać. <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że
być 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ę, czego
spodziewa się serwer. Powyższe znaczniki mogą wydawać się znajome osobom, którym nieobcy jest deskryptor
EJB, 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 JNDI
mogą o nim myśleć jako o bazie URL-a lub katalogu w systemie plików. Kontekst java:comp/env ma
własności tylko do odczytu i jest unikatowy dla aplikacji WWW, tak więc jeżeli dwie różne aplikacje WWW
zdefiniują taką samą pozycję środowiskową, pozycje te nie kolidują. Skrót nazwy kontekstu oznacza component
environment (środowisko elementów).
Przykład 12.1 przedstawia serwlet wyświetlający wszystkie jego pozycje środowiskowe, wykorzystując interfejs
JNDI do przeglądania kontekstu java:comp/env.
Przykład 12.1.
Przeglądanie kontekstu java:comp/env
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.naming.*;
try {
Context poczKontekst = new InitialContext();
NamingEnumeration enum = poczKontekst.listBindings("java:comp/env");
15
Specyfikacja Servlet API 2.2 głosi, że „element ejb-ref-type zawiera przewidywany typ klasy Javy dla EJB
stanowiącego cel odwołania”. Jest to potwierdzony błąd. Właściwy cel jest taki, jak wymieniony powyżej.
• url/ dla fabryki java.net.URL
Element <res-type> określa PNK fabryki zasobów (a nie utworzonego zasobu). Typy fabryk na powyższej
liście to typy standardowe. Serwer posiada możliwość obsługi dodatkowych typów; nie mogą zostać zastosowane
fabryki użytkownika. Specyfikacja nadchodzącej wersji 1.3 J2EE zawiera mechanizm „łącznika” służący do
rozszerzania 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ść wynosi
CONTAINER, wtedy kontener serwletów (serwer J2EE) obsługuje uwierzytelnianie przed dowiązaniem fabryki
do JDNI, przy pomocy danych dostarczonych przez wdrożeniowca. Jeżeli wynosi SERVLET, uwierzytelnienie
musi 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 w
EJB dwie możliwe wartości <res-auth> to Container i Application (proszę zauważyć niewyjaśnioną
różnicę w wielkości liter).
Pomimo swojej nazwy, sieć WWW musi przebyć jeszcze długą drogę, zanim będzie mogła być uważana za
cał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 dla
języków innych niż angielski. Na przykład, strona główna firmy Netscape jest dostępna w języku angielskim pod
adresem http://home.netscape.com/index.html, natomiast w wersji francuskojęzycznej pod adresem
http://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 od
preferencji 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 prostu
kilkoma specjalnie nazwanymi wersjami statycznego dokumentu.
Podczas gdy te techniki działają dobrze w przypadku statycznych dokumentów, nie rozwiązują one jednak
problemu internacjonalizacji i lokalizacji zawartości dynamicznej. Jest to temat niniejszego rozdziału. Opisane
zostaną tu sposoby wykorzystania możliwości internacjonalizacji dodanych w JDK1.1. przez serwlety w celu
prawdziwego rozszerzenia sieci WWW na cały świat.
Po pierwsze należy omówić terminologię. Internacjonalizacja (Internationalization — słowo często miłosiernie
skracane do I18N, ponieważ rozpoczyna się od I, kończy na N, a pomiędzy nimi jest 18 liter) jest zadaniem
uczynienia 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 na
przykładach dat, godzin, liczb i innych obiektów, które domyślnie obsługuje Java.
Języki zachodnioeuropejskie
Na początku zostanie opisany sposób wyświetlania przez serwlet strony utworzonej w języku
zachodnioeuropejskim, 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, jak
przedstawiono 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ła
wprowadzona po raz pierwszy w przeglądarkach Netscape Navigator 4 i Microsoft Internet Explorer 4.
Rysunek 13.1.
En Español — ¡Hola
Mundo!
Proszę zauważyć zastosowanie specjalnych znaków ñ i ¡. Znaki takie jak te, chociaż praktycznie nieobecne w
języku angielskim, są przypisane do języków zachodnioeuropejskich. Serwlety posiadają dwa sposoby
generowania takich znaków — poprzez encje znakowe HTML i kody ucieczkowe Unicode.
wyj.println("<HTML><HEAD><TITLE>En Español</TITLE></HEAD>");
wyj.println("<BODY>");
wyj.println("<H3>En Español:</H3>");
wyj.println("¡Hola Mundo!");
wyj.println("</BODY<>/HTML>");
}
}
Można zauważyć, że oprócz wykorzystywania encji znakowych powyższy serwlet nadaje swojemu nagłówkowi
Content-Language wartość es. Nagłówek Content-Language jest wykorzystywany do określania
języka następujących po nim encji. W tym przypadku serwlet wykorzystuje nagłówek do wskazania klientowi, że
strona jest napisana w języku hiszpańskim (Español). Większość klientów ignoruje tę informację, lecz do
dobrego tonu należy jej wysyłanie. Języki są zawsze przedstawiane przy pomocy dwuliterowych skrótów
zapisanych małymi literami. Kompletna lista jest dostępna w standardzie ISO-639 pod adresem
http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt.
Encje znakowe mogą być również określane przy pomocy liczb. Na przykład, ñ przedstawia ñ, a ¡
przedstawia ¡. Liczba odnosi się do dziesiętnej wartości znaku w standardzie ISO-8859-1 (Latin-1), który
zostanie opisany w dalszej części niniejszego rozdziału. Kompletna lista wartości numerycznych encji
znakowych również znajduje się w dodatku E. Przykład 13-2 przedstawia serwlet WitajHiszpania
zmieniony tak, aby wykorzystywał encje numeryczne.
Przykład 13.2.
Powitanie dla posługujących się językiem hiszpańskim, przy pomocy numerycznych encji znakowych
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
wyj.println("<HTML><HEAD><TITLE>En Español</TITLE></HEAD>");
wyj.println("<BODY>");
wyj.println("<H3>En Español:</H3>");
wyj.println("¡Hola Mundo!");
wyj.println("</BODY<>/HTML>");
}
}
Niestety, z wykorzystaniem encji znakowych wiąże się jeden poważny problem — działają one jedynie ze
stronami HTML. Jeżeli wynikiem serwletu nie jest kod HTML, strona będzie wyglądać podobnie do
przedstawionej na rysunku 13.2. Aby obsługiwać wyniki nie będące HTML, należy wykorzystać kody
ucieczkowe Unicode.
Rysunek 13.2.
Nie do końca hiszpański
wyj.println("En Espa\u00f1ol:");
wyj.println("\u00a1Hola Mundo!");
}
}
Wynik powyższego serwletu wyświetla się prawidłowo, kiedy wykorzystywany jako część strony HTML lub
kiedy wykorzystywany jest do wyświetlania zwykłego tekstu.
Powyższy przykład stanowi jedynie bardzo krótki podgląd możliwości dynamicznego formatowania zawartości
Javy. Osoby zainteresowane bardziej skomplikowanym formatowaniem powinny obejrzeć kilka przydatnych klas
znajdujących się w pakiecie java.text. Szczególnie należy zwracać uwagę na te będące rozszerzeniem
java.text.Format.
Kodowanie
Na początku sytuacja zostanie omówiona z punktu widzenia przeglądarki. Proszę wyobrazić sobie spełnianie
pracy przeglądarki. Wykonuje się żądanie HTTP dla pewnego URL-a i otrzymuje odpowiedź. Odpowiedź ta w
najprostszym 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żliwych
znaków, a następnie przyjęcie, że znak, który bajt reprezentuje może zostać określony przez wyszukanie wartości
bajtu 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ęzykach
zachodnioeuropejskich. 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ęzyku
zachodnioeuropejskim? 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ży
zastosować inne kodowanie17. Istnieje nieskończona ilość potencjalnych kodowań. Na szczęście, najczęściej
stosuje 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łne
wyjaśnienie można znaleźć w dokumencie RFC 2278 pod adresem http://www.ietf.org/rfc/rfc2278.txt.
Niektóre kodowania wykorzystują znaki jednobajtowe w sposób podobny do ISO-8859-1, chociaż z innym
odwzorowaniem bajt-do-znaku. Na przykład, ISO-8859-5 definiuje odwzorowanie bajt-do-znaku dla znaków
cyrylicy (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ęcej
niż 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 tych
języków to między innymi Big5 (chiński), Shift_JIS (japoński) i EUC-KR (koreański). Tabela zawierająca języki
i 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ób
interpretacji otrzymanych bajtów. Pozostaje jedno pytanie — jak określić kodowanie? Można wykonać to na
dwa sposoby. Po pierwsze, można zażądać ujawnienia kodowania przez użytkownika. W przeglądarce Netscape
Navigator 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 Microsoft
Internet 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 przez
serwer (lub serwlet) w nagłówku Content-Type. Na przykład, następująca wartość Content-Type:
text/html; charset=ISO-8859-5
wskazuje, że wykorzystywane kodowanie to ISO-8859-5. Niestety, niektóre starsze przeglądarki mogą błędnie
zinterpretować dodanie kodowania do nagłówka Content-Type.
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.
Rysunek 13.4.
Royjskie powitanie
Powyższy serwlet rozpoczyna działanie od ustawienia typu zawartości na text/plain oraz kodowania na
ISO-8859-5. Następnie wywołuje odp.getWriter() tak, jak zazwyczaj — poza tym, że w tym przypadku
wynik tej metody otrzymuje specjalny PrintWriter. Ten PrintWriter koduje cały wynik działania
serwletu przy pomocy kodowania ISO-8859-5., ponieważ to kodowanie zostało wymienione w nagłówku
Content-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ątek
UnsupportingEncodingException, jeżeli kodowanie nie zostanie rozpoznane przez Javę19, lub
IllegalStateException, 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żenie
ró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 ich
ekwiwalent. Poza tym, aby znaki rosyjskie (lub innego języka) były wyświetlane poprawnie w przeglądarce, musi
ona 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ępna
pod adresem http://home.netscape.com/eng/intl/index.html. Większa ilość informacji na temat możliwości
internacjonalizacji przeglądarki Microsoft Internet Explorer jest dostępna pod adresem
http://www.microsoft.com/ie/intlhome.htm.
19
W niektórych wczesnych wersjach Javy może ona w niektórych sytuacjach błędnie wywoływać wyjątek
IllegalArgumentException, jeżeli kodowanie nie zostanie rozpoznane.
Przykład 13.6.
Wysyłanie zlokalizowanego wyniku odczytanego z pliku
import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
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-5
zakodowany w standardzie ISO-8859-5, i wewnętrznie konwertuje go do Unicode. Następnie wyświetla ten sam
tekst poprzez konwersję go z Unicode do ISO-8859-5.
UCS-2 i UTF-8
Najlepszym 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 znaki
Unicode w czymś, co może być nazwane ich naturalnym formatem, 2 bajty na znak. Wszystkie znaki, włączając
w to znaki US-ASCII, wymagają dwóch bajtów. UTF-8 (UCS Transformation Format, 8-bit form — Format
Transformacji UTF, forma 8-bitowa) to kodowanie o zmiennej długości. Przy pomocy UTF-8, znak Unicode jest
zmieniany 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 powodu
zastosowanie w sieci WWW UTF-8 mocno przewyższa UCS-2. Większa ilość informacji na temat UTF-8 jest
dostę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 raz
pierwszy wprowadził obsługę kodowania UTF-8 w przeglądarce Netscape Navigator 4, a Microsoft w Internet
Explorer 4.
Tworzenie UTF-8
Przykł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 Rosetty
import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.oreilly.servlet.ServletUtils;
try {
odp.setContentType("text/plain; charset=UTF-8");
PrintWriter wyj = odp.getWriter();
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 pobrania
PrintWriter, 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, podczas
gdy 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 dla
kodowania 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.
Preferencje kodowania
Opró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-8
Powyższa wartość wskazuje, że przeglądarka zna kodowania ISO-8859-1 i UTF-8. Jeżeli Accept-Charset
nie jest ustawiony, lub jego wartość zawiera gwiazdkę (*), można przyjąć, że klient przyjmuje wszystkie
kodowania. Proszę zauważyć, że aktualna przydatność tego nagłówka jest ograniczona — wysyłają go tylko
niektóre przeglądarki, a i one zazwyczaj wysyłają gwiazdkę.
Pakiety zasobów
Wykorzystują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ć kilkoma
zlokalizowanymi wersjami strony? Można w tym celu wykorzystać wbudowaną w Javę obsługę pakietów
zasobó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świetlanych
przez serwlet. Następnie, kiedy serwlet określi, że preferowanym językiem klienta jest francuski, może
załadować ten pakiet zasobów i wykorzystać zapamiętane zdania. Wszystkie pakiety zasobów to rozszerzenia
java.util.ResourceBundle. Serwlet może załadować pakiet zasobów przy pomocy statycznej metody
ResourceBundle.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 klas
serwera. Plik powinien zostać nazwany w specjalny sposób, według wzoru nazwapakietu_jezyk.properties lub
nazwapakietu_język_kraj.properties. Na przykład, można wykorzystać Wiadomosci_fr.properties dla pakietu
francuskiego lub Wiadomosci_zh_TW.properties dla pakietu chińsko-tajwańskiego. Plik powinien zawierać znaki
US-ASCII w następującej formie:
nazwa1=wartosc1
nazwa1=wartosc1
...
Każda linia może również zawierać puste miejsca i kody ucieczkowe Unicode. Informacje zawarte w tym pliku
mogą zostać automatycznie pobrane przy pomocy metody getBundle().
import com.oreilly.servlet.LocaleNegotiator;
import com.oreilly.servlet.ServletUtils;
LocaleNegotiator negocjator =
new LocaleNegotiator(nazwaPakietu, akceptJezyk, akceptKod);
Klasa LocaleNegotiator
Kod 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 negocjacja
lokalizacji mogą opuścić ten podrozdział.
LocaleNegotiator działa poprzez skanowanie preferencji językowych klienta w poszukiwaniu dowolnego
języka, dla którego występuje odpowiadający mu pakiet zasobów. Kiedy go znajduje, wykorzystuje
LocaleToCharsetMap w celu określenia kodowania. Jeżeli wystąpi dowolny problem, próbuje powrócić do
amerykańskiego angielskiego. Logika ignoruje preferencje kodowania klienta.
Najbardziej skomplikowanym aspektem kodu LocaleNegotiator jest konieczność obsługi nieszczęśliwego
zachowania 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óbuje
odnaleźć taki, który jest podobny. Dla celów tego przykładu problemem jest fakt, że getBundle() uważa za
podobny pakiet zasobów dla domyślnej lokalizacji. Tak więc podczas przeglądania języków klienta ciężko jest
określić, kiedy występuje dokładne dopasowanie pakietu zasobów, a kiedy nie. Sposobem na to jest, po pierwsze
pobranie awaryjnego pakietu zasobów, po czym wykorzystanie odwołania do niego w celu określenia, czy
znaleziono dokładne dopasowanie. Logika ta zawarta jest w metodzie getBundleNoFallback().
Przykład 13.9.
Klasa LocaleNegotiator
package com.oreilly.servlet;
import java.io.*;
import java.util.*;
import com.oreilly.servlet.LocaleToCharsetMap;
while (tokenizer.hasMoreTokens()) {
// Pobranie następnego przyjmowanego języka
// (Język może wyglądać tak: "en; wartoscq=0.91")
String lang = tokenizer.nextToken();
return loc;
}
try {
// Pobranie pakietu dla określonej lokalizacji
ResourceBundle bundle = ResourceBundle.getBundle(bundleName, loc);
import java.util.*;
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");
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");
Formularze HTML
Zarzą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ę. Formularz
HTML jest wysyłany jako część strony rosyjskiej. Prosi on użytkownika o jego nazwisko, które on wprowadza
jako łańcuch rosyjskich znaków. Jak to nazwisko jest wysyłane do serwletu? I, co ważniejsze, jak serwlet może
je odczytać?
Odpowiedź na pierwsze pytanie jest taka, że wszystkie dane formularzy HTML są wysyłane jako sekwencja
bajtów. Bajty te to zakodowana reprezentacja oryginalnych znaków. W przypadku języków
zachodnioeuropejskich, kodowanie jest domyślne, ISO-8859-1, z jednym bajtem na znak. W przypadku innych
języków mogą występować inne kodowania. Przeglądarki kodują dane formularzy przy pomocy tych samych
kodowań, które zostały wykorzystane w przypadku strony zawierającej formularz. Tak więc jeżeli wspomniana
wcześ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, i
użytkownik musi samodzielnie wybrać kodowanie ISO-8859-5 w celu przeglądania, duża część przeglądarek
wyśle dane przy pomocy ISO-8859-120. Generalnie, zakodowany łańcuch bajtów zawiera dużą ilość specjalnych
bajtów, które muszą zostać zakodowane w URL-u. Jeżeli przyjmie się, że rosyjski formularz wysyła nazwisko
uż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 bardziej
skomplikowana. Serwlet posiada dwie opcje wyboru. Po pierwsze, serwlet może pozostawić dane formularza w
ich surowej formie zakodowania, traktując je jak sekwencję bajtów — z każdym bajtem obrzydliwie
zakodowanym jako znak w łańcuchu parametrów. Taktyka ta jest użyteczna jedynie wtedy, gdy serwlet nie musi
manipulować danymi i może być pewny, że dane te zostaną wyświetlone jedynie temu samemu użytkownikowi
wykorzystującemu to samo kodowanie. Drugą metodą jest konwersja przez serwlet danych formularza z ich
począ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 danych
formularza. W przyszłości własność ta może zostać dodana (można na przykład wykorzystać w tym celu
nagłówek Content-Type w żądaniu POST), ale aktualnie to serwlet jest odpowiedzialny za śledzenie tej
informacji.
Ukryte kodowanie
Szeroko akceptowaną techniką śledzenia kodowania wysłanych danych formularza jest wykorzystanie ukrytego
pola formularza zawierającego kodowanie21. Jego wartość powinna zostać ustawiona na kodowanie strony, która
go 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, aby
pasowało ono do kodowania strony. Poniżej znajduje się angielski pakiet zasobów, który może zostać dołączony
do serwletu, przechowany jako FormKod_en.properties:
tytul=FormKod
naglowek=<h1>Charset Form</h1>
tekst=Enter text:
Poniżej znajduje się pakiet rosyjski, przechowywany jako FormKod_ru.properties:
tytul=FormKod
naglowek=<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 formularza
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.oreilly.servlet.LocaleNegotiator;
import com.oreilly.servlet.ServletUtils;
LocaleNegotiator negocjator =
20
Większa ilość informacji na temat internacjonalizacji HTML i formularzy HTML jest dostępna w dokumencie RFC 2070
pod 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.
new LocaleNegotiator(nazwaPakiet, akceptJezyk, akceptKod);
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 ten
odczytuje wysłany tekst i dokonuje jego konwersji na Unicode, po czym wyświetla znaki przy pomocy
kodowania UTF-8. Dodatkowo, wyświetla on również otrzymany łańcuch jak łańcuch kodów ucieczkowych
Unicode, 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ątkowego
na łańcuch Unicode.
Przykład 13.12.
Otrzymywanie kodowania w ukrytym polu formularza
import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AkcjaKod extends HttpServlet {
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();
}
W tym momencie można przejść do omawiania opartych na serwletach szkieletów służących do tworzenia
zawartoś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 jako
Tea) to produkt Walt Disney Internet Group (WDIG) — dawniej GO.com — rozwijany przez wiele lat wewnątrz
firmy 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 Open
Source 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 dla
programistów, ponieważ według niej można wykorzystywać Tea do tworzenia nowych produktów i witryn bez
udostępniania tych produktów jako Open Source. Ten model licencji wykorzystywany jest przez wszystkie
projekty 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ów
technicznych. 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, jeden
programista tworzy aplikację służącą do zarządzania statystykami zespołów, inny (pracujący niezależnie) tworzy
aplikację służącą do zarządzania statystykami zawodników, a producent techniczny umieszcza dane w sieci przy
pomocy szablonów Tea. Właściwie kilku producentów pracujących niezależnie może wykorzystywać te same
dane wspierające w celu utworzenie stron ukierunkowanych na różnych użytkowników — działanie, które firma
WDIG wykonała w swoich witrynach włączając w to Disney.com, Movies.com, ESPN.com i GO.com. Szablony
tworzone są w języku Tea w celu wymuszenia całkowitego oddzielenia zawartości od prezentacji.
Język Tea
Tea to język programowania zaprojektowany do formatowania tekstu. Posiada on silnie zaznaczone typy, musi
być kompilowany i został zaprojektowany do pracy w środowisku opartym na Javie. Środowiskiem tym jest
TeaServlet, narzędzie wykorzystujące Tea do tworzenia stron WWW i umożliwiające dostęp do mechanizmów
serwletó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ęcania
podstawowych konstruktów programistycznych. Do czasu pisania tej książki, Tea została utworzona na
podstawie 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 nad
klasą aplikacji tworzącą rysunek i łatwo jest stworzyć wspólnie dynamiczny obrazek. Inaczej niż pozostałe narzędzia, które
zostaną omówione, szablony Tea mogą wyświetlać również dane binarne, tak samo jako znakowe.
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 w
popularnych 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? Tea
Template 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 od
tego, czy jest to C, Perl, czy Java. Podejście to jest odpowiednie dla małych lub początkujących
projektó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 szablonowego
mechanizmu 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ługi
specjalnych konstrukcji służących do formatowania tabeli, formularzy i prostej logiki warunkowej.
Podczas wprowadzania konstrukcji programistycznych do szablonu, podstawową trudnością jest
stworzenie czegoś, co będzie posiadać wystarczającą moc, a zarazem będzie proste i bezpieczne. Jeżeli
jest to zbyt potężne, wtedy kompletne aplikacje mogą być tworzone przez szablony. Jeżeli jest zbyt
sł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 jest
językiem zaprojektowanym specjalnie do tego, aby spełniać wymagania systemu szablonów. Jest on
bezpieczny, prosty, wydajny i potężny.
Każdy egzemplarz Tea jest zintegrowany ze specjalnym serwletem. Serwlet ten daje szablonom Tea
kontrolę nad tworzeniem strony, zachowując silne powiązania z aplikacją wspierającą utworzoną przez
programistę Javy. Podczas gdy serwlet ten dostarcza funkcjonalności podobnej do JSP, Tea wymusza
poprawne rozdzielenie model-widok ze względu na międzynarodowe ograniczenia języka. Chociaż jest
to 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ć wykorzystane
nieodpowiedzialnie. Modyfikacje nie muszą przechodzić przez ścisłą korektę i fazę testów, która jest
wymagana w przypadku JSP.
Każda osoba pracująca nad projektem powinna mieć dostęp do narzędzi pozwalających na pracę z
maksymalną wydajnością, a Tea wykonuje swoją część pracy poprzez umożliwienie programiście
wykonywania dokładnie tego, co jest mu potrzebne, tak łatwo jak to tylko możliwe, i żadnej dodatkowej
pracy ponad to. Nawet w przypadku projektów tworzonych przez początkujących programistów
wykorzystanie Tea posiada swoje zalety. Narzędzie wspomaga nabywanie dobrych praktyk
programistycznych i ułatwia utrzymanie aplikacji.
Czytelnicy niniejszej książki powinni również pamiętać, że Tea nie jest językiem zaprojektowanym dla
programistów, ale dla producentów technicznych. Jest językiem prostszym niż Java, bezpieczniejszym, ale tak
samo wydajnym.
Początki
Aby 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, kto
uzyska 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 przez
szablon 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 w
klasach Javy, nazwa pliku musi być taka sama jak nazwa szablonu zadeklarowana przez konstruktora (łącznie z
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ć plik
TeaServlet.jar w ścieżce klas serwera WWW. Następnie należy dokonać edycji deskryptora web.xml aplikacji
WWW 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żka
bezwzglę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 plik
TeaServlet.properties:
# TeaServlet.properties
#
# Określenie miejsca szablonów Tea
template.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. Ostatnim
krokiem jest ponowne uruchomienie serwera tak, aby mógł odczytać nowy plik web.xml (w przypadku niektórych
serwerów nie jest to konieczne).
Kiedy TeaServlet zostanie właściwie zainstalowany, dostęp do ProstaStrona można uzyskać w miejscu takim jak
http://localhost:8080/herbatka/tea/ProstaStrona. Część ścieżki /herbatka określa aplikację WWW, do której
uzyskiwany jest dostęp. Następna część /tea jest tak sama, jak odwzorowanie przedrostków podane w web.xml
herbatka, 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 żądaniu
Tworzenie statycznej zawartości wiąże się z dużą ilością pracy, tak więc teraz utworzone zostanie coś bardziej
dynamicznego. 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 te
mogą 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
danej nazwie, lub konwersja typu nie powiedzie się, szablon otrzymuje wartość parametru null. Szablon
przedstawiony 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 nic
poza wyświetleniem nazwiska: <% nazwisko ?>
Jeżeli uzyska się dostęp do szablonu jako herbatka/tea/ProstaStrona?imię=Janek, na stronie wyświetlone
zostanie „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 nic
poza 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żytkownikowi
ostrzeżenie. Szablon sprawdza również, czy podane nazwisko brzmi Sędzia i , jeżeli tak, traktuje użytkownika
w 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 w
przypadku wszystkich obiektów). Oznacza to, że nie jest konieczne wyjaśnianie producentowi technicznemu celu
metody .equals(). Po drugie, łańcuch null może być porównany z innym łańcuchem bez ryzyka wystąpienia
wyjątku NullPointerException, co mogłoby się zdarzyć w przypadku .equals().
Sięganie głębiej
Inne 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 szablon
Tea przeglądający informacje o żądaniu. W praktycznych przypadkach, rzadkością jest konieczność uzyskania
przez szablon Tea bezpośredniego dostępu do tych informacji, zamiast tego można wykorzystać aplikację, która
moż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>
<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 cookies</h1>
<%
foreach (cookie in request.cookies.all) {
cookie.name ': ' cookie.value '<BR>'
}
%>
Wartość cookie identyfikatora sesji wynosi<% zadanie.cookies["JSESSIONID"].value %
>.
"<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. W
języku istnieje słowo kluczowe isa pozwalające na obsługę kwestii związanych z tworzeniem zmiennych, jeżeli
jest to konieczne.
Wykorzystane zostało wyrażenie foreach, które obsługuje często występujące zadanie wykonywania pętli nad
zestawem obiektów. Jest to elastyczna konstrukcja, która może działać zarówno na tablicach, jak i na każdych
innych obiektach będących rozszerzeniem java.util.Collection. Wyrażenie foreach obsługuje
ró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 z
pętlami, nie występują wyrażenia for i while. (W związku z tym w Tea nie występują żadne pętle
nieskoń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 celu
obliczenia 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órzy
instynktownie 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ących
wskazówek, w którym miejscu powinno wystąpić łączenie. W celu uzyskania bardziej zaawansowanego łączenia
należy wykorzystać znak &. Znak + nie jest wykorzystywany, ponieważ jego nadmierne stosowanie powoduje
niejasności.
Komentarze w Tea tworzone są według standardów Javy, w których /* i */ ograniczają komentarze o długości
wię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 i
właściwie wysyłanie czegokolwiek w żądaniu. Zdolność do uzyskania dostępu do sesji może zostać dodana w
późniejszym czasie jako część procesu projektowania Open Source tworzącego Tea. Nie jest ona obecnie
obsługiwana tylko dlatego, że WDIG wewnętrznie stosuje inny mechanizm śledzenia sesji. Inne własności
celowo nie są obsługiwane i muszą zostać wykonane przez kod Javy w aplikacji wspierającej, z powodów
założeń projektowych, które głoszą, że „otrzymane dane nie mogą być modyfikowane w żaden sposób”.
Administracja Tea
Szkielet 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 klasami
aplikacji. Ekran administratora umożliwia przeglądanie aktualnie załadowanych szablonów i uruchomionych
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ów
lub ich wybranych części, w oparciu o szablon i poziom dziennika (debug, info, warn lub error). Ekran
administracyjny 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.
Administrowanie
załadowanymi
obecnie
szablonami
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 żaden
poś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 i
HotSpot w celu osiągnięcia maksymalnej wydajności, ale inaczej niż w przypadku stron JSP, kompilator pracuje
bezpośrednio na źródle utworzonym przez człowieka i w związku z tym potrafi lepiej zdiagnozować błędy bez
pośredniczącego pliku rozmywającego bezpośredniość. Poza tym, Tea nie wymaga dołączenia kompilatora Javy
do serwera WWW, który jest potrzebny w przypadku JSP. Wymaganie to sprawia problemy związane z
licencjonowaniem. Tea może również prekompilować szablony, umożliwiając dystrybucję szablonów bez ich
źródeł.
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ępniania
oryginalnego 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 bez
kodó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 przez
JSP jest często uzależniony od klas związanych z danym kontenerem, a tworzona klasa musi być zgodna z
zależ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 konkretnego
kontenera.
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, Compiled
Code)
at com.go.teaservlet.TeaServlet.doGet(TeaServlet.java:238)
<itd.>
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 dodatkowych
informacji w celu zachowania bezpieczeństwa.
Tea posiada również własność „strażnik wyjątków”. Własność ta pozwala Tea na kontynuowanie przetwarzania
strony podczas zapisywania błędu w dzienniku. Tea tworzy wszystkie części strony, które może omijając
niewielką część, na którą wpływ ma wyjątek. Dla wielu witryn strona, której brakuje niewielkiej części jest
lepsza niż błąd serwera zwracany użytkownikowi.
Zastosowania Tea
Jak dotychczas omawiane były samotne szablony Tea, które są bardzo słabe — celowo słabe. Ale szablony Tea
są jak frontowi żołnierze piechoty — kiedy samotni, słabi, ale kiedy w zespole z zaawansowanym wsparciem
lotniczym, 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 poprzez
plik TeaServlet.properties. (Każda aplikacja WWW może posiadać inny plik TeaServlet.properties i w związku z
tym inny zestaw zainstalowanych aplikacji.) Szablony wywołują funkcję poprzez wymienienie w bloku kodu
nazwy 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 zainstalowanych
aplikacji. 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 w
podstawowych zadaniach przetwarzania tekstu. Poniższe podrozdziały wymieniają niektóre z najczęściej
wywoływanych funkcji narzędziowych.
Przetwarzanie tekstu
Poniż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)
Obsługa zawartości
Poniżej wymieniono popularne metody obsługujące zawartość.
void InsertFile (string nazwapliku)
Obsługa żądań/odpowiedzi
Dodatkowo przedstawiono kilka często wykorzystywanych metod żądań/odpowiedzi.
com.go.teaservlet.HttpContext.Request getRequest()
com.go.teaservlet.HttpContext.Request getRequest(String kodowanie)
<%
// Ustawienie lokalizacji. Nie zmienia się ona dla wszystkich danych wyświetlanych
// później przez szablon
set locale ("fr", "CA") // Quebec
</BODY></HTML>
Szablon wyświetla dane podobne do poniższych, z datą i liczbami właściwymi dla kogoś, kto mieszka w
Quebecu:
23 octobre 2001
Le pourcentage: 6,32%
ApplicationRequest zadanie;
ApplicationResponse odpowiedz;
String nazwa;
<%
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ć kwestia
związana z mechanizmem ładowania klas. Problemy mogą wystąpić, jeżeli TeaServlet.jar i klasy aplikacji są ładowane przez
inne mechanizmy. Należy albo umieścić obie w ścieżce klas systemu, gdzie zostaną odnalezione przez początkowy
mechanizm, albo umieścić obie w katalogu WEB-INF, gdzie zostaną odnalezione przez mechanizm ładowania klas aplikacji
WWW. (Proszę umieścić TeaServlet.jar w katalogu WEB-INF/lib, a klasy aplikacji w WEB-INF/classes.) Proszę także
pamiętać, że z tych samych powodów klasy wspierające nie są ładowane ponownie nawet po wybraniu Reload All (Przeładuj
wszystko) ze strony administracyjnej TeaServlet.
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 do
tworzenia zawartości (aplikacja mniej więcej taka, jakiej potrzebuje Servlets.com). Informacje o narzędziach
będą pochodzić z pliku XML, chociaż równie dobrze mogłyby pochodzić z bazy danych. Przykład 14.8
przedstawia 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.*;
<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, blisko
związana z serwletami. Tak jak w przypadku serwletów, Sun udostępnia specyfikację
JSP i inni producenci rywalizują swoimi implementacjami standardu. To, że jest
dziełem firmy Sun, stawia JSP w bardzo uprzywilejowanej pozycji i jeżeli JSP
rozwiązywałaby wystarczającą ilość problemów użytkowników, przypuszczalnie
zagarnęłaby rynek jeszcze przed pojawieniem się alternatyw. Jednak zadziwiająco
duż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ętrznie
przez 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ęść projektu
Shimari, włączony teraz do Apache Jakarta Project. WebMacro było wykorzystywane w
komercyjnych witrynach takich jak AltaVista.com, zintegrowane w szkieletach Open
Source takich jak Turbine i Melati oraz wykorzystywane w znanych projektach Open
Source 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ór
klas utworzonych na podstawie htmlKona, produktu WebLogic (teraz BEA Systems). ECS
posiada 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 wielu
jego ograniczeń. Technika ta została utworzona przez Lutris jako część ich serwera
Open Source — Enhydra Application Server, może być stosowana jako osobny element.
</komentarz>
<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 bazy
danych, można to uczynić po prostu zamieniając SAXBuilder JDOM (który tworzy Document z pliku lub
potoku przy pomocy analizatora SAX) na ResultSetBuilder JDOM (który tworzy Document z
java.sql.ResultSet, klasy przekazanej JDOM).24 W trakcie pisania niniejszej książki, JDOM ciągle był w fazie
tworzenia. 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. Kod
NarzedziaKontekst jest przedstawiony w przykładzie 14.10.
Przykład 14.10.
Kontekst zawierający wszystkie dostępne funkcje
import com.go.teaservlet.*;
ApplicationRequest zadanie;
ApplicationResponse odpowiedz;
NarzedziaAp ap;
24
Jason Hunter jest jednym z twórców JDOM, razem z Brettem McLaughlinem. Większa ilość informacji na temat JDOM
dostępna jest pod adresem http://jdom.org.
public String pobierzKomentarz() { return komentarz; }
public String pobierzZnacznikStanu() { return znacznikStanu; }
public Timestamp pobierzCzasUtworzenia() { return czasUtworzenia; }
public Timestamp pobierzCzasModyfikacji() { return czasModyfikacji; }
// 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;
}
<%
if (stan == null) {
narzedzia = pobierzNarzedzia("zyje");
}
else {
narzedzia = pobierzNarzedzia(stan)
}
%>
<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>
<% } %>
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, jak
przedstawiono 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ę."
%>
<% call naglowek(tytul, tytul2, opis) %>
<%
if (stan == null) {
narzedzia = pobierzNarzedzia("zyje");
}
else {
narzedzia = pobierzNarzedzia(stan)
}
%>
<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>
<% } %>
<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>
<P>
<FONT FACE="Arial,Helvetica">
<% 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>
<A HREF="/hosting.html">Hosting</A>
<A HREF="/mechanizmy.html">mechanizmy</A> <P>
</FONT>
<TABLE WIDTH=100%>
<TR>
<TD WIDTH=260 ALIGN=LEFT VALIGN=TOP>
<FONT FACE="Arial,Helvetica">
<A HREF="/wlasnosc.html">Wlasność</A> © 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:webmaster@servlets.com">webmaster@servlets.com</A>
</FONT></TD>
</TR>
</TABLE>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Rysunek 14.4.
Lista narzędzi z otaczającymi dodatkami
Według powyższego projektu zmiany w nagłówku, paku bocznym i stopce są ograniczone do pojedynczych
plików. Możliwe jest również wykorzystanie pojedynczego pliku do stworzenia wyglądu całej strony, wstawiając
właściwą zawartość przy pomocy polecenia <% ... %>. Podejście to dostarcza większych możliwości i
pozwala na oddzielenie właściwej zwartości od formatowania, chociaż trzeba się do tego podejścia
przyzwyczaić. Proszę przeczytać dokumentację TeaServlet w celu znalezienia dodatkowych informacji.
Ostatnie słowo
Jedną z najbardziej przydatnych własności Tea trudno opisać w książce — jest to graficzne środowisko
programistyczne 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żąco
wszystkie błędy składni na zielono, błędy semantyczne na czerwono, a błędy kompilacji na niebiesko. Podobna
funkcjonalność w narzędziach wspierających inne mechanizmy tworzenia zawartości byłaby wspaniała, ale
praktycznie niemożliwa do osiągnięcia. Kettle oferuje również przywoływaną listę właściwości, która pozwala
twórcy szablonu na natychmiastowe sprawdzenie, jakie własności są dostępne dla danego obiektu. Kettle jest
bezpłatne, ale działa jedynie pod Windows i nie jest to Open Source. Dodatkowo istnieją plany utworzenia
reimplementacji 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 jest
ciągle narzędziem nowym, posiadającym duży potencjał.
Rozdział 15. WebMacro
Szkielet programistyczny WebMacro, dostępny pod adresem http://www.webmacro.org, pochodzi z tej samej
rodziny, co Tea, ale charakteryzuje się podejściem opartym w większym stopniu na serwletach. WebMacro
został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. Obecnie
istnieją plany włączenia WebMacro do Apache Jakarta Project dostępnym pod adresem
http://jakarta.apache.org w celu uzyskania szerszego wsparcia programistycznego. Aktualnie w projekcie
Jakarta 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 zadania
zamiany 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 opisane
zostanie WebMacro, ponieważ posiada ono największą popularność, jest wykorzystywane w dużych
komercyjnych witrynach takich jak AltaVista, zostało zintegrowane z dużymi szkieletami Open Source takimi jak
Turbine (http://java.apache.org/turbine) i Melati (http://www.melati.org) oraz zostało wykorzystane jako
podstawa znanych projektów Open Source takich jak JetSpeed (http://java.apache.org/jetspeed). Podobnie jak
Tea, WebMacro jest rozprowadzane według licencji typu Apache, tak więc może zostać bez problemu
wykorzystane w połączeniu z aplikacjami komercyjnymi.
Niniejszy rozdział opisuje WebMacro 0.94, wersję próbną z października 2000. Na pewno dużo szczegółów
zmieni 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 wersji
2.0 lub późniejszej i JDK 1.1.7 lub późniejszego.
Szkielet WebMacro
Szkielet 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), po
czym tworzy obiekt kontekstowy wypełniony „obiektami odpowiedzi” zawierającymi wyniki logiki, które
powinny zostać wyświetlone klientowi. Następnie serwlet wybiera plik szablonu (zazwyczaj w oparciu o wyniki
logiki) 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 szablonu
wyświetlający dane. Szablon składa się z zawartości HTML i XML wypełnionej prostą logiką do podstawiania i
niewielkimi możliwościami skryptowymi. Składnia jest zgodna z edytorami HTML i XML, omija nawiasy
kątowe, aby zapewnić, że analizatory HTML i XML nie będą popełniać omyłek. Programista przekazuje
inż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ślnie
wykorzystuje model przepychania w większym stopniu oparty na serwletach, nie obsługuje zagnieżdżania
szablonów i wykorzystuje bardziej skomplikowaną składnię skryptową. Zdaniem autorów, WebMacro posiada
przewagę nad Tea przy tworzeniu wysoko funkcjonalnych stron WWW, które posiadają pewien punkt
oznaczający zakończenie pracy nad nimi, Tea natomiast przoduje w tworzeniu stron, w których stale dodawana
jest nowa zawartość szablonów, konieczne jest zagnieżdżanie szablonów i wymagane jest mniejsze
zaangażowanie programistyczne.
Powitanie przy pomocy WebMacro
Przykład 15.1 przedstawia prosty serwlet wykorzystujący system szablonów WebMacro. Przykład 15.2
przedstawia plik szablonu witaj.wm. Wspólnie wyświetlają one aktualna datę i godzinę W następnym
podrozdziale opisane zostaną miejsca, w których powinny one zostać umieszczone.
Przykład 15.1.
Prosty serwlet prowadzący szablon Witaj
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.webmacro.*;
import org.webmacro.servlet.*;
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
<HTML><HEAD><TITLE>Test WebMacro</TITLE></HEAD><BODY>
Witaj! <P>
</BODY></HTML>
Na początku opisany zostanie serwlet. Jest to normalny serwlet, będący rozszerzeniem HttpServlet i
wykorzystują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ądzenie
zapisują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 jedynie
pamiętać, by (przynajmniej obecnie) wywołać metodę flush() urządzenia zapisującego w celu wysłania
zbuforowanej zawartości urządzenia do klienta.
Kod wewnątrz bloku try tworzy nowy obiekt WebMacro, który działa jako jego podstawowy zaczep do
systemu WebMacro. WebMacro to interfejs, tak więc serwlet właściwie konstruuje egzemplarz konkretnej klasy
WM, 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óre
zostaną przekazane szablonowi. Kontekst działa podobnie jak tablica asocjacyjna Hashtable. Posiada on
metodę 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, a
serwlet mógłby wywoływać kontekst nieświadomy sieci WWW — Context przy pomocy getContext().
Własność ta jest przydatna w niektórych sytuacjach, takich jak zastosowania offline lub niezwiązane z
serwletami.
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, ale
wię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łniony
obiektami odpowiedzi. Wyświetlane dane powinny również zostać ujęte jako łańcuch, przy pomocy metody
Template String evaluate(Context kontekst).
Jeżeli dowolna metoda spowoduje wyjątek WebMacroException, serwlet został przygotowany na wywołanie
go 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że
wywoł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że
IOException, jeżeli wystąpił problem z zapisem do klienta. Wszystkie zdefiniowane przez WebMacro
wyjątki są rozszerzeniami WebMacroException, który nie jest wyjątkiem czasu wykonania, i często stosuje
się 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 superklasy
org.webmacro.servlet.WMServlet, jak przedstawiono w przykładzie 15.3.
Przykład 15.3.
Inne podejście do tworzenia serwletu WebMacro
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.webmacro.*;
import org.webmacro.servlet.*;
Instalacja WebMacro
W celu uruchomienia serwletu WebMacro i szablonu konieczne jest wykonanie niewielkiej pracy związanej z
instalacją. 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łasny
mechanizm ł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żce
klas, gdzie mogą zostać odnalezione przez podstawowy mechanizm ładowania klas, lub umieścić oba w katalogu
WEB-INF, gdzie zostaną odnalezione przez mechanizm aplikacji WWW. (webmacro.jar należy umieścić w
katalogu 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órych
pierwsza część jest przedstawiona w przykładzie 15.4. Większość aspektów tej klasy jest dobrze
udokumentowana 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:
#
TemplatePath = /tomcat/webapps/webmacro/WEB-INF/szablony;/local/webmacro/szablony
TemplateExpireTime = 0
25
Archiwum collections.jar zawiera klasy Java Collections (wprowadzone w JDK 1.2) utworzone do stosowania z
wcześ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ła
przystosowana dla JDK 1.1.7 i w związku z tym ten plik jest wymagany nawet podczas korzystania z JDK 1.2 lub
późniejszego.
# 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 = TRUE
# LogFile = /usr/local/webmacro/wm.log
ErrorTemplate = blad.wm
Zaawansowany użytkownik może utworzyć wiele wersji powyższego pliku i przełączać się pomiędzy nimi w celu
dokonania szybkiej i kompletnej zmiany wyglądu witryny (zmiana TemplatePath), lub po prostu by
przesunąć 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 powinien
zostać odczytany. Jeżeli serwlet potrzebuje bezpośredniego dostępu do właściwości w tym pliku, są one dostępne
przy pomocy metody getConfig(String klucz) obecną zarówno w interfejsie WebMacro i klasie
WMServlet. Pozwala to plikowi konfiguracyjnemu na przechowywanie ekwiwalentów parametrów inicjacji
kontekstu, 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 jest
wykonywane tak, jak wywoływanie wszystkich innych serwletów. Należy wywołać serwlet jako
http://localhost:8080/servlet/WMWitaj lub, w przypadku serwletów umieszczonych w kontekście /webmacro, jak
przedstawiono w pliku WebMacro.properties, URL wynosi http://localhost:8080/webmacro/servlet/WMWitaj.
Proszę spojrzeć na rysunek 15.1.
Rysunek
15.1.
Test
WebMacr
o
26
Jednak z powodu dużych nacisków operacje arytmetyczne są dodawane i mogą być dostępne w momencie wydania książki
Instrukcje WebMacro
Instrukcje to wyrażenia WebMacro wykonujące pewne operacje, warunkowo dołączające tekst lub powtarzające
blok wewnątrz szablonu. Lista dostępnych instrukcji jest przechowywana w pliku WebMacro.properties jako
właściwość Directives. Wszystkie instrukcje rozpoczynają się znakiem #. Niektóre instrukcje działają na
bloku, 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ć swoje
własne instrukcje dodawane do domyślnych, chociaż nie jest to tak popularne. Poniżej przedstawiona jest
domyślna siódemka.
#if
#if (warunek) { ... } #else { ... }
Instrukcja #if, samodzielna lub zastosowana w połączeniu z instrukcją #else, może zostać wykorzystana do
warunkowego dołączania tekstu. Tekst jest dołączany, jeżeli warunek jest prawdziwy, a nie dołączany, jeżeli
fał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 celu
okreś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ść publicznego
ustawiania w pewien sposób. Jeżeli takiej zmiennej nie ma w zakresie, tworzona jest nowa zmienna i przypisana
jej zostaje podana wartość. Zmiennym w ukryty sposób przypisywany jest typ String lub Integer. Typ
String to typ domyślny, chyba że wartość może zostać zanalizowana jako Integer i nie jest otoczona
podwójnymi nawiasami, Dla ułatwienia, wartości Integer mogą zostać przekonwertowane do wartości
String, 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. Przy
każ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ą):
#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óżnych
plików szablonów. Plik może zostać określony przy pomocy ścieżki bezpośredniej lub pośredniej umieszczonej
wewną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ładnia
moż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ący
szablon. 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 w
kontekś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 obiekty
String 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 nawet
całkowitą wymianę składni WebMacro. Generalnie nie jest ona jednak stosowana do tak dużych zadań. Jej dwa
najbardziej popularne zastosowania to dosłowne dołączenie bloku tekstu (przy pomocy analizatora text) lub
usunię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że
zostać zastąpiony przez #text, a #use 'null' przez #comment.
Szablony WebMacro
Aby zademonstrować narzędzia kontekstu i instrukcje dostępne szablonom WebMacro, przykład 15.6
przedstawia prosty szablon wyświetlający informacje z żądania.
Przykład 15.6.
Przeglądanie żądania przy pomocy WebMacro
## przegl.wm
<HTML><HEAD><TITLE>Przegladanie!</TITLE></HEAD>
<BODY>
<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>
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. Wykorzystuje
instrukcje #if i #else w celu określenia, czy klient utworzył sesję przy pomocy cookie, czy przepisywania
URL-a, czy też nie jest częścią sesji. Wykorzystuje także instrukcję #foreach do wykonania pętli na
wartoś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 wzoru
isWlasciwosc() nie jest automatycznie odnajdywana przez WebMacro i musi zostać osobno wywołana
poprzez wywoływanie metod. Po drugie, obecnie nie istnieje instrukcja #elseif, tak więc konstrukcja
if/elseif/else musi być wykonywana poprzez zagnieżdżanie.
import org.webmacro.*;
import org.webmacro.servlet.*;
import org.webmacro.engine.*;
import org.webmacro.broker.*;
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();
}
}
Przetwarzanie szablonów
WebMacro 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. WebMacro
analizuje szablony podczas ich pierwszego wykorzystania, a następnie umieszcza reprezentację odpowiedniego
szablonu w pamięci, w celu ich szybkiego wykonywania. WebMacro automatycznie przeładowuje i ponownie
analizuje zawartość szablonu po zmianie pliku szablonu, ale dla zachowania wydajności sprawdza znaczniki
czasu 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 produkcyjnego
znacznik 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 wszystkich
szablonó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ępuje
odwoł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ść WARN
w dzienniku, a na wygenerowanej stronie umieszcza komentarz HTML/XML w miejscu błędu (podejście to
posiada 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 w
dzienniku 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 tych
błędach, wybierając, do którego szablonu przekazać kontrolę i co zawrzeć w kontekście, zależnie od błędu. Na
przykład, jeżeli wystąpił błąd bazy danych , kontrola powinna zostać przekazana do pliku sqlException.wm
razem 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” z
poprzedniego rozdziału przy pomocy WebMacro zamiast Tea. Jest to najprostsze do zrozumienia, jeżeli na
początku spojrzy się na szablon, przedstawiony w przykładzie 15.9.
Przykład 15.9.
Szablon WebMacro aplikacji „Narzędzia”
## widoknarz.wm
#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ładni
szablonów mogą spowodować wyświetlenie przez WebMacro pustej strony zamiast strony zawierającej opis błędu Bez
wątpienia błąd ten zostanie naprawiony w następnych wersjach.
## Zdefiniowanie wartości, które powinny zostać odczytane przez serwlet
#param $domyslnyStan = "ZYJE"
<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ów
kontynuacji opisu). Zmienne te są wykorzystywane wewnątrz zanalizowanego szablonu naglowek.wm w celu
utworzenia struktury strony otaczającej podstawową zawartość. Zmienne są również widoczne wewnątrz
szablonu stopka.wm przeanalizowanego na końcu szablonu, chociaż stopka nie wykorzystuje ich. Szablony
nagłó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ści
parametru szablonu domyslnyStan, pozwalając serwletowi wywołującemu ten szablon na poznanie
domyś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 narzedzie
przekazanej przez serwlet. Zmienna narzędzi może być tablicą, Iterator, Enumeration lub obiektem takim
jak Vector() lub List() posiadającym metodę iterator() lub elements() — nie jest to ważne dla
szablonu. Dla każdego egzemplarza narzędzi, szablon wyświetla jego nazwę, informację, czy jest nowy lub
uaktualniony, 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ą lub
uaktualnioną, Szablon powinien mieć dostęp do $narzedzie.CzasUtworzeniaDni i
$narzedzie.CzasModyfikacjiDni, jednak wymagałoby to obsługi narzędzia kontekstu MatTool z
przykł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>
<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>
<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>
<A HREF="/hosting.html">Hosting</A>
<A HREF="/mechanizmy.html">mechanizmy</A> <P>
</FONT>
<TABLE WIDTH=100%>
<TR>
<TD WIDTH=260 ALIGN=LEFT VALIGN=TOP>
<FONT FACE="Arial,Helvetica">
<A HREF="/wlasnosc.html">Własność</A> © 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:webmaster@servlets.com">webmaster@servlets.com</A>
</FONT></TD>
</TR>
</TABLE>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Kod klasy Narzedzie pozostaje taki sam jak w przypadku aplikacji Tea, chociaż przy stosowaniu WebMacro
nie 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.*;
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());
}
}
Filtry
W 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 mniej
ograniczającej licencji w stylu Apache'a. Wynikiem udostępnienia narzędzia większej ilości programistów jest
aktualna ich praca nad wieloma interesującymi i przydatnymi rozszerzeniami.
Najbardziej istotne rozszerzenie zawiera dołączany mechanizm filtrów. Jego ideą jest wyświetlanie zmiennej
poprzez 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 celu
usunięcia specjalnych znaków, które mogłyby przypadkowo lub rozmyślnie sprawić kłopoty.
• Lokalizacja — filtrowanie daty poprzez pewną zlokalizowaną logikę wyświetlania.
• Przepisywanie URL-i — filtrowanie URL-i w celu dodania śledzącej sesji wartości jsessionid.
• Wyświetlanie wartości null — filtrowanie wyświetlania zmiennych o wartości null w celu
wyświetlenia komunikatu o błędzie lub ukrytego zignorowania zmiennej.
Filtry niestandardowe
Filtry niestandardowe wykonują konkretne działania. Na przykład, zmienna klient może zostać przefiltrowana
przez szablon klienta zaprojektowany w celu wyświetlania zmiennej i jej właściwości w oparciu o zewnętrzny
plik szablonu. Szablon klienta może nawet przefiltrować adres klienta poprzez zewnętrzny szablon adresu.
Pozwala to na ponowne stosowanie i łatwe dostosowywanie logiki wyświetlania. Filtry mogą być także łączone w
łańcuchy. Pozwoliłoby to na przykład na zarówno lokalizację jak i wyłączenie zmiennej, określoną wcześniej
wartością, jeżeli zmienna posiadałaby wartość null.
Możliwe zastosowania filtrów znajdują się ciągle jedynie w wyobraźni ludzi. Interesujące może okazać się
śledzenie, które zastosowania filtrów WebMacro staną się popularne.
Rozdział 16. Element
Construction Set
Pakiet Element Construction Set (ECS) prezentuje całkowicie inne podejście do tworzenia zawartości niż JSP,
Tea i WebMacro. ECS odchodzi daleko od tekstu HTML i traktuje HTML jako jedynie kolejny zestaw obiektów
Javy. Strona WWW w ECS jest zarządzana jak obiekt, który może zawierać inne obiekty HTML (takie jak listy i
tabele), które mogą zawierać jeszcze więcej obiektów HTML (takich jak elementy listy i komórki tabeli). Ten
model „obiektowego tworzenia HTML” okazuje się być bardzo potężny, ale również skoncentrowany na
programiście.
Stephan Nagy i Jon Stevens stworzyli ECS i udostępnili go jako Open Source jako część Java Apache Project,
oczywiście w licencji Apache. Biblioteka został utworzona według produktu htmlKona firmy WebLogic,
produktu, który stracił wsparcie po wykupieniu WebLogic przez BEA Systems. W niniejszym rozdziale
opisywany jest ECS w wersji 1.3.3, dostępny pod adresem http://jakarta.apche.org/ecs.
import org.apache.ecs.*;
import org.apache.ecs.html.*;
odp.setContentType("text/html");
PrintWriter wyj = odp.getWriter();
29
Osoby zainteresowane programowym tworzeniem XML przy pomocy Javy powinny się raczej skupić na wykorzystaniu
JDOM (http://jdom.org), ponieważ JDOM jest lepiej zintegrowany z technologiami XML.
Przykład 16.1.
Ulepszona tabela ZbiorWynik
import java.io.*;
import java.sql.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.ecs.*;
import org.apache.ecs.html.*;
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 ona
element HTML <TABLE>. Wykonuje ona całą swoją pracę w konstruktorze, odczytując ResultSet i jego
ResultSetMetaData w celu wygenerowania prostej tablicy wyników. Konstruktor po pierwsze wywołuje
setBorder(1) w celu ustawienia atrybutu obramowania tabeli. Następnie tworzy wiersz (TR) wypełniony
elementami 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ąc
wiersz elementami danych tabeli (TD) zawierającymi prostą reprezentację String zbioru wyników.
Klasa ProstaTabelaResultSet może zostać bezpośrednio wykorzystana przez serwlet przy pomocy
fragmentu kodu przedstawionego w przykładzie 16.3.
Przykład 16.3.
Wykorzystanie ProstaTabelaResultSet
Statement wyraz = lacz.createStatement();
boolean gotResultSet = wyraz.execute(sql); // SQL od użytkownika
if (!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.
Rysunek
16.1.
Surowy
widok
wyników
Powyższa klasa może zostać wykorzystana w połączeniu z technologiami szablonów. Nie istnieje powód, dla
którego szablon musiałby tworzyć całą zawartość HTML samodzielnie; jeżeli pewna część strony potrzebuje
mocy 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 jako
specjalne, 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 element
używa tego filtru jako filtru domyślnego, ale z drugiej strony domyślnie całe filtrowanie jest wyłączone w celu
przyśpieszenia działania. Filtrowanie można włączyć dla danego elementu poprzez wywołanie
setFilterState(true) lub dla całego systemu poprzez edycję ecs.properties i ustawienie wartości
filter_state i filter_attribute_state na true. W takim przypadku znaki specjalne w całym
wyświetlanym tekście zostaną automatycznie przekonwertowane na encje znakowe.
Dostosowywanie wyświetlania
ECS wykorzystuje obiektową naturę Javy w celu stworzenia modelu wyświetlania danych o wysokim stopniu
dostosowalności. Przy pomocy niewielkiej ilości kodu Javy możliwe jest utworzenie na klasie
ProstaTabelaResultSet nie uproszczonej w takim stopniu tabeli TabelaResultSet. Ta nowa klasa
tabeli będzie przyjmować tablicę obiektów TabelaDostosuj w celu kontrolowania zawartości dodanej do
tablicy. Przykład 16.4 przedstawia interfejs TabelaDostosuj.
Przykład 16.4.
Klasa TabelaDostosuj
import java.sql.*;
import org.apache.ecs.*;
import org.apache.ecs.html.*;
DateFormat fmt;
NumberFormat fmt;
String bladWidokSerwlet;
import org.apache.ecs.*;
import org.apache.ecs.html.*;
if (dostosuj == null) {
dostosuj = new TabelaDostosuj[0];
}
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);
addElement(td);
}
addElement(rzad);
}
}
}
Zewnętrzna pętla while dokonuje iteracji na wierszach tabeli, podczas gdy zewnętrzna pętla for dokonuje
iteracji 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 jest
przedstawiony w przykładzie 16.10.
Przykład 16.10.
Serwlet pracujący z TabelaResultSet
import 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.*;
odp.setContentType("text/html");
PrintWriter wyj = odp.getWriter();
String url = zad.getParameter("url");
String driver = zad.getParameter("driver");
String sql = zad.getParameter("sql");
try {
Class.forName(driver);
lacz = DriverManager.getConnection(url, wlasc);
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 teraz
identyfikator błędu jest hiperłączem, data jest ładnie sformatowana, a opis null wyświetla „niedostepne”.
Rysunek
16.2.
Dostosowany
widok zbioru
wyników
ResultSet
(data
europejska)
<P>
Niniejsza aplikacja wykonuje zapytanie SQL lub uaktualnia dowolną bazę danych
znajdującą się w sieci.
</P>
XMLC wykorzystuje technologię XML w celu zmiany plików HTML w zasoby Javy. Został on utworzony przez
firmę Lutris jako część serwera Open Source Enhydra Application Server i może zostać pobrany niezależnie, w
celu zastosowania go w połączeniu z dowolnym innym kontenerem serwletów30. W niniejszym rozdziale
omówiony zostanie XMLC 1.2b1 dostępny pod adresem http://xmlc.enhydra.org. XMLC wymaga JDK1.1 lub
póź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 w
istniejącym kodzie powinny być przesłane z powrotem do producenta, ale pozwala ona na prywatne, a nawet
komercyjne rozszerzenia.
XMLC oznacza XML Compiler (Kompilator XML). Narzędzie to pobiera standardowy dokument HTML lub
XML i „kompiluje” go do klasy Javy. Klasa ta zawiera instrukcje Javy potrzebne do utworzenia reprezentacji
dokumentu 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, a
po 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. Makiety
to 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 w
formie drzewa DOM. Programista Javy wykorzystuje publiczny interfejs tej klasy w celu utworzenia klasy
manipulacyjnej, która tworzy egzemplarz drzewa, odnajduje części dokumentu, które powinny zostać zmienione
i odpowiednio jej modyfikuje. Części te są odnajdywane przy pomocy metod dostępu dodanych do drzewa przez
XMLC dla każdego atrybutu ID w dokumencie. W ten sposób pliki HTML stają się właściwie zasobami dla
Javy.
Jeżeli na stronie powinna znaleźć się lista elementów, makieta może zawierać pięć lub dziesięć z nich, aby
wyglądać realistycznie. Programista Javy może usunąć wszystkie elementy oprócz pierwszego, a następnie
skonstruować właściwą listę poprzez powtarzane klonowanie pierwszego elementu — w ten sposób
dostosowując się do stylu makiety, ale wykorzystując dane tworzone dynamicznie. Możliwe jest nawet nakazanie
kompilatorowi XMLC automatycznego usunięcia wszystkich elementów oprócz pierwszego, co poprawia
wydajność.
XMLC osiąga wysoki poziom oddzielenia zawartości i prezentacji. Plik HTML to czysty HTML, pliki Javy to
czysta 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żnego
znacznika nie ma w nowej makiecie, wywołany zostaje błąd kompilacji. Dzieje się tak, kiedy klasa
manipulacyjna próbuje wywołać metodę odczytującą nieistniejący ID.
30
Enhydra Lutris to naukowa nazwa morskiej wydry, co jest odpowiednim identyfikatorem dla firmy z siedzibą w Santa
Cruz. W pobliżu tego miasta znajduje się zatoka Monterey, w której żyje mnóstwo wydr.
wiadomości czeka na niego. Tekst, który zostanie podmieniony jest otoczony zgodnymi z HTML 4.0
znacznikami <SPAN>.
Przykład 17.1.
Proste podstawienie XMLC
<!-- witaj.html -->
<HTML>
<HEAD><TITLE>Witaj</TITLE></HEAD>
<BODY>
</BODY>
</HTML>
W celu uruchomienia XMLC z powyższym plikiem, po pierwsze należy zainstalować XMLC. Należy pobrać plik
z wiryny http://xmlc.enhydra.org, rozpakować archiwum i postępować według instrukcji w pliku README
mówiących o uruchomieniu xmlc-config. Potem można już uruchomić kompilator xmlc:
xmlc –class Witaj –keep witaj.html
Powyższa linia kodu nakazuje XMLC skompilowanie pliku witaj.html do klasy o nazwie Witaj, zatrzymując
wygenerowany 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 do
DOM. Proszę jednak pamiętać, że Tidy może popełniać błędy podczas obsługi plików HTML zbytnio
oddalonych od prawidłowego HTML, a kiedy to nastąpi, podczas kompilacji XMLC wyświetlona zostanie długa
lista 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 o
nazwie 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ładnia
linii 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
31
XMLC jest dostępny również jako zadanie Ant, co może zainteresować osoby wykorzystujące doskonałe narzędzie
kompilacji ANT. Proszę zobaczyć http://jakarta.apache.org.
Określa URL, który powinien zostać zmodyfikowany w DOM według
jego znacznika ID. Url o identyfikatorze id zostanie
zmieniony na nowyURL.
-Java prog.
/**
* klasa dokument XMLC utworzona z pliku
* witaj.html
*/
public class Witaj extends org.enhydra.xml.xmlc.html.HTMLObjectImpl implements
org.enhydra.xml.xmlc.XMLObject, org.enhydra.xml.xmlc.html.HTMLObject {
private int $elementId_Powitanie = 8;
/**
* 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ą.
*/
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);
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();
}
}
/**
* 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);
/**
* 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;
parentNode.insertBefore($node1, $docElement);
$elem1 = document.getDocumentElement();
((org.enhydra.xml.lazydom.LazyElement)$elem1).makeTemplateNode(2);
((org.enhydra.xml.lazydom.LazyElement)$elem1).setPreFormattedText
("<HTML>");
$elem1.appendChild($elem2);
$elem2.appendChild($elem3);
$elem3.appendChild($node4);
$elem1.appendChild($elem2);
$elem3 = document.createTemplateElement("H2", 7, "<H2>");
$elem2.appendChild($elem3);
$elem3.appendChild($elem4);
$elem4.setAttributeNode($attr4);
$attr4.appendChild($node5);
$elem4.appendChild($node5);
$elem2.appendChild($node3);
$elem2.appendChild($elem3);
$elem3.setAttributeNode($attr3);
$attr3.appendChild($node4);
$elem3.appendChild($node4);
$elem2.appendChild($node3);
/**
* Kolonowanie dokumentu.
*/
public Node cloneNode(boolean deep) {
cloneDeepCheck(deep);
/**
* Pobranie fabryki XMLC DOM skojarzonej z klasą.
*/
protected final org.enhydra.xml.xmlc.dom.XMLCDomFactory getDomFactory() {
return fDOMFactory;
}
/**
* 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;
} else if (id.equals("Wiadomosci")) {
$elementId_Wiadomosci = 13;
$element_Wiadomosci =
(org.enhydra.xml.lazydom.html.LazyHTMLElement)node;
syncWithDocument(child);
child = child.getNextSibling();
}
Powyższy przykład zawiera dużą ilość kodu, nie jest jednak skomplikowany. Klasa nosi nazwę Witaj, jak
podano 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 zbiorem
interfejsów i do wyboru jest wiele jego implementacji. W metodzie createDocument() można dostrzec, że
niniejsza 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.html
public 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 jest
zawarta w nazwach metod. Dla wartości, które nie mogą zostać przekonwertowane na prawidłowe identyfikatory
Javy nie zostaną wygenerowane metody dostępu. Tak więc należy być ostrożnym i stosować jedynie litery, cyfry
i kreski dolne. Proszę również pamiętać, że wartości identyfikatorów nie powinny powtarzać się na jednej
stronie.
Klasa manipulacyjna
Po skompilowaniu pliku HTML do jego opartej na Javie reprezenatcji XML, następnym krokiem jest utworzenie
tak zwanej klasy manipulacyjnej, Klasa ta tworzy egzemplarz dokumentu, modyfikuje jego zawartość i wyświetla
go 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 Witaj
import java.io.*;
import org.w3c.dom.*;
import org.w3c.dom.html.*;
import org.enhydra.xml.io.DOMFormatter;
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ładna
reprezentacja oryginalnego pliku HTML. Klasa manipulacyjna następnie ustawia tytuł strony, wartość powitania
i wartość ilości wiadomości. Powitanie i ilość wiadomości są ustawiane przy pomocy metod dostępu dodanych
przez XMLC. Tytuł ustawiany jest przy pomocy standardowej metody DOM. Istnieje wiele ciekawych
standardowych 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 klas
DOM jest zawarta w dokumentacji dołączonej do XMLC. Na końcu klasa wykorzystuje
org.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 celu
sprawdzenia, jak wyświetlany jest XML, ale w przypadku standardowych przeglądarek WWW domyślne
zachowanie 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> nowe
wiadomości.</BODY><!-- witaj.html --></HTML>
Proszę zauważyć, że wartości z makiety zostały zastąpione nowymi i usunięta została niepotrzebna wolna
przestrzeń 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 klasy
XMLC może zostać odnaleziony w ścieżce klas (lub ścieżce klas serwera, jeżeli XMLC jest wywoływany przez
serwlet). 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ć na
zewnę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 xmlc
kompilator 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. Prawdziwym
problemem jest niedopasowanie dwóch wersji DOM. Rozwiązaniem jest upewnienie się że najnowsza wersja
występuje wcześniej w ścieżce klas. Rozwiązanie to działa, dopóki wersje pakietów są wstecznie kompatybilne.
Modyfikacja listy
Teraz zostanie opisana nieco bardziej zaawansowana operacja — modyfikacja listy. Po pierwsze utworzona
zostanie 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>
</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óle
interesujące. Strona zostaje skompilowana przy pomocy kompilatora XMLC, z opcją –delete-class
makieta w celu automatycznego usunięcia w fazie kompilacji elementów oznaczonych jako makieta:
% xmlc –class Przegl –keep –delete-class makieta –methods przegl.html
public org.w3c.dom.html.HTMLLIElement getElementLokal();
Proszę zauważyć, że do dokumentu została dodana tylko jedna metoda dostępu — getElementLokal(),
która zwraca element <ID> oznaczony jako lokal w obiekcie HTMLLIElement. Klasa manipulacyjna
dynamicznie zmieniająca powyższy dokument jest przedstawiona w przykładzie 17.5.
Przykład 17.5.
Klasa manipulacyjna dla listy języków
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.w3c.dom.*;
import org.w3c.dom.html.*;
import org.enhydra.xml.io.DOMFormatter;
// Wyświetlenie dokumentu
DOMFormatter formatuj = new DOMFormatter(); // może być poprawiony
formatuj.write(przegl, out);
}
}
Interesującą częścią powyższego serwletu jest pętla while. Dla każdej lokalizacji serwlet klonuje element
prototypowy, zmienia tekst przechowywany przez klon i wstawia klon na końcu listy. Po pętli while serwlet
usuwa element prototypowy, pozostawiając listę pełną klonów zawierającą prawdziwe dane. Wynik
przedstawiono na rysunku 17.132.
32
Proszę pamiętać o interakcjach w ścieżce klas podczas uruchamiania powyższego przykładu. Wykorzystywane są biblioteki
potrzebne ZMLC, JDOM i przypuszczalnie samemu serwerowi. Usatysfakcjonowanie wymagań biblioteki wersji XML dla
wszystkich trzech składników bez konfliktów może wymagać sporych umiejętności w czarnej magii i nie zawsze jest
możliwe.
Rysunek 17.1. Skróty lokalizacji — angielski (USA), wietnamski i tajski
W powyższym przykładzie można dostrzec, że jedną z trudności XMLC jest obsługa nieintuicyjnego modelu
obiektów DOM. Ze stosowaniem DOM związane są pewne sztuczki. Na przykład, można dostrzec, że zawartość
String może zostać utworzona jedynie przy pomocy metody fabryki createTextNode() wywoływanej na
dokumencie, do którego ma zostać dodany tekst, a kiedy zawartość jest już gotowa, model dodawania tekstu do
dokumentu wymaga dodania go jako węzła potomnego swojego elementu i usunięcia istniejącego potomka.
Rozważa się możliwość dołączenia dużo łatwiejszego w obsłudze modelu obiektów JDOM do przyszłych wersji
XMLC. Poza tym, pakiety narzędziowe dla XMLC przejmują DOM i odchodzą od niektórych z bardziej
nieprzyjemnych jego wymagań.
Aplikacja „Narzędzia”
W celu zakończenia dyskusji nad XMLC opisany zostanie sposób tworzenia przy pomocy XMLC aplikacji
„Narzędzia” wykorzystanej wcześniej do zademonstrowania Tea i WebMacro. Na początku opisany zostanie
szablon HTML przedstawiający wspólny projekt graficzny witryny, ale nie posiadający żadnej zawartości
odwołującej się do listy narzędzi. Jest on przedstawiony w przykładzie 17.6.
Przykład 17.6
Plik szablonu aplikacji „Narzędzia”
<!-- szablon.html -->
<HTML><HEAD><TITLE>Przykładowy Tytuł</TITLE></HEAD>
<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>
<P>
<FONT FACE="Arial,Helvetica">
<SPAN ID="opis">Przykładowy opis</SPAN>
</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>
<A HREF="/hosting.html">Hosting</A>
<A HREF="/mechanizmy.html">Mechanizmy</A> <P>
</FONT>
<TABLE WIDTH=100%>
<TR>
<TD WIDTH=260 ALIGN=LEFT VALIGN=TOP>
<FONT FACE="Arial,Helvetica">
<A HREF="/wlasnosc.html">Własność</A> © 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:webmaster@servlets.com">webmaster@servlets.com</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 nazwach
tytul, 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” jest
przedstawiony 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>
<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>
<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>
</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>
<A HREF="/hosting.html">Hosting</A>
<A HREF="/mechanizmy.html">Mechanizmy</A> <P>
</FONT>
<TABLE WIDTH=100%>
<TR>
<TD WIDTH=260 ALIGN=LEFT VALIGN=TOP>
<FONT FACE="Arial,Helvetica">
<A HREF="/wlasnosc.html">Własność</A> © 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:webmaster@servlets.com">webmaster@servlets.com</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ły
wymienione na realistyczne i wypełniono obszar zawartości pewnymi prototypowymi rekordami. Oto sposób
wykorzystania tego pliku: programowo pobrane zostaną jego kluczowe elementy (tytuły, opis i prototypowy
rekord), po czym zostaną one skopiowane i umieszczone w pliku szablonu w celu utworzenia ostatecznej wersji
strony.
Po co wykorzystywać dwa pliki? Czy nie jest możliwe po prostu bezpośrednie zmodyfikowanie pliku
widoknarz.html? Jest to możliwe, ale szablon wykorzystywany jest po to, by w przypadku przyszłego
uaktualnienia nagłówka, paska bocznego lub stopki konieczne było uaktualnienie jedynie pliku szablon.html i
zmiana ta została uwidoczniona na wszystkich stronach. Innymi słowy, szablon narzuca ogólny wygląd strony.
Plik widoknarz.html jest wykorzystywany jedynie w kluczowych częściach.
Pliki HTML są poddawane kompilacji XMLC przy pomocy poniższych poleceń. Mogą wystąpić ostrzeżenia,
ponieważ HTML w nich zawarty nie jest prawidłowym XML:
% xmlc –class Szablon –keep –methods szablon.html
% xmlc –class WidokNarz –keep –methods widoknarz.html
Następnie można utworzyć serwlet który działa jako klasa manipulacyjna. Jest on przedstawiony w przykładzie
17.8.
Przykład 17.8.
Klasa manipulacyjna aplikacji „Narzędzia”
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.w3c.dom.*;
import org.w3c.dom.html.*;
import org.enhydra.xml.io.DOMFormatter;
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("");
}
// Wyświetlenie dokumentu
DOMFormatter formatuj = new DOMFormatter(); // może być poprawiony
formatuj.write(szablon, out);
}
}
Metoda init() serwletu pobiera dane narzędzi z pliku określonego w parametrze inicjacji plikNarz, przy
pomocy klasy Narzedzie z rozdziału 14, „Szkielet Tea”. Ciekawe działanie następuje w metodzie doGet().
Tworzone są egzemplarze dokumentów Szablon i WidokNarz. Następnie odnajdywany jest prototypowy
rekord narzędzia w WidokNarz oraz punkt wstawiania dla rekordów narzędzie w Szablon. Następnie
odczytywane są tytuły i opis z dokumentu WidokNarz, po czym wartości te zostają skopiowane do Szablon.
W pętli for dokonywana jest obsługa zadania tworzenia listy rekordów narzędzi. Dla każdego narzędzia do
prototypowego rekordu przypisane zostają odpowiednie wartości — na początku nazwa i komentarz, po czym
łącze. Po zmodyfikowaniu rekordu zostaje on dodany do szablonu w punkcie wstawiania. W DOM węzły
przypisane są do dokumentu, który je utworzył, tak więc zastosowana zostaje metoda importNode() w celu
udostępnienia rekordu przesuwanego z dokumentu WidokNarz do Szablon. Metoda importNode() wykonuje
głębokie kopiowanie (ponieważ przekazano jej jako drugi argument true), tak więc każda iteracja pętli for dodaje
inna kopię zawartości rekordu. Po zakończeniu pętli for punkt wstawiania zostaje usunięty z szablonu i dokument
jest wyświetlany klientowi.
Podczas uruchamiania powyższego serwletu należy pamiętać, że metoda importNode() jest nowością w
DOM Level 2, tak więc, aby mógł on zostać uruchomiony, serwer musi posiadać ścieżkę klas zawierającą DOM
Level 2 przed wszystkimi klasami DOM Level 1. Archiwum xmlc.jar zawiera DOM Level 2, tak więc można
pomyśleć, że nie ma żadnego problemu, ale niektóre serwery (włączając w to Tomcat 3.2) posiadają w swojej
domyślnej ścieżce implementację DOM Level 1 pomagającą w odczytywaniu plików web.xml. Aby powyższy
serwlet mógł pracować na takich serwerach konieczna jest edycja ścieżki klas serwera w celu upewnienia się, że
plik XMLC xmlc.jar znajduje się przed własnymi bibliotekami XML serwera. (Tomcat 3.2 automatycznie ładuje
pliki JAR w porządku alfabetycznym, tak więc konieczna może okazać się zmiana nazw plików JAR.)
Jednym z problemów związanych z XMLC jest przedstawiony przez powyższy przykład fakt, że warunkowo
dołączane bloki tekstu, których zawartość może się zmieniać, podobnie jak uwagi (Nowość! i Uaktualnienie!)
mogą sprawiać trudności przy dołączaniu do strony, ponieważ szablon może zadeklarować tylko jeden możliwy
blok tekstu do dołączenia. W powyższym przykładzie oczywiste jest, że kod Javy wie więcej niż powinien na
temat tworzenia uwag.
Miłą własnością XMLC jest automatyczne umieszczanie kodów ucieczkowych przed znakami specjalnymi takimi
jak <, > i & podczas wyświetlania ich do klienta, ponieważ wtedy narzędzie dokonujące wyświetlenia może bez
problemu rozpoznać, co jest strukturą, a co zawartością. Ta wiedza o strukturze dokumentu umożliwia również
mechanizmowi formatującemu na wykonywanie zaawansowanych zadań takich jak modyfikacja wszystkich łączy
w celu zakodowania identyfikatorów sesji, chociaż ta konkretna własność nie została jeszcze wprowadzona.
Strona wygenerowana przez powyższy serwlet wygląda identycznie jak strona utworzona przez przykłady Tea,
albo WebMacro.
Rozdział 18. JavaServer Pages
JavaServer Pages, znana powszechnie jako JSP, to technologia utworzona przez Sun Microsystems, blisko
związana z serwletami. JSP była jedną z pierwszych prób utworzenia ogólno dostępnego systemu tworzenia
zawartości opartego na serwletach. Osoby od dawna zajmujące się serwletami mogą pamiętać, że JSP została
przedstawiona po raz pierwszy na wiosnę 1998 — na tyle wcześnie, by pierwsze wydanie niniejszej książki
mogło zawierać krótki samouczek testowej wersji 0.91 JSP. Oczywiście JSP zmieniła się bardzo od tego czasu.
W tym rozdziale opisana zostanie JSP 1.1, opierająca się na Servlet API 2.2.
Podobnie jak w przypadku serwletów, Sun udostępnia specyfikację JSP (utworzoną przez grupę ekspertów
składającą się z niezależnych producentów i osób), po czym inni producenci konkurują w swoich
implementacjach tego standardu. Różnica pomiędzy JSP i innymi technologiami opartymi bezpośrednio na
serwletach polega na tym, że JSP jest specyfikacją, nie produktem i wymaga wsparcia ze strony serwera.
Większość producentów kontenerów serwletów zapewnia tę obsługę, między innymi Tomcat, w którym
mechanizm JSP nosi nazwę Jasper. JSP jest podstawowym składnikiem Java 2, Enterprise Edition (J2EE).
Jednym z głoszonych celów JSP (cytat ze specyfikacji) jest „umożliwienie oddzielenia zawartości dynamicznej i
statycznej”. Innym celem jest „umożliwienie tworzenia stron WWW, które zawierają elementy dynamiczne w
łatwy sposób, ale z maksymalną mocą i elastycznością”. JSP spełnia dobrze oba te zadania. Jednak, zgodnie z
kwestią „maksymalnej mocy” przy tworzeniu JSP, autor strony JSP zawsze posiada całkowitą kontrolę nad
systemem, i w związku z tym można powiedzieć, że JSP umożliwia oddzielenie zawartości od grafiki, ale nie
wymusza jej ani nie narzuca, jak to się dzieje w przypadku jej alternatyw. Jest to zjawisko podobne do tego, że
C++ umożliwia projektowanie obiektowe, ale nie promuje tego doskonałego sposobu tak mocno, jak to się dzieje
w przypadku Javy.
JSP jest również technologią bardzo elastyczną, i istnieje wiele sposobów jej wykorzystania. Jednym z nich jest
„skrótowy” sposób tworzenia serwletów — programista serwletów, dobrze znający Javę, może umieścić kod
Javy bezpośrednio na stronie JSP, zamiast pisać kompletny serwlet — co daje ułatwienia takie jak
wyeliminowanie wywołań wyj.println(), umożliwienie bezpośredniego wskazywania na pliki JSP i
wykorzystanie własności autokompilacji JSP, Jednak poprzez tworzenie strony JSP zamiast serwletu programista
traci pełny kontakt z kodem oraz możliwość kontroli prawdziwego środowiska uruchomieniowego (np.
rozszerzanie com.oreilly.servlet.CacheHttpServlet lub tworzenie wyniku binarnego (obrazka)).
Z tego powodu najlepiej jest pozostawić prawdziwe kodowanie zwykłym serwletom, a strony JSP wykorzystać
przede wszystkim do tworzenia logiki prezentacyjnej.
Nawet podczas zastosowania stron JSP jedynie do logiki prezentacyjnej, można je wykorzystać na wiele
sposobów. Jednym z nich jest użycie komponentów JavaBeans osadzonych na stronie. Innym tworzenie
własnych znaczników wyglądających jak HTML, ale będących tak naprawdę punktami zaczepienia do
wspomagającego kodu Javy. Jeszcze innym jest wysyłanie wszystkich żądań do serwletu, który wykonuje logikę
biznesową, po czym przesyła żądanie do JSP tworzącej stronę przy pomocy mechanizmu
RequestDispatcher. Technika ta jest często nazywana architekturą Model 2, która to nazwa pochodzi od
specyfikacji JSP 0.92. Istnieją również inne mechanizmy noszące dziwne nazwy takie jak Model 1½, Model 2½,
czy Model 2+1. Niemożliwe jest wskazanie najlepszej techniki stosowania JSP.
W niniejszym rozdziale opisane zostaną różne sposoby wykorzystania JSP, począwszy od „skrótu do serwletu” a
skończywszy na własnych znacznikach. Opis będzie krótki, ale powinien dostarczyć podstaw do porównania JSP
z jej alternatywami. Nie zostaną opisane wszystkie opcje architektury, a zamiast tego nacisk zostanie położony na
szczegóły techniczne. Opcje architektury i większa ilość informacji na temat JavaServer Pages jest dostępna na
stronie głównej JSP pod adresem http://java.sun.com/products/jsp („ściąga” składni jest dostępna pod adresem
http://java.sun.com/products/jsp/syntax.pdf) oraz w książce „JavaServer Pages” autorstwa Hansa Bergstena
(O'Reilly).
Sesja użytkownika.
ServletContext application
Aplikacja WWW.
javax.servlet.jsp.PageContext pageContext
33
Przed rozpoczęciem opisu warto zauważyć, że umieszczanie kodu Javy na stronie JSP jest uważane za działanie w złym
stylu. Uważane za lepsze zaawansowane zastosowania JSP zostaną opisane w dalszej części rozdziału.
</BODY></HTML>
Przykładowy wynik uruchomienia powyższego programu przedstawiony jest na rysunku 18.1.
Rysunek 18.1.
Powitanie przy pomocy
JavaServer Pages
Zasady działania
Jak działa JSP? Poza obszarem widoczności serwer automatycznie tworzy, kompiluje, ładuje i uruchamia
specjalny serwlet tworzący zawartość strony, jak przedstawiono na rysunku 18.2. Można myśleć o tym
specjalnym serwlecie jak o serwlecie roboczym, działającym w tle. Statyczne części strony HTML są tworzone
przez serwlet roboczy przy pomocy ekwiwalentów wywołań wyj.println(), podczas gdy części dynamiczne
są dołączane bezpośrednio. Na przykład, serwlet przedstawiony w przykładzie 18.2 mógłby być serwletem
roboczym dla witaj1.jsp działającym na serwerze Tomcat34.
Rysunek 18.2.
Generowanie stron JavaServer Pages
Przykład 18.2.
Generowany automatycznie serwlet roboczy dla witaj1.jsp
import javax.servlet.*;
34
Osoby zainteresowane zobaczeniem prawdziwego kodu źródłowego serwletu dla strony JSP, w większości przypadków
mogą odnaleźć go w tymczasowym katalogu określonym w atrybucie kontekstu javax.servlet.context.tempdir
(proszę zobaczyć rozdział 4, „Pobieranie informacji”. Kiedy odnajdzie się prawdziwe źródło serwletu można zobaczyć, że
jest ono o wiele bardziej skomplikowane niż to przedstawione w tym miejscu.
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.*;
static {
}
public _0002fwitaj_00031_0002ejspwitaj1_jsp_0( ) {
}
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();
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
Wyrażenia i deklaracje
Oprócz skryptletów, JavaServer Pages pozwala na umieszczanie kodu na stronie przy pomocy wyrażeń i
deklaracji. Wyrażenie JSP rozpoczyna się znacznikiem <%=, a kończy %>. Każde wyrażenie Javy pomiędzy tymi
dwoma znacznikami jest oceniane, jego wynik konwertowany do łańcucha String, a tekst dołączany
bezpośrednio do strony. Technika ta eliminuje nieporządek wywołania wyj.println(). Na przykład, <%=
buu => dołącza wartość zmiennej buu.
Deklaracja rozpoczyna się od <%!, a kończy na %>. Pomiędzy tymi znacznikami można umieścić dowolny kod
serwletu, który powinien pozostać poza metodą usługową serwletu. Zadeklarować można zmienne statyczne lub
egzemplarzowe, albo też zdefiniować nowe metody. Przykład 18-3 przedstawia stronę JSP, która wykorzystuje
deklarację do zdefiniowania metody pobierzNazwa() oraz wyrażenie ją wyświetlające. Komentarz na
początku pliku pokazuje, że komentarze JSP oznaczane są znacznikami <%-- 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";
Instrukcje
Instrukcja JSP pozwala stronie JSP na kontrolowanie pewnych aspektów jej serwletu roboczego. Instrukcje mogą
zostać wykorzystane do nakazania serwletowi roboczemu ustawienia jego typu zawartości, importowania
pakietu, kontroli buforowania wyświetlania i zarządzania sesją, rozszerzania różnych superklas oraz specjalnej
obsługi błędów. Instrukcja może nawet określać wykorzystanie języka skryptowego innego niż Java.
Składnia instrukcji musi zawierać jej nazwę oraz parę nazwa-wartość atrybutu, a całość musi być zwarta
pomiędzy znacznikami <%@ i %>. Cudzysłowy zamykające wartość atrybutu są obowiązkowe:
<%@ nazwaInstrukcji nazwaAtrybutu="wartoscAtrybutu" %>
Instrukcja page pozwala JSP na kontrolowanie generowanego serwletu poprzez ustawienie specjalnych
atrybutów wymienionych poniżej:
contentType
Określa typ zawartości generowanej strony. Na przykład:
<%@ page contentType="text/plain" %>
Wykorzystanie instrukcji
Przykład 18.4 przedstawia stronę JSP noszącą nazwę bladRob.jsp, która wykorzystuje kilka instrukcji. Po
pierwsze ustawia atrybut session dyrektywy page na false, ponieważ strona nie wykorzystuje obiektu sesji
i w związku z tym nie istnieje potrzeba, by serwer tworzył sesję. Następnie ustawia atrybut errorPage na /
bladBierz.jsp, tak więc jeżeli na stronie wydarzy się niewyłapany wyjątek, strona bladBierz.jsp będzie
obsługiwać wyświetlanie komunikatu o błędzie. Główna część strony jest prosta. Wyzwala ona wyjątek w celu
wywołania zachowania errorPage. (Powód wykorzystania sprawdzenia if zostanie opisany później.)
Przykład 18.4.
Zły chłopiec
<%-- bladRob.jsp --%>
<HTML>
<HEAD><TITLE>Błąd: <%= exception.getClass().getName() %></TITLE></HEAD>
<BODY>
<H1>
<%= exception.getClass().getName() %>
</H1>
<PRE>
<%= ServletUtils.getStackTraceAsString(exception) %>
</PRE>
</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.
Rysunek 18.3.
Własna strona błędów
Tak więc dlaczego w bladRob.jsp występuje wywołanie System.currentTime Millis()? Bierze się to z
faktu, że strony JSP muszą przechowywać wszystkie puste miejsca z tekstu dokumentu. Pozwala to na
wykorzystanie JSP do tworzenia nie tylko HTML, ale także XML, w którym puste miejsca mogą być niezwykle
ważne. Niestety zachowanie wszystkich pustych miejsc oznacza, że prosty skryptlet:
<% throw new Exception("uups"); %>
musi wygenerować kod w celu wyświetlenia nowej linii po wywołaniu błędu — kod, który nigdy nie zostanie
osiągnięty! Kompilacja tego skryptu na Tomcat-cie, generuje następujący komunikat o błędzie.
org.apache.jasper.JasperException: unable to compile class for
JSPC: \engines\jakarta-tomcat\work\localhost_8080%2Fjsp\
-0001fbladRob_0002ejspbladRob-jsp-3.java:75:
Statement not reached.
out.write("\r\n");
^
Poprzez dodanie sprawdzenia, czy System.currentTimeMillis() jest większe niż zero, kompilator
przyjmuje, że istnieje możliwość, że wyjątek nie zostanie wywołany i w związku z tym nie wystąpi błąd
kompilacji. Podobne problemy mogą pojawiać się w przypadku wyrażeń return i throw wewnątrz
skryptletów.
Tak naprawdę istnieje wiele sztuczek powiązanych z wykorzystaniem skryptletów w JSP. Jeżeli przypadkowo
napisze się skryptlet zamiast wyrażenia (poprzez pominięcie znaku równości), zadeklaruje statyczną zmienną
wewnątrz skryptletu (gdzie zmienne te nie są dozwolone), zapomni przecinka (nie są konieczne w wyrażeniach,
ale konieczne w skryptletach) lub napisze się cokolwiek nie będącego idealnym kodem Javy, przypuszczalnie
otrzyma się dziwny komunikat o błędzie, ponieważ kompilator działa na wygenerowanym kodzie Javy, nie na
pliku JSP. Aby zademonstrować problem, proszę wyobrazić sobie, że w pliku bladBierz.jsp <%= 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.
JSP i JavaBeans
Jedną z najbardziej interesujących i potężnych metod wykorzystania JavaServer Pages jest współpraca z
komponentami JavaBeans. JavaBeans to klasy Javy zaprojektowane do wielokrotnego wykorzystania, a nazwy
ich metod i zmiennych tworzone są według pewnej konwencji, która daje im dodatkowe możliwości. Mogą one
być osadzane bezpośrednio na stronie Javy przy pomocy znacznika <jsp:useBean>, czyli działania
nazywanego przez JSP akcją. Komponent JavaBean może wykonywać dobrze zdefiniowane zadanie (zapytanie
bazy danych, łączenie się z serwerem poczty, utrzymywanie informacji na temat klienta itp.), którego wynik jest
dostępny stronie JSP poprzez proste metody dostępu. Większa ilość informacji na temat JavaBeans znajduje się
pod adresem http://java.sun.com/bean i w książce „Developing JavaBeans” autorstwa Roberta Englandera
(O'Reilly).
Różnica pomiędzy komponentem JavaBeans osadzonym na stronie JSP i każdą inną dodatkową klasą
wykorzystywaną przez wygenerowany serwlet jest taka, że serwer WWW może traktować JavaBeans osadzone
na stronie w specjalny sposób. Na przykład, serwer może automatycznie skonfigurować komponent (zwłaszcza
zmienne egzemplarza) przy pomocy wartości parametrów w żądaniu klienta. Innymi słowy, jeżeli żądanie
zawiera parametr nazwa, a serwer odkryje poprzez introspekcję (technikę, w której metody i zmienne klasy Javy
mogą zostać określone programowo w czasie uruchomienia), że komponent posiada parametr nazwa i metodę
ustawNazwe(String nazwa), serwer może automatycznie wywołać pobierzNazwe() z wartością
parametru nazwa. Nie jest konieczne wykorzystanie metody getParameter().
Serwer może także automatycznie zarządzać zakresem komponentu. Komponent może zostać przypisany do
konkretnej strony (gdzie jest wykorzystany raz i zniszczony), konkretnego żądania (podobne do zakresu strony
poza tym, że komponent może przetrwać wewnętrzne wywołania dołączenia i przekazania), sesji klienta (w
którym komponent jest automatycznie udostępniany, kiedy tan sam klient połączy się ponownie) lub aplikacji (w
którym to zakresie ten sam komponent jest udostępniany całej aplikacji WWW).
<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 JSP
działa jedynie jako szablon.
Kod WitajKomp jest przedstawiony w przykładzie 18.7. Plik jego klasy powinien zostać umieszczony w
standardowym katalogu klas wspierających (WEB-INF/classes).
Przykład 18.7.
Klasa WitajKomp
public class WitajKomp {
private String nazwa = "świecie";
Istnieje jedna kwestia, na którą należy zwrócić uwagę: na niektórych serwerach (włączając w to Tomcat 3.2),
jeżeli istnieje komponent o zakresie session lub application, a implementacja klasy komponentu zostanie
zmieniona, w późniejszych żądaniach może pojawić się ClassCastException. Wyjątek ten pojawia się,
ponieważ kod generowanego serwletu musi wywołać egzemplarz komponentu, który pobierany jest z sesji lub
aplikacji, a typ starego komponentu przechowywany w sesji lub aplikacji nie pasuje do spodziewanego typu
nowego komponentu. Najprostszym rozwiązaniem jest ponowne uruchomienie serwera.
Dołączenia i przekazania
Poprzez połączenie instrukcji i akcji, JSP umożliwia obsługę dołączeń i przekazań. Istnieją dwa typy dołączeń i
jedno przekazań. Pierwszym dołączeniem jest instrukcja include:
<%@ include file="SciezkaDoPliku" %>
Powyższe dołączenie następuje w czasie tłumaczenia, co oznacza, że następuje podczas tworzenia serwletu
działającego w tle. Cała zawartość zewnętrznego pliku jest dołączana tak, jak gdyby była bezpośrednio
umieszczona na stronie JSP. Programiści C mogą dostrzec jej bliskie podobieństwo do #include. Ścieżka do
pliku jest zakotwiczona w katalogu macierzystym kontekstu (tak więc /indeks.jsp odnosi się do index.jsp
aktualnej aplikacji WWW), a ścieżka nie może sięgać poza katalog macierzysty kontekstu (proszę nie próbować
../../../innyKontekst). Zawartość wewnątrz pliku jest zazwyczaj fragmentem strony, nie całą stroną, tak więc
można pomyśleć o zastosowaniu rozszerzenia pliku .inc lub .jin w celu wskazania tego faktu. Poniższa strona
JSP wykorzystuje kilka instrukcji dołączenia w celu utworzenia strony ze zbioru elementów (elementy nie są
pokazane):
<%@ 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 do
klienta. Niewidocznie akcja <jsp:include> generuje kod, który wywołuje metodę include()
RequestDispatcher, tak więc do <jsp:include> stosują się te same zasady wykonania, co do include()
— dołączana strona musi być dynamiczna, nie może ustawiać kodu stanu, nie może ustawiać żadnych
nagłówków, a ścieżka strony jest zakotwiczona w katalogu macierzystym kontekstu35. Wymagany jest atrybut
flush, który w JSP 1.1 musi być ustawiony na true. Wskazuje to na konieczność opróżnienia bufora
odpowiedzi przed dołączeniem. Zważywszy na możliwości Servlet API 2.2, wartość false nie może być
obsługiwana; spodziewane jest, że JSP 1.2 utworzone na podstawie Servlet API 2.3 będzie zezwalać na wartość
false.
Jako przykład, poniższa akcja <jsp:include> dołącza zawartość generowaną przez wywołanie strony
powitanie.jsp. Strona powitanie.jsp może wyglądać podobnie do witaj1.jsp poza tym, że pożądane może być
usunięcie otaczającego kodu HTML sprawiającego, że witaj1.jsp jest kompletną stroną HTML.
<jsp:include page="/powitanie.jsp" />
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.
Akcja <jsp:include> może opcjonalnie zawierać w swojej głównej części dowolną ilość znaczników
<jsp:param>. Znaczniki te dodają parametry do dołączanego żądania. Na przykład, poniższa akcja
przekazuje powitaniu domyślną nazwę:
<jsp:include page="/powitanie.jsp">
<jsp:param name="domyslnaNazwa" value="Nowy użytkownik" />
</jsp: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ły
zbuforowany wynik zostaje wyczyszczony, a jeżeli stało się to wcześniej, system zgłasza wyjątek. Poniższa akcja
przekazuje żądanie specjalnej stronie, jeżeli użytkownik nie jest zalogowany:
<% 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ć te
własności do stworzenia aplikacji wyświetlającej narzędzia. Na stronie JSP zostanie osadzony komponent dający
stronie dostęp do informacji na temat narzędzi i pozwalający na traktowanie strony jako przeglądarki tych
danych. Przykład 18.8 przedstawia stronę JSP o nazwie widoknarz.jsp.
Przykład 18.8.
Aplikacja przeglądająca narzędzia przy pomocy JSP
<%-- 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ę.";
%>
<%
Narzedzie[] narzedzia = plikNarz.pobierzNarz(request.getParameter("stan"));
<H3>
<%= narzedzie.nazwa %>
</H3>
<A HREF="<%= narzedzie.domURL %>"><%= narzedzie.domURL %></A><BR>
<% } %>
<%@ include file="/stopka.jsp" %>
Na początku wewnątrz skryptletu, jako łańcuchy String, definiowane są tytuły i opis strony. Następnie
wykorzystana jest instrukcja include w celu dołączenia standardowej zawartości nagłówka z pliku naglowek.jsp
oraz standardowej zawartości stopki (na końcu pliku) z pliku stopka.jsp. Instrukcja include umieszcza
zawartość plików nagłówka i stopki do pliku podczas fazy tłumaczenia, tak więc zmienne tytul, tytul2 i
opis są widoczne wewnątrz dołączanych plików, i plik naglowek.jsp wykorzystuje te zmienne. Pliki
naglowek.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 --%>
<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>
<P>
<FONT FACE="Arial,Helvetica">
</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>
<A HREF="/hosting.html">Hosting</A>
<A HREF="/mechanizmy.html">Mechanizmy</A> <P>
</FONT>
<TABLE WIDTH=100%>
<TR>
<TD WIDTH=260 ALIGN=LEFT VALIGN=TOP>
<FONT FACE="Arial,Helvetica">
<A HREF="/wlasnosc.html">Własność</A> © 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:webmaster@servlets.com">webmaster@servlets.com</a>
</FONT></TD>
</TR>
</TABLE>
</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Następnie na stronie widoknarz.jsp wykorzystana zostaje akcja <jsp:useBean> w celu osadzenia
komponentu JavaBean o nazwie narzkomp, będącego egzemplarzem klasy NarzKomp. Komponent posiada
zakres application, tak więc pojedynczy egzemplarz danych zostanie automatycznie zachowany w
kontekś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żeli
komponent mógłby uzyskać bezpośredni dostęp do zmiennej aplikacji w celu odczytania tych wartości, ale
komponenty mają dostęp jedynie do informacji udostępnionych przez stronę JSP. Eleganckim rozwiązaniem
byłaby możliwość akceptowania przez komponent nazwy pliku jako argumentu jego konstruktora, ale
egzemplarze 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 komponentu
bezpoś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ło
wykorzystanie pojedynczych nawiasów dookoła wartości, ponieważ podwójne cudzysłowy zostały zastosowane
wewnątrz wyrażenia. Pozwala to na uniknięcie błędów analizatora JSP. Możliwe jest również wyłączenie
podwójnych cudzysłowów z wyrażenia przy pomocy lewych ukośników. Po drugie, jeżeli parametr inicjacji nie
istnieje 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, na
wypadek 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 do
wartoś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ętli
for dokonującej iteracji na tablicy. Wewnątrz pętli for umieszczona zostaje logika wyświetlająca każde osobne
narzędzie. Wyrażenia zostają wykorzystane do wyświetlenia prostych wartości takich, jak nazwa narzędzia, URL
i komentarz. Skryptlety służą do wyświetlania uwag na temat stanu — Nowość lub Uaktualnienie.37 Kod klasy
NarzKomp przedstawiony jest w przykładzie 18.11. Kod klasy Narzedzie jest identyczny jak ten
przedstawiony 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łasza
wyjątek.
37
Z uwagi na zachowujące puste miejsca zasady JSP należy być ostrożnym podczas tworzenia wyrażeń if/else wewnątrz
skryptletów. Poniższy kod(narzedzie.czyNowy(45))
<% if nie działałby: { %>
<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'.
import java.util.*;
public 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 po
skonstruowaniu 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. Metoda
pobiera 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 ostrzega
użytkownika przed problemem.
Pozycja <taglib-uri> musi być ścieżką URI, tak więc jej prawidłowe wartości to struts,
odnosi się do prawdziwego umiejscowienia zasobów, ale w powyższym przykładzie została zastosowana
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 „URI
poszukiwań” 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 tym
przypadku, 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>.
Aplikacja „Narzędzia” wykorzystująca bibliotekę własnych znaczników
Wykorzystanie znaczników Struts <iterate> i <property> może uprościć stronę widoknarz.jsp. Przy
okazji 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.12
przedstawia serwlet kontrolujący.
Przykład 18.12.
Serwlet kontrolujący Model 2
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
<%
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ę.";
%>
<H3>
<%-- Automatycznie wartości opuszczane przez HTML --%>
<struts:property name="narzedzie" property="nazwa" />
</H3>
<A HREF="<struts:property name="narzedzie" property="domURL"/>">
<struts:property name="narzedzie" property="domURL"/></A><BR>
</struts:iterate>
dodatkowe
W każdym domu znajduje się szuflada na szpargały — szuflada załadowana do pełna rzeczami, które nie do
końca pasują do żadnej zorganizowanej szuflady, ale nie mogą też zostać wyrzucone, ponieważ kiedy są
potrzebne, to są naprawdę potrzebne. Niniejszy rozdział pełni funkcję takiej szuflady. Zawiera spory zestaw
przydatnych przykładów serwletów i porad, które nie pasują do żadnego innego miejsca. Zawarto tu serwlety,
które analizują parametry, wysyłają pocztę elektroniczną, uruchamiają programy, wykorzystują mechanizmy
wyrażeń regularnych i rdzenne metody oraz działają jako klienty RMI. Rozdział ten zawiera również
demonstrację technik usuwania błędów, a także pewne sugestie dotyczące poprawy wydajności serwletów.
Analiza parametrów
Osoby, które próbowały tworzyć własne serwlety podczas lektury niniejszej książki przypuszczalnie zauważyły,
że pobieranie i analiza parametrów żądania może być dość nieprzyjemnym zajęciem, zwłaszcza jeżeli parametry
te muszą zostać przekonwertowane do formatu innego niż String. Na przykład, konieczne jest odczytanie
parametru count i zmiany jego wartości na format int. Poza tym, obsługa warunków błędów powinna być
wykonywana przez wywołanie obslugaBezCount(), jeżeli count nie jest podany, a obslugaZlyCount
(), jeżeli count nie może zostać przekształcony na liczbę całkowitą. Wykonanie tego działania przy pomocy
standardowego Servlet API wymaga następującego kodu:
int count;
Kod ParameterParser
Klasa ParameterParser zawiera ponad tuzin metod, które zwracają parametry żądania — dwie dla każdego
rdzennego typu Javy. Posiada również dwie metody getStringParameter() w przypadku, gdyby
konieczne było pobranie parametru w jego surowym formacie String. Kod ParameterParser jest
przedstawiony w przykładzie 19.1. Wyjątek ParameterNotFoundExceptoion znajduje się w przykładzie
19.2.
Przykład 19.1.
Klasa ParameterParser
package com.oreilly.servlet;
import java.io.*;
import java.util.*;
import javax.servlet.*;
public ParameterNotFoundException() {
super();
}
public ParameterNotFoundException(String s) {
super(s);
}
}
Wysyłanie poczty elektronicznej
Czasami wysłanie przez serwlet wiadomości jest konieczne, a czasami jest to po prostu ułatwienie. Na przykład,
proszę sobie wyobrazić serwlet otrzymujący dane z formularza użytkownika, który umieszcza tam swoje
komentarze. Serwlet ten może chcieć przesłać dane formularza na listę dystrybucyjną zainteresowanych stron.
Albo proszę sobie wyobrazić serwlet, który napotyka niespodziewany problem i może wysłać administratorowi
pocztą stronę prosząc o pomoc.
Serwlet może wykorzystać do wysłania poczty elektronicznej jedną z czterech metod:
• Może sam zarządzać szczegółami — nawiązać połączenie przez zwykły port z serwerem poczty i
wysyłając wiadomość poprzez protokół pocztowy niskiego poziomu, zazwyczaj tak zwany Simple Mail
Transfer Protocol (Prosty Protokół Transferu Poczty — SMTP).
• Może uruchomić z wiersza poleceń zewnętrzny program pocztowy, jeżeli system serwera posiada taki
program.
• Może wykorzystać interfejs JavaMail, zaprojektowany do pomocy w skomplikowanej obsłudze,
tworzeniu i przetwarzaniu poczty (proszę zobaczyć http://java.sun.com/products/javamail).
• Może wykorzystać jedną z wielu dostępnych bezpłatnie klas pocztowych, które zmieniają szczegóły
wysyłania poczty na proste i łatwe do wywołania metody.
W przypadku nieskomplikowanego wysyłania poczty poleca się ostatni sposób ze względu na jego prostotę. Dla
bardziej skomplikowanych zastosowań rekomendowany jest JavaMail — zwłaszcza w przypadku serwletów
działających pod kontrolą serwera J2EE, w którym na pewno dostępne są dwa pliki JAR wymagane przez
JavaMail.
import com.oreilly.servlet.MailMessage;
import com.oreilly.servlet.ParameterParser;
import com.oreilly.servlet.ServletUtils;
try {
MailMessage wiad = new MailMessage(); // przyjęcie, że localhost
wiad.from(from);
wiad.to(to);
wiad.setSubject("Komentarz klienta");
glowny glowny.tytul2();
glowny.println("---");
body.println("Wysłany przez " + HttpUtils.getRequestURL(zad));
wiad.sendAndClose();
wyj.println("Dziękujemy za komentarz...");
}
catch (IOException w) {
wyj.println("Wystąpił problem z obsługą komentarza...");
log("Wystąpił problem z wysyłaniem wiadomości", e);
}
}
}
Serwlet po pierwsze określa adresy „Od” i „Do” wiadomości. Domyślne wartości są ustawione w zmiennych
FROM i TO, chociaż wysyłany formularz może zawierać (najprawdopodobniej ukryte) pola określające
alternatywy adresów Od i Do. Serwlet następnie rozpoczyna tworzenie wiadomości pocztowej SMTP. Łączy się
z lokalnym komputerem i adresuje wiadomość. Następnie ustawia temat i wstawia dane formularza do
zawartości, ignorując zmienne FROM i TO. Na końcu wysyła wiadomość i dziękuje użytkownikowi za komentarz.
Jeżeli wystąpi problem, informuje o tym użytkownika i zapisuje wyjątek w dzienniku zdarzeń.
Klasa MailMessage aktualnie nie obsługuje załączników, ale ich obsługa może zostać w prosty sposób
dodana. W przypadku zastosowań bardziej zaawansowanych, dobrą alternatywą jest JavaMail.
import com.oreilly.servlet.*;
import org.apache.regexp.*;
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>");
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.
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 powinny
zostać pobrane poprzez odszukanie dodatkowych informacji ścieżki. Oznacza to, że powyższy serwlet powinien
zostać wywoływany podobnie do http://localhost:8080/servlet/Lacza/http://www.servlets.com. Następnie serwlet
odczytuje zawartość tego URL-a przy pomocy klasy HttpMessage i przechowuje stronę jako String. Dla
bardzo 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 istnieje
już znacznik <BASE>, to można go wykorzystać, tak więc następuje jego poszukiwanie przy pomocy wyrażenia
regularnego <base[^>]*>. Łańcuch ten zostaje przekazany do konstruktora org.apache.regexp.RE
razem ze znacznikiem wskazującym na niezależność od wielkości liter. Ta składnia wyrażenia regularnego jest
standardowa i identyczna jak w Perlu. Nakazuje ona wyszukanie tekstu <base>, po którym następuje dowolna
ilość znaków nie będących >, po których następuje tekst >. Jeżeli właściwy tekst zostanie odnaleziony, jest on
pobierany 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 prostego
wyrażenia regularnego http://.*/. Nakazuje ono dopasowanie do tekstu http://, po którym następuje
dowolna ilość znaków kończąca się /. Wzór .* odczytuje maksymalną możliwą ilość znaków, która nie koliduje
z innymi warunkami wyrażenia regularnego (terminologia wyrażeń regularnych nazywa to działanie
zachłannym), tak więc wyrażenie to zwraca wszystko łącznie z wiodącym ukośnikiem. Jeżeli ukośnik taki nie
wystę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 przekazanym
serwletowi.
Na końcu ze strony pobrane zostają znaczniki <A HREF>, przy pomocy stosunkowo skomplikowanego
wyrażenia regularnego <a\s+[^<]*</a\s*>. (Można dostrzec, że w kodzie znaki \ zostały wyłączone przy
pomocy dodatkowego znaku \.) Nakazuje to poszukiwanie tekstu <a, po którym następuje dowolna liczba
znaków, które nie są <, następnie tekst </a, dalej dowolna ilość pustego miejsca, potem >. Jeżeli złoży się te
informacje, 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 dopasowaniem
znaczników takich jak <APPLET>. Po odnalezieniu każdego pasującego tekstu jest on wyświetlany na liście, a
poszukiwanie 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 jest
dostępna w dokumentacji dołączonej do biblioteki.
Uruchamianie programów
Czasami serwlet musi wykonać program zewnętrzny. Jest to generalnie działanie ważne w sytuacjach, w których
zewnętrzny program oferuje funkcjonalność niedostępną przy pomocy samej Javy. Na przykład, serwlet może
wywołać zewnętrzny program w celu manipulowania obrazkiem lub sprawdzenia stanu serwera. Uruchamianie
programów zewnętrznych utrudnia przenośność i zachowanie bezpieczeństwa. Jest to działanie, które powinno
być 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 na
wywoływanie metody exec() java.lang.Runtime.
Finger
Program finger zapytuje (przypuszczalnie zdalny) komputer o listę aktualnie zalogowanych użytkowników. Jest
on dostępny w praktycznie wszystkich systemach Uniksowych i niektórych komputerach z Windows NT
posiadających zdolność obsługi sieci. Program finger działa przez połączenie z demonem finger (zazwyczaj
noszącym nazwę fingerd), który nasłuchuje na porcie 79. finger wykonuje swoje żądanie do fingerd przy pomocy
własnego protokołu „finger”, a fingerd odpowiada właściwą informacją. Większość systemów Uniksowych
posiada fingerd, ale duża część skoncentrowanych na bezpieczeństwie administratorów wyłącza go w celu
ograniczenia ilości informacji, które mogłyby zostać wykorzystane podczas prób włamania. Ciągle ciężko jest
odnaleźć fingerd w systemach Windows. Uruchomiony bez żadnych argumentów, finger zgłasza wszystkich
użytkowników na lokalnym komputerze. Na przykład:
% finger
Login Name TTY Idle When Office
jkowalski Jan Kowalski q0 3:13 Thu 12:13
anowak Anna Nowak q1 Thu 12:18
Uruchomiony z nazwą użytkownika jako argumentem, finger zwraca informacje na temat tego użytkownika:
% finger jkowalski
Login name: jkowalski In real life: Jan Kowalski
Directory: /usr/people/jkowalski Shell: /bin/tcsh
On since Jan 1 12:13:28 on ttyq0 from :0.0
3 hours 13 minutes Idle Time
On since Jan 1 12:13:28 on ttyq2 from :0.0
Uruchomiony z nazwą komputera jako argumentem, finger zwraca informacje o wszystkich użytkownikach
określonego komputera. Zdalny komputer musi posiadać działający fingerd:
% finger @fikcja
Login Name TTY Idle When Office
ppawlak Piotr Pawlak q0 17d Mon 10:45
I, oczywiście, jeżeli uruchomiony z nazwą użytkownika i nazwą komputera, finger zwraca informacje na temat
określonego użytkownika na określonym komputerze:
% finger ppawlak@fikcja
[fikcja.calkowita.com]
Login name: ppawlak In real life: Piotr Pawlak
Directory: /usr/people/ppawlak Shell: /bin/tcsh
On since Dec 15 10:45:22 on ttyq0 from :0.0
17 days Idle Time
import com.oreilly.servlet.ServletUtils;
import com.oreilly.servlet.ServletUtils;
SerwerGodziny godzina;
try {
Registry rejestr =
LocateRegistry.getRegistry(pobierzRejestrKomp(), pobierzRejestrPort());
return (SerwerGodziny)registry.lookup(pobierzRejestrNazwa());
}
catch (Exception w) {
getServletContext().log(w, "Problem z pobraniem odwołania do
SerwerGodziny");
return null;
}
}
Usuwanie błędów
Faza 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, ale
utrudnia 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ą na
mocno wielowątkowych i ogólnie złożonych serwerach WWW. Poniżej umieszczono kilka rad i sztuczek
pomocnych przy usuwaniu błędów.
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();
}
}
}
Socket s;
public WatekObslugi(Socket s) {
this.s = s;
}
40
Oczywiście jeżeli sprawdzenia dokonuje osoba nie znająca struktury HTTP, to ona może się pomylić, W tym przypadku
warto jest przeczytać wprowadzenie do HTTP w rozdziale 2, „Podstawy serwletów HTTP” oraz książkę „HTTP Pocket
Reference” autorstwa Clintona Wonga (O'Reilly).
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ży
upewnić się, że na wybranym porcie nie nasłuchuje żaden inny serwer. Po uruchomieniu serwera można do niego
przekierować żą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 HTTP
przeglą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ć przez
naciśnięcie przycisku Stop.
Poniżej przedstawiono przykładowy wynik uruchomienia PatrzPort, który pokazuje szczegóły żądania GET
na http://localhost:8080:
GET / HTTP 1.0
Connection: Keep-Alive
User-Agent: Mozilla/4.7 [pl] (X11; U; IRIX 6.2 IP22)
Pragma: no-cache
Host: localhost:8080
Accept: image.gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Cookie: JSESSIONIN=To1010mC10934500694587412At
Wuery String:
Nazwa=wartosc
Request Parameters
Nazwa (0): wartosc
Connection closed by foreign host.
Jak często się to zdarza, Windows zachowuje się nieco inaczej, niż jest to pożądane. Domyślny program
telnet.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,
programiści Windows mogą wykorzystać zachowujący się bardziej odpowiednio program Javy przedstawiony w
przykładzie 19.9.
Przykład 19.9.
Inny sposób łączenia się z serwerem WWW
import java.io.*;
import java.net.*;
import java.util.*;
try {
// Otwarcie portu do serwera
Socket s = new Socket(host, port);
InputStream in;
Socket s;
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
server.flush();
}
}
catch (Exception w) {
w.printStackTrace();
}
}
}
Powyższy program KlientHttp działa podobnie do telnet:
% java KlientHttp localhost 8080
Połączono… Proszę wprowadzić żądanie HTTP
------------------------------------------
GET /index.html HTTP/1.0
Ostatnie wskazówki
Jeżeli wszystkie powyższe porady nie pomogły w odnalezieniu i usunięciu błędu, proszę obejrzeć poniższe
ostateczne wskazówki dotyczące suwania błędów z serwletów:
• Proszę wykorzystać System.getProperty("java.class.path") przy pomocy serwletu w
celu uzyskania pomocy w rozwiązywaniu problemów związanych ze ścieżką klas. Ponieważ serwlety są
często uruchamiane na serwerach WWW z osadzonymi wirtualnymi maszynami Javy, trudne może być
dokładne określenie ścieżki klas poszukiwanej przez JVM. Określić to może właściwość
java.class.path.
• Proszę pamiętać, że klasy odnalezione w bezpośredniej ścieżce klas serwera
(katalog_macierzysty/classes) przypuszczalnie nie są przeładowywane podobnie jak, w większości
serwerów niezwiązanych z serwletami, klasy wspierające w katalogu klas aplikacji WWW (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.
• Należy zażądać od przeglądarki pokazania surowej zawartości wyświetlanej strony. Może to pomóc w
zidentyfikowaniu problemów z formatowaniem. Zazwyczaj jest to opcja w menu Widok.
• Należy upewnić się, że przeglądarka nie przechowuje w pamięci podręcznej wyników poprzedniego
żądania poprzez wymuszenie pełnego przeładowania strony. W przeglądarce Netscape Navigator,
należy zastosować Shift-Reload; W Internet Explorer należy zastosować Shift-Refresh.
• Jeżeli pomija się wersję init(), która pobiera ServletConfig należy upewnić się, że pomijająca
metoda od razu wywołuje super.init(config).
Poprawa wydajności
Serwlety poprawiające wydajność wymagają nieco innego podejścia niż aplikacje lub aplety Javy wykonujące to
samo działanie. Powodem tego jest fakt, że JVM uruchamiająca serwlety zazwyczaj obsługuje kilkadziesiąt,
jeżeli nie kilkaset wątków, z których każdy uruchamia serwlet. Te współistniejące serwlety muszą dzielić się
zasobami JVM w sposób inny, niż zwykłe aplikacje. Tradycyjne sztuczki poprawiające wydajność oczywiście
działają, ale posiadają mniejszy wpływ, kiedy zostają wykorzystane w systemie wielowątkowym. Poniżej
przedstawiono niektóre sztuczki najczęściej wykorzystywane przez programistów serwletów.
Nie łączyć
Należy unikać łączenia kilku łańcuchów. Zamiast StringBuffer poleca się zastosowanie metody append
(). To również zawsze była dobra rada, ale w przypadku serwletów szczególnie pociągające jest napisanie kodu
przygotowującego łańcuch do późniejszego wyświetlania w następujący sposób:
String wyswietl;
wyswietl += "<TITLE>";
wyswietl += "Witaj, " + uzyt;
wyswietl += "</TITLE>";
Chociaż powyższy kod wygląda miło i sprawnie, w trakcie uruchomienia działa, jak gdyby wyglądał mnie więcej
tak jak poniżej, z nowymi StringBuffer i String tworzonymi w każdej linii:
String wyswietl;
wyswietl = new StringBuffer().append("<TITLE>").toString();;
wyswietl = new StringBuffer(wyswietl).append("Witaj, ").toString();
wyswietl = new StringBuffer(wyswietl).append(uzyt).toString();
wyswietl = new StringBuffer(wyswietl).append("</TITLE>").toString();
Kiedy wydajność jest ważną kwestią, należy przepisać oryginalny kod tak, aby wyglądał jak poniższy, tak aby
utworzone zostały tylko pojedyncze StringBuffer i String:
StringBuffer buf = new StrngBuffer();
buf.append("<TITLE>");
buf.append("Witaj, "").append(uzyt);
buf.append("</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 w
serwlecie wydłuża czas odpowiedzi serwletu. Ponieważ ten sam egzemplarz serwletu może obsługiwać wiele
współbieżnych żądań, musi, oczywiście, zająć się ochroną swojej klasy i zmiennych egzemplarza przy pomocy
zsynchronizowanych bloków. Jednak przez cały czas jeden wątek żądania znajduje się w zsynchronizowanym
bloku i żaden inny wątek nie może zostać do bloku wprowadzony. W związku z tym najlepiej jest uczynić te
bloki jak najmniejszymi.
Należy także przyjrzeć się najgorszemu z możliwych wyników sporu między wątkami. Jeżeli najgorszy
przypadek jest znośny (jak w przykładzie licznika w rozdziale 3, „Okres trwałości serwletów”), można rozważyc
całkowite usuniecie bloków synchronizacji. Można również rozważyć zastosowanie interfejsu znaczników
SingleThreadModel, w którym serwer zarządza pulą egzemplarzy serwletów, aby zagwarantować, że każdy
egzemplarz jest wykorzystywany przez co najwyżej jeden wątek w jednym czasie. Serwlety będące
implementacją SingleThreadModel nie muszą synchronizować dostępu do swoich zmiennych egzemplarza.
W końcu należy również pamiętać, że java.util.Vector i java.util.Hashtable są zawsze
wewnętrznie zsynchronizowane, podczas gdy równoważne im java.util.ArrayList i
java.util.HashMap, wprowadzone w JDK 1.2, nie są zsynchronizowane, jeżeli nie nastąpi odpowiednie
żądanie. Tak wiec jeżeli dany Vector lub Hashtable nie potrzebuje synchronizacji, można go zastąpić
ArrayList lub HashMap.
Dostępnych jest kilka narzędzi profilujących Javy które mogą wspomóc w odnalezieniu wąskich gardeł w kodzie.
Większość problemów z wydajnością w Javie działającej po stronie serwera jest powodowana nie przez język
czy JVM, ale kilka wąskich gardeł. Sztuką jest odnalezienie tych wąskich gardeł. Narzędzia analizujące pracują
w tle, obserwując obsługę żądań przez serwer i zwracając dokładne podsumowanie spędzonego czasu, a także
opis alokacji pamięci. Dwa popularne narzędzia to OptimizeIT! Firmy Intuitive Systems
(http://www.optimizeit.com) i Jprobe formy Sitraka, dawniej KL Group (http://sitraka.com/jprobe). Duża ilość
JVM może przyjmować również znaczniki z linii poleceń (-prof w JDK 1.1 i Xrunhproof() w JDK 1.2).
Aby uruchomić obciążony serwer, można wykorzystać narzędzie takie jak Apache JMeter
(http://java.apache.org).
Rozdział 20. Zmiany w Servlet
API 2.3
Krótko przed przekazaniem niniejszej książki do druku firma Sun Microsystems opublikowała Proposed Final
Draft (Proponowaną Ostateczną Próbę) specyfikacji Servlet API 2.342. Nie jest to ostateczna wersja specyfikacji;
Proposed Final Draft to krok do formalnej Final Release (Wersji Ostatecznej), tak więc szczegóły techniczne
ciągle mogą ulec zmianie. Jednak zmiany te nie powinny być znaczące — tak naprawdę producenci serwerów już
rozpoczęli implementację nowych funkcji. W niniejszym rozdziale opisane zostaną w skrócie wszystkie zmiany,
jakie zaszły pomiędzy API 2.2 i 2.3. Wyjaśnione także zostaną powody zmian i przedstawione zostaną sposoby
tworzenia serwletów przy pomocy nowych funkcji43.
42
Chociaż specyfikacja ta została opublikowana przez firmę Sun, Servlet API 2.3. został tak naprawdę utworzony przez
wiele osób i przedsiębiorstw działających jako grupa ekspertów JSR-053, zgodnie z procesem Java Community Process
(JCP) 2.0. Ta grupa ekspertów działała pod przewodnictwem Danny'ego Cowarda z Sun Microsystems.
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 za
pozwoleniem. (Proszę zobaczyć także http://www.javaworld.com/j2-01-2001/jw-0126-servletapi.html).
Przed rozpoczęciem przyglądania się zmianom należy zaznaczyć, że wersja 2.3 została udostępniona jedynie
jako specyfikacja próbna. Większość opisanych tu funkcji nie będzie działać na wszystkich serwerach. Aby
przetestować te funkcje, poleca się pobranie oficjalnego wzorcowego serwera, Apache Tomcat 4.0. Jest to Open
Source i może być pobrany za darmo. Tomcat 4.0 jest aktualnie dostępny w wersji beta; obsługa Servlet API 2.3
staje się coraz lepsza, ale ciągle jest niekompletna. Proszę przeczytać plik NEW_SPECS.txt dołączony do
Tomcata 4.0 w celu poznania jego poziomu wsparcia dla wszystkich funkcji nowej specyfikacji.
Filtry
Najbardziej znaczącą częścią API 2.3 jest dodanie filtrów. Filtry są obiektami potrafiącymi zmieniać kształt
żądania lub modyfikować odpowiedź. Proszę zauważyć, że nie są to serwlety; tak naprawdę nie tworzą one
odpowiedzi. Przetwarzają one wstępnie żądanie zanim dotrze ono do serwletu, oraz przetwarzają odpowiedź
opuszczającą serwlet. W skrócie, filtry są dojrzałą wersją starego pomysłu „łańcuchów serwletów”. Filtr potrafi:
• Przechwycić wywołanie serwletu przed jego wywołaniem.
• Sprawdzić żądanie przed wywołaniem serwletu.
• Zmodyfikować nagłówki i dane żądania poprzez dostarczenie własnej wersji obiektu żądania, który
zostaje dołączony do prawdziwego żądania.
• Zmodyfikować nagłówki i dane odpowiedzi poprzez dostarczenie własnej wersji obiektu odpowiedzi,
który zostaje dołączony do prawdziwej odpowiedzi.
• Przechwycić wywołanie serwletu po jego wywołaniu.
Filtr może być skonfigurowany tak, by działał jako serwlet lub grupa serwletów; ten serwlet lub grupa może
zostać przefiltrowana przez dowolną ilość filtrów. Niektóre praktyczne idee filtrów to między innymi filtry
uwierzytelniania, filtry logowania i nadzoru, filtry konwersji obrazków, filtry kompresji danych, filtry
kodowania, filtry dzielenia na elementy, filtry potrafiące wywołać zdarzenia dostępu do zasobów, filtry XSLT
przetwarzające zawartość XML lub filtry łańcuchowe typu MIME (podobne do łańcuchów serwletów).
Filtr jest implementacją javax.servlet.filter i definiuje trzy metody tej klasy:
void setFilterConfig(FilterConfig konfig)
Ta metoda ustawia obiekt konfiguracyjny filtra.
FilterConfig getFilterConfig()
Ta metoda zwraca obiekt konfiguracyjny filtra.
void doFilter(ServletRequest zad, ServletResponse odp, FilterChain lancuch)
Ta metoda wykonuje właściwą pracę filtra.
Serwer wywołuje jeden raz setFilterConfig() w celu przygotowania filtru do działania, następnie
wywołuje doFilter() dowolną ilość razy dla różnych obiektów. Interfejs FilterConfig posiada metody
pobierające nazwę filtra, jego parametry inicjacji oraz aktywny kontekst filtra. Serwer może również przekazać
setFilterConfig() wartość null aby wskazać, że filtr zostaje wyłączony.
UWAGA!!!!
Przewiduje się, że w ostatecznej wersji 2.3 metoda getFilterConfig() zostanie usunięta, a metoda
setFilterConfig(FilterConfig konfig)zostanie zastąpiona przez init(FilterConfig) i
destroy().
Każdy filtr pobiera aktualne żądanie i odpowiedź w swojej metodzie doFilter(), a także FilterChain
zawierający filtry, które muszą stale być przetwarzane. W metodzie doFilter() filtr może wykonać dowolne
działanie żądania i odpowiedzi. (Na przykład może zbierać dane przez wywoływanie ich metod, dołączać
informacje nadające im nowe zachowanie). Filtr następnie wywołuje lancuch.doFilter() w celu
przekazania kontroli następnemu filtrowi. Kiedy wywołanie to wraca, filtr może, na końcu swojej własnej
metody doFilter(), wykonać dodatkowe działania na odpowiedzi; na przykład może zapisać informacje na
temat odpowiedzi w dzienniku. Jeżeli filtr chce całkowicie zatrzymać przetwarzanie żądania i zyskać pełną
kontrolę nad odpowiedzią, może specjalnie nie wywoływać następnego filtra.
Filtr może zmieniać obiekty żądania i odpowiedzi w celu dostarczenia własnego zachowania, zmiany konkretnej
metody wywołują implementację wpływającą na późniejsze działania obsługujące żądania. Serwlet API 2.3
dostarcza nowych klas HttpServletRequestWrapper i HttpServletResponseWrapper w tym
pomagających. Posiadają one domyślną implementację wszystkich metod żądań i odpowiedzi oraz domyślnie
przekazują wywołania do oryginalnych żądań i odpowiedzi. Poniżej przedstawiony jest kod prostego filtra
logowania, który zapamiętuje czas trwania wszystkich żądań:
import javax.servlet.*;
import javax.servlet.http.*;
FilterConfig konfig;
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
if (uri == null) {
uri = zad.getRequestURI(); // gdyby nie podano URI
}
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>");
}
}
UWAGA!!!!
Nazwy atrybutów w wersji próbnej wykorzystują myślniki zamiast kresek dolnych. Jednak zostaną one
zmienione w wersji ostatecznej, jak przedstawiono powyżej, w celu uzyskania większej spójności z nazwami
istniejących atrybutów.
Niewielkie poprawki
W Servlet API 2.3 znajdzie się także pewna ilość drobnych zmian. Po pierwsze, metoda getAuthType(),
która zwraca typu uwierzytelnienia wykorzystywanego do identyfikacji klienta została zdefiniowana tak, aby
zwracać jedną z czterech nowych stałych typu String klasy HttpServletRequest — BASIC_AUTH,
DIGEST_AUTH, CLIENT_AUTH i FORM_AUTH. Umożliwia to wykorzystanie uproszczonego kodu:
if (zad.getAuthType() == zad.BASIC_AUTH) {
//obsługa uwierzytelniania podstawowego
}
Oczywiście cztery stałe posiadają ciągle tradycyjne wartości String, tak więc poniższy kod z API 2.2 również
działa, ale nie jest ani tak szybki, ani elegancki. Proszę zwrócić uwagę na odwrócone sprawdzenia equals() w
celu uniknięcia wyjątku NullPointerException, jeżeli getAuthType() zwróci null:
if ("BASIC".equals(zad.getAuthType())) {
//obsługa uwierzytelniania podstawowego
}
Inną zmianą w API 2.3 jest opuszczenie HttpUtils. Klasa HttpUtils była zawsze uważana za zbiór
różnych statycznych metod — wywołań, które mogły być czasami użyteczne, ale równie dobrze mogły się
znaleźć w innych miejscach. Klasa ta zawierała metody służące do zrekonstruowania oryginalnego URL-a z
obiektu żądania i do przeformatowania danych parametrów do tablicy asocjacyjnej. API 2.3 przesuwa tę
funkcjonalność do obiektu żądania, co jest miejscem bardziej odpowiednim i opuszcza HttpUtils. Nowe
metody obiektu żądania wyglądają następująco:
StringBuffer zad.getRequestURL()
Zwraca StringBuffer zawierający URL oryginalnego żądania, odtworzony na podstawie informacji
żądania.
java.util.Map zad.getParameterMap()
Zwraca niemodyfikowalne odwzorowanie Map parametrów żądania. Nazwy parametrów pełnią funkcję
kluczy, a wartości parametrów funkcję wartości odwzorowania. Nie zapadła decyzja co do obsługi
parametrów o wielu wartościach; najprawdopodobniej wszystkie wartości zostaną zwrócone jako String
[]. Metody te wykorzystują nową metodę setCharacterEncoding() do obsługi konwersji znaków.
API 2.3 dodaje również dwie nowe metody do ServletContext, które pozwalają na odczytanie nazwy
kontekstu i wyświetlenia wszystkich przechowywanych przez niego zasobów:
String kontekst.getServletContextName()
Zwraca nazwę kontekstu zadeklarowaną w pliku web.xml.
java.util.Set kontekst.getResourcePaths()
Zwraca ścieżki do wszystkich zasobów dostępnych w kontekście, jako niemodyfikowalny zbiór obiektów
String. Każdy String posiada wiodący ukośnik (/) i powinien być uważany za względny do katalogu
macierzystego kontekstu.
Pojawiła się także nowa metoda obiektu odpowiedzi zwiększająca kontrolę programisty nad buforem
odpowiedzi. API 2.2 wprowadził metodę odp.reset() pozwalającą na usunięcie odpowiedzi i wyczyszczenie
jej głównej części, nagłówków i kodu stanu. API 2.3 dodaje odp.resetBuffer(), który czyści jedynie
główną część odpowiedzi:
void resetBuffer()
Czyści bufor odpowiedzi, ale pozostawia nagłówki i kod stanu. Jeżeli odpowiedź został już wysłana,
zgłasza wyjątek IllegalStateException.
I wreszcie, po długich naradach grupy ekspertów, Servlet API 2.3 wyjaśnia raz na zawsze, co dokładnie dzieje
się podczas wywołania odp.sendRedirect("indeks.html") w przypadku serwletu nie działającego w
kontekście macierzystym serwera. Niejasność wynikała z tego, że Servlet API 2.2 wymaga, aby niekompletna
ścieżka taka jak "/indeks.html" została przetłumaczona przez kontener serwletów na ścieżkę kompletną,
nie określa jednak zasad obsługi ścieżek kontekstów. Jeżeli serwlet wykonujący wywołanie znajduje się w
kontekście o ścieżce "/sciezkakontekstu", czy URI przekierowania powinien być określony względem
katalogu macierzystego kontenera (http://serwer:port/indeks.html), czy katalogu macierzystego
kontekstu (http://serwer:port/sciezkakontekstu/indeks.html)? W celu zapewnienia
maksymalnej przenośności zdefiniowanie zachowania jest konieczne. Po długiej debacie eksperci wybrali
tłumaczenie ścieżek względem katalogu macierzystego serwera. Osoby pragnące tłumaczenia względem
kontekstu powinny dodać na początku URI wynik wywołania getContextPath().
Wyjaśnienia deskryptora DTD
Servlet API 2.3 rozwiewa kilka niejasności związanych z zachowaniem deskryptora web.xml. Obowiązkowe
stało się przycięcie wartości tekstowych w pliku web.xml przed jego zastosowaniem. (W standardowym nie
sprawdzanym XML wszystkie miejsce są ogólnie rzecz biorąc zachowywane.) Zasada ta zapewnia, że się
poniższe pozycje będą traktowane identycznie:
<servlet-name>witaj</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 to
na utworzenie zasady podobnej do poniższej, która pozwala na wejście wszystkim użytkownikom, jeżeli zostali
oni poprawnie rozpoznani jako posiadający dowolną rolę w aplikacji WWW:
<auth-constraint>
<role-name>*</role-name> <!—dopuść wszystkie rozpoznane role -->
</auth-constraint>
Ostatnią zmianą jest dopuszczenie nazwy roli zadeklarowanej przez zasadę <security-role> jako
parametru metody isUserInRole(). Na przykład, proszę spojrzeć na następujący fragment pozycji web.xml:
<servlet>
<servlet-name>
sekret
</servlet-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 i
isUserInRole("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 wykorzystanie
jedynie tych ról, które zostały zdefiniowane w zasadzie aliasów <security-role-ref>. (Osoby, które nie
zrozumiały powyższego wywodu, nie powinny się tym przejmować; należy po prostu pamiętać, że teraz funkcje
powinny działać tak, jak się tego po nich spodziewa.)
Konkluzja
Servlet 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ć w
tworzeniu programów niezależnych od platformy.
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ę GenericServlet
służącą do tworzenia prostych serwletów. Zawiera on również klasy służące do komunikowania się z serwerem i
klientem komputera (ServletRequest i ServletResponse) oraz komunikowania z klientem
(ServletInputStream i ServletOutputStream). Hierarchia klas pakietu javax.servlet jest
przedstawiona na rysunku A.1. Serwlety powinny graniczyć z klasami tego pakietu w sytuacjach, w których
nieznany jest leżący poniżej protokół.
Rysunek A.1.
Pakiet javax.servlet
GenericServlet
Zestawienie
Nazwa Klasy javax.servlet.GenericServlet
Superklasa java.lang.Object
Bezpośrednie podklasy javax.servlet.http.HttpServet
Implementowane interfejsy javax.servlet.Servlet
javax.servlet.ServletConfig
java.io.Serializable
Dostępność Servlet API 1.0 i późniejsze
Opis
GenericServlet dostarcza podstawowej implementacji interfejsu Servlet dla serwletów niezależnych od
protokołu. Jako ułatwienie implementuje ona również interfejs ServletConfig. Większość programistów
serwletów tworzy swoje klasy jako podklasy tej klasy lub HttpServlet, zamiast bezpośrednio
implementować interfejs Servlet.
GenericServlet zawiera podstawowe wersje metod init() i destroy(), które wykonują podstawowe
zadania tworzenia i czyszczenia, takie jak zarządzanie obiektem serwera ServletConfig. Serwlet omija
jedną z tych metod powinien wywoływać wersje tych metod pochodzące z superklasy. GenericServlet
zawiera również metody log() umożliwiające dostęp do funkcji zapisujących dane w dzienniku pochodzących
z ServletContext.
Metoda service() jest deklarowana abstrakcyjnie musi zostać ominięta. Dobrze napisane serwlety omijają
również getServletInfo().
Podsumowanie klasy
public 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ść w
2.2
public void init() throws ServletException; // Nowość w
2.1
public void init(ServletConfig konfig) throws ServletException;
public void log(String wiad);
public void log(String wiad, Throwable t); // Nowość w
2.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 z
inicjalizacją 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 jest
wywoływana jedynie po wyłączeniu wszystkich wątków wewnątrz metody usługowej serwletu lub po określonym
czasie. Po wywołaniu tej metody przez kontener serwletów, nie będzie wywoływał on już metody usługowej
serwletu. 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 bazami
danych, wątki, uchwyty plików itd.) itp.
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 przez
GenericServlet 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ę i
dostarczać własnego łańcucha identyfikacyjnego (na przykład „Serwlet Wiadomości Janka v.1.21”), ale nie jest
to wymagane.
getServletName()
public String getServletName()
Zwraca nawę aktualnego egzemplarza serwletu. Nazwa może być dostarczona prze administrację serwera lub
przypisana w deskryptorze aplikacji WWW, lub, w przypadku niezarejestrowanego (i w związku z tym
nienazwanego) egzemplarza serwletu, będzie równa nazwie klasy serwletu. Pochodzi z interfejsu
ServletConfig. Metoda ta została wprowadzona w Servlet API 2.2.
init()
public void init() throws ServletException
public void init(ServletConfig konfig) throws ServletException
Wywoływana przez kontener serwletów po pierwszym pobraniu serwletu i przed wywołaniem jego metody
service(). Serwlet może omijać tę metodę w celu przeprowadzenia jednorazowej konfiguracji, stworzenia
zasobó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ślna
implementacja init() zapisuje w dzienniku inicjalizację serwletu oraz przechowuje obiekt ServletConfig
w celu wykorzystania go przez metody interfejsu ServletConfig. Serwlet implementujący wersję
pobierającą ServletConfig musi wywołać super.init(konfig) przed wykonaniem własnego kodu
inicjacji. 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żne
od 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 poprzez
obiekt ServletRequest i odsyła dane poprzez obiekt ServletResponse. Jest to jedyna metoda, która
musi zostać ominięta podczas rozszerzania GenericServlet.
RequestDipatcher
Zestawienie
Nazwa interfejsu javax.servlet.RequestDispatcher
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 2.1 i późniejsze
Opis
Interfejs obiektu, który może wewnętrznie rozsyłać żądania do dowolnych zasobów (takich jak serwlety, pliki
HTML lub pliki JSP) na serwerze, Kontener serwletów tworzy obiekt RequestDispatcher, który następnie
wykorzystywany jest jako obwódka wokół zasobu serwera umieszczonego w konkretnej ścieżce lub
posiadającego konkretną nazwę. Interfejs ten został zaprojektowany w celu wykorzystania z serwletami i plikami
JSP, ale kontener serwletów może utworzyć obiekty RequestDispatcher służące jako obwódki dowolnego
typu zasobu. Rozsyłanie może zostać wykorzystane do przekazania żądania do zasobu lub do dołączenia
zawartości tego zasobu do aktualnej odpowiedzi. Klasa ta została wprowadzona w Servlet API 2.1.
Deklaracja interfejsu
public interface RequestDispatcher {
//Metody
public abstract void forward(ServletRequest zad, ServletResponse odp)
throws ServletException, java.io.IOException // Nowość w
2.1.
public abstract void include(ServletRequest zad, ServletResponse odp)
throws ServletException, java.io.IOException // Nowość w
2.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 na
wykonanie wstępnego przetwarzania żądania, po czym innemu na wygenerowanie odpowiedzi. W przypadku
RequestDispatcher otrzymanego przez getRequestDispatcher(), obiekt ServletRequest
posiada elementy ego ścieżki i parametry dołączone w celu dopasowania ścieżki zasobu docelowego. Metoda ta
powinna 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 zostaje
automatycznie wyczyszczona przed przekazaniem. Parametry żądania i odpowiedzi muszą być tymi samymi
obiektami, które zostały przekazane metodzie usługowej serwletu wywołującego. Metoda ta została
wprowadzona 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 ServletRequest
pozostają niezmienione w porównaniu z zasobem wywołującym; jeżeli dołączane zasoby wymagają dostępu do
swoich 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łączony
serwlet nie może zmieniać kodu stanu odpowiedzi ani ustawiać nagłówków; każda próba zmiany jest
ignorowana. Parametry żądania i odpowiedzi muszą być tymi samymi obiektami, które zostały przekazane
metodzie usługowej serwletu wywołującego. Metoda został wprowadzona w Servlet API 2.1.
Servlet
Zestawienie
Nazwa interfejsu javax.servlet.Servlet
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez javax.servlet.GenericServlet
Dostępność Servlet API 1.0 i późniejsze
Opis
Wszystkie serwlety implementują interfejs Servlet, czy to bezpośrednio, czy poprzez podklasę GenericServlet
lub HttpServlet. Większość programistów serwletów uważa za łatwiejsze uczynienie serwletu podklasą jednej z
dwóch istniejących klas serwletów niż bezpośrednią implementację tego interfejsu. Interfejs deklaruje
podstawową funkcjonalność serwletów — inicjowanie serwletu, obsługę żądania klienta i niszczenie serwletu.
Deklaracja interfejsu
public 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 metody
service() 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 przez
obiekt ServletRequest i wysyła dane z powrotem do klienta przez obiekt ServletResponse.
ServletConfig
Zestawienie
Nazwa interfejsu javax.servlet.ServletConfig
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez javax.servlet.GenericServlet
Dostępność Servlet API 1.0 i późniejsze
Opis
Kontenery serwletów wykorzystują obiekty ServletConfig do przekazywania serwletom informacji o
inicjacji i kontekście. Informacje inicjacji ogólnie składają się z serii parametrów inicjacji i obiektu
ServletContext 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 robi
GenericServlet.
Deklaracja interfejsu
public interface ServletConfig {
// Metody
public abstract String getInitParameter(String nazwa);
public abstract Enumeration getInitParameterNames();
public abstract ServletContext getServletContext();
public abstract String getServletName(); // Nowość w
2.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 lub
przypisana w deskryptorze aplikacji WWW, lub, w przypadku niezarejestrowanego (i w związku z tym
nienazwanego) egzemplarza serwletu, będzie równa nazwie klasy serwletu. Pochodzi z interfejsu
ServletConfig. Metoda ta została wprowadzona w Servlet API 2.2.
ServletContext
Zestawienie
Nazwa interfejsu javax.servlet.ServletContext
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Interfejs ServletContext definiuje zbiór metod wykorzystywanych do komunikacji z kontenerem serwletów
w sposób niezależny od żądania. Oznacza to odnajdywanie informacji o ścieżce, uzyskiwanie dostępu do innych
serwletów działających na serwerze i zapis w pliku dziennika serwera. Każda aplikacja WWW posiada inny
kontekst serwletów.
Deklaracja interfejsu
public interface ServletContext {
// Metody
public abstract Object getAttribute(String nazwa);
public abstract Enumeration getAttributeNames(); // Nowość w
2.1
public abstract ServletContext getContext(String sciezkauri); // Nowość w
2.1
public abstract String getInitParameter(String nazwa); // Nowość w
2.2
public abstract Enumeration getInitParameterNames(); // Nowość w
2.2
public abstract int getMajorVersion(); // Nowość w
2.1
public abstract String getMimeType(string plik);
public abstract int getMinorVersion(); // Nowość w
2.1
public abstract RequestDispatcher getNamedDispatcher(String nazwa); // Nowość w
2.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ść w
2.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ść w
2.1
public abstract void removeAttribute(String nazwa); // Nowość w
2.1
public abstract void setAttribute(String name, Object o); // Nowość w
2.1
}
Metody
getAttribute()
public abstract Object getAttribute(String nazwa)
Zwraca wartość nazwanego atrybutu kontekstu jako Object lub null, jeżeli atrybut nie istnieje. Atrybuty
zależne od serwera mogą zostać automatycznie ustawione przez kontener serwletów w celu dostarczenia
serwletom 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 WWW
reprezentowanej przez dany kontekst. 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 Software firmy Sun
Microsystems (dawniej znany jako JavaSoft), a com.sun.* jest zarezerwowany dla użytku firmy Sun
Microsystems. 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 zapewnia
serwletowi dostęp do kontekstu innego niż własny, umożliwiając przeglądanie danych wewnątrz tego kontekstu
lub otrzymanie RequestDispatcher do zasobów wewnątrz tego kontekstu. W środowisku zabezpieczonym
lub rozproszonym kontener serwletów może zwrócić null dla dowolnej ścieżki. Metoda została wprowadzona
w Servlet API 2.1.
getInitParameter()
public abstract String getInitParameter(String nazwa)
Zwraca wartość danego parametru inicjacji lub null, jeżeli nie odnajdzie pasującego parametru. Metoda ta może
udostępnić przydatne informacje o konfiguracji całej aplikacji WWW. Na przykład, może udostępnić adres
poczty elektronicznej administratora systemu lub nazwę systemu przechowującego dane krytyczne. Parametry
inicjacji kontekstu są przydzielane w deskryptorze aplikacji WWW. Metoda została wprowadzona w Servlet API
2.2.
getInitParameterNames()
public abstract Enumeration getInitParameterNames()
Zwraca nazwy wszystkich parametrów inicjacji danego kontekstu jako Enumeration obiektu String. Zwraca ona
pustą Enumeration, jeżeli kontekst nie posiada żadnych atrybutów. Parametry inicjacji kontekstu są przydzielane
w 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, kontener
implementują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 i
image/jpeg.
getMinorVersion()
public abstract int getMinorVersion()
Zwraca numer pobocznej wersji Servlet API obsługiwanego przez kontener serwletów. Na przykład, kontener
implementują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 Servlet
API 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ło
zostać przeprowadzone (na przykład kiedy plik znajduje się w zdalnym systemie plików lub jest dostępny jedynie
wewnątrz archiwum .war). Jeżeli dana ścieżka wynosi /, metoda zwraca katalog macierzysty dokumentów
kontekstu. 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 jest
interpretowana jako względna do katalogu macierzystego kontekstu. Metoda może zwrócić null, jeżeli
skojarzenie jakiegokolwiek URL-a ze ścieżką nie będzie możliwe. Metoda ta umożliwia kontenerowi
udostępnienie zasobów serwletom z dowolnego źródła. Zasoby mogą zostać odnalezione lokalnie, w zdalnym
systemie plików, bazie danych lub pliku .war. Niektóre kontenery mogą również umożliwiać zapis w obiekcie
URL przy pomocy metod klasy URL. Metoda ta powinna zostać zastosowana kiedy nie wszystkie zasoby to pliki
lokalne — 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ępne
bezpoś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 w
oparciu o mechanizm ładowania klas. Metoda ta nie wykorzystuje mechanizmów ładowania klas. Została ona
wprowadzona w Servlet API 2.1.
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że
zwracać 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 na
temat zasobów, takie jak wielkość i typ jej zawartości, dostępne przy pomocy getResource(). Metoda
został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ści
zmiennej 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 podanej
nazwy i URL-u lub null, jeżeli serwlet nie mógł zostać odnaleziony. W API 2.1 metoda została opuszczona i
zdefiniowana tak, by zwracała null, ponieważ bezpośredni dostęp do innego egzemplarza serwletu otwiera zbyt
wiele możliwości dla błędu. Dzieje się tak dlatego, że serwlety mogą zostać zniszczone przez kontener w
dowolnym czasie, tak więc żaden obiekt oprócz kontenera nie powinien posiadać bezpośredniego odwołania do
serwletu. 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. Zamiast
tego 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 wstecznej
kompatybilnoś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 Enumeration
zawierającą nazwy obiektów serwletów załadowanych do danego kontekstu. Metoda ta została opuszczona i
zdefiniowana do zwracania pustej Enumeration w Servlet API 2.1 z tych samych powodów, dla których
getServlet() została opuszczona i zdefiniowana tak, by zwracała. Metoda ta została wprowadzona w
Servlet API 2.0.
getServlets()
public abstract Enumeration getServlets();
Zwraca pustą Enumeration w Servlet API 2.1 i późniejszych. Poprzednio zwracała Enumeration
zawierającą obiekty Servlet załadowane do danego kontekstu. Metoda ta została opuszczona w Servlet API
2.0 na rzecz getServletNames(). Została zdefiniowana do zwracania pustej Enumeration w Servlet API
2.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 plik
dziennika 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żne
od 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 od
serwera. Proszę zauważyć niestandardowe umieszczenie parametru Exception na pozycji pierwszej, a nie
ostatniej. 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żdego
obiektu Trowable, nie tylko Exception. Metoda została wprowadzona w Servlet API 2.0.
removeAttribute()
public abstract void removeAttribute(String nazwa);
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 API
2.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 zostaje
zastąpione. Metoda została wprowadzona w Servlet API 2.1.
ServletException
Zestawienie
Nazwa klasy javax.servlet.ServletException
Superklasa java.lang.exception
Bezpośrednie podklasy javax.servlet.UnavailableException
Implementowane interfejsy Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Ogólny wyjątek zgłaszany przez serwlety napotykające trudności.
Podsumowanie klasy
public class ServletException extends java.lang.Exception {
// Konstruktory
public ServletException(); // Nowość w
2.0
public ServletException(String wiad);
public ServletException(String wiad, Throwable podstPrzycz); // Nowość w
2.1
public ServletException(Throwable podstPrzycz); // Nowość w
2.1
public Throwable getRootCause(); // Nowość w
2.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 pomocy
wywołania getMessage(); jeżeli określona została podstawowa przyczyna, może ona być odczytana przy
pomocy wywołania getRootCause(). Wiadomości i podstawowe przyczyny są zazwyczaj dołączane do
dziennikó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 podstawowa
przyczyna. Metoda została wprowadzona w Servlet API 2.1.
ServletInputStream
Zestawienie
Nazwa klasy javax.servlet.ServletInputStream
Superklasa java.io.InputStream
Bezpośrednie podklasy Brak
Implementowane interfejsy Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Dostarcza potoku wyjściowego służącego do odczytywania danych binarnych z żądania klienta oraz metody
readLine() służącej do odczytywania danych linia po linii. ServletInputStream jest zwracany przez
metodę getInputStream() ServletRequest. W przypadku serwletów HTTP
ServletInputStream umożliwia dostęp do wysłanych danych POST.
Podsumowanie klasy
public 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ć swojego
wł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 \n
jest 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 linii
dłuższej niż bufor. Błąd ten został naprawiony w Servlet API 2.1.
ServletOutputStream
Zestawienie
Nazwa klasy javax.servlet.ServletOutputStream
Superklasa java.io.OutputStream
Bezpośrednie podklasy Brak
Implementowane interfejsy Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Dostarcza strumienia wyjściowego służącego do wysyłania danych binarnych z powrotem do klienta. Serwlet
uzyskuje 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
do wysyłania danych binarnych. Jeżeli tworzy się podklasę ServletOutputStream, należy dostarczyć
implementacji metody write(int).
Podsumowanie klasy
public 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/line
feed —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 prostu
CRLF.
ServletRequest
Zestawienie
Nazwa interfejsu javax.servlet.ServletRequest
Superinterfejs Brak
Bezpośrednie podinterfejsy javax.servlet.http.HttpServletRequest
Implementowany przez Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Obiekt 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 z
głównej części żądania. Możliwe jest utworzenie podklasy ServletRequest w celu dostarczenia dodatkowych
informacji zależnych od protokołu. Na przykład HttpServletRequest zawiera metody służące do manipulowania
nagłówkami HTTP.
Deklaracja interfejsu
public interface ServletRequest {
// Metody
public abstract Object getAttribute(String nazwa);
public abstract Enumeration getAttributeNames(String nazwa); // Nowość w
2.1
public abstract String getCharacterEncoding(); // Nowość w
2.0
public abstract int getContentLength()
public abstract String getContentType();
public abstract ServletInputStream getInputStream() throws IOException;
public abstract Locale getLocale(); // Nowość w
2.2
public abstract Enumeration getLocales(); // Nowość w
2.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ść w
2.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ść w
2.2
public abstract void removeAttribute(String nazwa); // Nowość w
2.2
public abstract void setAttribute(String nazwa, Object o); // Nowość w
2.1
}
Metody
getAttribute()
public abstract Object getAttribute(String nazwa)
Zwraca wartość danego atrybutu żądania jako Object lub null, jeżeli atrybut nie istnieje. Atrybuty żądania
zależne od serwera mogą być ustawiane automatycznie przez kontener serwletów w celu dostarczenia serwletom
informacji 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ędzy
serwletami 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ł Java
Software firmy Sun Microsystems (dawniej znany jako JavaSoft), a com.sun.* jest zarezerwowany dla użytku
firmy Sun Microsystems. 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.
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.
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 (na
przykł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 nie
wystę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 obiekt
InputStream, z dodatkową możliwością efektywnego odczytywania wpisanych danych linia po linii do
tablicy bajtów. Metoda ta powinna być wykorzystywana do odczytywania wpisów binarnych. Zgłasza ona
wyją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 klienta
Accept_Language. Jeżeli żądanie klienta nie posiada nagłówka Accept_Language, metoda zwraca
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.
getLocales()
public abstract Enumeration getLocales()
Zwraca Enumeration obiektów Locale wskazujących na lokalizacje akceptowane przez klienta, na
podstawie nagłówka Accept-Language, od najbardziej do najmniej preferowanej. Jeżeli żądania klienta nie
zawiera 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 swojej
zwykł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 parametrze
posiadającym kilka wartości, zwracana wartość jest równa pierwszemu elementowi tablicy zwracanej przez
getParameterValues(). Jeżeli informacje na temat parametrów nadeszły w formie zakodowanych danych
POST, mogą być one niedostępne, jeżeli dane POST zostały wcześniej ręcznie odczytane przy pomocy metody
getReader() lub getInputStream(). Metoda ta była przez pewien czas opuszczona na rzecz
getParameterValues(), ale dzięki powszechnym protestom środowiska programistów, została
przywró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 nie
istnieje. Pojedyncza wartość jest zwracana jako tablica o długości 1.
getProtocol()
public abstract String getProtocol()
Zwraca nazwę i wersję protokołu wykorzystywanego przez żądanie jako String o wzorze
protokol/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 do
odczytania wprowadzonych danych opartych na znakach, ponieważ mechanizm odczytujący odpowiednio
tłumaczy kodowania. Metoda zgłasza IllegalStateException, jeżeli na tym samym żądaniu poprzednio
wywołano getInputStream(). Zgłasza UnsupportedEncodingException, jeżeli kodowanie
wykorzystywane we wprowadzonych danych nie jest obsługiwane lub znane. Metoda została wprowadzona w
Servlet 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 nie
mogł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łaby
zwrócona przez getPathTraslated(). Dla tej metody nie istnieje równoważnik CGI. Metoda została
opuszczona 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 z
klientem, 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ęc
nazwa 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łączenia
zawartość tego zasobu do aktualnej odpowiedzi. Zasób może być dynamiczny lub statyczny. Określona ścieżka
może być względna, chociaż nie może wykraczać poza aktualny kontekst serwletów. Jeżeli ścieżka rozpoczyna
się od /, jest interpretowana jako względna do aktualnego macierzystego katalogu dokumentów. Metoda zwraca
null, jeżeli kontener serwletów nie może zwrócić RequestDispatcher, niezależnie od powodu. Różnica
pomiędzy tą metodą i getRequestDispatcher() w ServletContext jest taka, że ta metoda może
pobierać ś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 i
ftp, 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 do
zmiennej CGI SERVER_NAME.
GetServerPort()
public abstract int getServerPort()
Zwraca numer portu, który otrzymał żądanie, Równoważna zmiennej CGI SERVER_PORT.
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 nazwie
zostają usunięte. Atrybuty żądania są najczęściej ustawiane jako sposób przekazywania informacji pomiędzy
serwletami 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 Software
firmy Sun Microsystems (dawniej znany jako JavaSoft), a com.sun.* jest zarezerwowany dla użytku firmy
Sun Microsystems. Metoda została wprowadzona w Servlet API 2.2.
ServletResponse
Zestawienie
Nazwa interfejsu javax.servlet.ServletResponse
Superinterfejs Brak
Bezpośrednie podinterfejsy javax.servlet.http.HttpServletResponse
Implementowany przez Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Serwlety wykorzystują obiekty ServletResponse do wysyłania zakodowanych w MIME danych z powrotem
do klienta. Aby wysłać dane binarne, należy wykorzystać ServletOutputStream zwrócony przez
getOutputStream(). W celu wysłania danych zawierających znaki należy wykorzystać PrintWriter
zwrócony przez getWriter(). Można otwarcie ustawić typ MIME wyświetlanych danych przy pomocy
metody setContentType(), a lokalizację odpowiedzi przy pomocy setLocale(). Należy wykonać te
wywołania przed wywołaniem getWriter(), ponieważ metoda getWriter() sprawdza typ zawartości i
lokalizację w celu określenia kodowania, które należy wykorzystać. Więcej informacji na temat MIME jest
dostępnych w dokumencie RFC 2045 pod adresem http://www.ietf.org/rfc/rfc2045.txt.
Deklaracja interfejsu
public interface ServletResponse {
// Metody
public abstract void flushBuffer() throws IOException; // Nowość w
2.2
public abstract int getBufferSize(); // Nowość w
2.2
public abstract String getCharacterEncoding(); // Nowość w
2.0
public abstract Locale getLocale(); // Nowość w
2.2
public abstract ServletOutputStream getOutputStream()
throws IOException, IllegalStateException;
public abstract PrintWriter getWriter() // Nowość w
2.0
throws IOException, IllegalStateException;
public abstract boolean isCommitted(); // Nowość w
2.2
public abstract void reset() throws IllegalStateException; // Nowość w
2.2
public abstract void setBufferSize(int wielkosc) // Nowość w
2.2
throws IllegalStateException;
public abstract void setContentLength(int dlug);
public abstract void setContentType(String typ);
public abstract void setLocale(Locale lok); // Nowość w
2.2
}
Metody
FlushBuffer()
public abstract void flushBuffer() throws IOException;
Wymusza wyświetlenie klientowi wszystkich danych znajdujących się w buforze. Wywołanie tej metody
automatycznie zatwierdza odpowiedź, co oznacza, że kod stanu oraz nagłówki zostaną zapisane, a metoda
reset() 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 buforowanie
nie 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 przez
przydzielony typ zawartości, lub ISO-8859-1, jeżeli nie określono żadnego kodowania. Metoda została
wprowadzona w Servlet API 2.0.
getLocale()
public abstract Locale getLocale()
Zwraca lokalizację Locale aktualnie przypisaną do odpowiedzi. Metoda została wprowadzona w Servlet API
2.2.
GetOutputStream()
public abstract ServletOutputStream getOutputStream()
throws IOException, IllegalStateException;
Zwraca ServletOutputStream służący do zapisywania binarnych (bit-po-bicie) danych odpowiedzi. Nie
jest wykonywane żadne kodowanie. Zgłasza IllegalStateException, jeżeli na aktualnej odpowiedzi
wywołano wcześniej getWriter().
getWriter()
public abstract PrintWriter getWriter()
throws IOException, IllegalStateException;
Zwraca PrintWriter służący do zapisywania opartych na znakach danych odpowiedzi. Mechanizm
wyświetlający koduje znaki zależnie od jakiegokolwiek kodowania podanego w typie zawartości. Jeżeli w typie
zawartości nie określono żadnego kodowania, jak to się zdarza najczęściej, mechanizm wykorzystuje kodowanie
ISO-8859-1 (Latin-1), odpowiednie dla języków zachodnioeuropejskich. Zgłasza
IllegalStateException, jeżeli na aktualnej odpowiedzi wywołano wcześniej getOutputStream(), a
UnsupportedEncodingException, 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 metoda
zwróci true, jest już za późno, by zmienić kody stanu i nagłówki. Metoda została wprowadzona w Servlet API
2.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() jest
automatycznie wywoływana przez metody sendError() i sendRedirect(). Metoda została wprowadzona
w Servlet API 2.2.
setBufferSize()
public abstract void setBufferSize(int wielkosc) throws IllegalStateException;
Przekazuje serwerowi minimalną wielkość (w bajtach) bufora odpowiedzi, którą zaakceptuje serwlet. Serwer
może dostarczyć większego bufora niż ten, którego zażądano — na przykład w celu utrzymania buforów jako
bloków o wielkości 8Kb w celu ułatwienia ich ponownego wykorzystania. Większy bufor pozwala na zapisanie
dłuższej zawartości przez jej właściwym wysłaniem, a w związku z tym daje serwletowi więcej czasu na
ustawienie odpowiednich kodów stanu i nagłówków. Mniejszy bufor zmniejsza obciążenie pamięci serwera i
pozwala na szybsze otrzymywanie danych przez klienta. Metoda ta musi zostać wywołana przed zapisaniem
dowolnej zawartości w głównej części odpowiedzi; jeżeli zawartość została zapisana, metoda zgłasza wyjątek
IllegalStateException. 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 HTTP
Content-Length. Serwlety HTTP wykorzystuję tę metodę w celu umożliwienia trwałych połączeń i
wspomożenia monitorów postępu klienta. Jej wykorzystanie jest opcjonalne. Jeżeli zawartość odpowiedzi mieści
się 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łówek
HTTP Content-Type.
setLocale()
public abstract void setLocale(Locale lok)
Ustawia lokalizację odpowiedzi. Serwer modyfikuje nagłówki Content-Language i Content-Type
odpowiednio do danej lokalizacji. Metoda powinna zostać wywołana po ustawieniu Content-Type, a przed
wywołaniem getWriter(). Domyślnie lokalizacja odpowiedzi jest równa domyślnej lokalizacji serwera.
Metoda została wprowadzona w Servlet API 2.2.
SingleThreadModel
Zestawienie
Nazwa interfejsu javax.servlet.SingleThreadModel
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 2.0 i późniejsze
Opis
SingleThreadModel jest interfejsem znaczników nie posiadającym metod. Jeżeli serwlet implementuje ten
interfejs, kontener serwletów upewnia się, że każdy egzemplarz serwletu obsługuje jedynie jedno żądanie usługi
w jednym czasie. Na przykład, kontener serwletów może zaimplementować tę funkcjonalność poprzez
utrzymywanie puli egzemplarzy serwletów i przekierowywanie przychodzących żądań do wolnych serwletów w
puli. Wykorzystanie SingleThreadModel zabezpiecza sam serwlet przed wątków; jednak wykorzystanie
tego interfejsu nie przeciwdziała problemom z synchronizacją, które są powodowane przez serwlety uzyskujące
dostęp do współdzielonych zasobów takich jak statyczne zmienne klas lub zmienne zdalne dla serwletu. Interfejs
ten jest przydatny w bardzo niewielkiej ilości sytuacji.
Deklaracja interfejsu
public interface SingleThreadModel {
}
UnavailableException
Zestawienie
Nazwa klasy javax.servlet.UnavailableException
Superklasa java.io.ServletException
Bezpośrednie podklasy Brak
Implementowane interfejsy Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Serwlet może zgłosić wyjątek UnavailableException w dowolnym czasie w celu wskazania, że nie jest
dostępny w celu zaspokajania żądań klientów. Istnieją dwa typy niedostępności — stała (w której problem nie
może sam się rozwiązać i musi zostać podjęta akcja administracyjna) i tymczasowa (w której problem
najprawdopodobniej sam się rozwiąże po pewnym czasie). Aby oznaczyć serwlet jako czasowo niedostępny
należy podczas konstruowania wyjątku określić czas (w sekundach). Dobrze napisane kontenery serwletów
wykorzystają określony czas w celu utworzenia lepszych komunikatów o błędach dla klienta. Implementacje
serwletów mogą traktować niedostępność tymczasową jako niedostępność stałą.
Podsumowanie klasy
public 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ść w
2.2
public UnavailableException(String wiad, int sekundy); // Nowość w
2.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że
zostać opcjonalnie określony (w sekundach). Dwa konstruktory z podpisami, które przyjmują parametr Servlet
zostały opuszczone w Servlet API 2.2 na rzecz prostszych i bezpieczniejszych wersji konstruktora nie
przyjmują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ł dostarczony
konstruktorowi. 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 na
niedostępność stałą. Nie jest wykonywana żadna próba obliczenia czasu określonego od zgłoszenia wyjątku.
isPermanent()
public boolean isPermanent()
Zwraca true, jeżeli serwlet jest niedostępny stale, a w przeciwnym wypadku false.
Dodatek B. Krótki opis HTTP
Servlet API
Pakiet javax.servlet.http umożliwia obsługę serwletów porozumiewających się przy pomocy protokołu
HTTP. Klasy w tym pakiecie są utworzone na podstawie podstawowej funkcjonalności pakietu
javax.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 pakietu
javax.servlet.http.
Rysunek B.1.
Pakiet javax.servlet.http
Cookie
Zestawienie
Nazwa klasy javax.servlet.http.Cookie
Superklasa java.lang.Object
Bezpośrednie podklasy Brak
Implementowane interfejsy java.lang.Cloneable
Dostępność Servlet API 2.0 i późniejsze
Opis
Klasa Cookie dostarcza serwletom łatwego sposobu odczytywania, tworzenia i manipulowania cookies w stylu
HTTP, które umożliwiają serwletom przechowywanie niewielkich ilości danych na komputerze klienta. Cookies
są ogólnie wykorzystywane do śledzenia sesji lub przechowywania niewielkich ilości informacji
konfiguracyjnych 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ą
ustawiane przy pomocy nagłówków HTTP, addCookie() musi zostać wywołana przed zatwierdzeniem
odpowiedzi.
Metody getXXX() są rzadko wykorzystywane, ponieważ kiedy cookie zostaje wysłane do serwera, zawiera
jedynie jego nazwę, wartość i wersję, Jeżeli ustawi się atrybut w cookie otrzymanym od klienta, należy dodać go
do 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 klasy
public 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; kiedy
cookie 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 po
ustawieniu 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 po
ustawieniu maksymalnego wieku; kiedy cookie jest zwracane przez klienta, informacja ta nie zostaje dołączona.
getName()
public String getName()
Zwraca nazwę aktualnego cookie.
getPath()
public String getPath()
Zwraca ograniczenia ścieżki związane z aktualnym cookie. Informacja ta jest dostępna jedynie zaraz po
ustawieniu ś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 WWW
moż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ślnie
cookies 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 pozycji
poza początkową kropką. Na przykład wzór .buu.com jest prawidłowy i pasuje do www.buu.com i
wyslij.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łanie
domyślne, czyli na wygaśnięcie cookie z chwilą wyłączenia przeglądarki. Wartość zerowa nakazuje przeglądarce
natychmiastowe 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ślnie
cookie są wysyłane do strony, która je ustawiła oraz do wszystkich stron w jej katalogu lub podkatalogach. Na
przykład, jeżeli /servlet/CiasteczkowyPotwor ustawiła cookie, domyślną ścieżką jest /servlet. Ścieżka ta
wskazuje, że cookie powinno zostać wysłane do /servlet/Elmo i /servlet/podkat/WielkiPtak — ale nie do aliasu
serwletu /Oskar.html ani do żadnego programu CGI w /cgi-bin. Ścieżka ustawiona na „/” powoduje wysłanie
cookie 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 jak
SSL. Domyślna wartość wynosi false.
SetValue()
public void setValue(String nowaWartosc)
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ów
zapytania, dwukropków i średników. Puste wartości mogą zachowywać się w różny sposób w różnych
przeglądarkach.
SetVersion()
public void setVersion(int w)
Serwlety mogą wysyłać i przyjmować cookies sformatowane tak, aby zgadzały się zarówno trwałymi cookies
Netscape (wersja 0), jak i nowszymi, w pewnym sensie eksperymentalnymi cookies zgodnymi z dokumentem
RFC 2109 (wersja 1). Nowo skonstruowane cookies są domyślnie sformatowane zgodnie z wersją 0 w celu
zmaksymalizowania 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
Zestawienie
Nazwa klasy javax.servlet.http.HttpServlet
Superklasa Javax.servlet.GenericServlet
Bezpośrednie podklasy Brak
Implementowane interfejsy java.servlet.Servlet
java.io.Serializable
Dostępność Servlet API 1.0 i późniejsze
Opis
HttpServlet jest abstrakcyjną klasą służącą jako podstawowa klasa dla serwletów HTTP (WWW). Publiczna
metoda service() przekierowuje żądania do specyficznej dla HTTP, chronionej metody service(), która
nastę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 tych
metod, to jeżeli ominie się chronioną metodę service(), należy samodzielnie obsłużyć przekierowania, lub
nie wykorzystywać funkcji obsługujących dla metod żądań HTTP.
Podsumowanie klasy
public abstract class HttpServlet extends javax.servlet.GenericServlet
implements javax.servlet.Servlet, java.io.Serializable {
// Konstruktory
public HttpServlet();
doGet()
public void doGet(HttpServletRequest zad, HttpServletResponse odp)
throws ServletException, IOException
Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP GET do tej
metody. Serwlety implementują tę metodę w celu obsługi żądań DELETE. Domyślna implementacja zwraca błąd
HTTP SC_BAD_REQUEST.
doOptions()
public void doOptions(HttpServletRequest zad, HttpServletResponse odp)
throws ServletException, IOException
Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP OPTIONS
do tej metody. Domyślna implementacja określa, które opcje są obsługiwane i zwraca odpowiedni nagłówek. Na
przykł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ła
wprowadzona 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 tej
metody. Serwlety implementują tę metodę w celu obsługi żądań POST. Domyślna implementacja zwraca błąd
HTTP 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 tej
metody. Serwlety implementują tę metodę w celu obsługi żądań PUT. Domyślna implementacja zwraca błąd
HTTP SC_BAD_REQUEST. Większa ilość informacji na temat żądań HTTP PUT jest dostępna w dokumencie
RFC 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
Domyślna implementacja service() w HttpServlet przekierowuje wszystkie żądania HTTP TRACE do
tej metody. Serwlety implementują tę metodę w celu obsługi żądań TRACE. Niemal nigdy nie ma powodu, by
omijać 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 modyfikacji
zawartości tworzonej przez serwlet. Wartości ujemne oznaczają, że czas ten nie jest znany. Domyślna
implementacja 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ę w
rozdziale 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ługuje
przekierowywanie żą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
Zestawienie
Nazwa interfejsu javax.servlet.http.HttpServletRequest
Superinterfejs javax.servlet.ServletRequest
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
HttpServletRequest rozszerza podstawową klasę ServerRequest dostarczając dodatkowej
funkcjonalności serwletom HTTP (WWW). Zawiera obsługę cookies i śledzenia sesji oraz dostęp do informacji z
nagłówków HTTP. HttpServletRequest analizuje również przychodzące dane formularzy HTTP i
przechowuje je jako parametry serwletów. Serwer przekazuje obiekt HttpServletRequest metodzie
usługowej HttpServlet.
Deklaracja interfejsu
public interface HttpServletRequest extends javax.servlet.ServletRequest {
// Metody
public abstract String getAuthType();
public abstract String getContextPath(); // Nowość w
2.2
public abstract Cookie[] getCookies(); // Nowość w
2.0
public abstract long getDateHeader(String nazwa);
public abstract String getHeader(String nazwa);
public abstract Enumeration getHeaderNames();
public abstract Enumeration getHeaders(String nazwa); // Nowość w
2.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ść w
2.0
public abstract String getRequestURI();
public abstract String getServletPath();
public abstract HttpSession getSession(); // Nowość w
2.1
public abstract HttpSession getSession(boolean tworz); // Nowość w
2.0
public abstract java.security.Principal getUserPrincipal(); // Nowość w
2.2
public abstract boolean isRequestedSessionIdFromCookie(); // Nowość w
2.0
public abstract boolean isRequestedSessionIdFromUrl(); // Opuszczona
public abstract boolean isRequestedSessionIdFromURL(); // Nowość w
2.1
public abstract boolean isRequestedSessionIdValid(); // Nowość w
2.0
public abstract boolean isUserInRole(String rola); // Nowość w
2.2
}
Metody
getAuthType()
public abstract String getAuthType()
Zwraca schemat uwierzytelniania serwletu lub null, jeżeli serwlet nie jest chroniony przez mechanizm kontroli
dostępu. Możliwe schematy to BASIC, DIGEST, FORM i CLIENT-CERT. Równoważna zmiennej CGI
AUTH_TYPE.
getContextPath()
public abstract String getContextPath()
Zwraca część żądanego URI wskazującą na kontekst (aplikację WWW) żądania. Ścieżka kontekstu zawsze
znajduje się na początku URI żądania. Ścieżka rozpoczyna się znakiem /, ale nie kończy się / i jest zwracana
bezpoś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ęść żądania
lub 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 stycznia
1970 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ść nie
może zostać przekonwertowana na Date. Metoda ta jest przydatna podczas obsługi nagłówków takich jak
Last-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 jako
część żądania. Nazwa nie zwraca uwagi na wielkość liter. Metoda ta może odczytywać wszystkie typy
nagłó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ów
String lub pustą Enumeration, jeżeli nie występują żądne nagłówki. Niektóre implementacje serwletów
mogą 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, takie
jak Accept-Language, mogą być wysyłane klientom jako kilka nagłówków, każdy z inną wartością. Ta
metoda pozwala na odczytanie wszystkich wartości. Jeżeli żądania nie zawiera żadnych nagłówków o danej
nazwie, metoda zwraca pustą Enumeration. Nazwa nie zwraca uwagi na wielkość liter. Metoda została
wprowadzona w Servlet API 2.2.
getIntHeader()
public abstract int getIntHeader(String nazwa)
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żeli
zostanie 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 nie
wystę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żeli
dodatkowe informacje nie występują lub kontener serwletów nie mógł stworzyć prawidłowej ścieżki do pliku (na
przykład kiedy plik znajduje się w zdalnym systemie plików lub może zostać odnaleziony jedynie wewnątrz
archiwum .war). Zwracana ścieżka nie musi koniecznie wskazywać na istniejący plik lub katalog. Wywołanie jest
podobne 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 poprzez
getParameter(), 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 przy
pomocy uwierzytelniania HTTP. Nie istnieje porównywalna metoda służąca do bezpośredniego odczytywania
hasł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ący
aktualnie w użyciu — na przykład, jeżeli sesja wygasła przez pojawieniem się żądania, serwer tworzy nowy
identyfikator 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ńcuchem
zapytania. 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 zwracana
bezpośrednio, bez dekodowania URL. Wczesne wersje Servlet API definiowały i implementowały tę metodę na
różne sposoby. Podczas tworzenia kodu zależnego od tej metody należy upewnić się co do jej wyników w
zależ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 nie
zawiera ż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.
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 aktualnej
prawidłowej sesji, metoda tworzy nową, jeżeli tworz posiada wartość true lub zwraca null, jeżeli tworz
wynosi 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 w
odpowiedzi 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 API
2.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 API
2.2.
isRequestedSessionIdFromCookie()
public abstract boolean isRequestedSessionIdFromCookie()
Zwraca true, jeżeli klient przesłał identyfikator sesji przez cookie, w przeciwnym przypadku false. Metoda
został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 przeciwnym
wypadku false. Metoda isRequestedSessionIdFromUrl() została wprowadzona w Servlet API 2.0,
następnie opuszczona w Servlet API 2.1 z wprowadzeniem bardziej standardowo nazwanej metody
isRequestedSessionIdFromURL().
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ą obecnie
wykorzystywaną. W przypadku sesji nowych i tych, które wygasły zwraca false. Metoda została wprowadzona
w 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ę”. Role
i członkostwo w rolach mogą być definiowane przy pomocy deskryptorów, a ich odwzorowanie względem
uż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
Zestawienie
Nazwa interfejsu javax.servlet.http.HttpServletResponse
Superinterfejs javax.servlet.ServletResponse
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
HttpServletResponse rozszerza klasę ServletResponse w celu umożliwienia manipulacji danymi
specyficznymi 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.
Deklaracja interfejsu
public 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ść w
2.0
public static final int SC_CREATED;
public static final int SC_EXPECTATION_FAILED; // Nowość w
2.2
public static final int SC_FORBIDDEN;
public static final int SC_GATEWAY_TIMEOUT; // Nowość w
2.0
public static final int SC_GONE; // Nowość w
2.0
public static final int SC_HTTP_VERSION_NOT_SUPPORTED; // Nowość w
2.0
public static final int SC_INTERNAL_SERVER_ERROR;
public static final int SC_LENGTH_REQUIRED; // Nowość w
2.0
public static final int SC_METHOD_NOT_ALLOWED; // Nowość w
2.0
public static final int SC_MOVED_PERMAMENTLY;
public static final int SC_MOVED_TEMPORARILY;
public static final int SC_MULTIPLE_CHOICES; // Nowość w
2.0
public static final int SC_NO_CONTENT;
public static final int SC_NON_AUTHORITATIVE_INFORMATION; // Nowość w
2.0
public static final int SC_NOT_ACCEPTABLE; // Nowość w
2.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ść w
2.0
public static final int SC_PAYMENT_REQUIRED; // Nowość w
2.0
public static final int SC_PRECONDITION_FAILED; // Nowość w
2.0
public static final int SC_PROXY_AUTHENTICATION_REQUIRED; // Nowość w
2.0
public static final int SC_REQUEST_ENTITY_TOO_LARGE; // Nowość w
2.0
public static final int SC_REQUEST_TIMEOUT; // Nowość w
2.0
public static final int SC_REQUEST_URI_TOO_LONG; // Nowość w
2.0
public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE; // Nowość w
2.2
public static final int SC_RESET_CONTENT; // Nowość w
2.0
public static final int SC_SEE_OTHER; // Nowość w
2.0
public static final int SC_SERVICE_UNAVAILABLE;
public static final int SC_SWITCHING_PROTOCOLS; // Nowość w
2.0
public static final int SC_UNAUTHORIZED;
public static final int SC_UNSUPPORTED_MEDIA_TYPE; // Nowość w
2.0
public static final int SC_USE_PROXY; // Nowość w
2.0
// Metody
public abstract void addCookie(Cookie cookie); // Nowość w
2.0
public abstract void addDateHeader(String nazwa, long data); // Nowość w
2.2
public abstract void addHeader(String nazwa, String wartosc); // Nowość w
2.2
public abstract void addIntHeader(String nazwa, int wartosc); // Nowość w
2.2
public abstract boolean containsHeader(String nazwa);
public abstract String encodeRedirectUrl(String url); // Opuszczona
public abstract String encodeRedirectURL(String url); // Nowość w
2.1
public abstract String encodeUrl(String url); // Opuszczona
public abstract String encodeURL(String url); // Nowość w
2.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łe
Dodatek 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 powtarzanych
wywoł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śnie
jedynie 20 cookies na witrynę, 300 na użytkownika, a wielkość każdego cookie może zostać ograniczona do
4096 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 kodowanie
nie jest potrzebne lub obsługiwane, metoda pozostawia URL niezmieniony. Zasady wykorzystywane do
podejmowania decyzji czy i kiedy kodować URL są zależne od serwera. Metoda ta może wykorzystywać inne
zasady niż encodeURL(). W celu umożliwienia śledzenia sesji, wszystkie URL-e przekazane metodzie
sendRedirect() powinny przejść prze tę metodę. Metoda encodeRedirectUrl() została
wprowadzona w Servlet API 2.0, po czym opuszczona w Servlet API 2.1 z wprowadzeniem bardziej
standardowo nazwanej metody encodeRedirectURL().
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 kodowanie
nie jest potrzebne lub obsługiwane, metoda pozostawia URL niezmieniony. Zasady wykorzystywane do
podejmowania 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ła
wprowadzona w Servlet API 2.0, po czym opuszczona w Servlet API 2.1 z wprowadzeniem bardziej
standardowo nazwanej metody encodeURL().
sendError()
public abstract void sendError(int sc)
throws IOException, IllegalStateException
public 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łąd
podczas obsługi żądania i serwlet chciałby, aby serwer wygenerował odpowiednią stronę błędu. Metoda ta
powinna zostać wywołana przed zatwierdzeniem odpowiedzi, ponieważ w innym przypadku zgłosi wyjątek
IllegalStateException. Metoda ta wykonuje ukryte wyczyszczenie bufora odpowiedzi przed
wygenerowaniem 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 nowej
lokacji w celu wspomożenia przeglądarek nie posiadających zdolności przekierowywania. W związku z tym nie
należy tworzyć własnej głównej części odpowiedzi podczas wykorzystywania tej metody. Specyfikacja HTTP
nakazuje, aby wszystkie URL-e przekierowań były bezwzględne; jednak począwszy od Servlet API 2.2 metoda ta
przyjmuje również URL-e względna — serwer przekształca je do formy bezwzględnej (automatycznie dodając
aktualny protokół, serwer i port — ale nie ścieżkę kontekstu; należy wykonać to działanie samodzielne, jeżeli
okaże się konieczne) przed wysłaniem go do klienta. Metoda ta powinna zostać wywołana przed zatwierdzeniem
odpowiedzi, ponieważ w innym przypadku zgłosi wyjątek IllegalStateException. Metoda ta wykonuje
ukryte wyczyszczenie bufora odpowiedzi przed wygenerowaniem strony błędu. Nagłówki ustawione przed
sendRedirect() 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ę w
formie 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 we
wszystkich 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 zatwierdzeniem
odpowiedzi.
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_XXX
zdefiniowanych w HttpServletResponse. Jako drugi parametr może zostać określona własna wiadomość o
błędzie protokołu HTTP; jednak nie powinno się wykonywać tego działania ponieważ wersja metody
przyjmująca String została opuszczona w ServletAPI 2.1. Kod stanu powinien zostać ustawiony przed
zatwierdzeniem odpowiedzi, w innym przypadku wywołanie jest ignorowane.
HttpSession
Zestawienie
Nazwa interfejsu javax.servlet.http.HttpSession
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 2.0 i późniejsze
Opis
Interfejs HttpSession dostarcza mechanizmu służącego do przechowywania tymczasowych informacji na
temat odwiedzających witrynę WWW. Dokładne omówienie śledzenia sesji znajduje się w rozdziale 7. Sam
interfejs HttpSession umożliwia serwletom przeglądanie i manipulację informacjami specyficznymi dla
danej sesji, takimi jak czas utworzenia i unikatowy identyfikator sesji. Zawiera również metody służące do
dowiązywania obiektów do sesji w celu późniejszego ich odczytania, umożliwiając „koszykom na zakupy” i
innym 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ć ustawione
programowo lub przed deskryptor aplikacji WWW.
W przypadku sesji związanych z nierozporoszoną aplikacją WWW, każdy obiekt może zostać dowiązany do
sesji. Obiekty implementujące java.io.Serializable mogą zostać zapisane na dysku w celu uwolnienia
pamię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ądzania
sesjami w środowisku rozproszonym, w którym często występuje kilka serwerów wspierających. Oznacza to, że
wszystkie żą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 serwerami
wspierającymi. Odpowiedzialność za sesję może zostać przekazana innemu serwerowi pomiędzy żądaniami
użytkownika, a w celu umożliwienia przenoszenia sesji wszystkie obiekty w niej umieszczone muszą być
Serializable.
Deklaracja interfejsu
public interface HttpSession {
// Metody
// Większość metod może zgłaszać IllegalStateException
public abstract Object getAttribute(String nazwa); // Nowość w
2.2
public abstract Enumeration getAttributeNames(); // Nowość w
2.2
public abstract long getCreationTime();
public abstract String getID();
public abstract long getLastAccessedTime();
public abstract int getMaxInactiveInterval(); // Nowość w
2.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ść w
2.2
public abstract void removeValue(String nazwa); // Opuszczona
public abstract void setAttribute(String nazwa, Object value); // Nowość w
2.2
public abstract void setMaxInactiveInterval(int sekundy); // Nowość w
2.1
}
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łasza
IllegalStateException, 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 obiekty
String lub pustą Enumeration, jeżeli nie występują żadne dowiązania. Zgłasza
IllegalStateException, 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 1970
GMT. 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 od
implementacji; powinna być po prostu trudna do odgadnięcia dla kogoś innego. Na przykład identyfikator
Tomcata mógłby wyglądać podobnie do awj4qyhsn2. Zgłasza IllegalStateException, jeżeli sesja jest
nieważ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łasza
IllegalStateException, jeżeli sesja jest nieważna.
getMaxInactiveInterval
public 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 ustawiony
wewną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ż od
Servlet 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 rzecz
getAttribute().
getValueNames()
public abstract String[] getValueNames()
throws IllegalStateException
Zwraca tablicę zawierającą nazwy wszystkich obiektów dowiązanych do aktualnej sesji lub pustą (zerowej
długości) tablicę, jeżeli nie występują żadne dowiązania. Zgłasza IllegalStateException, jeżeli sesja jest
nieważ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 na
cookies, 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 na
rzecz 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 nie
istnieje. 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 nie
istnieje. Zgłasza IllegalStateException, jeżeli sesja jest nieważna. Opuszczona w Servlet API 2.2 na
rzecz 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 w
Servlet 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że
zostać ustawiony wewnątrz deskryptora aplikacji WWW. Zgłasza IllegalStateException, jeżeli sesja
jest nieważna. Metoda została wprowadzona w Servlet API 2.1.
HttpSessionBindingEvent
Zestawienie
Nazwa klasy javax.servlet.http.HttpSessionBindingEvent
Superklasa java.util.EventObject
Bezpośrednie podklasy Brak
Implementowane interfejsy Brak
Dostępność Servlet API 2.0 i późniejsze
Opis
HttpSessionBindingEvent jest przekazywany HttpSessionBindingListener, kiedy obiekt
nasłuchujący jest dowiązywany do sesji, lub kiedy jego dowiązanie zostaje usunięte.
Podsumowanie klas
public 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 i
nazwę, pod którą dany obiekt zostaje przypisany (jest to ta sama nazwa, która zostaje przekazana metodzie
setAttribute() HttpSession). Programiści serwletów nie powinni nigdy zostać zmuszeni do
wykorzystania 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
Zestawienie
Nazwa interfejsu javax.servlet.http.HttpSessionBindingListener
Superinterfejs java.util.EventListener
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 2.0 i późniejsze
Opis
Obiekt implementujący HttpSessionBindingListener jest informowany poprzez wywołania
valueBound() i valueUnbound(), kiedy zostaje dowiązany (lub jego dowiązanie zostaje usunięte) do
HttpSession. Interfejs ten umożliwia między innymi uporządkowane wyczyszczenie zasobów związanych z
sesją, takich jak połączenia z bazą danych. Proszę zauważyć, że w środowisku rozproszonym wywołanie
valueBound() może nastąpić w innej JVM niż wywołanie valueUnbound().
Deklaracja interfejsu
public interface HttpSessionBindingListener extends java.util.EventListener {
// Metody
public abstract void valueBound(HttpSessionBindingEvent zdarzenie);
public abstract void valueUnbound(HttpSessionBindingEvent zdarzenie);
}
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 zniszczenie
sesji).
HttpSessionContext
Zestawienie
Nazwa interfejsu javax.servlet.http.HttpSessionContext
Superinterfejs Brak
Bezpośrednie podinterfejsy Brak
Implementowany przez Brak
Dostępność Servlet API 2.0 i późniejsze; opuszczony w Servlet API 2.1
Opis
HttpSessionContext jest opuszczona od czasów Servlet API 2.1. Poprzednio klasa ta umożliwiała dostęp
do wszystkich aktywnych w danym momencie sesji w kontenerze serwletów. Tworzyło to potencjalną dziurę w
systemie zabezpieczeń, ponieważ serwlet mógł wykorzystać te klasę w celu wyświetlenia wszystkich
identyfikatorów sesji odnalezionych wewnątrz kontekstu, a informacja ta mogła zostać wykorzystana przez
nieuczciwych klientów do utorowania sobie drogi do innej sesji. Ponieważ możliwość jednoczesnego dostępu do
wszystkich sesji jest nie jest prawie nigdy potrzebna, klasa została opuszczona ze względów bezpieczeństwa.
Deklaracja interfejsu
Public 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ą identyfikatory
wszystkich 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żdy
klient znający identyfikator sesji innego klienta mógłby, przy pomocy utworzonego samodzielnie cookie lub
URL-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. Lista
ważnych identyfikatorów sesji może zostać odczytana przez metodę getIds().
HttpUtils
Zestawienie
Nazwa klasy javax.servlet.http.HttpUtils
Superklasa java.lang.Object
Bezpośrednie podklasy Brak
Implementowane interfejsy Brak
Dostępność Servlet API 1.0 i późniejsze
Opis
Obiekt służący jako kontener dla kilku potencjalnie przydatnych, zorientowanych na HTTP metod.
Podsumowanie klasy
public 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. Zwraca
StringBuffer, który zawiera schemat, nazwę serwera, port serwera oraz dodatkowe informacje ścieżki, ale
nie łańcuch zapytania. Odtworzony URL powinien wyglądać niemal identycznie jak URL wykorzystany przez
klienta. Metoda ta może zostać wykorzystana do zgłaszania błędów, przekierowywania oraz tworzenia URL-i. W
przypadku aplikacji, które muszą bezbłędnie zidentyfikować konkretne serwlety. Lepszym wyborem jest
generalnie metoda getRequestURI() HttpServletRequest.
parsePostData()
public static Hashtable parsePostData(int dlug, ServletInputStream in)
Analizuje dlug danych parametrów ServletInputStream (zazwyczaj wysyłanych jako część operacji
POST). Zgłasza IllegalArgumentException, jeżeli dane parametrów okażą się nieprawidłowe.
Większość serwletów wykorzystuje getParameterNames(), getParameter() i
getParameterValues() 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ńcucha
zapytania, 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.
Dodatek C. Krótki opis
deskryptorów DTD
Plik Document Type Definition (Definicja Typu Dokumentu — DTD) określa prawidłową zawartość aplikacji w
poprzez plik XML. Niniejszy dodatek przedstawia oficjalny DTD Servlet API 2.2 dla deskryptora aplikacji
WWW. 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 jakim
porzą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ów
musi 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ątrz
nawiasów.
Rysunki od C.1 do C.4 przedstawiają graficznie strukturę elementów Przykład C.1 przedstawia sam DTD. Każdy
znacznik <!ATTLIST> kontroluje dozwolone atrybuty elementu. W niniejszym DTD znacznik ten jest
wykorzystany jedynie w celu dostarczenia domyślnych wartości id. Pozostała część niniejszego dodatku to opis
elementów DTD.
Rysunek C.1.
Struktura elementów deskryptora DTD
Rysunek C.2.
Rysunek C.3.
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*)>
<auth-constraint>
Zestawienie
<!ELEMENT auth-constraint (description?, role-name*)>
Opis
Element <auth-constraint> wskazuje na role użytkowników, które mogą zostać dopuszczone do zbioru
zasobó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>
<auth-method>
Zestawienie
<!ELEMENT auth-method (#DANE)>
Opis
Element <auth-method> jest wykorzystywany do konfigurowania mechanizmu uwierzytelniającego aplikacji
WWW. Podstawową zasadą dostępu do dowolnych zasobów WWW chronionych przez ograniczenia dostępu
jest uwierzytelnienie użytkownika przy pomocy skonfigurowanego mechanizmu. Prawidłowe wartości tego
elementu 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?>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <display-name> zawiera krótką nazwę serwletu, który powinien zostać wyświetlony przez
narzędzia graficzne.
<servlet>
<servlet-name>witaj</servlet-name>
<display-name>WitajSwiecie</display-name>
<servlet-class>WitajSwiecie</servlet-class>
</servlet>
<distributable>
Zestawienie
<!ELEMENT distributable (#DANE)>
Opis
Element <distributable> poprzez swoją obecność w deskryptorze aplikacji WWW wskazuje, że aplikacja
WWW 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)>
Opis
Element <ejb-link> jest wykorzystywany w elemencie <ejb-ref> w celu zaznaczenia, że odwołanie
połą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?)>
Opis
Element <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)>
Opis
Element <ejb-ref-name> zawiera nazwę odwołania do EJB. Jest to nazwa JNDI, którą wykorzystuje kod
serwletu 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>
<ejb-ref-type>
Zestawienie
<!ELEMENT ejb-ref-type (#DANE)>
Opis
Element <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)>
Opis
Element <env-entry-type> zawiera deklarację pozycji środowiskowej aplikacji, dostępnej poprzez
wyszukiwanie 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)>
Opis
Element <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)>
Opis
Element <env-entry-type> zawiera pełną nazwę typu Javy pozycji środowiskowej, który jest oczekiwany
przez 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, do
którego występuje odwołanie”.
<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)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <error-page> zawiera odwzorowanie pomiędzy kodem błędu lub typem wyjątku I ścieżką do
zasobu w aplikacji WWW.
<error-page>
<error-code>400</error-code>
<location>/400.html</location>
</error-page>
<exception-type>
Zestawienie
<!ELEMENT exception-type (#DANE)>
Opis
Element <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>
<extension>
Zestawienie
<!ELEMENT exception-type (#DANE)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <form-login-config> określa strony logowania i błędu, które powinny zostać wykorzystane
podczas 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)>
Opis
Element <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>
<home>
Zestawienie
<!ELEMENT home (#DANE)>
Opis
Element <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)>
Opis
Element <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?)>
Opis
Element <icon> może zawierać elementy <small-icon> i <large-icon> określające położenie
wewnątrz struktury plików aplikacji WWW obrazków — małego i dużego — wykorzystywanych do
przedstawiania 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?)>
Opis
Element <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>
<jsp-file>
Zestawienie
<!ELEMENT jsp-file (#DANE)>
Opis
Element <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)>
Opis
Element <large-icon> określa położenie wewnątrz aplikacji WWW pliku zawierającego duży (32 x 32
piksele) 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)>
Opis
Element <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 ładowania
serwletów. Jeżeli nie jest określona żadna wartość lub nie jest ona dodatnią liczbą całkowitą, kontener może
zał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)>
Opis
Element <location> zawiera położenie zasobu w aplikacji WWW.
<error-page>
<error-code>400</error-code>
<location>/400.html</location>
</error-page>
<login-config>
Zestawienie
<!ELEMENT login-config (auth-method?, realm-name?, form-login-config?)>
Opis
Element <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żeli
zostanie 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)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <realm-name> określa nazwę obszaru, która powinna zostać wykorzystana podczas podstawowego
uwierzytelniania TTP.
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Domyslny</realm-name>
</login-config>
<remote>
Zestawienie
<!ELEMENT remote (#DANE)>
Opis
Element <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)>
Opis
Element <auth-res> wskazuje, czy komponent kodu aplikacji przeprowadza przypisywanie zasobów
programowo, czy też kontener przypisuje zasoby w oparciu o ogólną zasadę odwzorowywania określoną przez
programistę. 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)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <resource-ref> zawiera deklarację odwołania aplikacji WWW do zasobu zewnętrznego, takiego
jak 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)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <security-role> zawiera deklarację roli bezpieczeństwa, która jest wykorzystywana w
ograniczeniach 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)>
Opis
Element <security-role-ref> określa alias dla roli bezpieczeństwa tworząc odwzorowanie pomiędzy
nazwą 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*)>
Opis
Element <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)>
Opis
Element <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)>
Opis
Element <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?)>
Opis
Element <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)>
Opis
Element <session-timeout> definiuje domyślny czas przedawnienia sesji dla wszystkich sesji utworzonych
w aplikacji WWW. Dany czas przedawnienia musi być określony w pełnych minutach.
<session-config>
<session-timeout>60 <!-- minuty --></session-timeout>
</session-config>
<small-icon>
Zestawienie
<!ELEMENT small-icon (#DANE)>
Opis
Element <small-icon> określa położenie wewnątrz aplikacji WWW pliku zawierającego mały (16 x 16
pikseli) obrazek ikony. Narzędzia przyjmują formaty przynajmniej GIF i JPEG.
<icon>
<small-icon>/obrazki/maly.gif</small-icon>
<large-icon>/obrazki/duzy.gif</large-icon>
</icon>
<taglib>
Zestawienie
<!ELEMENT taglib (taglib-uri, taglib-location)>
Opis
Element <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)>
Opis
Element <taglib-location> zawiera umiejscowienie (jako zasobu względnego do katalogu macierzystego
aplikacji WWW) pliku biblioteki znaczników (TLD).
<taglib>
<taglib-uri>/WEB-INF/struts.tld</taglib-uri>
<taglib-location>/WEB-INF/struts.tld</taglib-location>
</taglib>
<taglib-uri>
Zestawienie
<!ELEMENT taglib-uri (#DANE)>
Opis
Element <taglib-uri> opisuje URI względny do położenia dokumentu web.xml, który identyfikuje
bibliotekę 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)>
Opis
Element <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 ten
sposób, aby nie mogły zostać zmienione podczas transportu. CONFIDENTIAL oznacza, że aplikacja wymaga
przesyłania danych w ten sposób, aby zawartość transmisji nie mogła zostać zaobserwowana przez żaden inny
program lub egzemplarz programu. W większości przypadków obecność znacznika INTEGRAL lub
CONFIDENTIAL oznacza konieczność wykorzystania SSL.
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
<url-pattern>
Zestawienie
<!ELEMENT url-pattern (#DANE)>
Opis
Element <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, „Podstawy
serwletó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)>
Opis
Element <user-data-constraint> jest wykorzystywany w celu wskazania sposobu ochrony danych przesyłanych
pomiędzy klientem i kontenerem.
<security-constraint>
<web-resource-collection>
...
</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*)>
Opis
Element <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*)>
Opis
Element <web-resource-collection> jest wykorzystywany w celu zidentyfikowania podzbioru zasobów
i metod HTTP tych zasobów, do których odnoszą się ograniczenia dostępu. Jeżeli nie zostały określone żadne
metody HTTP, ograniczenia dostępu odnoszą się do wszystkich metod HTTP.
<security-constraint>
<web-resource-collection>
<web-resource-name>TajnaOchrona</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>
<welcome-file>
Zestawienie
<!ELEMENT welcome-file (#DANE)>
Opis
Element <welcome-file> zawiera nazwę pliku, który powinien zostać wykorzystany jako domyślny plik
powitalny.
<welcome-file-list>
<welcome-file>indeks.html</welcome-file>
<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ądarka
zażą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>
Dodatek D. Kody stanu HTTP
Poniższa tablica wymienia różne kody ucieczkowe Unicode, encje nazwane i numeryczne HTML dla wszystkich
możliwych do wyświetlenia znaków ISO-8859 (Latin-1).
Encje nazwane i numeryczne mogą zostać wykorzystane na stronach HTML; są one konwertowane na symbole
przez przeglądarki WWW. Kody ucieczkowe Unicode mogą zostać wykorzystane w kodzie serwletów; są one
interpretowane przez kompilator Javy. Na przykład, znak funta (£) może zostać osadzony na stronie HTML jako
£ lub £. Może również zostać osadzony bezpośrednio w kodzie Javy jako \u00A3.
Proszę zauważyć, że nie każdy znak HTML jest uniwersalnie obsługiwany. Kolumna Obsługa pokazuje poziom
obsługi znaku. Wartość S oznacza, że numeryczne i nazwane wartości encji dla danego symbolu są częścią
standardu HTML. P wskazuje, że wartości encji są proponowanym standardem — nie są częścią standardu
HTML, ale większości przypadków są powszechnie obsługiwane. N w tej kolumnie oznacza, że wartości encji
nie są standardem i w związku z tym ich obsługa jest ograniczona. W przypadku tych symboli najlepiej jest
zastosować kody ucieczkowe Unicode.
Poniższa tabela zawiera sugerowane kodowania dla dużej ilości języków. Kodowania są wykorzystywane przez
serwlety generujące informacje w wielu językach; określają one, które kodowanie znaków powinien wykorzystać
PrintWriter serwletów. Domyślnie PrintWriter wykorzystuje kodowanie ISO-8859-1 (Latin-1),
właściwe dla większości języków zachodnioeuropejskich. Aby określić alternatywne kodowanie, wartość
kodowania musi zostać przekazana metodzie setContentType() zanim serwlet pobierze swój
PrintWriter, na przykład:
odp.setContentType("text/html; charset=Shift_JIS"); // Kodowanie japońskie
PrintWriter wyj = odp.getWriter; // Zapisuje japoński Shift_JIS
Kodowanie może być również ustawione pośrednio, przy pomocy metody setLocale(), na przykład:
odp.setContentType("text/html");
odp.setLocale(new Locale("ja", "")); // Ustawia kodowanie na Shift_JIS
PrintWriter wyj = odp.getWriter; // Zapisuje japoński Shift_JIS
Metoda setLocale() przypisuje odpowiedzi kodowanie zgodnie z poniższą tabelą. W przypadku, gdy
możliwe jest więcej niż jedno kodowanie, wybierane jest kodowanie umieszczone w tabeli na pierwszej pozycji.
Proszę zauważyć, że nie wszystkie przeglądarki WWW obsługują wszystkie kodowania, lub posiadają czcionki,
dzięki którym istnieje możliwość wyświetlania wszystkich znaków, chociaż wszystkie klienty obsługują
przynajmniej ISO-8859-1. Proszę także pamiętać, że kodowanie UTF-8 może reprezentować wszystkie znaki
Unicode, a w związku z tym może być uznane za właściwe dla wszystkich języków.
Język Kod języka Sugerowane kodowania
angielski en ISO-8859-1
Albański sq ISO-8859-2
Arabski ar ISO-8859-6
białoruski be ISO-8859-5
Bułgarski bg ISO-8859-5
chiński (tradycyjny / Tajwan) zh (kraj TW) Big5
chiński (uproszczony / kontynentalny zh GB2312
Chorwacki hr ISO-8859-2
czeski cs ISO-8859-2
duński da ISO-8859-1
holenderski nl ISO-8859-1
estoński et ISO-8859-1
fiński fi ISO-8859-1
francuski fr ISO-8859-1
grecki el ISO-8859-7
hebrajski he (dawniej iw) ISO-8859-8
hiszpański es ISO-8859-1
islandzki is ISO-8859-1
japoński ja Shift_JIS, ISO-2022-JP, EUC-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, tak
więc w celu zapewnienia przenośności można ustawić kodowanie na EUC_JP i samodzielnie skonstruować PrintWriter
EUCJIS.
koreański ko EUC-KR46
litewski lt ISO-8859-2
łotewski lv ISO-8859-2
macedoński mk ISO-8859-5
niemiecki de ISO-8859-1
polski pl ISO-8859-2
portugalski pt ISO-8859-1
rosyjski ru ISO-8859-5, KOI8-R
rumuński ro ISO-8859-2
serbski sr ISO-8859-5, KOI8-R
serbsko-chorwacki sh ISO-8859-5, ISO-8859-2, KOI8-R
słowacki sk ISO-8859-2
słoweński sl ISO-8859-2
szwedzki sv ISO-8859-1
turecki tr ISO-8859-9
ukraiński uk ISO-8859-5, KOI8-R
węgierski hu ISO-8859-2
włoski it ISO-8859-1
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.